honeytree 1.1.3 → 1.1.5
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 +169 -22
- package/bin/honeydew.js +12 -27
- package/package.json +8 -15
- package/src/badge.js +34 -16
- package/src/init.js +86 -0
- package/src/markdown.js +43 -13
- package/src/plant.js +108 -0
- package/src/renderer.js +349 -0
- package/src/sprites.js +245 -0
- package/src/state.js +53 -0
- package/src/viewer.js +169 -0
- package/src/commands/export.js +0 -23
- package/src/commands/init.js +0 -70
- package/src/commands/plant.js +0 -11
- package/src/commands/stats.js +0 -48
- package/src/commands/watch.js +0 -120
- package/src/core/animation.js +0 -154
- package/src/core/environment.js +0 -323
- package/src/core/progression.js +0 -149
- package/src/core/sprites.js +0 -400
- package/src/core/state.js +0 -211
- package/src/renderers/terminal.js +0 -437
- package/src/tracker/activity.js +0 -43
- package/src/tracker/files.js +0 -44
- package/src/tracker/git.js +0 -77
package/README.md
CHANGED
|
@@ -1,45 +1,192 @@
|
|
|
1
1
|
# Honeytree
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/honeytree)
|
|
4
|
+
[](https://github.com/Varun2009178/honeytree/blob/main/LICENSE)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Grow a pixel-art forest in your terminal every time you use Claude Code.
|
|
7
|
+
|
|
8
|
+
Each prompt plants a new tree. Each tree grows over time. Your forest evolves from a quiet clearing into an ancient woodland — and it never resets.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
6
13
|
|
|
7
14
|
```bash
|
|
8
15
|
npm install -g honeytree
|
|
9
16
|
honeytree init
|
|
17
|
+
honeytree
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
That's it. Three commands:
|
|
21
|
+
|
|
22
|
+
1. **Install** the CLI globally
|
|
23
|
+
2. **Init** creates your forest file and registers a Claude Code hook
|
|
24
|
+
3. **Run the viewer** in a separate terminal to watch your forest grow
|
|
25
|
+
|
|
26
|
+
After setup, trees are planted automatically after every Claude Code response. No manual steps needed.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## How It Works
|
|
31
|
+
|
|
32
|
+
When you run `honeytree init`, it does two things:
|
|
33
|
+
|
|
34
|
+
- Creates `~/.honeydew/forest.json` to store your forest state
|
|
35
|
+
- Adds a `Stop` hook to `~/.claude/settings.json` that runs after every Claude Code response
|
|
36
|
+
|
|
37
|
+
From then on, every time Claude Code responds to a prompt, a new tree is planted in your forest automatically. Open the viewer in a second terminal to watch them grow in real time.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Streaks
|
|
42
|
+
|
|
43
|
+
Honeytree tracks your coding streak — consecutive days where you use Claude Code.
|
|
44
|
+
|
|
45
|
+
- **Active streak**: The viewer and badge show your current streak count (e.g. `7-day streak`)
|
|
46
|
+
- **Broken streak**: Miss a day and your forest starts **wilting** — trees desaturate toward brown, and fog rolls in across the scene
|
|
47
|
+
- **Recovery**: Your next prompt resets the streak to 1 and clears the wilting immediately
|
|
48
|
+
|
|
49
|
+
The longer you go without coding, the worse it gets:
|
|
50
|
+
|
|
51
|
+
| Days idle | Effect |
|
|
52
|
+
|----------:|--------|
|
|
53
|
+
| 1 | Light desaturation, sparse fog |
|
|
54
|
+
| 2 | Noticeable browning, moderate fog |
|
|
55
|
+
| 3 | Heavy browning, dense fog |
|
|
56
|
+
| 4+ | Near-dead forest, thick fog |
|
|
57
|
+
|
|
58
|
+
Plant a tree to bring it all back to life.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Badge
|
|
63
|
+
|
|
64
|
+
Generate a badge for your GitHub README that shows your forest stats and links back to [Honeytree](https://github.com/Varun2009178/honeytree):
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
honeytree badge
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
This creates a `honeytree-badge.svg` file in your current directory and prints the markdown to embed it:
|
|
71
|
+
|
|
72
|
+
```markdown
|
|
73
|
+
[](https://github.com/Varun2009178/honeytree)
|
|
10
74
|
```
|
|
11
75
|
|
|
12
|
-
|
|
76
|
+
The badge displays your tree count and streak status. It links to the [Honeytree repo](https://github.com/Varun2009178/honeytree) so anyone who sees it can install it themselves.
|
|
13
77
|
|
|
14
|
-
|
|
78
|
+
| State | Badge color | Example |
|
|
79
|
+
|-------|-------------|---------|
|
|
80
|
+
| Active streak | Green | `42 trees · 7d streak` |
|
|
81
|
+
| Wilting | Orange-red | `42 trees · wilting` |
|
|
82
|
+
| No streak data | Grey | `42 trees` |
|
|
15
83
|
|
|
16
|
-
|
|
17
|
-
| --- | --- |
|
|
18
|
-
| `honeytree init` | Creates `~/.honeytree/state.json`, migrates old `~/.honeydew/forest.json`, and prepares the forest project |
|
|
19
|
-
| `honeytree` | Opens the animated terminal forest |
|
|
20
|
-
| `honeydew watch` | Explicit watch alias for the same forest view |
|
|
84
|
+
Re-run `honeytree badge` any time to update the SVG with your latest stats. Commit it to your repo to keep it current.
|
|
21
85
|
|
|
22
|
-
|
|
86
|
+
---
|
|
23
87
|
|
|
24
|
-
##
|
|
88
|
+
## FOREST.md
|
|
25
89
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
90
|
+
Generate a shareable markdown snapshot of your forest:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
honeytree md
|
|
94
|
+
```
|
|
31
95
|
|
|
32
|
-
|
|
96
|
+
This creates a `FOREST.md` in your current directory with:
|
|
33
97
|
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
98
|
+
- Your Honeytree badge (links to the [Honeytree repo](https://github.com/Varun2009178/honeytree))
|
|
99
|
+
- Stats: tree count, streak, biome
|
|
100
|
+
- A plain-text rendering of your forest (tree silhouettes, stars, ground)
|
|
101
|
+
- Total prompts and forest age
|
|
102
|
+
|
|
103
|
+
Commit `FOREST.md` to your repo root so your team can see the forest. When teammates see it, they can install Honeytree themselves — one install spreads to the whole team.
|
|
104
|
+
|
|
105
|
+
Run `honeytree badge` first to generate the SVG, then `honeytree md` to generate the markdown that embeds it.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Biomes
|
|
110
|
+
|
|
111
|
+
Your forest evolves visually as it grows — the sky, ground, and atmosphere all change:
|
|
112
|
+
|
|
113
|
+
| Trees | Biome | What changes |
|
|
114
|
+
|------:|-------|-------------|
|
|
115
|
+
| 0–9 | Clearing | Sparse stars, light ground |
|
|
116
|
+
| 10–24 | Grove | More stars, richer ground |
|
|
117
|
+
| 25–49 | Woodland | Dense canopy, varied starlight |
|
|
118
|
+
| 50–99 | Old Growth | Deep greens, warm starlight |
|
|
119
|
+
| 100+ | Ancient Forest | Richest palette, brightest sky |
|
|
120
|
+
|
|
121
|
+
Trees are never deleted. The forest only grows.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Tree Species
|
|
126
|
+
|
|
127
|
+
Five species are randomly assigned when a tree is planted:
|
|
128
|
+
|
|
129
|
+
| Species | Look |
|
|
130
|
+
|---------|------|
|
|
131
|
+
| Oak | Wide, rounded canopy |
|
|
132
|
+
| Pine | Tall, triangular shape |
|
|
133
|
+
| Birch | Light trunk, bright leaves |
|
|
134
|
+
| Willow | Drooping canopy |
|
|
135
|
+
| Cherry | Pink blossoms |
|
|
136
|
+
|
|
137
|
+
Each species has 4 growth stages (seed, sapling, young, full). Existing trees grow a little with each new prompt.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## CLI Reference
|
|
142
|
+
|
|
143
|
+
| Command | Description |
|
|
144
|
+
|---------|-------------|
|
|
145
|
+
| `honeytree init` | Create forest and register Claude Code hook |
|
|
146
|
+
| `honeytree` | Launch the live viewer |
|
|
147
|
+
| `honeytree plant` | Plant a tree manually (normally runs via hook) |
|
|
148
|
+
| `honeytree badge` | Generate `honeytree-badge.svg` in current directory |
|
|
149
|
+
| `honeytree md` | Generate `FOREST.md` in current directory |
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Viewer
|
|
154
|
+
|
|
155
|
+
The viewer adapts to your terminal width — expand your terminal and new trees will spread across the full width.
|
|
156
|
+
|
|
157
|
+
Press `Ctrl+C` to exit. The viewer shows a summary of your forest when you close it.
|
|
158
|
+
|
|
159
|
+
### Reading the Stats Bar
|
|
160
|
+
|
|
161
|
+
Below your forest you'll see a stats bar like this:
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
honeytree · 42 trees · 7-day streak · ████████░░░░ next: oak [woodland]
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Here's what each part means:
|
|
168
|
+
|
|
169
|
+
| Segment | What it tells you |
|
|
170
|
+
|---------|-------------------|
|
|
171
|
+
| `42 trees` | Total trees in your forest — one planted per prompt, never deleted |
|
|
172
|
+
| `7-day streak` | Consecutive days you've used Claude Code. Resets to 1 if you skip a day |
|
|
173
|
+
| `wilting (2d idle)` | Appears instead of streak when you've been inactive — your forest is dying |
|
|
174
|
+
| `████████░░░░` | Progress bar toward the next milestone (10, 25, 50, 100, 250, 500, 1000 trees) |
|
|
175
|
+
| `next: oak` | The species of the next tree that will be planted |
|
|
176
|
+
| `[woodland]` | Your current biome — evolves as your tree count grows |
|
|
177
|
+
|
|
178
|
+
---
|
|
38
179
|
|
|
39
180
|
## Requirements
|
|
40
181
|
|
|
41
182
|
- Node.js 18+
|
|
42
|
-
-
|
|
183
|
+
- [Claude Code](https://claude.com/claude-code) (for the automatic hook)
|
|
184
|
+
|
|
185
|
+
## Links
|
|
186
|
+
|
|
187
|
+
- **npm**: [npmjs.com/package/honeytree](https://www.npmjs.com/package/honeytree)
|
|
188
|
+
- **GitHub**: [github.com/Varun2009178/honeytree](https://github.com/Varun2009178/honeytree)
|
|
189
|
+
- **Issues**: [github.com/Varun2009178/honeytree/issues](https://github.com/Varun2009178/honeytree/issues)
|
|
43
190
|
|
|
44
191
|
## License
|
|
45
192
|
|
package/bin/honeydew.js
CHANGED
|
@@ -1,39 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
3
|
const command = process.argv[2];
|
|
6
|
-
const invokedAs = path.basename(process.argv[1] || "honeytree");
|
|
7
|
-
|
|
8
|
-
function printUsage() {
|
|
9
|
-
console.error("Usage:");
|
|
10
|
-
console.error(" honeytree show your forest");
|
|
11
|
-
console.error(" honeytree init create or migrate your forest");
|
|
12
|
-
console.error(" honeytree watch open the terminal forest");
|
|
13
|
-
console.error(" honeytree stats show a non-TUI summary");
|
|
14
|
-
console.error(" honeytree export write honeytree-badge.svg and FOREST.md");
|
|
15
|
-
}
|
|
16
4
|
|
|
17
5
|
if (command === "init") {
|
|
18
|
-
const { init } = await import("../src/
|
|
6
|
+
const { init } = await import("../src/init.js");
|
|
19
7
|
await init();
|
|
20
|
-
} else if (command === "export") {
|
|
21
|
-
const { exportForest } = await import("../src/commands/export.js");
|
|
22
|
-
await exportForest();
|
|
23
|
-
} else if (command === "watch" || !command) {
|
|
24
|
-
const { watch } = await import("../src/commands/watch.js");
|
|
25
|
-
await watch();
|
|
26
|
-
} else if (command === "stats") {
|
|
27
|
-
const { stats } = await import("../src/commands/stats.js");
|
|
28
|
-
await stats();
|
|
29
8
|
} else if (command === "plant") {
|
|
30
|
-
const { plant } = await import("../src/
|
|
9
|
+
const { plant } = await import("../src/plant.js");
|
|
31
10
|
await plant();
|
|
11
|
+
} else if (command === "badge") {
|
|
12
|
+
const { badge } = await import("../src/badge.js");
|
|
13
|
+
await badge();
|
|
14
|
+
} else if (command === "md") {
|
|
15
|
+
const { generateForestMd } = await import("../src/markdown.js");
|
|
16
|
+
await generateForestMd();
|
|
17
|
+
} else if (!command) {
|
|
18
|
+
const { viewer } = await import("../src/viewer.js");
|
|
19
|
+
await viewer();
|
|
32
20
|
} else {
|
|
33
21
|
console.error(`Unknown command: ${command}`);
|
|
34
|
-
|
|
35
|
-
console.error("Run `honeydew watch` or `honeytree init`.");
|
|
36
|
-
}
|
|
37
|
-
printUsage();
|
|
22
|
+
console.error("Usage: honeytree [init|plant|badge|md]");
|
|
38
23
|
process.exit(1);
|
|
39
24
|
}
|
package/package.json
CHANGED
|
@@ -1,28 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "honeytree",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.5",
|
|
4
|
+
"description": "Grow a forest in your terminal every time you use Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"honeytree": "./bin/honeydew.js"
|
|
8
|
-
"honeydew": "./bin/honeydew.js"
|
|
7
|
+
"honeytree": "./bin/honeydew.js"
|
|
9
8
|
},
|
|
10
9
|
"files": [
|
|
11
10
|
"bin",
|
|
12
11
|
"src"
|
|
13
12
|
],
|
|
14
13
|
"scripts": {
|
|
15
|
-
"test": "node --test test
|
|
16
|
-
"postinstall": "echo \"🌳 Thanks for planting! Star us: https://github.com/Varun2009178/honeytree\""
|
|
14
|
+
"test": "node --test test/*.test.js"
|
|
17
15
|
},
|
|
18
16
|
"keywords": [
|
|
19
17
|
"cli",
|
|
20
18
|
"terminal",
|
|
21
19
|
"forest",
|
|
20
|
+
"claude-code",
|
|
22
21
|
"pixel-art",
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"ascii-art"
|
|
22
|
+
"ascii-art",
|
|
23
|
+
"hooks"
|
|
26
24
|
],
|
|
27
25
|
"repository": {
|
|
28
26
|
"type": "git",
|
|
@@ -35,12 +33,7 @@
|
|
|
35
33
|
"author": "Varun Nukala",
|
|
36
34
|
"license": "MIT",
|
|
37
35
|
"dependencies": {
|
|
38
|
-
"chalk": "^5.4.1"
|
|
39
|
-
"chokidar": "^4.0.3",
|
|
40
|
-
"date-fns": "^4.1.0",
|
|
41
|
-
"ink": "^5.2.1",
|
|
42
|
-
"react": "^18.3.1",
|
|
43
|
-
"simple-git": "^3.36.0"
|
|
36
|
+
"chalk": "^5.4.1"
|
|
44
37
|
},
|
|
45
38
|
"engines": {
|
|
46
39
|
"node": ">=18"
|
package/src/badge.js
CHANGED
|
@@ -1,28 +1,44 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { readForest } from "./state.js";
|
|
5
|
+
import { buildScene, getWiltFactor } from "./renderer.js";
|
|
6
6
|
|
|
7
7
|
const CELL = 6;
|
|
8
8
|
const BG = "#0d1117";
|
|
9
9
|
const TEXT_PAD = 18;
|
|
10
10
|
|
|
11
|
-
function
|
|
12
|
-
|
|
11
|
+
function buildStatsText(forest, biome) {
|
|
12
|
+
const count = forest.trees.length;
|
|
13
|
+
const streak = forest.streak || 0;
|
|
14
|
+
const wilt = getWiltFactor(forest.lastActiveDate);
|
|
15
|
+
|
|
16
|
+
const parts = [`${count} tree${count === 1 ? "" : "s"}`];
|
|
17
|
+
|
|
18
|
+
if (wilt > 0) {
|
|
19
|
+
const a = new Date(forest.lastActiveDate + "T00:00:00");
|
|
20
|
+
const b = new Date(new Date().toISOString().slice(0, 10) + "T00:00:00");
|
|
21
|
+
const idle = Math.round((b - a) / (24 * 60 * 60 * 1000));
|
|
22
|
+
parts.push(`wilting (${idle}d idle)`);
|
|
23
|
+
} else if (streak > 0) {
|
|
24
|
+
parts.push(`${streak}d streak`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
parts.push(biome.label);
|
|
28
|
+
return parts.join(" · ");
|
|
13
29
|
}
|
|
14
30
|
|
|
15
|
-
function generateForestSVG(
|
|
16
|
-
const cols = 80;
|
|
17
|
-
const { buffer,
|
|
31
|
+
function generateForestSVG(forest) {
|
|
32
|
+
const cols = Math.max(40, Math.min(forest.viewerWidth || 60, 80));
|
|
33
|
+
const { buffer, biome, sceneRows } = buildScene(forest, cols);
|
|
18
34
|
|
|
19
35
|
const artW = cols * CELL;
|
|
20
|
-
const artH =
|
|
36
|
+
const artH = sceneRows * CELL;
|
|
21
37
|
const totalW = artW;
|
|
22
38
|
const totalH = artH + TEXT_PAD;
|
|
23
39
|
|
|
24
40
|
let rects = "";
|
|
25
|
-
for (let y = 0; y <
|
|
41
|
+
for (let y = 0; y < sceneRows; y += 1) {
|
|
26
42
|
for (let x = 0; x < cols; x += 1) {
|
|
27
43
|
const cell = buffer[y][x];
|
|
28
44
|
if (!cell.color) continue;
|
|
@@ -30,15 +46,17 @@ function generateForestSVG(state) {
|
|
|
30
46
|
}
|
|
31
47
|
}
|
|
32
48
|
|
|
49
|
+
const stats = buildStatsText(forest, biome);
|
|
50
|
+
|
|
33
51
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="${totalW}" height="${totalH}" viewBox="0 0 ${totalW} ${totalH}">
|
|
34
52
|
<rect width="${totalW}" height="${totalH}" fill="${BG}" rx="6"/>
|
|
35
53
|
${rects}
|
|
36
|
-
<text x="${totalW / 2}" y="${artH + 13}" text-anchor="middle" fill="#8e8a84" font-family="monospace" font-size="10">${
|
|
54
|
+
<text x="${totalW / 2}" y="${artH + 13}" text-anchor="middle" fill="#8e8a84" font-family="monospace" font-size="10">${stats}</text>
|
|
37
55
|
</svg>`;
|
|
38
56
|
}
|
|
39
57
|
|
|
40
|
-
export function writeBadgeSVG(
|
|
41
|
-
fs.writeFileSync(outPath, generateForestSVG(
|
|
58
|
+
export function writeBadgeSVG(forest, outPath) {
|
|
59
|
+
fs.writeFileSync(outPath, generateForestSVG(forest));
|
|
42
60
|
}
|
|
43
61
|
|
|
44
62
|
export function findBadgeFile() {
|
|
@@ -53,14 +71,14 @@ export function findBadgeFile() {
|
|
|
53
71
|
}
|
|
54
72
|
|
|
55
73
|
export async function badge() {
|
|
56
|
-
const
|
|
57
|
-
if (!
|
|
58
|
-
console.error('No
|
|
74
|
+
const forest = readForest();
|
|
75
|
+
if (!forest) {
|
|
76
|
+
console.error('No forest found. Run "honeytree init" first.');
|
|
59
77
|
process.exit(1);
|
|
60
78
|
}
|
|
61
79
|
|
|
62
80
|
const outPath = path.resolve("honeytree-badge.svg");
|
|
63
|
-
writeBadgeSVG(
|
|
81
|
+
writeBadgeSVG(forest, outPath);
|
|
64
82
|
|
|
65
83
|
console.log(`Badge written to ${outPath}`);
|
|
66
84
|
console.log("");
|
package/src/init.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
createEmptyForest,
|
|
7
|
+
getHoneydewDir,
|
|
8
|
+
readForest,
|
|
9
|
+
writeForest,
|
|
10
|
+
} from "./state.js";
|
|
11
|
+
|
|
12
|
+
function getClaudeSettingsPath() {
|
|
13
|
+
const claudeRoot =
|
|
14
|
+
process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
|
|
15
|
+
return path.join(claudeRoot, "settings.json");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const HONEYDEW_STOP_HOOK = {
|
|
19
|
+
matcher: "",
|
|
20
|
+
hooks: [
|
|
21
|
+
{
|
|
22
|
+
type: "command",
|
|
23
|
+
command: "honeytree plant",
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function hasHoneydewHook(settings) {
|
|
29
|
+
return (
|
|
30
|
+
settings?.hooks?.Stop?.some((entry) =>
|
|
31
|
+
entry?.hooks?.some((hook) => hook?.command === "honeytree plant"),
|
|
32
|
+
) ?? false
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function init() {
|
|
37
|
+
const honeydewDir = getHoneydewDir();
|
|
38
|
+
fs.mkdirSync(honeydewDir, { recursive: true });
|
|
39
|
+
|
|
40
|
+
const existing = readForest();
|
|
41
|
+
if (!existing) {
|
|
42
|
+
writeForest(createEmptyForest());
|
|
43
|
+
console.log(`Created ${path.join(honeydewDir, "forest.json")}`);
|
|
44
|
+
} else {
|
|
45
|
+
let migrated = false;
|
|
46
|
+
if (existing.lastActiveDate === undefined) {
|
|
47
|
+
existing.lastActiveDate = new Date().toISOString().slice(0, 10);
|
|
48
|
+
migrated = true;
|
|
49
|
+
}
|
|
50
|
+
if (existing.streak === undefined) {
|
|
51
|
+
existing.streak = 0;
|
|
52
|
+
migrated = true;
|
|
53
|
+
}
|
|
54
|
+
if (migrated) {
|
|
55
|
+
writeForest(existing);
|
|
56
|
+
console.log(`Updated forest with streak tracking (${existing.trees.length} trees kept)`);
|
|
57
|
+
} else {
|
|
58
|
+
console.log(`Forest already up to date at ${path.join(honeydewDir, "forest.json")}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const settingsPath = getClaudeSettingsPath();
|
|
63
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
64
|
+
|
|
65
|
+
let settings = {};
|
|
66
|
+
try {
|
|
67
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
68
|
+
} catch {
|
|
69
|
+
settings = {};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
settings.hooks ??= {};
|
|
73
|
+
settings.hooks.Stop ??= [];
|
|
74
|
+
|
|
75
|
+
if (hasHoneydewHook(settings)) {
|
|
76
|
+
console.log(`Claude Code hook already configured in ${settingsPath}`);
|
|
77
|
+
} else {
|
|
78
|
+
settings.hooks.Stop.push(HONEYDEW_STOP_HOOK);
|
|
79
|
+
fs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
|
|
80
|
+
console.log(`Added honeytree Stop hook to ${settingsPath}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log("");
|
|
84
|
+
console.log("Setup complete.");
|
|
85
|
+
console.log("Run `honeytree` in a separate terminal to watch the forest grow.");
|
|
86
|
+
}
|
package/src/markdown.js
CHANGED
|
@@ -1,28 +1,58 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { renderTerminalFrame } from "./renderers/terminal.js";
|
|
4
|
+
import { readForest } from "./state.js";
|
|
5
|
+
import { renderPlainText, getWiltFactor } from "./renderer.js";
|
|
7
6
|
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
function getBiomeLabel(count) {
|
|
8
|
+
if (count < 10) return "clearing";
|
|
9
|
+
if (count < 25) return "grove";
|
|
10
|
+
if (count < 50) return "woodland";
|
|
11
|
+
if (count < 100) return "old growth";
|
|
12
|
+
return "ancient forest";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getDayCount(createdAt) {
|
|
16
|
+
const created = new Date(createdAt).getTime();
|
|
17
|
+
const diff = Date.now() - created;
|
|
18
|
+
return Math.max(1, Math.floor(diff / (24 * 60 * 60 * 1000)) + 1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function buildMarkdown(forest) {
|
|
22
|
+
const count = forest.trees.length;
|
|
23
|
+
const streak = forest.streak || 0;
|
|
24
|
+
const biome = getBiomeLabel(count);
|
|
25
|
+
const wilt = getWiltFactor(forest.lastActiveDate);
|
|
26
|
+
const days = getDayCount(forest.createdAt);
|
|
27
|
+
const width = Math.min(forest.viewerWidth || 60, 80);
|
|
28
|
+
|
|
29
|
+
const art = renderPlainText(forest, width);
|
|
30
|
+
|
|
31
|
+
const statParts = [`**${count} tree${count === 1 ? "" : "s"}**`];
|
|
32
|
+
if (wilt > 0) {
|
|
33
|
+
const a = new Date(forest.lastActiveDate + "T00:00:00");
|
|
34
|
+
const b = new Date(new Date().toISOString().slice(0, 10) + "T00:00:00");
|
|
35
|
+
const idle = Math.round((b - a) / (24 * 60 * 60 * 1000));
|
|
36
|
+
statParts.push(`**wilting (${idle}d idle)**`);
|
|
37
|
+
} else if (streak > 0) {
|
|
38
|
+
statParts.push(`**${streak}-day streak**`);
|
|
39
|
+
}
|
|
40
|
+
statParts.push(`**${biome}**`);
|
|
11
41
|
|
|
12
42
|
const lines = [
|
|
13
43
|
`<div align="center">`,
|
|
14
44
|
``,
|
|
15
45
|
`[](https://github.com/Varun2009178/honeytree)`,
|
|
16
46
|
``,
|
|
17
|
-
|
|
47
|
+
statParts.join(" · "),
|
|
18
48
|
``,
|
|
19
49
|
"```",
|
|
20
50
|
art,
|
|
21
51
|
"```",
|
|
22
52
|
``,
|
|
23
|
-
`${
|
|
53
|
+
`${forest.totalPrompts} prompts over ${days} day${days === 1 ? "" : "s"}`,
|
|
24
54
|
``,
|
|
25
|
-
`<sub>Grown with <a href="https://github.com/Varun2009178/honeytree">honeytree</a> — a
|
|
55
|
+
`<sub>Grown with <a href="https://github.com/Varun2009178/honeytree">honeytree</a> — a forest that grows in your terminal every time you use Claude Code</sub>`,
|
|
26
56
|
``,
|
|
27
57
|
`</div>`,
|
|
28
58
|
``,
|
|
@@ -32,13 +62,13 @@ function buildMarkdown(state) {
|
|
|
32
62
|
}
|
|
33
63
|
|
|
34
64
|
export async function generateForestMd() {
|
|
35
|
-
const
|
|
36
|
-
if (!
|
|
37
|
-
console.error('No
|
|
65
|
+
const forest = readForest();
|
|
66
|
+
if (!forest) {
|
|
67
|
+
console.error('No forest found. Run "honeytree init" first.');
|
|
38
68
|
process.exit(1);
|
|
39
69
|
}
|
|
40
70
|
|
|
41
|
-
const md = buildMarkdown(
|
|
71
|
+
const md = buildMarkdown(forest);
|
|
42
72
|
const outPath = path.resolve("FOREST.md");
|
|
43
73
|
fs.writeFileSync(outPath, md);
|
|
44
74
|
|