habithive-code 0.2.0 → 0.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.
Files changed (3) hide show
  1. package/README.md +21 -0
  2. package/package.json +1 -1
  3. package/src/logo.js +55 -12
package/README.md CHANGED
@@ -10,6 +10,11 @@ two things effortless:
10
10
  > them from their official sources under their own licenses. All credit for those
11
11
  > projects belongs to their authors (see [Credits](#credits)).
12
12
 
13
+ ## Prerequisites
14
+
15
+ - **Node.js ≥ 18**
16
+ - Internet access to fetch `opencode` and `oh-my-openagent` on first `habithive setup` (nothing else is required up front)
17
+
13
18
  ## Install
14
19
 
15
20
  ```sh
@@ -17,6 +22,22 @@ npm i -g habithive-code
17
22
  habithive setup # full stack (below). add --minimal to skip the extras
18
23
  ```
19
24
 
25
+ ### Clone from source (GitLab)
26
+
27
+ For development, or if you'd rather run from a checkout than the published package:
28
+
29
+ ```sh
30
+ git clone https://repository.hclhealthcare.in/hive/habit-hive-code.git
31
+ cd habit-hive-code
32
+ npm test # sanity check — expect "16 passed, 0 failed"
33
+ node bin/habithive.mjs status # smoke test without installing anything globally
34
+ npm i -g . # optional: makes `habithive` available globally from this checkout
35
+ habithive setup # wire opencode + oh-my-opencode + the habithive plugin
36
+ ```
37
+
38
+ Cloning requires a GitLab account with access to the `hive` group on
39
+ `repository.hclhealthcare.in` — ask your GitLab admin to add you if you don't have it yet.
40
+
20
41
  `habithive setup` sets up, from each tool's **official source** (nothing but the two
21
42
  skills is bundled):
22
43
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "habithive-code",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "A control layer for opencode: one-command install, live simple/complex mode switching, and a model tree for oh-my-opencode agents.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/logo.js CHANGED
@@ -1,14 +1,29 @@
1
1
  // habithive-code — home-screen wordmark in opencode's TUI.
2
2
  //
3
- // Renders a big <ascii_font> "HABITHIVE" (block font, clean outline) into the
4
- // `home_logo` slot. A @opentui/core renderable is a valid slot node, so there's
5
- // no JSX build step. We probe-construct once at register time (inside try/catch,
6
- // OUTSIDE opencode's render loop) so any failure aborts gracefully instead of
7
- // crashing the home screen — the slot is only registered if the probe succeeds.
3
+ // Renders a big <ascii_font> "HABITHIVE" (block font: white fill, black outline —
4
+ // same monochrome, high-contrast look as opencode's own wordmark) into the
5
+ // `home_logo` slot, with a slow breathing-brightness animation. A @opentui/core
6
+ // renderable is a valid slot node, so there's no JSX build step. We probe-construct
7
+ // once at register time (inside try/catch, OUTSIDE opencode's render loop) so any
8
+ // failure aborts gracefully instead of crashing the home screen — the slot is only
9
+ // registered if the probe succeeds.
8
10
 
9
- const TEXT = "habithive"; // ascii_font uppercases it
10
- const FONT = "block"; // tiny | slick | block | huge | shade | grid | pallet
11
- const COLOR = "#7c3aed"; // violet
11
+ const TEXT = "habithive"; // ascii_font uppercases it
12
+ const FONT = "block"; // tiny | slick | block | huge | shade | grid | pallet
13
+ // `block` renders two color layers: [fill, outline]. White-on-black, opencode-style.
14
+ const FILL = "#ffffff";
15
+ const OUTLINE = "#000000";
16
+ const PULSE_MS = 90; // animation frame interval
17
+ const PULSE_STEPS = 40; // full breathing cycle length
18
+
19
+ // Brightness for step i of a triangle-wave breathing cycle, as a hex gray "#rrggbb".
20
+ function breathGray(i) {
21
+ const t = i / PULSE_STEPS; // 0..1
22
+ const tri = t < 0.5 ? t * 2 : (1 - t) * 2; // 0 -> 1 -> 0
23
+ const v = Math.round(150 + tri * 105); // 150..255, never fully dims out
24
+ const hex = v.toString(16).padStart(2, "0");
25
+ return `#${hex}${hex}${hex}`;
26
+ }
12
27
 
13
28
  export async function registerLogo(api) {
14
29
  try {
@@ -18,11 +33,15 @@ export async function registerLogo(api) {
18
33
  try { rctx = (rnd && rnd.renderContext) || (rnd && rnd.root && rnd.root.ctx) || null; } catch {}
19
34
  if (!rctx) return;
20
35
 
21
- let AsciiFont;
22
- try { AsciiFont = (await import("@opentui/core")).ASCIIFontRenderable; } catch { return; }
36
+ let AsciiFont, RenderableEvents;
37
+ try {
38
+ const mod = await import("@opentui/core");
39
+ AsciiFont = mod.ASCIIFontRenderable;
40
+ RenderableEvents = mod.RenderableEvents;
41
+ } catch { return; }
23
42
  if (typeof AsciiFont !== "function") return;
24
43
 
25
- const make = () => new AsciiFont(rctx, { text: TEXT, font: FONT, color: COLOR });
44
+ const make = () => new AsciiFont(rctx, { text: TEXT, font: FONT, color: [FILL, OUTLINE] });
26
45
 
27
46
  // Prove construction works before registering (a throw here is caught → no crash).
28
47
  try {
@@ -30,7 +49,31 @@ export async function registerLogo(api) {
30
49
  if (probe && typeof probe.destroyRecursively === "function") probe.destroyRecursively();
31
50
  } catch { return; }
32
51
 
33
- api.slots.register({ slots: { home_logo: () => make() } });
52
+ api.slots.register({
53
+ slots: {
54
+ home_logo: () => {
55
+ const node = make();
56
+ try {
57
+ let step = 0;
58
+ const timer = setInterval(() => {
59
+ try {
60
+ step = (step + 1) % PULSE_STEPS;
61
+ node.color = [breathGray(step), OUTLINE];
62
+ } catch { clearInterval(timer); }
63
+ }, PULSE_MS);
64
+ const stop = () => clearInterval(timer);
65
+ if (RenderableEvents && typeof node.once === "function") {
66
+ node.once(RenderableEvents.DESTROYED, stop);
67
+ } else if (typeof node.on === "function") {
68
+ node.on("destroyed", stop);
69
+ }
70
+ } catch {
71
+ // animation is best-effort; a static logo still renders if this fails
72
+ }
73
+ return node;
74
+ },
75
+ },
76
+ });
34
77
  } catch {
35
78
  // never break the home screen
36
79
  }