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/renderer.js
DELETED
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
|
|
3
|
-
import { getSprite, TREE_TYPES } from "./sprites.js";
|
|
4
|
-
|
|
5
|
-
const SKY_ROWS = 4;
|
|
6
|
-
const TREE_ROWS = 7;
|
|
7
|
-
const GROUND_ROWS = 2;
|
|
8
|
-
const SPACER_ROWS = 1;
|
|
9
|
-
const STATS_ROWS = 1;
|
|
10
|
-
|
|
11
|
-
export const SCENE_HEIGHT =
|
|
12
|
-
SKY_ROWS + TREE_ROWS + GROUND_ROWS + SPACER_ROWS + STATS_ROWS;
|
|
13
|
-
|
|
14
|
-
const STATS_ACCENT = "#f5a50b";
|
|
15
|
-
const STATS_TEXT = "#8e8a84";
|
|
16
|
-
const STATS_WARN = "#c4653a";
|
|
17
|
-
const STREAK_COLOR = "#e8a33a";
|
|
18
|
-
const BAR_FILL = "#6cb95e";
|
|
19
|
-
const BAR_EMPTY = "#3d3d3d";
|
|
20
|
-
const MILESTONES = [10, 25, 50, 100, 250, 500, 1000];
|
|
21
|
-
|
|
22
|
-
// Wilting — lerp toward dry brown when idle
|
|
23
|
-
const WILT_TARGET = { r: 0x8a, g: 0x6a, b: 0x4a };
|
|
24
|
-
|
|
25
|
-
function parseHex(hex) {
|
|
26
|
-
const h = hex.startsWith("#") ? hex.slice(1) : hex;
|
|
27
|
-
return {
|
|
28
|
-
r: parseInt(h.slice(0, 2), 16),
|
|
29
|
-
g: parseInt(h.slice(2, 4), 16),
|
|
30
|
-
b: parseInt(h.slice(4, 6), 16),
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function toHex({ r, g, b }) {
|
|
35
|
-
const c = (v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, "0");
|
|
36
|
-
return `#${c(r)}${c(g)}${c(b)}`;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function wiltColor(hex, factor) {
|
|
40
|
-
if (factor <= 0) return hex;
|
|
41
|
-
const c = parseHex(hex);
|
|
42
|
-
return toHex({
|
|
43
|
-
r: c.r + (WILT_TARGET.r - c.r) * factor,
|
|
44
|
-
g: c.g + (WILT_TARGET.g - c.g) * factor,
|
|
45
|
-
b: c.b + (WILT_TARGET.b - c.b) * factor,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function getWiltFactor(lastActiveDate) {
|
|
50
|
-
if (!lastActiveDate) return 0;
|
|
51
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
52
|
-
const a = new Date(lastActiveDate + "T00:00:00");
|
|
53
|
-
const b = new Date(today + "T00:00:00");
|
|
54
|
-
const days = Math.round((b - a) / (24 * 60 * 60 * 1000));
|
|
55
|
-
if (days <= 0) return 0;
|
|
56
|
-
if (days === 1) return 0.25;
|
|
57
|
-
if (days === 2) return 0.45;
|
|
58
|
-
if (days === 3) return 0.65;
|
|
59
|
-
return Math.min(0.85, 0.65 + (days - 3) * 0.05);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Fog — procedural haze that thickens with inactivity
|
|
63
|
-
const FOG_CHARS = ["░", "░", "▒"];
|
|
64
|
-
const FOG_COLOR_UPPER = "#9a9a9a";
|
|
65
|
-
const FOG_COLOR_LOWER = "#6a6a6a";
|
|
66
|
-
|
|
67
|
-
function applyFog(buffer, wilt, width) {
|
|
68
|
-
if (wilt <= 0) return;
|
|
69
|
-
// Higher wilt → lower threshold → more fog
|
|
70
|
-
const threshold = Math.max(3, Math.round(18 * (1 - wilt)));
|
|
71
|
-
const fogStart = SKY_ROWS - 2; // creep into lower sky
|
|
72
|
-
const fogEnd = SKY_ROWS + TREE_ROWS + GROUND_ROWS;
|
|
73
|
-
|
|
74
|
-
for (let y = Math.max(0, fogStart); y < fogEnd; y += 1) {
|
|
75
|
-
for (let x = 0; x < width; x += 1) {
|
|
76
|
-
const h = hash(x * 31 + y * 97 + 12345);
|
|
77
|
-
if (h % threshold !== 0) continue;
|
|
78
|
-
const fogChar = FOG_CHARS[h % FOG_CHARS.length];
|
|
79
|
-
const blend = (y - fogStart) / (fogEnd - fogStart);
|
|
80
|
-
const fogColor = blend > 0.5 ? FOG_COLOR_LOWER : FOG_COLOR_UPPER;
|
|
81
|
-
buffer[y][x] = { char: fogChar, color: fogColor };
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Biomes evolve as the forest grows — never resets, only gets richer
|
|
87
|
-
const BIOMES = [
|
|
88
|
-
{ // 0-9: sparse clearing
|
|
89
|
-
ground: ["#2a3a28", "#1e2d1c"],
|
|
90
|
-
starGlyphs: ["·", ".", " ", " "],
|
|
91
|
-
starDensity: 12,
|
|
92
|
-
starColors: ["#3a3a3a", "#444444"],
|
|
93
|
-
label: "clearing",
|
|
94
|
-
},
|
|
95
|
-
{ // 10-24: young grove
|
|
96
|
-
ground: ["#22492d", "#18361f"],
|
|
97
|
-
starGlyphs: ["·", "·", "✦", "."],
|
|
98
|
-
starDensity: 9,
|
|
99
|
-
starColors: ["#444444", "#5d5d5d"],
|
|
100
|
-
label: "grove",
|
|
101
|
-
},
|
|
102
|
-
{ // 25-49: dense woodland
|
|
103
|
-
ground: ["#1e4a28", "#163a1e"],
|
|
104
|
-
starGlyphs: ["·", "✦", "✧", "·", "."],
|
|
105
|
-
starDensity: 7,
|
|
106
|
-
starColors: ["#4d4d4d", "#5d5d5d", "#6a6a55"],
|
|
107
|
-
label: "woodland",
|
|
108
|
-
},
|
|
109
|
-
{ // 50-99: old growth
|
|
110
|
-
ground: ["#1a5230", "#124020"],
|
|
111
|
-
starGlyphs: ["✦", "✧", "·", "·", "✦", "."],
|
|
112
|
-
starDensity: 6,
|
|
113
|
-
starColors: ["#5d5d5d", "#6d6d5a", "#7a7a60"],
|
|
114
|
-
label: "old growth",
|
|
115
|
-
},
|
|
116
|
-
{ // 100+: ancient forest
|
|
117
|
-
ground: ["#165a32", "#0e4822"],
|
|
118
|
-
starGlyphs: ["✦", "✧", "·", "✦", "⋆", "."],
|
|
119
|
-
starDensity: 5,
|
|
120
|
-
starColors: ["#6d6d5a", "#7a7a60", "#8a8a6a"],
|
|
121
|
-
label: "ancient forest",
|
|
122
|
-
},
|
|
123
|
-
];
|
|
124
|
-
|
|
125
|
-
function getBiome(treeCount) {
|
|
126
|
-
if (treeCount < 10) return BIOMES[0];
|
|
127
|
-
if (treeCount < 25) return BIOMES[1];
|
|
128
|
-
if (treeCount < 50) return BIOMES[2];
|
|
129
|
-
if (treeCount < 100) return BIOMES[3];
|
|
130
|
-
return BIOMES[4];
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function createBuffer(width) {
|
|
134
|
-
return Array.from({ length: SCENE_HEIGHT }, () =>
|
|
135
|
-
Array.from({ length: width }, () => ({ char: " ", color: null })),
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function hash(seed) {
|
|
140
|
-
let value = seed >>> 0;
|
|
141
|
-
value = Math.imul((value >>> 16) ^ value, 0x45d9f3b) >>> 0;
|
|
142
|
-
value = Math.imul((value >>> 16) ^ value, 0x45d9f3b) >>> 0;
|
|
143
|
-
return ((value >>> 16) ^ value) >>> 0;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function generateStars(width, biome, twinkle = 0) {
|
|
147
|
-
const stars = [];
|
|
148
|
-
for (let x = 0; x < width; x += 1) {
|
|
149
|
-
const seeded = hash(x + width * 17 + twinkle * 101);
|
|
150
|
-
if (seeded % biome.starDensity !== 0) continue;
|
|
151
|
-
stars.push({
|
|
152
|
-
x,
|
|
153
|
-
y: seeded % SKY_ROWS,
|
|
154
|
-
char: biome.starGlyphs[seeded % biome.starGlyphs.length],
|
|
155
|
-
color: biome.starColors[seeded % biome.starColors.length],
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
return stars;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function compositeSprite(buffer, sprite, centerX, baseY) {
|
|
162
|
-
const offsetX = centerX - Math.floor(sprite.width / 2);
|
|
163
|
-
for (let rowIndex = 0; rowIndex < sprite.rows.length; rowIndex += 1) {
|
|
164
|
-
const targetY = baseY - rowIndex;
|
|
165
|
-
if (targetY < 0 || targetY >= buffer.length) continue;
|
|
166
|
-
const row = sprite.rows[rowIndex];
|
|
167
|
-
for (let columnIndex = 0; columnIndex < row.length; columnIndex += 1) {
|
|
168
|
-
const targetX = offsetX + columnIndex;
|
|
169
|
-
if (targetX < 0 || targetX >= buffer[0].length) continue;
|
|
170
|
-
const [char, color] = row[columnIndex];
|
|
171
|
-
if (!color) continue;
|
|
172
|
-
buffer[targetY][targetX] = { char, color };
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function getNextMilestone(treeCount) {
|
|
178
|
-
return MILESTONES.find((value) => treeCount < value) ?? treeCount + 100;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function getNextTreeType(treeCount) {
|
|
182
|
-
return TREE_TYPES[treeCount % TREE_TYPES.length];
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function buildStreakSegment(forest) {
|
|
186
|
-
const wilt = getWiltFactor(forest.lastActiveDate);
|
|
187
|
-
const streak = forest.streak || 0;
|
|
188
|
-
|
|
189
|
-
if (wilt > 0) {
|
|
190
|
-
const a = new Date(forest.lastActiveDate + "T00:00:00");
|
|
191
|
-
const b = new Date(new Date().toISOString().slice(0, 10) + "T00:00:00");
|
|
192
|
-
const idle = Math.round((b - a) / (24 * 60 * 60 * 1000));
|
|
193
|
-
return chalk.hex(STATS_WARN)(`wilting (${idle}d idle)`);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (streak <= 0) return chalk.hex(STATS_TEXT)("no streak");
|
|
197
|
-
return chalk.hex(STREAK_COLOR)(`${streak}-day streak`);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function buildStatsLine(forest, biome) {
|
|
201
|
-
const treeCount = forest.trees.length;
|
|
202
|
-
const milestone = getNextMilestone(treeCount);
|
|
203
|
-
const progress = milestone === 0 ? 0 : treeCount / milestone;
|
|
204
|
-
const barWidth = 12;
|
|
205
|
-
const filledWidth = Math.max(0, Math.min(barWidth, Math.round(progress * barWidth)));
|
|
206
|
-
const bar =
|
|
207
|
-
chalk.hex(BAR_FILL)("█".repeat(filledWidth)) +
|
|
208
|
-
chalk.hex(BAR_EMPTY)("░".repeat(barWidth - filledWidth));
|
|
209
|
-
|
|
210
|
-
return (
|
|
211
|
-
chalk.hex(STATS_ACCENT)(" honeytree") +
|
|
212
|
-
chalk.hex(STATS_TEXT)(
|
|
213
|
-
` · ${treeCount} tree${treeCount === 1 ? "" : "s"} · `,
|
|
214
|
-
) +
|
|
215
|
-
buildStreakSegment(forest) +
|
|
216
|
-
chalk.hex(STATS_TEXT)(" · ") +
|
|
217
|
-
bar +
|
|
218
|
-
chalk.hex(STATS_TEXT)(` next: ${getNextTreeType(treeCount)}`) +
|
|
219
|
-
chalk.hex("#555555")(` [${biome.label}]`)
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export function renderFrame(forest, termWidth = 80, options = {}) {
|
|
224
|
-
const width = Math.max(40, termWidth);
|
|
225
|
-
const buffer = createBuffer(width);
|
|
226
|
-
const groundStart = SKY_ROWS + TREE_ROWS;
|
|
227
|
-
const biome = getBiome(forest.trees.length);
|
|
228
|
-
const wilt = getWiltFactor(forest.lastActiveDate);
|
|
229
|
-
|
|
230
|
-
for (const star of generateStars(width, biome, options.twinkleSeed ?? 0)) {
|
|
231
|
-
buffer[star.y][star.x] = { char: star.char, color: star.color };
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
for (let rowIndex = 0; rowIndex < GROUND_ROWS; rowIndex += 1) {
|
|
235
|
-
for (let x = 0; x < width; x += 1) {
|
|
236
|
-
buffer[groundStart + rowIndex][x] = {
|
|
237
|
-
char: "█",
|
|
238
|
-
color: biome.ground[rowIndex],
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const treeBaseY = groundStart - 1;
|
|
244
|
-
for (const tree of forest.trees) {
|
|
245
|
-
compositeSprite(buffer, getSprite(tree.type, tree.growth), tree.x, treeBaseY);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
applyFog(buffer, wilt, width);
|
|
249
|
-
|
|
250
|
-
const lines = [];
|
|
251
|
-
for (let y = 0; y < SCENE_HEIGHT - SPACER_ROWS - STATS_ROWS; y += 1) {
|
|
252
|
-
let line = "";
|
|
253
|
-
for (const cell of buffer[y]) {
|
|
254
|
-
if (!cell.color) {
|
|
255
|
-
line += cell.char;
|
|
256
|
-
} else {
|
|
257
|
-
// Apply wilting to tree rows and ground (skip sky)
|
|
258
|
-
const color = wilt > 0 && y >= SKY_ROWS ? wiltColor(cell.color, wilt) : cell.color;
|
|
259
|
-
line += chalk.hex(color)(cell.char);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
lines.push(line);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
lines.push("");
|
|
266
|
-
lines.push(buildStatsLine(forest, biome));
|
|
267
|
-
|
|
268
|
-
return lines.join("\n");
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
export function renderPlainText(forest, width = 60) {
|
|
272
|
-
const w = Math.max(40, Math.min(width, 80));
|
|
273
|
-
const buffer = createBuffer(w);
|
|
274
|
-
const groundStart = SKY_ROWS + TREE_ROWS;
|
|
275
|
-
const biome = getBiome(forest.trees.length);
|
|
276
|
-
|
|
277
|
-
for (const star of generateStars(w, biome, 0)) {
|
|
278
|
-
buffer[star.y][star.x] = { char: star.char, color: star.color };
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
for (let rowIndex = 0; rowIndex < GROUND_ROWS; rowIndex += 1) {
|
|
282
|
-
for (let x = 0; x < w; x += 1) {
|
|
283
|
-
buffer[groundStart + rowIndex][x] = { char: "█", color: "#333" };
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const treeBaseY = groundStart - 1;
|
|
288
|
-
for (const tree of forest.trees) {
|
|
289
|
-
compositeSprite(buffer, getSprite(tree.type, tree.growth), tree.x, treeBaseY);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const lines = [];
|
|
293
|
-
for (let y = 0; y < SCENE_HEIGHT - SPACER_ROWS - STATS_ROWS; y += 1) {
|
|
294
|
-
let line = "";
|
|
295
|
-
for (const cell of buffer[y]) {
|
|
296
|
-
line += cell.char;
|
|
297
|
-
}
|
|
298
|
-
lines.push(line.trimEnd());
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return lines.join("\n");
|
|
302
|
-
}
|
package/src/sprites.js
DELETED
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
export const TREE_TYPES = ["oak", "pine", "birch", "willow", "cherry"];
|
|
2
|
-
|
|
3
|
-
const COLORS = {
|
|
4
|
-
canopyDark: "#3f7132",
|
|
5
|
-
canopyMid: "#5b9a4a",
|
|
6
|
-
canopyLight: "#7cc96a",
|
|
7
|
-
canopyDeep: "#2d5b29",
|
|
8
|
-
canopyBright: "#a4e28d",
|
|
9
|
-
trunkDark: "#6f4c2f",
|
|
10
|
-
trunkMid: "#8e6238",
|
|
11
|
-
trunkLight: "#b18552",
|
|
12
|
-
birchTrunk: "#d9d6d2",
|
|
13
|
-
cherryPink: "#de93b8",
|
|
14
|
-
cherryBloom: "#f0b7cf",
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
function parse(template, palette) {
|
|
18
|
-
const lines = template.trim().split("\n");
|
|
19
|
-
const width = Math.max(...lines.map((line) => line.length));
|
|
20
|
-
const rows = lines
|
|
21
|
-
.map((line) => line.padEnd(width, " "))
|
|
22
|
-
.map((line) =>
|
|
23
|
-
Array.from(line, (token) => {
|
|
24
|
-
const color = palette[token] ?? null;
|
|
25
|
-
return color ? ["█", color] : [" ", null];
|
|
26
|
-
}),
|
|
27
|
-
)
|
|
28
|
-
.reverse();
|
|
29
|
-
|
|
30
|
-
return { rows, width };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const SPRITES = {
|
|
34
|
-
oak: {
|
|
35
|
-
seed: parse(
|
|
36
|
-
`
|
|
37
|
-
g
|
|
38
|
-
t
|
|
39
|
-
`,
|
|
40
|
-
{ g: COLORS.canopyMid, t: COLORS.trunkMid },
|
|
41
|
-
),
|
|
42
|
-
sapling: parse(
|
|
43
|
-
`
|
|
44
|
-
gg
|
|
45
|
-
ggg
|
|
46
|
-
t
|
|
47
|
-
`,
|
|
48
|
-
{ g: COLORS.canopyMid, t: COLORS.trunkMid },
|
|
49
|
-
),
|
|
50
|
-
young: parse(
|
|
51
|
-
`
|
|
52
|
-
gg
|
|
53
|
-
gGGg
|
|
54
|
-
ggGGgg
|
|
55
|
-
tt
|
|
56
|
-
tt
|
|
57
|
-
`,
|
|
58
|
-
{ g: COLORS.canopyMid, G: COLORS.canopyDark, t: COLORS.trunkMid },
|
|
59
|
-
),
|
|
60
|
-
full: parse(
|
|
61
|
-
`
|
|
62
|
-
gg
|
|
63
|
-
gGGGG
|
|
64
|
-
ggGGGGgg
|
|
65
|
-
gGGGGg
|
|
66
|
-
tt
|
|
67
|
-
tt
|
|
68
|
-
`,
|
|
69
|
-
{ g: COLORS.canopyMid, G: COLORS.canopyDark, t: COLORS.trunkMid },
|
|
70
|
-
),
|
|
71
|
-
},
|
|
72
|
-
pine: {
|
|
73
|
-
seed: parse(
|
|
74
|
-
`
|
|
75
|
-
g
|
|
76
|
-
t
|
|
77
|
-
`,
|
|
78
|
-
{ g: COLORS.canopyDeep, t: COLORS.trunkDark },
|
|
79
|
-
),
|
|
80
|
-
sapling: parse(
|
|
81
|
-
`
|
|
82
|
-
g
|
|
83
|
-
gg
|
|
84
|
-
ggg
|
|
85
|
-
t
|
|
86
|
-
`,
|
|
87
|
-
{ g: COLORS.canopyDeep, t: COLORS.trunkDark },
|
|
88
|
-
),
|
|
89
|
-
young: parse(
|
|
90
|
-
`
|
|
91
|
-
g
|
|
92
|
-
ggg
|
|
93
|
-
gGGGg
|
|
94
|
-
ggGGGG
|
|
95
|
-
t
|
|
96
|
-
t
|
|
97
|
-
`,
|
|
98
|
-
{ g: COLORS.canopyDeep, G: COLORS.canopyDark, t: COLORS.trunkDark },
|
|
99
|
-
),
|
|
100
|
-
full: parse(
|
|
101
|
-
`
|
|
102
|
-
g
|
|
103
|
-
ggg
|
|
104
|
-
gGGGg
|
|
105
|
-
gGGGGGg
|
|
106
|
-
ggGGGGGG
|
|
107
|
-
gGGGGG
|
|
108
|
-
t
|
|
109
|
-
t
|
|
110
|
-
`,
|
|
111
|
-
{ g: COLORS.canopyDeep, G: COLORS.canopyDark, t: COLORS.trunkDark },
|
|
112
|
-
),
|
|
113
|
-
},
|
|
114
|
-
birch: {
|
|
115
|
-
seed: parse(
|
|
116
|
-
`
|
|
117
|
-
g
|
|
118
|
-
b
|
|
119
|
-
`,
|
|
120
|
-
{ g: COLORS.canopyLight, b: COLORS.birchTrunk },
|
|
121
|
-
),
|
|
122
|
-
sapling: parse(
|
|
123
|
-
`
|
|
124
|
-
gg
|
|
125
|
-
ghg
|
|
126
|
-
b
|
|
127
|
-
`,
|
|
128
|
-
{ g: COLORS.canopyLight, h: COLORS.canopyBright, b: COLORS.birchTrunk },
|
|
129
|
-
),
|
|
130
|
-
young: parse(
|
|
131
|
-
`
|
|
132
|
-
hg
|
|
133
|
-
hggg
|
|
134
|
-
ggghhg
|
|
135
|
-
bb
|
|
136
|
-
bb
|
|
137
|
-
`,
|
|
138
|
-
{ g: COLORS.canopyLight, h: COLORS.canopyBright, b: COLORS.birchTrunk },
|
|
139
|
-
),
|
|
140
|
-
full: parse(
|
|
141
|
-
`
|
|
142
|
-
hh
|
|
143
|
-
hgggh
|
|
144
|
-
ggghhgg
|
|
145
|
-
hgggh
|
|
146
|
-
bb
|
|
147
|
-
bb
|
|
148
|
-
`,
|
|
149
|
-
{ g: COLORS.canopyLight, h: COLORS.canopyBright, b: COLORS.birchTrunk },
|
|
150
|
-
),
|
|
151
|
-
},
|
|
152
|
-
willow: {
|
|
153
|
-
seed: parse(
|
|
154
|
-
`
|
|
155
|
-
g
|
|
156
|
-
t
|
|
157
|
-
`,
|
|
158
|
-
{ g: COLORS.canopyLight, t: COLORS.trunkMid },
|
|
159
|
-
),
|
|
160
|
-
sapling: parse(
|
|
161
|
-
`
|
|
162
|
-
ggg
|
|
163
|
-
ggggg
|
|
164
|
-
ttt
|
|
165
|
-
`,
|
|
166
|
-
{ g: COLORS.canopyLight, t: COLORS.trunkMid },
|
|
167
|
-
),
|
|
168
|
-
young: parse(
|
|
169
|
-
`
|
|
170
|
-
gggg
|
|
171
|
-
gggggg
|
|
172
|
-
gg ggg gg
|
|
173
|
-
gg gg
|
|
174
|
-
tt
|
|
175
|
-
tt
|
|
176
|
-
`,
|
|
177
|
-
{ g: COLORS.canopyLight, t: COLORS.trunkMid },
|
|
178
|
-
),
|
|
179
|
-
full: parse(
|
|
180
|
-
`
|
|
181
|
-
ggggg
|
|
182
|
-
gggggggg
|
|
183
|
-
gg ggggg gg
|
|
184
|
-
gg ggg gg
|
|
185
|
-
gg gg
|
|
186
|
-
tt
|
|
187
|
-
tt
|
|
188
|
-
`,
|
|
189
|
-
{ g: COLORS.canopyLight, t: COLORS.trunkMid },
|
|
190
|
-
),
|
|
191
|
-
},
|
|
192
|
-
cherry: {
|
|
193
|
-
seed: parse(
|
|
194
|
-
`
|
|
195
|
-
p
|
|
196
|
-
t
|
|
197
|
-
`,
|
|
198
|
-
{ p: COLORS.cherryPink, t: COLORS.trunkLight },
|
|
199
|
-
),
|
|
200
|
-
sapling: parse(
|
|
201
|
-
`
|
|
202
|
-
pp
|
|
203
|
-
pPp
|
|
204
|
-
t
|
|
205
|
-
`,
|
|
206
|
-
{ p: COLORS.cherryBloom, P: COLORS.cherryPink, t: COLORS.trunkLight },
|
|
207
|
-
),
|
|
208
|
-
young: parse(
|
|
209
|
-
`
|
|
210
|
-
pP
|
|
211
|
-
pPPp
|
|
212
|
-
pPPpPP
|
|
213
|
-
tt
|
|
214
|
-
tt
|
|
215
|
-
`,
|
|
216
|
-
{ p: COLORS.cherryBloom, P: COLORS.cherryPink, t: COLORS.trunkLight },
|
|
217
|
-
),
|
|
218
|
-
full: parse(
|
|
219
|
-
`
|
|
220
|
-
pPp
|
|
221
|
-
pPPPPp
|
|
222
|
-
pPPpPPPp
|
|
223
|
-
pPPPpp
|
|
224
|
-
tt
|
|
225
|
-
tt
|
|
226
|
-
`,
|
|
227
|
-
{ p: COLORS.cherryBloom, P: COLORS.cherryPink, t: COLORS.trunkLight },
|
|
228
|
-
),
|
|
229
|
-
},
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
function getGrowthStage(growth) {
|
|
233
|
-
if (growth < 0.2) return "seed";
|
|
234
|
-
if (growth < 0.5) return "sapling";
|
|
235
|
-
if (growth < 0.8) return "young";
|
|
236
|
-
return "full";
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
export function getSprite(type, growth) {
|
|
240
|
-
const spriteSet = SPRITES[type];
|
|
241
|
-
if (!spriteSet) {
|
|
242
|
-
throw new Error(`Unknown tree type: ${type}`);
|
|
243
|
-
}
|
|
244
|
-
return spriteSet[getGrowthStage(growth)];
|
|
245
|
-
}
|
package/src/state.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import crypto from "node:crypto";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
|
|
6
|
-
function resolveHoneydewDir() {
|
|
7
|
-
return process.env.HONEYDEW_DIR || path.join(os.homedir(), ".honeydew");
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function resolveForestFile() {
|
|
11
|
-
return path.join(resolveHoneydewDir(), "forest.json");
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const HONEYDEW_DIR = resolveHoneydewDir();
|
|
15
|
-
export const FOREST_FILE = resolveForestFile();
|
|
16
|
-
|
|
17
|
-
export function getHoneydewDir() {
|
|
18
|
-
return resolveHoneydewDir();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function getForestFile() {
|
|
22
|
-
return resolveForestFile();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function createEmptyForest() {
|
|
26
|
-
return {
|
|
27
|
-
trees: [],
|
|
28
|
-
totalPrompts: 0,
|
|
29
|
-
createdAt: new Date().toISOString(),
|
|
30
|
-
lastActiveDate: new Date().toISOString().slice(0, 10),
|
|
31
|
-
streak: 0,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function readForest() {
|
|
36
|
-
try {
|
|
37
|
-
return JSON.parse(fs.readFileSync(resolveForestFile(), "utf8"));
|
|
38
|
-
} catch {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function writeForest(state) {
|
|
44
|
-
const dir = resolveHoneydewDir();
|
|
45
|
-
const file = resolveForestFile();
|
|
46
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
47
|
-
const tmpFile = path.join(
|
|
48
|
-
dir,
|
|
49
|
-
`forest.${process.pid}.${crypto.randomBytes(4).toString("hex")}.tmp`,
|
|
50
|
-
);
|
|
51
|
-
fs.writeFileSync(tmpFile, JSON.stringify(state, null, 2));
|
|
52
|
-
fs.renameSync(tmpFile, file);
|
|
53
|
-
}
|