honeytree 1.2.0 → 1.2.1

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 CHANGED
@@ -5,77 +5,67 @@
5
5
  [![npm version](https://img.shields.io/npm/v/honeytree.svg)](https://www.npmjs.com/package/honeytree)
6
6
  [![license](https://img.shields.io/npm/l/honeytree.svg)](https://github.com/Varun2009178/honeytree/blob/main/LICENSE)
7
7
 
8
- A 3D forest that grows from your codebase. Every file becomes a tree — rendered as a rotatable point cloud right in your terminal using block characters (░▒▓█).
8
+ Honeytree grows a pixel-art forest in your terminal as you code with Claude Code. Then it lets you plant real ones.
9
9
 
10
- No browser. No app. No install beyond npm.
10
+ ## Quick start
11
11
 
12
12
  ```bash
13
- npx honeytree
13
+ npm install -g honeytree
14
+ honeytree init
15
+ honeytree login
16
+ honeytree
14
17
  ```
15
18
 
16
- That's it. One command. Run it in any project directory.
19
+ `honeytree init` registers a Claude Code hook that plants a tree after every prompt. `honeytree login` links your terminal to your account so your forest syncs to the cloud. Open a second terminal pane, run `honeytree`, and watch your forest grow in real time.
17
20
 
18
- ---
21
+ **Live growth:** keep `honeytree` open in a second pane while you use Claude Code — the
22
+ current turn's tree grows in real time as the response streams, and bigger turns grow
23
+ taller trees. One prompt still plants one tree.
19
24
 
20
- ## What You See
25
+ ## Tree varieties
21
26
 
22
- - Each **file** is a **tree**
23
- - **Tree height** = file size (bigger files are taller)
24
- - **Tree species/color** = file type
25
- - **Canopy density** = git churn (more commits = denser canopy)
26
- - **Spatial layout** = directory structure (files in the same folder cluster together)
27
+ New trees are drawn from the varieties you've unlocked. Everyone starts with the
28
+ standard species (birch, willow); planting real trees unlocks more:
27
29
 
28
- ### Species
30
+ | Variety | Real trees | Look |
31
+ |---------|-----------|------|
32
+ | Cherry Blossom | 1 | Pink blossom canopy |
33
+ | Pine | 5 | Tall evergreen |
34
+ | Oak | 10 | Broad rounded canopy |
35
+ | Ancient | 25 | Rare tall golden tree |
36
+ | Mythic | 50 | Glowing purple tree |
29
37
 
30
- | Extension | Species | Color | Shape |
31
- |-----------|---------|-------|-------|
32
- | `.js` `.jsx` `.mjs` | Oak | Bright green | Wide ellipsoid |
33
- | `.ts` `.tsx` | Pine | Teal | Narrow cone |
34
- | `.css` `.scss` | Birch | Pink | Slim ellipsoid |
35
- | `.py` | Willow | Lime green | Drooping |
36
- | `.md` `.json` `.yaml` | Cherry | Pink-purple | Sphere |
37
-
38
- ---
39
-
40
- ## Controls
41
-
42
- | Input | Action |
43
- |-------|--------|
44
- | Mouse drag | Rotate the forest |
45
- | `+` / `=` | Zoom in |
46
- | `-` / `_` | Zoom out |
47
- | Arrow keys | Rotate camera |
48
- | Hover | Show file path (displayed at top) |
49
- | `r` | Rescan codebase |
50
- | `q` | Quit |
51
-
52
- ---
38
+ The first time a new variety is unlocked, the viewer opens with a full-screen
39
+ celebration screen. Check progress anytime with `honeytree rewards`.
53
40
 
54
- ## Install Globally (optional)
41
+ ## Plant real trees
55
42
 
56
43
  ```bash
57
- npm install -g honeytree
58
- honeytree
59
- ```
60
-
61
- Or point it at a specific directory:
62
-
63
- ```bash
64
- honeytree view ~/my-project
44
+ honeytree plant
65
45
  ```
66
46
 
67
- ---
47
+ `honeytree plant` shows how many real trees you've unlocked (every 50 virtual trees = 1 real tree), opens a $1 checkout via the Good API, then prints your receipt and any newly-unlocked varieties right in the terminal. A keepsake receipt also lands in your inbox.
68
48
 
69
- ## How It Works
49
+ ## Your public profile
70
50
 
71
- A custom terminal-based 3D engine:
51
+ Every account gets a public profile at `tryhoney.xyz/<username>` — your forest, totals,
52
+ CO₂ impact, and unlocked varieties, ready to share.
72
53
 
73
- 1. **Scanner** — walks your project, collects file metadata (size, extension, git history)
74
- 2. **Point cloud** — generates 3D points for each tree based on species shape
75
- 3. **Camera** — orbital rotation with perspective projection
76
- 4. **Rasterizer** — z-buffer depth sorting, block character shading, point splatting
54
+ ---
77
55
 
78
- Everything renders to Unicode block characters with 24-bit true color. No WebGL, no canvas, no browser — just your terminal.
56
+ ## CLI reference
57
+
58
+ | Command | What it does |
59
+ |---------|-------------|
60
+ | `honeytree` | Open the forest viewer |
61
+ | `honeytree init` | Register Claude Code hook |
62
+ | `honeytree plant` | Plant your unlocked real trees |
63
+ | `honeytree login` | Link terminal to your account |
64
+ | `honeytree logout` | Remove stored credentials |
65
+ | `honeytree sync` | Push forest to cloud |
66
+ | `honeytree rewards` | Show variety unlocks and progress |
67
+ | `honeytree status` | Check login status |
68
+ | `honeytree badge` | Generate `honeytree-badge.svg` |
79
69
 
80
70
  ---
81
71
 
package/bin/honeydew.js CHANGED
@@ -5,21 +5,84 @@ const command = process.argv[2];
5
5
  if (command === "init") {
6
6
  const { init } = await import("../src/init.js");
7
7
  await init();
8
+ } else if (command === "__session") {
9
+ // Hidden: UserPromptSubmit hook — marks the start of a turn (token baseline + chosen tree).
10
+ try {
11
+ const { readStdin } = await import("../src/stdin.js");
12
+ const { startTurn } = await import("../src/turn.js");
13
+ const payload = JSON.parse((await readStdin()) || "{}");
14
+ startTurn(payload);
15
+ } catch {}
16
+ } else if (command === "__tick") {
17
+ // Hidden: Stop hook — finalizes this turn's token-sized tree (random fallback).
18
+ const { tick } = await import("../src/plant.js");
19
+ let shape = null;
20
+ try {
21
+ const { readStdin } = await import("../src/stdin.js");
22
+ const { computeTickShape } = await import("../src/turn.js");
23
+ const payload = JSON.parse((await readStdin()) || "{}");
24
+ shape = computeTickShape(payload);
25
+ } catch {}
26
+ await tick(shape);
8
27
  } else if (command === "plant") {
9
- const { plant } = await import("../src/plant.js");
28
+ const { plant } = await import("../src/plant-real.js");
10
29
  await plant();
11
30
  } else if (command === "badge") {
12
31
  const { badge } = await import("../src/badge.js");
13
32
  await badge();
14
- } else if (command === "md") {
15
- const { generateForestMd } = await import("../src/markdown.js");
16
- await generateForestMd();
17
- } else if (!command || command === "view") {
18
- const targetDir = !command ? process.cwd() : process.argv[3] || process.cwd();
19
- const { viewer } = await import("../src/viewer.js");
20
- await viewer(targetDir);
33
+ } else if (!command) {
34
+ const { viewer } = await import("../src/viewer2d.js");
35
+ await viewer();
36
+ } else if (command === "login") {
37
+ const { loginWithDevice } = await import("../src/auth.js");
38
+ const success = await loginWithDevice();
39
+ if (success) {
40
+ const { syncNow } = await import("../src/sync.js");
41
+ console.log(" Syncing your forest...");
42
+ await syncNow();
43
+ console.log(" Forest synced to the cloud.");
44
+ }
45
+ } else if (command === "logout") {
46
+ const { clearAuth } = await import("../src/auth.js");
47
+ clearAuth();
48
+ console.log("Logged out.");
49
+ } else if (command === "sync") {
50
+ const { isLoggedIn } = await import("../src/auth.js");
51
+ if (!isLoggedIn()) {
52
+ console.log("Not logged in. Run: honeytree login");
53
+ process.exit(1);
54
+ }
55
+ const { syncNow } = await import("../src/sync.js");
56
+ const { syncRewards } = await import("../src/rewards.js");
57
+ console.log(" Syncing forest...");
58
+ await syncNow();
59
+ const rewards = await syncRewards();
60
+ if (rewards && rewards.badges.length > 0) {
61
+ console.log(` Badges: ${rewards.badges.map(b => b.label).join(", ")}`);
62
+ if (rewards.cherry) console.log(" Cherry blossom trees unlocked!");
63
+ }
64
+ console.log(" Done.");
65
+ } else if (command === "rewards") {
66
+ const { isLoggedIn } = await import("../src/auth.js");
67
+ const { syncRewards, printRewardsStatus } = await import("../src/rewards.js");
68
+ const { readForest } = await import("../src/state.js");
69
+ if (isLoggedIn()) {
70
+ console.log(" Fetching rewards...");
71
+ await syncRewards();
72
+ }
73
+ const forest = readForest();
74
+ const realTrees = 0; // local doesn't know real trees; printRewardsStatus uses cached data
75
+ printRewardsStatus(realTrees);
76
+ } else if (command === "status") {
77
+ const { getAuth } = await import("../src/auth.js");
78
+ const auth = getAuth();
79
+ if (auth && auth.username) {
80
+ console.log(`Logged in as ${auth.username}`);
81
+ } else {
82
+ console.log("Not logged in. Run: honeytree login");
83
+ }
21
84
  } else {
22
85
  console.error(`Unknown command: ${command}`);
23
- console.error("Usage: honeytree [init|plant|badge|md|view <dir>]");
86
+ console.error("Usage: honeytree [init|login|plant|badge|logout|sync|status|rewards]");
24
87
  process.exit(1);
25
88
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "honeytree",
3
- "version": "1.2.0",
4
- "description": "Your codebase is a forest 3D terminal visualization of any codebase",
3
+ "version": "1.2.1",
4
+ "description": "code with claude, and watch your forest grow! ",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "honeytree": "./bin/honeydew.js"
@@ -34,7 +34,10 @@
34
34
  "author": "Varun Nukala",
35
35
  "license": "MIT",
36
36
  "dependencies": {
37
- "chalk": "^5.4.1"
37
+ "chalk": "^5.4.1",
38
+ "honeytree": "^1.2.0",
39
+ "ink": "^7.0.4",
40
+ "react": "^19.2.6"
38
41
  },
39
42
  "engines": {
40
43
  "node": ">=18"
@@ -0,0 +1,21 @@
1
+ const TREE = `
2
+ /\\
3
+ / \\
4
+ / \\
5
+ /______\\
6
+ ||
7
+ ||
8
+ `;
9
+
10
+ const TREE_BLOSSOM = `
11
+ .::.
12
+ .:(@@):.
13
+ :(@@@@@@):
14
+ ':(@@):'
15
+ ||
16
+ ||
17
+ `;
18
+
19
+ export function asciiTree(hasBloomer) {
20
+ return hasBloomer ? TREE_BLOSSOM : TREE;
21
+ }
package/src/auth.js ADDED
@@ -0,0 +1,83 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+
5
+ const AUTH_DIR = path.join(os.homedir(), ".honeydew");
6
+ const AUTH_FILE = path.join(AUTH_DIR, "auth.json");
7
+
8
+ export function getAuth() {
9
+ try {
10
+ return JSON.parse(fs.readFileSync(AUTH_FILE, "utf8"));
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+
16
+ export function saveAuth(data) {
17
+ fs.mkdirSync(AUTH_DIR, { recursive: true });
18
+ fs.writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2));
19
+ }
20
+
21
+ export function clearAuth() {
22
+ try {
23
+ fs.unlinkSync(AUTH_FILE);
24
+ } catch {}
25
+ }
26
+
27
+ export function isLoggedIn() {
28
+ const auth = getAuth();
29
+ return !!(auth && auth.access_token);
30
+ }
31
+
32
+ export async function loginWithDevice(apiUrl = process.env.HONEYTREE_API_URL || "https://tryhoney.xyz") {
33
+ const res = await fetch(`${apiUrl}/api/auth/device`, { method: "POST" });
34
+ const contentType = res.headers.get("content-type") || "";
35
+ if (!contentType.includes("application/json")) {
36
+ console.error(" Server returned unexpected response. Is the Honeytree web app running?");
37
+ return false;
38
+ }
39
+ const { device_code, user_code, interval } = await res.json();
40
+
41
+ console.log();
42
+ console.log(` Your code: ${user_code}`);
43
+ console.log();
44
+ console.log(" Enter this code on your Honeytree dashboard to link your terminal.");
45
+ console.log(" Waiting...");
46
+
47
+ while (true) {
48
+ await new Promise((r) => setTimeout(r, (interval || 5) * 1000));
49
+
50
+ try {
51
+ const pollRes = await fetch(`${apiUrl}/api/auth/device/poll`, {
52
+ method: "POST",
53
+ headers: { "Content-Type": "application/json" },
54
+ body: JSON.stringify({ device_code }),
55
+ });
56
+
57
+ const pollContentType = pollRes.headers.get("content-type") || "";
58
+ if (!pollContentType.includes("application/json")) continue;
59
+
60
+ if (!pollRes.ok) {
61
+ if (pollRes.status === 410) {
62
+ console.error(" Code expired. Run honeytree login again.");
63
+ return false;
64
+ }
65
+ continue;
66
+ }
67
+
68
+ const data = await pollRes.json();
69
+
70
+ if (data.status === "complete") {
71
+ saveAuth({
72
+ access_token: data.access_token,
73
+ user_id: data.user.id,
74
+ username: data.user.username,
75
+ });
76
+ console.log(` Linked as ${data.user.username}`);
77
+ return true;
78
+ }
79
+ } catch {
80
+ continue;
81
+ }
82
+ }
83
+ }