honeytree 1.0.1 → 1.0.2

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 +52 -25
  2. package/package.json +1 -1
  3. package/src/viewer.js +69 -16
package/README.md CHANGED
@@ -1,44 +1,49 @@
1
1
  # Honeytree
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/honeytree.svg)](https://www.npmjs.com/package/honeytree)
4
+ [![license](https://img.shields.io/npm/l/honeytree.svg)](https://github.com/Varun2009178/honeytree/blob/main/LICENSE)
5
+
3
6
  Grow a pixel-art forest in your terminal every time you use Claude Code.
4
7
 
5
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.
6
9
 
7
- ## Install
10
+ ---
11
+
12
+ ## Quick Start
8
13
 
9
14
  ```bash
10
15
  npm install -g honeytree
16
+ honeytree init
17
+ honeytree
11
18
  ```
12
19
 
13
- ## Setup
20
+ That's it. Three commands:
14
21
 
15
- ```bash
16
- honeytree init
17
- ```
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
18
25
 
19
- This creates `~/.honeydew/forest.json` and adds a `Stop` hook to your Claude Code settings so a tree is planted after every response.
26
+ After setup, trees are planted automatically after every Claude Code response. No manual steps needed.
20
27
 
21
- ## Watch your forest
28
+ ---
22
29
 
23
- ```bash
24
- honeytree
25
- ```
30
+ ## How It Works
26
31
 
27
- Open this in a separate terminal. It watches your forest file and animates new trees as they appear. Press `Ctrl+C` to exit.
32
+ When you run `honeytree init`, it does two things:
28
33
 
29
- ## How it works
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
30
36
 
31
- 1. **`honeytree init`** Creates the forest state file and registers a Claude Code hook
32
- 2. **`honeytree`** — Opens the viewer that renders your forest in real time
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.
33
38
 
34
- After init, a tree is automatically planted after every Claude Code response via the hook. Each tree is a random species (oak, pine, birch, willow, or cherry) and growth stage. Existing young trees grow a little each time too.
39
+ ---
35
40
 
36
41
  ## Biomes
37
42
 
38
- Your forest evolves as it grows:
43
+ Your forest evolves visually as it grows — the sky, ground, and atmosphere all change:
39
44
 
40
45
  | Trees | Biome | What changes |
41
- |-------|-------|-------------|
46
+ |------:|-------|-------------|
42
47
  | 0–9 | Clearing | Sparse stars, light ground |
43
48
  | 10–24 | Grove | More stars, richer ground |
44
49
  | 25–49 | Woodland | Dense canopy, varied starlight |
@@ -47,20 +52,42 @@ Your forest evolves as it grows:
47
52
 
48
53
  Trees are never deleted. The forest only grows.
49
54
 
50
- ## Tree types
55
+ ---
51
56
 
52
- - **Oak** — Wide, rounded canopy
53
- - **Pine** — Tall, triangular shape
54
- - **Birch** — Light trunk, bright leaves
55
- - **Willow** — Drooping canopy
56
- - **Cherry** — Pink blossoms
57
+ ## Tree Species
57
58
 
58
- Each type has 4 growth stages: seed, sapling, young, and full.
59
+ Five species are randomly assigned when a tree is planted:
60
+
61
+ | Species | Look |
62
+ |---------|------|
63
+ | Oak | Wide, rounded canopy |
64
+ | Pine | Tall, triangular shape |
65
+ | Birch | Light trunk, bright leaves |
66
+ | Willow | Drooping canopy |
67
+ | Cherry | Pink blossoms |
68
+
69
+ Each species has 4 growth stages (seed, sapling, young, full). Existing trees grow a little with each new prompt.
70
+
71
+ ---
72
+
73
+ ## Viewer
74
+
75
+ The viewer adapts to your terminal width — expand your terminal and new trees will spread across the full width.
76
+
77
+ Press `Ctrl+C` to exit. The viewer shows a summary of your forest when you close it.
78
+
79
+ ---
59
80
 
60
81
  ## Requirements
61
82
 
62
83
  - Node.js 18+
63
- - Claude Code (for the automatic hook)
84
+ - [Claude Code](https://claude.com/claude-code) (for the automatic hook)
85
+
86
+ ## Links
87
+
88
+ - **npm**: [npmjs.com/package/honeytree](https://www.npmjs.com/package/honeytree)
89
+ - **GitHub**: [github.com/Varun2009178/honeytree](https://github.com/Varun2009178/honeytree)
90
+ - **Issues**: [github.com/Varun2009178/honeytree/issues](https://github.com/Varun2009178/honeytree/issues)
64
91
 
65
92
  ## License
66
93
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "honeytree",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Grow a forest in your terminal every time you use Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
package/src/viewer.js CHANGED
@@ -64,10 +64,12 @@ export async function viewer() {
64
64
  }
65
65
 
66
66
  // Save terminal width so plant knows how wide to spread trees
67
+ let ignoreNextChange = false;
67
68
  function syncWidth() {
68
69
  const cols = process.stdout.columns || 80;
69
70
  if (forest.viewerWidth !== cols) {
70
71
  forest.viewerWidth = cols;
72
+ ignoreNextChange = true;
71
73
  writeForest(forest);
72
74
  }
73
75
  }
@@ -78,6 +80,8 @@ export async function viewer() {
78
80
  renderForest(forest);
79
81
 
80
82
  let lastMaxId = forest.trees.reduce((max, tree) => Math.max(max, tree.id), 0);
83
+ let lastTotalPrompts = forest.totalPrompts;
84
+ let animating = false;
81
85
 
82
86
  const cleanup = () => {
83
87
  showCursor();
@@ -96,21 +100,70 @@ export async function viewer() {
96
100
  renderForest(forest);
97
101
  });
98
102
 
99
- let debounceTimer;
100
- fs.watch(forestFile, () => {
101
- clearTimeout(debounceTimer);
102
- debounceTimer = setTimeout(async () => {
103
- const updated = readForest();
104
- if (!updated) return;
105
-
106
- const nextMaxId = updated.trees.reduce((max, tree) => Math.max(max, tree.id), 0);
107
- forest = updated;
108
- if (nextMaxId > lastMaxId) {
109
- lastMaxId = nextMaxId;
110
- await animateNewTree(forest, nextMaxId);
111
- } else {
112
- renderForest(forest);
103
+ // Check for changes — used by both fs.watch and polling fallback
104
+ async function checkForUpdates() {
105
+ if (animating) return;
106
+
107
+ if (ignoreNextChange) {
108
+ ignoreNextChange = false;
109
+ return;
110
+ }
111
+
112
+ const updated = readForest();
113
+ if (!updated) return;
114
+
115
+ // Only re-render if something actually changed
116
+ if (updated.totalPrompts === lastTotalPrompts) return;
117
+
118
+ const nextMaxId = updated.trees.reduce((max, tree) => Math.max(max, tree.id), 0);
119
+ forest = updated;
120
+ lastTotalPrompts = forest.totalPrompts;
121
+
122
+ if (nextMaxId > lastMaxId) {
123
+ lastMaxId = nextMaxId;
124
+ animating = true;
125
+ await animateNewTree(forest, nextMaxId);
126
+ animating = false;
127
+ } else {
128
+ renderForest(forest);
129
+ }
130
+ }
131
+
132
+ // fs.watch can drop events on macOS after atomic renames, so
133
+ // use it for fast response but also poll as a reliable fallback
134
+ function startWatcher() {
135
+ try {
136
+ const watcher = fs.watch(forestFile, () => {
137
+ checkForUpdates();
138
+ });
139
+ watcher.on("error", () => {});
140
+ return watcher;
141
+ } catch {
142
+ return null;
143
+ }
144
+ }
145
+
146
+ let watcher = startWatcher();
147
+
148
+ // Poll every 800ms as fallback — cheap since it only reads if mtime changed
149
+ let lastMtime = 0;
150
+ try {
151
+ lastMtime = fs.statSync(forestFile).mtimeMs;
152
+ } catch {}
153
+
154
+ setInterval(() => {
155
+ try {
156
+ const mtime = fs.statSync(forestFile).mtimeMs;
157
+ if (mtime !== lastMtime) {
158
+ lastMtime = mtime;
159
+ checkForUpdates();
160
+
161
+ // Re-establish watcher in case rename killed it
162
+ if (watcher) {
163
+ try { watcher.close(); } catch {}
164
+ }
165
+ watcher = startWatcher();
113
166
  }
114
- }, 100);
115
- });
167
+ } catch {}
168
+ }, 800);
116
169
  }