honeytree 1.0.5 → 1.0.7
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 +22 -169
- package/bin/honeydew.js +9 -12
- package/package.json +15 -8
- package/src/badge.js +42 -68
- package/src/commands/export.js +23 -0
- package/src/commands/init.js +54 -0
- package/src/commands/plant.js +12 -0
- package/src/commands/stats.js +48 -0
- package/src/commands/watch.js +117 -0
- package/src/core/animation.js +135 -0
- package/src/core/environment.js +324 -0
- package/src/core/progression.js +129 -0
- package/src/core/sprites.js +388 -0
- package/src/core/state.js +173 -0
- package/src/markdown.js +13 -44
- package/src/renderers/terminal.js +225 -0
- package/src/tracker/activity.js +43 -0
- package/src/tracker/files.js +44 -0
- package/src/tracker/git.js +77 -0
- package/src/init.js +0 -86
- package/src/plant.js +0 -101
- package/src/renderer.js +0 -302
- package/src/sprites.js +0 -245
- package/src/state.js +0 -53
- package/src/viewer.js +0 -169
package/src/viewer.js
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
|
|
3
|
-
import { renderFrame } from "./renderer.js";
|
|
4
|
-
import { getForestFile, readForest, writeForest } from "./state.js";
|
|
5
|
-
|
|
6
|
-
function writeAnsi(code) {
|
|
7
|
-
process.stdout.write(code);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function clearScreen() {
|
|
11
|
-
writeAnsi("\x1b[2J\x1b[H");
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function hideCursor() {
|
|
15
|
-
writeAnsi("\x1b[?25l");
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function showCursor() {
|
|
19
|
-
writeAnsi("\x1b[?25h");
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function moveHome() {
|
|
23
|
-
writeAnsi("\x1b[H");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function renderForest(forest, twinkleSeed = 0) {
|
|
27
|
-
moveHome();
|
|
28
|
-
process.stdout.write(renderFrame(forest, process.stdout.columns || 80, { twinkleSeed }));
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function delay(ms) {
|
|
32
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function animateNewTree(forest, newTreeId) {
|
|
36
|
-
const tree = forest.trees.find((entry) => entry.id === newTreeId);
|
|
37
|
-
if (!tree) {
|
|
38
|
-
renderForest(forest);
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const originalGrowth = tree.growth;
|
|
43
|
-
const frames = [0.12, 0.32, 0.6, originalGrowth].filter(
|
|
44
|
-
(value, index, values) => value <= originalGrowth && values.indexOf(value) === index,
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
for (let index = 0; index < frames.length; index += 1) {
|
|
48
|
-
tree.growth = frames[index];
|
|
49
|
-
renderForest(forest, index);
|
|
50
|
-
await delay(120);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
tree.growth = originalGrowth;
|
|
54
|
-
renderForest(forest);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export async function viewer() {
|
|
58
|
-
const forestFile = getForestFile();
|
|
59
|
-
let forest = readForest();
|
|
60
|
-
|
|
61
|
-
if (!forest || !fs.existsSync(forestFile)) {
|
|
62
|
-
console.error('No forest found. Run "honeytree init" first.');
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Save terminal width so plant knows how wide to spread trees
|
|
67
|
-
let ignoreNextChange = false;
|
|
68
|
-
function syncWidth() {
|
|
69
|
-
const cols = process.stdout.columns || 80;
|
|
70
|
-
if (forest.viewerWidth !== cols) {
|
|
71
|
-
forest.viewerWidth = cols;
|
|
72
|
-
ignoreNextChange = true;
|
|
73
|
-
writeForest(forest);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
syncWidth();
|
|
78
|
-
hideCursor();
|
|
79
|
-
clearScreen();
|
|
80
|
-
renderForest(forest);
|
|
81
|
-
|
|
82
|
-
let lastMaxId = forest.trees.reduce((max, tree) => Math.max(max, tree.id), 0);
|
|
83
|
-
let lastTotalPrompts = forest.totalPrompts;
|
|
84
|
-
let animating = false;
|
|
85
|
-
|
|
86
|
-
const cleanup = () => {
|
|
87
|
-
showCursor();
|
|
88
|
-
clearScreen();
|
|
89
|
-
console.log(
|
|
90
|
-
`Forest summary: ${forest.trees.length} trees across ${forest.totalPrompts} prompts`,
|
|
91
|
-
);
|
|
92
|
-
process.exit(0);
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
process.on("SIGINT", cleanup);
|
|
96
|
-
process.on("SIGTERM", cleanup);
|
|
97
|
-
process.stdout.on("resize", () => {
|
|
98
|
-
syncWidth();
|
|
99
|
-
clearScreen();
|
|
100
|
-
renderForest(forest);
|
|
101
|
-
});
|
|
102
|
-
|
|
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();
|
|
166
|
-
}
|
|
167
|
-
} catch {}
|
|
168
|
-
}, 800);
|
|
169
|
-
}
|