honeytree 1.0.5 → 1.0.6

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/bin/honeydew.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "honeytree",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Grow a forest in your terminal every time you use Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
package/src/badge.js CHANGED
@@ -2,70 +2,74 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
4
  import { readForest } from "./state.js";
5
+ import { buildScene, getWiltFactor } from "./renderer.js";
5
6
 
6
- const CHAR_WIDTH = 6.8;
7
- const PADDING = 10;
8
- const HEIGHT = 20;
7
+ const CELL = 6;
8
+ const BG = "#0d1117";
9
+ const TEXT_PAD = 18;
9
10
 
10
- function sectionWidth(text) {
11
- return Math.round(text.length * CHAR_WIDTH + PADDING * 2);
12
- }
13
-
14
- function buildMessage(forest) {
11
+ function buildStatsText(forest, biome) {
15
12
  const count = forest.trees.length;
16
13
  const streak = forest.streak || 0;
17
- const treePart = `${count} tree${count === 1 ? "" : "s"}`;
14
+ const wilt = getWiltFactor(forest.lastActiveDate);
18
15
 
19
- if (!forest.lastActiveDate) return treePart;
16
+ const parts = [`${count} tree${count === 1 ? "" : "s"}`];
20
17
 
21
- const today = new Date().toISOString().slice(0, 10);
22
- const a = new Date(forest.lastActiveDate + "T00:00:00");
23
- const b = new Date(today + "T00:00:00");
24
- const idle = Math.round((b - a) / (24 * 60 * 60 * 1000));
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
+ }
25
26
 
26
- if (idle > 0) return `${treePart} · wilting`;
27
- if (streak > 0) return `${treePart} · ${streak}d streak`;
28
- return treePart;
27
+ parts.push(biome.label);
28
+ return parts.join(" · ");
29
29
  }
30
30
 
