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 +42 -52
- package/bin/honeydew.js +72 -9
- package/package.json +6 -3
- package/src/ascii-tree.js +21 -0
- package/src/auth.js +83 -0
- package/src/components/ForestApp.js +404 -0
- package/src/components/ForestScene.js +26 -0
- package/src/components/LiveTree.js +14 -0
- package/src/components/StatsBar.js +70 -0
- package/src/components/TreeInfoPopup.js +28 -0
- package/src/components/UnlockCelebration.js +40 -0
- package/src/growth.js +32 -0
- package/src/init.js +48 -2
- package/src/plant-real.js +129 -0
- package/src/plant.js +39 -17
- package/src/renderer.js +85 -12
- package/src/rewards.js +119 -0
- package/src/session.js +34 -0
- package/src/sprites.js +70 -5
- package/src/stdin.js +28 -0
- package/src/sync.js +44 -0
- package/src/transcript.js +62 -0
- package/src/turn.js +54 -0
- package/src/varieties.js +30 -0
- package/src/viewer2d.js +24 -0
- package/src/camera.js +0 -54
- package/src/diffactions.js +0 -32
- package/src/diffpanel.js +0 -83
- package/src/diffparser.js +0 -44
- package/src/diffwatch.js +0 -52
- package/src/markdown.js +0 -77
- package/src/pointcloud.js +0 -193
- package/src/renderer3d.js +0 -135
- package/src/scanner.js +0 -102
- package/src/viewer.js +0 -461
package/README.md
CHANGED
|
@@ -5,77 +5,67 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/honeytree)
|
|
6
6
|
[](https://github.com/Varun2009178/honeytree/blob/main/LICENSE)
|
|
7
7
|
|
|
8
|
-
|
|
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
|
-
|
|
10
|
+
## Quick start
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
|
|
13
|
+
npm install -g honeytree
|
|
14
|
+
honeytree init
|
|
15
|
+
honeytree login
|
|
16
|
+
honeytree
|
|
14
17
|
```
|
|
15
18
|
|
|
16
|
-
|
|
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
|
-
##
|
|
25
|
+
## Tree varieties
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
41
|
+
## Plant real trees
|
|
55
42
|
|
|
56
43
|
```bash
|
|
57
|
-
|
|
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
|
-
##
|
|
49
|
+
## Your public profile
|
|
70
50
|
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
15
|
-
const {
|
|
16
|
-
await
|
|
17
|
-
} else if (
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
|
|
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|
|
|
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.
|
|
4
|
-
"description": "
|
|
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
|
+
}
|