@polderlabs/bizar 4.0.0 → 4.2.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.
Files changed (38) hide show
  1. package/README.md +11 -14
  2. package/bizar-dash/CHANGELOG.md +1 -1
  3. package/bizar-dash/src/server/api.mjs +2 -2
  4. package/bizar-dash/src/server/artifacts-store.mjs +4 -4
  5. package/bizar-dash/src/server/memory-git.mjs +80 -0
  6. package/bizar-dash/src/server/memory-lightrag.mjs +767 -0
  7. package/bizar-dash/src/server/memory-store.mjs +47 -0
  8. package/bizar-dash/src/server/mod-security.mjs +2 -2
  9. package/bizar-dash/src/server/routes/memory.mjs +174 -15
  10. package/bizar-dash/src/server/server.mjs +1 -1
  11. package/bizar-dash/src/server/state.mjs +2 -2
  12. package/bizar-dash/src/web/views/Config.tsx +461 -1
  13. package/bizar-dash/tests/memory-cli.test.mjs +542 -0
  14. package/bizar-dash/tests/memory-config.test.mjs +422 -0
  15. package/bizar-dash/tests/memory-git.test.mjs +109 -1
  16. package/bizar-dash/tests/memory-lightrag.test.mjs +153 -0
  17. package/bizar-dash/tests/memory-protocol-drift.test.mjs +45 -0
  18. package/cli/banner.mjs +1 -1
  19. package/cli/bin.mjs +4 -4
  20. package/cli/bootstrap.mjs +1 -1
  21. package/cli/copy.mjs +22 -16
  22. package/cli/doctor.mjs +4 -4
  23. package/cli/doctor.test.mjs +2 -2
  24. package/cli/init.mjs +2 -2
  25. package/cli/install.mjs +21 -16
  26. package/cli/memory.mjs +710 -31
  27. package/cli/utils.mjs +6 -3
  28. package/config/AGENTS.md +7 -7
  29. package/config/agents/_shared/AGENT_BASELINE.md +59 -61
  30. package/config/opencode.json +13 -38
  31. package/config/skills/memory-protocol/SKILL.md +105 -0
  32. package/config/skills/obsidian/SKILL.md +58 -1
  33. package/install.sh +11 -1
  34. package/package.json +2 -2
  35. package/plugins/bizar/index.ts +7 -0
  36. package/plugins/bizar/src/commands.ts +42 -1
  37. package/plugins/bizar/src/tools/open-kb.ts +191 -0
  38. package/plugins/bizar/tests/commands.test.ts +36 -0
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  12 agents across 4 cost tiers. Odin routes, subagents execute, Forseti audits.
8
8
 