31
- function badgeColor(forest) {
32
- if (!forest.lastActiveDate) return "#555";
33
- const today = new Date().toISOString().slice(0, 10);
34
- const a = new Date(forest.lastActiveDate + "T00:00:00");
35
- const b = new Date(today + "T00:00:00");
36
- const idle = Math.round((b - a) / (24 * 60 * 60 * 1000));
37
- if (idle > 0) return "#c4653a";
38
- return "#2ea043";
39
- }
31
+ function generateForestSVG(forest) {
32
+ const cols = Math.max(40, Math.min(forest.viewerWidth || 60, 80));
33
+ const { buffer, biome, sceneRows } = buildScene(forest, cols);
34
+
35
+ const artW = cols * CELL;
36
+ const artH = sceneRows * CELL;
37
+ const totalW = artW;
38
+ const totalH = artH + TEXT_PAD;
39
+
40
+ let rects = "";
41
+ for (let y = 0; y < sceneRows; y += 1) {
42
+ for (let x = 0; x < cols; x += 1) {
43
+ const cell = buffer[y][x];
44
+ if (!cell.color) continue;
45
+ rects += `<rect x="${x * CELL}" y="${y * CELL}" width="${CELL}" height="${CELL}" fill="${cell.color}"/>`;
46
+ }
47
+ }
40
48
 
41
- function generateSVG(label, message, color) {
42
- const lw = sectionWidth(label);
43
- const mw = sectionWidth(message);
44
- const tw = lw + mw;
45
- const lx = lw / 2;
46
- const mx = lw + mw / 2;
47
-
48
- return `<svg xmlns="http://www.w3.org/2000/svg" width="${tw}" height="${HEIGHT}" role="img" aria-label="${label}: ${message}">
49
- <title>${label}: ${message}</title>
50
- <linearGradient id="s" x2="0" y2="100%">
51
- <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
52
- <stop offset="1" stop-opacity=".1"/>
53
- </linearGradient>
54
- <clipPath id="r"><rect width="${tw}" height="${HEIGHT}" rx="3" fill="#fff"/></clipPath>
55
- <g clip-path="url(#r)">
56
- <rect width="${lw}" height="${HEIGHT}" fill="#555"/>
57
- <rect x="${lw}" width="${mw}" height="${HEIGHT}" fill="${color}"/>
58
- <rect width="${tw}" height="${HEIGHT}" fill="url(#s)"/>
59
- </g>
60
- <g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="11">
61
- <text aria-hidden="true" x="${lx}" y="15" fill="#010101" fill-opacity=".3">${label}</text>
62
- <text x="${lx}" y="14">${label}</text>
63
- <text aria-hidden="true" x="${mx}" y="15" fill="#010101" fill-opacity=".3">${message}</text>
64
- <text x="${mx}" y="14">${message}</text>
65
- </g>
49
+ const stats = buildStatsText(forest, biome);
50
+
51
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${totalW}" height="${totalH}" viewBox="0 0 ${totalW} ${totalH}">
52
+ <rect width="${totalW}" height="${totalH}" fill="${BG}" rx="6"/>
53
+ ${rects}
54
+ <text x="${totalW / 2}" y="${artH + 13}" text-anchor="middle" fill="#8e8a84" font-family="monospace" font-size="10">${stats}</text>
66
55
  </svg>`;
67
56
  }
68
57
 
58
+ export function writeBadgeSVG(forest, outPath) {
59
+ fs.writeFileSync(outPath, generateForestSVG(forest));
60
+ }
61
+
62
+ export function findBadgeFile() {
63
+ let dir = process.cwd();
64
+ const root = path.parse(dir).root;
65
+ while (dir !== root) {
66
+ const candidate = path.join(dir, "honeytree-badge.svg");
67
+ if (fs.existsSync(candidate)) return candidate;
68
+ dir = path.dirname(dir);
69
+ }
70
+ return null;
71
+ }
72
+
69
73
  export async function badge() {
70
74
  const forest = readForest();
71
75
  if (!forest) {
@@ -73,17 +77,12 @@ export async function badge() {
73
77
  process.exit(1);
74
78
  }
75
79
 
76
- const label = "honeytree";
77
- const message = buildMessage(forest);
78
- const color = badgeColor(forest);
79
- const svg = generateSVG(label, message, color);
80
-
81
80
  const outPath = path.resolve("honeytree-badge.svg");
82
- fs.writeFileSync(outPath, svg);
81
+ writeBadgeSVG(forest, outPath);
83
82
 
84
83
  console.log(`Badge written to ${outPath}`);
85
84
  console.log("");
86
- console.log("Add this to your README to show your Honeytree stats.");
85
+ console.log("Add this to your README to show your Honeytree forest.");
87
86
  console.log("The badge links to https://github.com/Varun2009178/honeytree");
88
87
  console.log("");
89
88
  console.log(
package/src/plant.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { getSprite, TREE_TYPES } from "./sprites.js";
2
2
  import { createEmptyForest, readForest, writeForest } from "./state.js";
3
+ import { findBadgeFile, writeBadgeSVG } from "./badge.js";
3
4
 
4
5
  const MIN_GAP = 4;
5
6
  const DEFAULT_WIDTH = 80;
@@ -98,4 +99,10 @@ export async function plant() {
98
99
  forest.totalPrompts += 1;
99
100
 
100
101
  writeForest(forest);
102
+
103
+ // Auto-refresh badge if one exists in the repo
104
+ try {
105
+ const badgePath = findBadgeFile();
106
+ if (badgePath) writeBadgeSVG(forest, badgePath);
107
+ } catch {}
101
108
  }
package/src/renderer.js CHANGED
@@ -7,9 +7,10 @@ const TREE_ROWS = 7;
7
7
  const GROUND_ROWS = 2;
8
8
  const SPACER_ROWS = 1;
9
9
  const STATS_ROWS = 1;
10
+ const CTA_ROWS = 1;
10
11
 
11
12
  export const SCENE_HEIGHT =
12
- SKY_ROWS + TREE_ROWS + GROUND_ROWS + SPACER_ROWS + STATS_ROWS;
13
+ SKY_ROWS + TREE_ROWS + GROUND_ROWS + SPACER_ROWS + STATS_ROWS + CTA_ROWS;
13
14
 
14
15
  const STATS_ACCENT = "#f5a50b";
15
16
  const STATS_TEXT = "#8e8a84";
@@ -248,7 +249,7 @@ export function renderFrame(forest, termWidth = 80, options = {}) {
248
249
  applyFog(buffer, wilt, width);
249
250
 
250
251
  const lines = [];
251
- for (let y = 0; y < SCENE_HEIGHT - SPACER_ROWS - STATS_ROWS; y += 1) {
252
+ for (let y = 0; y < SCENE_HEIGHT - SPACER_ROWS - STATS_ROWS - CTA_ROWS; y += 1) {
252
253
  let line = "";
253
254
  for (const cell of buffer[y]) {
254
255
  if (!cell.color) {
@@ -264,10 +265,56 @@ export function renderFrame(forest, termWidth = 80, options = {}) {
264
265
 
265
266
  lines.push("");
266
267
  lines.push(buildStatsLine(forest, biome));
268
+ lines.push(
269
+ chalk.hex("#555555")(" add your forest to your README → ") +
270
+ chalk.hex(STATS_ACCENT)("honeytree badge"),
271
+ );
267
272
 
268
273
  return lines.join("\n");
269
274
  }
270
275
 
276
+ export function buildScene(forest, width) {
277
+ const w = Math.max(40, width);
278
+ const sceneRows = SKY_ROWS + TREE_ROWS + GROUND_ROWS;
279
+ const buffer = Array.from({ length: sceneRows }, () =>
280
+ Array.from({ length: w }, () => ({ char: " ", color: null })),
281
+ );
282
+ const groundStart = SKY_ROWS + TREE_ROWS;
283
+ const biome = getBiome(forest.trees.length);
284
+ const wilt = getWiltFactor(forest.lastActiveDate);
285
+
286
+ for (const star of generateStars(w, biome, 0)) {
287
+ if (star.y < sceneRows) {
288
+ buffer[star.y][star.x] = { char: star.char, color: star.color };
289
+ }
290
+ }
291
+
292
+ for (let rowIndex = 0; rowIndex < GROUND_ROWS; rowIndex += 1) {
293
+ for (let x = 0; x < w; x += 1) {
294
+ buffer[groundStart + rowIndex][x] = { char: "█", color: biome.ground[rowIndex] };
295
+ }
296
+ }
297
+
298
+ const treeBaseY = groundStart - 1;
299
+ for (const tree of forest.trees) {
300
+ compositeSprite(buffer, getSprite(tree.type, tree.growth), tree.x, treeBaseY);
301
+ }
302
+
303
+ applyFog(buffer, wilt, w);
304
+
305
+ if (wilt > 0) {
306
+ for (let y = SKY_ROWS; y < sceneRows; y += 1) {
307
+ for (let x = 0; x < w; x += 1) {
308
+ if (buffer[y][x].color) {
309
+ buffer[y][x].color = wiltColor(buffer[y][x].color, wilt);
310
+ }
311
+ }
312
+ }
313
+ }
314
+
315
+ return { buffer, biome, sceneRows };
316
+ }
317
+
271
318
  export function renderPlainText(forest, width = 60) {
272
319
  const w = Math.max(40, Math.min(width, 80));
273
320
  const buffer = createBuffer(w);
@@ -290,7 +337,7 @@ export function renderPlainText(forest, width = 60) {
290
337
  }
291
338
 
292
339
  const lines = [];
293
- for (let y = 0; y < SCENE_HEIGHT - SPACER_ROWS - STATS_ROWS; y += 1) {
340
+ for (let y = 0; y < SCENE_HEIGHT - SPACER_ROWS - STATS_ROWS - CTA_ROWS; y += 1) {
294
341
  let line = "";
295
342
  for (const cell of buffer[y]) {
296
343
  line += cell.char;