habithive-code 0.2.3 → 0.3.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,94 @@
1
+ # Changelog
2
+
3
+ All notable changes to habithive-code are recorded here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [0.3.0] - 2026-11-12
8
+
9
+ ### Breaking
10
+
11
+ - The model tree moved from `/model` to `/hive-models` (alias `hivemodels`). Hive no longer registers `/model`.
12
+ - Node.js 20.19 or later is required (OpenSpec 1.5.0 minimum). Hive checks at start and explains how to upgrade.
13
+ - `--minimal` is accepted but ignored with a notice. Setup always installs everything: OpenSpec, graphify, skills, Playwright MCP and OmniRoute.
14
+ - Toolchain versions are pinned and move only with a Hive release: opencode 1.19.4, oh-my-openagent 4.19.1, OpenSpec 1.5.0. Setup moves your installs to these versions.
15
+ - opencode self-update is turned off (`autoupdate: false`). Use `habithive update`.
16
+ - OpenCode 2 is not supported. Do not run `opencode upgrade --major`; complex mode and the model tree do not work on OpenCode 2.
17
+ - Launching `habithive` no longer repairs or rewrites opencode config. Repair happens in `habithive setup` and `habithive update`.
18
+
19
+ ### Added
20
+
21
+ - `habithive update`: installs the latest `habithive-code` from npm, then re-applies the release pins and config steps. Your mode, model overrides, baseline and other config are kept. For a source checkout it prints `git pull` instructions instead.
22
+ - Update notice: the TUI plugin checks npm at most once every 24 hours (cached, silent offline) and shows a toast when a newer version exists.
23
+ - `hive` as a second name for the `habithive` command. Setup warns if another `hive` program is on PATH.
24
+ - `habithive spec init`: sets up a project with OpenSpec and the `intent-driven` schema. See the spec workflow chapter in `docs/guide/`.
25
+ - The `intent-driven` schema ships with the package and is installed to OpenSpec's user-level data directory by setup and update.
26
+ - OmniRoute provider with 38 `auto/*` models, installed by setup and update. No API key is shipped; add yours with `/connect`. `habithive --gateway <ip>` sets the gateway address for a developer.
27
+ - "This screen (live)" in `/hive-models`, which opens opencode's native model picker with no relaunch.
28
+ - OpenCode 2 detection in the launcher, `habithive status` and setup, with recovery steps for npm, brew and curl installs. When setup moves opencode to the tested version, it tells you to ignore opencode's `opencode upgrade --major` suggestion.
29
+ - A warning when a chosen model will be rerouted by oh-my-openagent (Sisyphus with older GPT models, Hephaestus with non-GPT models).
30
+ - `docs/COMPATIBILITY.md` (tested version set and findings), the user guide in `docs/guide/`, and this changelog.
31
+ - Tests for the config editor, versions, update, OmniRoute, OpenSpec, spec init and a package check that fails on any IPv4 address in published files.
32
+
33
+ ### Changed
34
+
35
+ - Mode switching renames between two complete config file sets (`~/.habithive/modes/<mode>/`) instead of editing config. A launch in the active mode writes nothing. The mode is recorded in `~/.habithive/mode`.
36
+ - All setup and update writes go through a safe JSONC editor: comments and trailing commas are tolerated, strings are never altered, nothing is written if the file does not parse, a timestamped backup is made first, and unchanged files are not rewritten. A file Hive changes is saved as plain JSON: every setting is kept, comments and layout are not (the backup keeps them).
37
+ - Main-agent model changes are automatic: Hive writes config, relaunches into the same session, then moves the on-screen agent to the chosen model. If that cannot be confirmed, the native picker opens with the model at the top of Recent.
38
+ - Relaunch is safer: never while a permission or question prompt is open, works on older plugin APIs, does not quit an opencode that `habithive` did not start, and tells you when the quit did not happen.
39
+ - Menu labels show the configured model and mark changes that wait for a relaunch.
40
+ - "ALL" is now "Everything (agents + tiers)".
41
+ - Setup upgrades an existing graphify install; OpenSpec is held at the pin.
42
+ - OmniRoute entries are Hive-owned: name, adapter and the 38 models are reset on each setup or update. Models you added and any existing `apiKey` or base URL are kept.
43
+ - The published package contains no gateway address. OmniRoute is installed with the placeholder `http://localhost:20128/v1`.
44
+
45
+ ### Fixed
46
+
47
+ - A trailing comma in `opencode.jsonc` no longer causes your config to be overwritten with a minimal file.
48
+ - Glob strings such as `"src/**/*.ts"` and URLs are no longer corrupted by comment stripping.
49
+ - Launching `habithive` and switching modes no longer rewrite your config, so they no longer remove your comments.
50
+ - Fresh install: with no `oh-my-openagent.json`, the baseline was empty and every model change was refused ("baseline not captured"). An empty baseline is now valid and is re-captured once the file gains content Hive did not write.
51
+ - "Main agent - this screen" never took effect, and "All main agents" skipped the agent on screen. Both are replaced by the automatic flow above.
52
+ - A misleading "run: habithive update" line on OpenCode 2, and `habithive setup` trying `opencode upgrade <pin>` against OpenCode 2.
53
+ - A user-edited copy of the `intent-driven` schema is backed up to `~/.habithive/schema-backups/`, not into OpenSpec's schemas folder, which lists every folder as a schema.
54
+
55
+ ### Removed
56
+
57
+ - The "Quick" section of the model tree, including "Main agent - this screen".
58
+ - The `--minimal` install mode (see Breaking).
59
+ - The `/model` command registration (see Breaking).
60
+
61
+ ### Upgrading from 0.2.x
62
+
63
+ `habithive update` does not exist in 0.2.4 or earlier. Upgrade once by hand, then use it from then on:
64
+
65
+ ```bash
66
+ npm i -g habithive-code@latest && habithive setup
67
+ habithive update # for every later release
68
+ ```
69
+
70
+ Check that `node --version` is 20.19 or later first. If you were on a newer opencode than 1.19.4, setup moves it to the pin. If you ran `opencode upgrade --major`, follow the recovery steps that `habithive status` prints.
71
+
72
+ Setup leaves you in complex mode. If you used simple mode, run `habithive simple` once afterwards; your simple-mode settings are kept.
73
+
74
+ Setup saves the opencode config files it changes (usually `~/.config/opencode/opencode.jsonc`) as plain JSON. Every setting is kept, but comments and layout are removed. The original is saved next to the file as `opencode.jsonc.habithive-bak-<timestamp>`. Hive keeps only the newest 5 backups per file, so copy out any comments you want soon after upgrading.
75
+
76
+ ## [0.2.4] - 2026-08-20
77
+
78
+ See the previous README for details.
79
+
80
+ ## [0.2.3] - 2026-08-20
81
+
82
+ See the previous README for details.
83
+
84
+ ## [0.2.2] - 2026-08-20
85
+
86
+ See the previous README for details.
87
+
88
+ ## [0.2.1] - 2026-08-20
89
+
90
+ See the previous README for details.
91
+
92
+ ## [0.2.0] - 2026-08-20
93
+
94
+ First release on the public npm registry (published 2026-08-20). See the previous README for details.
package/README.md CHANGED
@@ -1,158 +1,129 @@
1
- # habithive-code
1
+ # habithive-code (Hive)
2
2
 