9
9
  [![npm](https://img.shields.io/npm/v/bizar?color=cb3837)](https://www.npmjs.com/package/bizar)
10
- [![RTK](https://img.shields.io/badge/rtk-integrated-8A2BE2)](https://github.com/rtk-ai/rtk)
10
+ [![Headroom](https://img.shields.io/badge/headroom-integrated-8A2BE2)](https://github.com/headroomlabs-ai/headroom)
11
11
  [![Semble](https://img.shields.io/badge/semble-integrated-0ea5e9)](https://github.com/semble-ai/semble)
12
12
  [![Skills](https://img.shields.io/badge/skills.sh-integrated-f59e0b)](https://www.skills.sh)
13
13
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
@@ -201,40 +201,37 @@ bizar graph build
201
201
  - The `blessed` TUI library may render with quirks inside Windows Terminal; if you see garbled output in the TUI, use the browser UI via `bizar dash start --bg` and open http://localhost:4321 instead.
202
202
  - Some shell scripts under `config/skills/embedded-esp-idf/` are bash-only and require Git Bash (or WSL).
203
203
  - The bash `install.sh` does not run on Windows cmd/PowerShell; use the `npm install -g` path above.
204
- - RTK (Rust Token Killer) ships a bash-only installer. On Windows, install it manually via [Cargo](https://github.com/rtk-ai/rtk#installation) or a [prebuilt binary](https://github.com/rtk-ai/rtk/releases), then run `rtk init -g --opencode`.
204
+ - Headroom ships a Python/npm installer. On Windows, install it manually: `pip install "headroom-ai[all]"` or `npm install -g headroom-ai`, then run `headroom wrap opencode`.
205
205
 
206
206
  ### Prerequisites
207
207
 
208
208
  - [opencode CLI](https://opencode.ai) installed and on `$PATH`
209
209
  - Provider connections (via `/connect` in opencode TUI)
210
- - [RTK](https://github.com/rtk-ai/rtk) (recommended) — CLI proxy that reduces LLM token consumption by 60-90%
210
+ - [Headroom](https://github.com/headroomlabs-ai/headroom) (recommended) — CLI proxy that reduces LLM token consumption by 60-90%
211
211
  - [Semble](https://github.com/semble-ai/semble) (recommended) — AI-powered code search (used by Mimir agent)
212
212
  - [Skills CLI](https://www.skills.sh) (recommended) — Agent skill package manager (`npx skills add <owner/repo>`)
213
213
 
214
- ### RTK Setup
214
+ ### Headroom Setup
215
215
 
216
- [RTK](https://github.com/rtk-ai/rtk) (Rust Token Killer) filters and compresses command output before it reaches the LLM context. It saves ~80% on common operations like `ls`, `git status`, `cargo test`, and `git diff`.
216
+ [Headroom](https://github.com/headroomlabs-ai/headroom) filters and compresses command output before it reaches the LLM context. It saves ~80% on common operations like `ls`, `git status`, `cargo test`, and `git diff`.
217
217
 
218
218
  Install:
219
219
 
220
220
  ```bash
221
- # Homebrew (macOS)
222
- brew install rtk
221
+ # Python (recommended)
222
+ pip install "headroom-ai[all]"
223
223
 
224
- # Linux/macOS quick install
225
- curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh
226
-
227
- # Cargo
228
- cargo install --git https://github.com/rtk-ai/rtk
224
+ # npm
225
+ npm install -g headroom-ai
229
226
  ```
230
227
 
231
228
  Enable for opencode:
232
229
 
233
230
  ```bash
234
- rtk init -g --opencode
231
+ headroom wrap opencode
235
232
  ```
236
233
 
237
- After setup, all Bash commands are transparently rewritten to their rtk equivalents — `git status` `rtk git status`, `cargo test` `rtk cargo test`. The LLM receives compact output, saving 60-90% on token costs.
234
+ After setup, `headroom wrap opencode` injects config into `opencode.json` and starts a proxy. The LLM receives compact output, saving 60-90% on token costs. The wrapping is durable — all subsequent opencode sessions route through the headroom proxy.
238
235
 
239
236
  ### Semble Setup
240
237
 
@@ -137,7 +137,7 @@
137
137
 
138
138
  - **Mod security layer (`src/server/mod-security.mjs`).** Mods declare permissions in `mod.json`; the loader enforces them at every privileged action:
139
139
  - **Filesystem sandboxing** — mods get scoped `readFileSync` / `writeFileSync` helpers. Paths outside `fs:read:<scope>` / `fs:write:<scope>` throw. Scope syntax: exact path, directory with implicit recursion, `/*` suffix, or `~/` home-relative.
140
- - **Subprocess allowlist** — only whitelisted binaries (`bizar`, `opencode`, `python3`, `git`, `node`, `npm`, `pip`, `uv`, `rtk`, `jq`, `graphify`, `pipx`, `opencode-ai`) can be spawned without an explicit `process:spawn:<bin>` permission. Path-style binaries (`/bin/ls`) require explicit permission.
140
+ - **Subprocess allowlist** — only whitelisted binaries (`bizar`, `opencode`, `python3`, `git`, `node`, `npm`, `pip`, `uv`, `headroom`, `jq`, `graphify`, `pipx`, `opencode-ai`) can be spawned without an explicit `process:spawn:<bin>` permission. Path-style binaries (`/bin/ls`) require explicit permission.
141
141
  - **Integrity hash** — every mod's files are SHA-256-hashed at install time; the hash is stored under `_integrity` in `mod.json`. On every load the hash is recomputed; a mismatch triggers a critical warning and refuses to load the mod's routes until reinstalled.
142
142
  - **Audit log** — every privileged action is logged as JSON lines to `~/.cache/bizar/logs/mod-audit.log` with timestamp, mod id, mod version, action, and details. Inspect via `GET /api/mods/audit?mod=<id>&limit=200`.
143
143
  - **Public mod registry.** New `bizarre-mods` repo at `DrB0rk/bizarre-mods` is the canonical source of mods. `GET /api/mods/registry` fetches `registry.json` and annotates each entry with installed state and upgrade hints. `POST /api/mods { id }` installs from the registry.
@@ -59,7 +59,7 @@ import { createMiscRouter } from './routes/misc.mjs';
59
59
  * @param {Function} [deps.broadcast]
60
60
  * @returns {import('express').Router}
61
61
  */
62
- export function createApiRouter({
62
+ export async function createApiRouter({
63
63
  state,
64
64
  watcher,
65
65
  projectRoot,
@@ -106,7 +106,7 @@ export function createApiRouter({
106
106
  router.use(createOpencodeSessionsRouter());
107
107
  router.use(createDialogsRouter({ broadcast }));
108
108
  router.use(createSkillsRouter({ broadcast }));
109
- router.use(createObsidianRouter({ projectRoot }));
109
+ router.use(await createObsidianRouter({ projectRoot }));
110
110
  // v3.24.0 — Bizar Memory Service (Phase 1). Lazy-imported so that if Thor's
111
111
  // memory routes haven't landed yet, the dashboard still boots.
112
112
  const { createMemoryRouter } = await import('./routes/memory.mjs');
@@ -11,7 +11,7 @@
11
11
  * edits so the editor never races the markdown regenerator.
12
12
  *
13
13
  * Storage locations, in order of preference:
14
- * 1. <projectRoot>/artifacts/<slug> (worktree, project-scoped)
14
+ * 1. <projectRoot>/.bizar/artifacts/<slug> (worktree, project-scoped)
15
15
  * 2. ~/.config/opencode/artifacts/<slug> (global fallback)
16
16
  */
17
17
  import {
@@ -102,7 +102,7 @@ export const artifactsStore = {
102
102
  */
103
103
  resolveDir(slug, projectRoot) {
104
104
  const candidates = [];
105
- if (projectRoot) candidates.push(join(projectRoot, 'artifacts', slug));
105
+ if (projectRoot) candidates.push(join(projectRoot, '.bizar', 'artifacts', slug));
106
106
  candidates.push(join(GLOBAL_PLANS_DIR, slug));
107
107
  for (const c of candidates) {
108
108
  if (existsSync(c)) return c;
@@ -116,7 +116,7 @@ export const artifactsStore = {
116
116
  */
117
117
  ensureDir(slug, projectRoot) {
118
118
  const target = projectRoot
119
- ? join(projectRoot, 'artifacts', slug)
119
+ ? join(projectRoot, '.bizar', 'artifacts', slug)
120
120
  : join(GLOBAL_PLANS_DIR, slug);
121
121
  mkdirSync(target, { recursive: true });
122
122
  return target;
@@ -126,7 +126,7 @@ export const artifactsStore = {
126
126
  const seen = new Set();
127
127
  const out = [];
128
128
  const dirs = [];
129
- if (projectRoot) dirs.push(join(projectRoot, 'artifacts'));
129
+ if (projectRoot) dirs.push(join(projectRoot, '.bizar', 'artifacts'));
130
130
  dirs.push(GLOBAL_PLANS_DIR);
131
131
  for (const dir of dirs) {
132
132
  if (!existsSync(dir)) continue;
@@ -247,3 +247,83 @@ export function addAll(dir) {
247
247
  return { ok: false, error: err.message };
248
248
  }
249
249
  }
250
+
251
+ /**
252
+ * Set the URL of a git remote, idempotently.
253
+ *
254
+ * @param {string} repoDir — the git repository root
255
+ * @param {string} remoteName — name of the remote (e.g. 'origin')
256
+ * @param {string} url — the remote URL
257
+ * @param {{ overwrite?: boolean }} [opts]
258
+ * @returns {{ ok: true, action: 'added' | 'updated' | 'unchanged' }}
259
+ * @throws if the remote already exists with a different URL and overwrite is false,
260
+ * or if the underlying git command fails.
261
+ */
262
+ export function addRemote(repoDir, remoteName, url, { overwrite = false } = {}) {
263
+ let existing = null;
264
+ try {
265
+ existing = execFileSync('git', ['remote', 'get-url', remoteName], {
266
+ cwd: repoDir,
267
+ encoding: 'utf8',
268
+ timeout: 5000,
269
+ stdio: ['pipe', 'pipe', 'pipe'],
270
+ }).trim();
271
+ } catch (err) {
272
+ // exit code 2 or 128 means remote doesn't exist; anything else is a real error
273
+ if (err.status !== 2 && err.status !== 128) {
274
+ throw new Error(`failed to query remote '${remoteName}': ${err.message}`);
275
+ }
276
+ }
277
+
278
+ if (existing && existing === url) {
279
+ return { ok: true, action: 'unchanged' };
280
+ }
281
+
282
+ if (existing && !overwrite) {
283
+ throw new Error(
284
+ `remote '${remoteName}' already exists with URL ${existing}.\n` +
285
+ ` To update it, call addRemote(dir, name, url, { overwrite: true }).`,
286
+ );
287
+ }
288
+
289
+ if (existing) {
290
+ execFileSync('git', ['remote', 'set-url', remoteName, url], {
291
+ cwd: repoDir,
292
+ timeout: 5000,
293
+ stdio: ['pipe', 'pipe', 'pipe'],
294
+ });
295
+ return { ok: true, action: 'updated' };
296
+ }
297
+
298
+ execFileSync('git', ['remote', 'add', remoteName, url], {
299
+ cwd: repoDir,
300
+ timeout: 5000,
301
+ stdio: ['pipe', 'pipe', 'pipe'],
302
+ });
303
+ return { ok: true, action: 'added' };
304
+ }
305
+
306
+ /**
307
+ * Test whether a git remote is reachable.
308
+ *
309
+ * @param {string} repoDir — the git repository root
310
+ * @param {string} remoteName — name of the remote (e.g. 'origin')
311
+ * @param {{ timeoutMs?: number }} [opts]
312
+ * @returns {string} the trimmed stdout from `git ls-remote` on success,
313
+ * or '' on any failure (network, auth, timeout).
314
+ * Does NOT throw.
315
+ */
316
+ export function lsRemote(repoDir, remoteName, { timeoutMs = 5000 } = {}) {
317
+ try {
318
+ const stdout = execFileSync('git', ['ls-remote', remoteName], {
319
+ cwd: repoDir,
320
+ timeout: timeoutMs,
321
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
322
+ encoding: 'utf8',
323
+ stdio: ['pipe', 'pipe', 'pipe'],
324
+ });
325
+ return stdout.trim();
326
+ } catch {
327
+ return '';
328
+ }
329
+ }