3
- A thin **control layer for [opencode](https://github.com/sst/opencode)** that makes
4
- two things effortless:
3
+ Hive is a control layer for [opencode](https://github.com/sst/opencode). It gives you a one-command install, live switching between simple and complex mode, a model tree for the [oh-my-openagent](https://github.com/code-yeongyu/oh-my-openagent) agents, an OmniRoute provider, a spec workflow (OpenSpec with the `intent-driven` schema), and a few bundled skills and tools.
5
4
 
6
- 1. **Mode switching** — flip between **complex** (full [oh-my-opencode](https://github.com/code-yeongyu/oh-my-openagent) — for hard tasks) and **simple** (vanilla opencode — for everyday use) with one command or an in-app menu, **resuming the same session**.
7
- 2. **A model tree** — set models across oh-my-opencode's many agents/categories from a single menu instead of hand-editing JSON.
5
+ Hive does not bundle opencode or oh-my-openagent. It installs them from their official sources under their own licenses. The command is `habithive`; `hive` is the same command.
8
6
 
9
- > habithive-code does **not** bundle opencode or oh-my-opencode. It installs / points to
10
- > them from their official sources under their own licenses. All credit for those
11
- > projects belongs to their authors (see [Credits](#credits)).
7
+ ## Quick start
12
8
 
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
-
18
- ## Install
9
+ Prerequisites: Node.js 20.19 or newer (for example `nvm install 22`) and internet access for the first setup.
19
10
 
20
11
  ```sh
21
12
  npm i -g habithive-code
22
- habithive setup # full stack (below). add --minimal to skip the extras
13
+ habithive setup # installs and wires everything
14
+ habithive # complex mode, new session
23
15
  ```
24
16
 
25
- ### Clone from source (GitLab)
26
-
27
- For development, or if you'd rather run from a checkout than the published package:
17
+ To use OmniRoute, enable it once with the gateway address your team gives you, then add your key inside opencode:
28
18
 
29
19
  ```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
20
+ habithive --gateway <ip> # Hive adds http:// and port 20128
21
+ # in opencode: /connect -> OmniRoute -> paste your key
36
22
  ```
37
23
 
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
-
41
- `habithive setup` sets up, from each tool's **official source** (nothing but the two
42
- skills is bundled):
43
-
44
- | # | Component | How | Type |
45
- |---|-----------|-----|------|
46
- | 1 | **opencode** | `npm i -g opencode-ai` (if missing) | required |
47
- | 2 | **oh-my-opencode** | pinned `oh-my-openagent@4.19.1` wired into config | required |
48
- | 3 | **habithive** plugin | wired into `tui.json` | required |
49
- | 4 | **playwright MCP** | added to the opencode `mcp` config | extra |
50
- | 5 | **openspec** | `npm i -g @fission-ai/openspec` (MIT) | extra |
51
- | 6 | **graphify** | `graphifyy` from PyPI via `uv`/`pip` (MIT) | extra |
52
- | 7 | **skills** | `karpathy-guidelines` (MIT) + `graphify` → `~/.config/opencode/skills/` | extra |
53
-
54
- Extras load/run in **complex mode**. `habithive setup --minimal` installs only 1–3.
55
- Bundled skills and their licenses are listed in [THIRD-PARTY-NOTICES.md](./THIRD-PARTY-NOTICES.md).
56
- > Note: the bundled `graphify` skill was authored for Claude Code's subagent runtime;
57
- > it drives the `graphify` (graphifyy) CLI and may need tuning under opencode.
58
-
59
- ## Use
60
-
61
- ```sh
62
- habithive # complex mode, NEW session
63
- habithive simple # vanilla opencode (oh-my-opencode off; the habithive switcher stays)
64
- habithive complex # full mode
65
- habithive -c # continue the last session
66
- habithive -s <id> # continue a specific session
67
- habithive status # show current mode
68
- ```
24
+ Full walk-through: [Getting started](docs/guide/01-getting-started.md).
25
+
26
+ ## What you get
27
+
28
+ | Component | What it is | Where it comes from |
29
+ |-----------|------------|---------------------|
30
+ | opencode | The coding agent, pinned to the tested version | npm `opencode-ai` |
31
+ | oh-my-openagent | Agent harness that complex mode enables, pinned | npm `oh-my-openagent@4.19.1` |
32
+ | Hive TUI plugin | Wordmark, `/habithive` menu, model tree, mode switch | this package |
33
+ | Playwright MCP | Browser tools for agents (`@playwright/mcp`) | added to the opencode `mcp` config |
34
+ | OpenSpec + intent-driven schema | Spec CLI pinned at **1.5.0**, plus the team schema | npm `@fission-ai/openspec`; schema shipped here |
35
+ | graphify | Knowledge-graph CLI (`graphifyy`) | PyPI, via `uv` or `pip` |
36
+ | Bundled skills | `karpathy-guidelines` and `graphify` | `skills/` in this package |
37
+ | OmniRoute | Provider with 38 `auto/*` models | written into both modes' config |
38
+
39
+ OpenSpec is pinned at **1.5.0** because newer releases reject the `intent-driven` schema. The Node 20.19 minimum comes from OpenSpec. There is no minimal install: setup always installs everything.
40
+
41
+ ## Commands
42
+
43
+ | Command | What it does |
44
+ |---------|--------------|
45
+ | `habithive` | Complex mode, new session |
46
+ | `habithive simple` / `habithive complex` | Launch in that mode |
47
+ | `habithive -c` | Continue the last session |
48
+ | `habithive -s <id>` | Continue a specific session |
49
+ | `habithive setup` | Install opencode, oh-my-openagent, the plugin, extras and skills |
50
+ | `habithive update` | Install the latest release from npm and re-apply its pins |
51
+ | `habithive status` | Show the current mode and any queued switch |
52
+ | `habithive --gateway <ip>` | Point OmniRoute at your team's gateway |
53
+ | `hive spec init [path]` | Set up OpenSpec with the `intent-driven` schema in a project |
69
54
 
70
55
  Inside opencode:
71
56
 
72
- - `/habithive` — menu (set model · switch mode · reset)
73
- - `/model` — the model tree (below)
74
- - `/habithive-mode` — switch simple ⇄ complex (relaunches, same session)
57
+ | Slash command | What it does |
58
+ |---------------|--------------|
59
+ | `/habithive` | Menu: model tree, switch mode, reset |
60
+ | `/hive-models` (alias `/hivemodels`) | The model tree |
61
+ | `/habithive-mode` | Switch simple or complex and reopen the same session |
62
+ | `/models` | opencode's own model picker |
63
+ | `/connect` | Add a provider key (OmniRoute, others) |
75
64
 
76
- The home screen shows a **HABITHIVE** wordmark, and the launcher prints a clean
77
- `▸ habithive` line on start and on exit (with the exact `habithive -c` / `habithive -s <id>`
78
- resume commands).
65
+ ## Guide
79
66
 
80
- ## The model tree (`/model`)
67
+ The guide lives in [docs/guide](docs/guide/README.md).
81
68
 
82
- ```
83
- Quick
84
- Main agent — this screen → opencode's on-screen model (model.json)
85
- ALL — on-screen + subagents+tiers
86
- Main agents (primary) → also set the on-screen model
87
- ★ All main agents (set together)
88
- sisyphus · prometheus · atlas · hephaestus (GPT-only)
89
- Subagents → oh-my-openagent.json (own model each)
90
- ★ All sub agents (set together)
91
- oracle · explore · librarian · multimodal-looker · metis · momus · sisyphus-junior
92
- Categories (tiers) → oh-my-openagent.json
93
- ultrabrain · deep · quick · visual-engineering · artistry · writing · unspecified-low · unspecified-high
94
- ```
69
+ 1. [Getting started](docs/guide/01-getting-started.md): prerequisites, install, setup, first launch.
70
+ 2. [Modes](docs/guide/02-modes.md): simple and complex mode, switching.
71
+ 3. [Model tree](docs/guide/03-model-tree.md): setting models per agent and tier.
72
+ 4. [OmniRoute](docs/guide/04-omniroute.md): provider, gateway, key.
73
+ 5. [Spec workflow](docs/guide/05-spec-workflow.md): OpenSpec and the `intent-driven` schema.
74
+ 6. [Agents, skills and tools](docs/guide/06-agents-skills-tools.md): what is bundled.
75
+ 7. [Updates and versions](docs/guide/07-updates-and-versions.md): `habithive update`, pins.
76
+ 8. [Files and safety](docs/guide/08-files-and-safety.md): every file Hive touches, guarantees, uninstall.
77
+ 9. [Troubleshooting](docs/guide/09-troubleshooting.md): symptom, cause, fix.
95
78
 
96
- Precedence: **agent > category > all > default**. Each group has a **★ set-together**
97
- row. Changes to subagents/tiers live in `oh-my-openagent.json` and apply on the next
98
- launch (habithive reopens your session automatically after a change).
79
+ ## Compatibility
99
80
 
100
- ## How it works
81
+ Each release is tested against exactly one opencode and one oh-my-openagent version (defined in `src/versions.js`).
101
82
 
102
- - **Mode switch** toggles `oh-my-openagent` in your opencode config (keeping the habithive
103
- plugin loaded in both modes) and relaunches opencode. A mode change can't be live —
104
- oh-my-opencode initializes at startup — so habithive relaunches you into the **same session**.
105
- - **On-screen model** = opencode's picker state (`~/.local/state/opencode/model.json`).
106
- **Subagent/category models** = `oh-my-openagent.json`. habithive writes the right layer for
107
- each level of the tree.
108
- - Your original `oh-my-openagent.json` is snapshotted to `~/.habithive/baseline.json`
109
- (with a byte-copy backup) so **Reset** restores it exactly.
83
+ | Component | Tested version |
84
+ |-----------|----------------|
85
+ | opencode (`OPENCODE_PIN`) | **1.19.4** |
86
+ | oh-my-openagent (`OMO_SPEC`) | **4.19.1** (5.x is not supported yet) |
110
87
 
111
- ## Compatibility
88
+ `habithive setup` installs opencode 1.19.4, or moves an existing install to it with `opencode upgrade 1.19.4`.
112
89
 
113
- Tested against: **opencode 1.18.x**, **oh-my-openagent 4.19.x** (pinned in the installer).
114
- opencode auto-updates its own binary; if a release changes internals, open an issue.
90
+ Setup also writes `"autoupdate": false` into `opencode.jsonc` in both modes' config sets, so opencode does not update itself past the tested version. Versions move only when a Hive release does: run `habithive update`. If the running opencode is not the tested version, the launcher and the in-app plugin each show a one-line warning and carry on.
115
91
 
116
- ## Migrating from the previous package name
92
+ Hive needs OpenCode 1. OpenCode 2 has a new plugin format and oh-my-openagent's agents do not run on it. **Do not run `opencode upgrade --major`** (opencode 1.19 suggests it). If OpenCode 2 is installed, the launcher, `habithive status` and `habithive setup` say so and show how to go back; setup never runs `opencode upgrade` against it. Use the command for how you installed OpenCode 2:
117
93
 
118
- `habithive-code` is a rebrand of an earlier package that used a different name and
119
- plugin entry. `habithive setup` only manages its own plugin entry, so if the old
120
- package is still installed or wired into your opencode config, both load together
121
- and you'll see two wordmarks stacked on the home screen. Fix it once with:
94
+ - npm: `npm rm -g @opencode/cli`, then `habithive setup`
95
+ - Homebrew: `brew uninstall opencode-v2`, then `habithive setup`
96
+ - curl installer: `curl -fsSL https://opencode.ai/install | bash -s -- --version 1.19.4`
122
97
 
123
- ```sh
124
- npm rm -g maliv-code
125
- rm -rf ~/.maliv
126
- node -e "
127
- const fs=require('fs'), os=require('os'), path=require('path');
128
- const p=path.join(os.homedir(),'.config','opencode','tui.json');
129
- const c=JSON.parse(fs.readFileSync(p,'utf8'));
130
- c.plugin=(c.plugin||[]).filter(e=>!String(e).includes('maliv'));
131
- fs.writeFileSync(p, JSON.stringify(c,null,2)+'\n');
132
- console.log('cleaned:', p);
133
- "
134
- habithive setup
135
- ```
136
- This is a one-time cleanup — anyone installing `habithive-code` fresh, who never had
137
- the old package, can skip this section entirely.
98
+ Sessions created under OpenCode 2 may not open in OpenCode 1. Per-version findings are in [docs/COMPATIBILITY.md](docs/COMPATIBILITY.md).
138
99
 
139
100
  ## Uninstall
140
101
 
102
+ Switch to the mode whose config you want to keep first: deleting `~/.habithive` also deletes the other mode's stored config set.
103
+
141
104
  ```sh
142
- habithive simple # (optional) drop back to vanilla first
105
+ habithive complex # switches back to complex mode; quit opencode when it opens
143
106
  npm rm -g habithive-code
107
+ npm rm -g @fission-ai/openspec # optional
108
+ rm -rf ~/.local/share/openspec/schemas/intent-driven # optional
144
109
  rm -rf ~/.habithive
145
110
  ```
146
- Then remove the `habithive` / `oh-my-openagent` lines from `~/.config/opencode/tui.json`
147
- and `opencode.jsonc` if you want them gone.
111
+
112
+ Then remove the Hive and oh-my-openagent entries from `~/.config/opencode/tui.json` and `opencode.jsonc`. Details: [Files and safety](docs/guide/08-files-and-safety.md#uninstall).
113
+
114
+ ## Patch notes
115
+
116
+ See [CHANGELOG.md](CHANGELOG.md).
148
117
 
149
118
  ## Credits
150
119
 
151
- Built entirely on the work of others — habithive-code only orchestrates them:
120
+ Hive only orchestrates the work of others:
121
+
122
+ - [opencode](https://github.com/sst/opencode), the AI coding agent this runs on.
123
+ - [oh-my-openagent](https://github.com/code-yeongyu/oh-my-openagent) by code-yeongyu, the harness complex mode enables (Sustainable Use License 1.0).
152
124
 
153
- - **[opencode](https://github.com/sst/opencode)** — the AI coding agent this runs on.
154
- - **[oh-my-opencode / oh-my-openagent](https://github.com/code-yeongyu/oh-my-openagent)** by **code-yeongyu** — the agent harness that "complex" mode enables (Sustainable Use License 1.0).
125
+ Bundled skills and their licenses: [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md).
155
126
 
156
127
  ## License
157
128
 
158
- habithive-code is [MIT](./LICENSE). opencode and oh-my-opencode remain under their own licenses.
129
+ habithive-code is [MIT](LICENSE). opencode and oh-my-openagent remain under their own licenses.
package/bin/habithive.mjs CHANGED
@@ -7,21 +7,32 @@
7
7
  // habithive -s <id> continue a specific session
8
8
  // habithive new fresh session (default)
9
9
  // habithive setup install opencode (if missing) + wire oh-my-opencode + habithive
10
+ // habithive update install the latest release from npm + reconcile the machine to it
11
+ // habithive spec init [path] scaffold OpenSpec (intent-driven) in a project
12
+ // habithive --gateway <ip> point OmniRoute at your team's gateway (IP only)
10
13
  // habithive status show current mode + any queued in-app switch
14
+ // (hidden) habithive reconcile [--yes] setup minus the mode change; update runs it
11
15
  //
12
16
  // Default is a fresh session. In-app /habithive-mode queues a switch + quits; this
13
17
  // loop relaunches in the queued mode with --continue (same session).
14
18
 
15
19
  import { spawnSync } from "node:child_process";
16
- import { readFileSync } from "node:fs";
17
- import { setMode, currentMode } from "../src/config.js";
20
+ import { readFileSync, existsSync, realpathSync } from "node:fs";
21
+ import { join, resolve } from "node:path";
22
+ import { fileURLToPath } from "node:url";
23
+ import { currentMode, readMode, switchTo, recoverSwitch, isHabithive, PATHS } from "../src/config.js";
18
24
  import { readNextMode, readNextSession, clearNextMode } from "../src/core.js";
19
- import { runSetup } from "../src/setup.js";
25
+ import { runSetup, reconcile } from "../src/setup.js";
26
+ import { specInit } from "../src/spec.js";
27
+ import { gatewayURL, setGateway } from "../src/omniroute.js";
28
+ import { ConfigParseError } from "../src/jsonc.js";
29
+ import { checkLatest, installedVersion } from "../src/update.js";
30
+ import { NODE_MIN, OPENCODE_PIN, compareVersions, versionIn, isUnsupportedMajor, opencodeInstallMethod, v2Notice } from "../src/versions.js";
20
31
 
21
32
  const C = {
22
33
  reset: "\x1b[0m", dim: "\x1b[2m", bold: "\x1b[1m", cyan: "\x1b[36m",
23
34
  violet: "\x1b[38;2;124;58;237m", // #7c3aed
24
- white: "\x1b[97m",
35
+ white: "\x1b[97m", yellow: "\x1b[33m", red: "\x1b[31m",
25
36
  };
26
37
  let VERSION = "0.1.0";
27
38
  try { VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version || VERSION; } catch {}
@@ -38,13 +49,46 @@ function help() {
38
49
  `habithive-code v${VERSION}\n\n` +
39
50
  `usage: habithive [simple|complex] [-c | -s <id>] [project]\n` +
40
51
  ` habithive setup install opencode + oh-my-opencode + habithive\n` +
41
- ` habithive status show current mode\n\n` +
52
+ ` habithive update install the latest release + re-apply its pins\n` +
53
+ ` habithive status show current mode\n` +
54
+ ` habithive --gateway <ip> point OmniRoute at your team's gateway (IP only)\n` +
55
+ ` hive spec init [path] scaffold OpenSpec (intent-driven) in a project\n\n` +
56
+ `hive and habithive are the same command.\n` +
42
57
  `default: complex mode, new session. -c continue last, -s <id> continue specific.\n`,
43
58
  );
44
59
  }
45
60
 
46
61
  function runOpencode(args) {
47
- return spawnSync("opencode", args, { stdio: "inherit", shell: process.platform === "win32" }).status ?? 0;
62
+ return spawnSync("opencode", args, { stdio: "inherit", shell: process.platform === "win32", env: { ...process.env, HABITHIVE_LAUNCHER: "1" } }).status ?? 0;
63
+ }
64
+
65
+ // Hint only: string-search the active tui.json (never parsed, never edited) for a
66
+ // Hive plugin file:// URL whose target is gone (e.g. the global npm install moved).
67
+ function staleHint() {
68
+ try {
69
+ for (const [u] of readFileSync(PATHS.TUI_JSON, "utf8").matchAll(/file:\/\/[^"'\s,\]]+/g)) {
70
+ if (!isHabithive(u) && !/habit-?hive/.test(u)) continue;
71
+ let p; try { p = fileURLToPath(u); } catch { continue; }
72
+ if (!existsSync(p)) { process.stdout.write(`${C.yellow}!${C.reset} habithive plugin path is stale — run: habithive setup\n`); return; }
73
+ }
74
+ } catch {}
75
+ }
76
+
77
+ const opencodeVersion = () => versionIn(spawnSync("opencode", ["--version"], { encoding: "utf8", shell: process.platform === "win32", timeout: 2000 }).stdout);
78
+
79
+ // Hint only, once per process (2 s cap, never blocks): warn when opencode is not the tested version.
80
+ // A different major (OpenCode 2, ADR-0010) gets the recovery notice instead; the launch continues.
81
+ let drift = false;
82
+ function driftHint() {
83
+ if (drift) return;
84
+ drift = true;
85
+ try {
86
+ const v = opencodeVersion();
87
+ if (isUnsupportedMajor(v)) {
88
+ const [head, ...steps] = v2Notice(v, opencodeInstallMethod()).split("\n");
89
+ process.stdout.write(`${C.red}! ${head}${C.reset}\n${steps.map((l) => `${C.dim}${l}${C.reset}`).join("\n")}\n`);
90
+ } else if (v && compareVersions(v, OPENCODE_PIN) !== 0) process.stdout.write(`${C.yellow}!${C.reset} opencode ${v} is not the tested ${OPENCODE_PIN} — run: habithive update\n`);
91
+ } catch {}
48
92
  }
49
93
 
50
94
  // Newest session id (the one just used) — `opencode session list` is newest-first.
@@ -56,14 +100,84 @@ function lastSessionId() {
56
100
  } catch { return null; }
57
101
  }
58
102
 
103
+ const real = (p) => { try { return realpathSync(p); } catch { return null; } };
104
+ const out = (m) => process.stdout.write(m + "\n");
105
+
106
+ // habithive update: npm install of the latest release, then the NEW version's reconcile (so its pins apply).
107
+ // A global install that links to a source checkout is never overwritten from npm.
108
+ async function update() {
109
+ const win = process.platform === "win32";
110
+ const pkgRoot = real(fileURLToPath(new URL("..", import.meta.url)));
111
+ const g = spawnSync("npm", ["root", "-g"], { encoding: "utf8", shell: win });
112
+ const gRoot = g.status === 0 ? real(g.stdout.trim()) : null; // realpath of the dir, not of the package: a link stays a link
113
+ const globalPkg = gRoot && join(gRoot, "habithive-code");
114
+
115
+ if (pkgRoot !== globalPkg) {
116
+ out(`${C.yellow}!${C.reset} running from a source checkout: ${pkgRoot}\n update it with: git -C ${pkgRoot} pull && habithive update`);
117
+ return reconcile({ yes: true });
118
+ }
119
+
120
+ const latest = await checkLatest(), installed = installedVersion();
121
+ let now = installed;
122
+ if (latest && compareVersions(latest, installed) > 0) {
123
+ out(`${C.cyan}▸${C.reset} updating habithive-code ${installed} → ${latest}…`);
124
+ if (spawnSync("npm", ["i", "-g", "habithive-code@latest"], { stdio: "inherit", shell: win }).status !== 0) {
125
+ out(`${C.red}✗ npm install failed — fix npm's global prefix permissions (see README) and retry${C.reset}`);
126
+ return 1;
127
+ }
128
+ try { now = JSON.parse(readFileSync(join(globalPkg, "package.json"), "utf8")).version || latest; } catch { now = latest; }
129
+ } else if (latest === null) out(`${C.yellow}!${C.reset} could not reach the npm registry — reconciling the installed version`);
130
+ else out(`${C.cyan}▸${C.reset} habithive-code ${installed} is the latest`);
131
+
132
+ const r = spawnSync(process.execPath, [join(globalPkg, "bin", "habithive.mjs"), "reconcile", "--yes"], { stdio: "inherit" });
133
+ if (r.status !== 0) return r.status ?? 1;
134
+ out(`\nrestart opencode to load habithive v${now}`);
135
+ return 0;
136
+ }
137
+
59
138
  async function main() {
60
- const argv = process.argv.slice(2);
139
+ if (compareVersions(process.versions.node, NODE_MIN) < 0) {
140
+ process.stderr.write(`habithive needs Node.js 20.19 or newer (running ${process.versions.node}) — upgrade, e.g. \`nvm install 22\`\n`);
141
+ process.exit(1);
142
+ }
143
+ let argv = process.argv.slice(2);
144
+ if (argv.some((a) => a === "--minimal" || a === "--min")) {
145
+ argv = argv.filter((a) => a !== "--minimal" && a !== "--min");
146
+ out("--minimal is no longer supported — installing everything");
147
+ }
148
+
149
+ // --gateway <ip>: enable OmniRoute on this machine, then exit (never launches opencode)
150
+ const gi = argv.indexOf("--gateway");
151
+ if (gi >= 0) {
152
+ const val = argv[gi + 1];
153
+ if (!val || val.startsWith("-")) { process.stderr.write("usage: habithive --gateway <ip>\n"); process.exit(1); }
154
+ const g = gatewayURL(val);
155
+ if (!g.ok) { process.stderr.write(`${g.error}\n`); process.exit(1); }
156
+ try {
157
+ const { files } = setGateway(g.url);
158
+ out(`OmniRoute enabled (${g.url}) — add your key with /connect`);
159
+ out(`${C.dim}${files.length ? `updated: ${files.join(", ")}` : "already set"}${C.reset}`);
160
+ } catch (e) {
161
+ if (!(e instanceof ConfigParseError)) throw e;
162
+ process.stderr.write(`cannot read ${e.file}:${e.line}:${e.column}\n`);
163
+ process.exit(1);
164
+ }
165
+ process.exit(0);
166
+ }
61
167
 
62
168
  // subcommands
63
- if (argv[0] === "setup") { process.exit(await runSetup({ yes: argv.includes("--yes") || argv.includes("-y"), minimal: argv.includes("--minimal") })); }
169
+ if (argv[0] === "setup") { process.exit(await runSetup({ yes: argv.includes("--yes") || argv.includes("-y") })); }
170
+ if (argv[0] === "reconcile") { process.exit(await reconcile({ yes: argv.includes("--yes") || argv.includes("-y") })); }
171
+ if (argv[0] === "spec") {
172
+ if (argv[1] === "init" && argv.length <= 3) process.exit(await specInit(argv[2] ? resolve(argv[2]) : process.cwd()));
173
+ out("usage: hive spec init [path]");
174
+ process.exit(1);
175
+ }
176
+ if (argv[0] === "update") { process.exit(await update()); }
64
177
  if (argv[0] === "status") {
65
178
  const q = readNextMode();
66
- process.stdout.write(`habithive: ${currentMode()} mode${q ? ` · queued → ${q}` : ""}\n`);
179
+ process.stdout.write(`habithive: ${readMode() ?? currentMode()} mode${q ? ` · queued → ${q}` : ""}\n`);
180
+ try { const v = opencodeVersion(); if (isUnsupportedMajor(v)) out(`${C.red}!${C.reset} OpenCode ${v} is not supported — run habithive to see how to go back to OpenCode 1`); } catch {}
67
181
  process.exit(0);
68
182
  }
69
183
  if (argv.includes("-h") || argv.includes("--help")) { help(); process.exit(0); }
@@ -82,10 +196,18 @@ async function main() {
82
196
  else passthru.push(a);
83
197
  }
84
198
 
199
+ const rec = recoverSwitch(); // finish/roll back an interrupted mode switch
200
+ if (rec?.notice) process.stdout.write(`${C.dim}${rec.notice}${C.reset}\n`);
201
+ staleHint();
85
202
  clearNextMode();
86
203
  // relaunch loop: honor an in-app queued mode switch, preserving the session
87
204
  for (;;) {
88
- setMode(mode);
205
+ if (mode !== currentMode()) { // a switch only renames files; a normal launch writes no config
206
+ const r = switchTo(mode);
207
+ r.notices.forEach((n) => process.stdout.write(`${C.dim}${n}${C.reset}\n`));
208
+ if (!r.ok) { process.stdout.write(`${C.yellow}habithive: ${r.error}${C.reset}\n`); mode = currentMode(); }
209
+ }
210
+ driftHint();
89
211
  banner(mode, contArgs.length > 0);
90
212
  runOpencode([...contArgs, ...passthru]);
91
213
  const next = readNextMode();