@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "4.0.0",
3
+ "version": "4.2.0",
4
4
  "description": "Norse-pantheon multi-agent system for opencode — 13 agents across 4 cost tiers with cost-aware routing, plans, and a configurable agent harness. v4 ships as a single npm package bundling the dashboard server, opencode plugin, and typed SDK.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,7 +21,7 @@
21
21
  "build:sdk": "tsc -p packages/sdk/tsconfig.json",
22
22
  "test:sdk": "npm test --prefix packages/sdk",
23
23
  "test:sdk:watch": "npm run test:watch --prefix packages/sdk",
24
- "test": "npm run typecheck && npm run test:sdk && bun test plugins/bizar/tests/loop.test.ts plugins/bizar/tests/block.test.ts plugins/bizar/tests/stall-think.test.ts plugins/bizar/tests/tools/bg-get-comments.test.ts plugins/bizar/tests/tools/bg-spawn-delegation.test.ts plugins/bizar/tests/tools/opencode-runner.test.ts plugins/bizar/tests/settings.test.ts plugins/bizar/tests/commands.test.ts plugins/bizar/tests/commands-impl.test.ts plugins/bizar/tests/tools/plan-action.test.ts plugins/bizar/tests/tools/wait-for-feedback.test.ts plugins/bizar/tests/tools/read-glyph-feedback.test.ts plugins/bizar/tests/reasoning-clean.test.ts plugins/bizar/tests/key-rotation.test.ts && node bizar-dash/tests/smoke-v2.mjs && node --test bizar-dash/tests/path-safe.test.mjs bizar-dash/tests/tmux-wrap.test.mjs bizar-dash/tests/opencode-runner.test.mjs bizar-dash/tests/mod-instructions.node.test.mjs bizar-dash/tests/mod-upgrade.node.test.mjs bizar-dash/tests/graphify-mod-spawn.node.test.mjs bizar-dash/tests/no-agent-browser.node.test.mjs bizar-dash/tests/providers-store-backup-keys.node.test.mjs bizar-dash/tests/dashboard-ports.test.mjs bizar-dash/tests/submit-feedback.test.mjs bizar-dash/tests/yaml.test.mjs bizar-dash/tests/memory-store.test.mjs bizar-dash/tests/memory-schema.test.mjs bizar-dash/tests/memory-secrets.test.mjs bizar-dash/tests/memory-git.test.mjs bizar-dash/tests/memory-sync.test.mjs bizar-dash/tests/obsidian-back-compat.test.mjs",
24
+ "test": "npm run typecheck && npm run test:sdk && bun test plugins/bizar/tests/loop.test.ts plugins/bizar/tests/block.test.ts plugins/bizar/tests/stall-think.test.ts plugins/bizar/tests/tools/bg-get-comments.test.ts plugins/bizar/tests/tools/bg-spawn-delegation.test.ts plugins/bizar/tests/tools/opencode-runner.test.ts plugins/bizar/tests/settings.test.ts plugins/bizar/tests/commands.test.ts plugins/bizar/tests/commands-impl.test.ts plugins/bizar/tests/tools/plan-action.test.ts plugins/bizar/tests/tools/wait-for-feedback.test.ts plugins/bizar/tests/tools/read-glyph-feedback.test.ts plugins/bizar/tests/reasoning-clean.test.ts plugins/bizar/tests/key-rotation.test.ts && node bizar-dash/tests/smoke-v2.mjs && node --test bizar-dash/tests/path-safe.test.mjs bizar-dash/tests/tmux-wrap.test.mjs bizar-dash/tests/opencode-runner.test.mjs bizar-dash/tests/mod-instructions.node.test.mjs bizar-dash/tests/mod-upgrade.node.test.mjs bizar-dash/tests/graphify-mod-spawn.node.test.mjs bizar-dash/tests/no-agent-browser.node.test.mjs bizar-dash/tests/providers-store-backup-keys.node.test.mjs bizar-dash/tests/dashboard-ports.test.mjs bizar-dash/tests/submit-feedback.test.mjs bizar-dash/tests/yaml.test.mjs bizar-dash/tests/memory-store.test.mjs bizar-dash/tests/memory-schema.test.mjs bizar-dash/tests/memory-secrets.test.mjs bizar-dash/tests/memory-git.test.mjs bizar-dash/tests/memory-sync.test.mjs bizar-dash/tests/obsidian-back-compat.test.mjs bizar-dash/tests/memory-lightrag.test.mjs bizar-dash/tests/memory-config.test.mjs bizar-dash/tests/memory-cli.test.mjs",
25
25
  "build": "npm run build:sdk"
26
26
  },
27
27
  "keywords": [
@@ -120,6 +120,7 @@ import { createBgStatusTool } from "./src/tools/bg-status.js";
120
120
  import { createBgCollectTool } from "./src/tools/bg-collect.js";
121
121
  import { createBgKillTool } from "./src/tools/bg-kill.js";
122
122
  import { createBgGetCommentsTool } from "./src/tools/bg-get-comments.js";
123
+ import { createOpenKbTool } from "./src/tools/open-kb.js";
123
124
 
124
125
  // v0.4.0 — visual plan flow: settings, slash commands, plan tools
125
126
  import { SettingsStore } from "./src/settings.js";
@@ -941,6 +942,12 @@ function buildHooks(ctx: RuntimeContext, bg: BgDeps): Hooks {
941
942
  worktree: ctx.worktree,
942
943
  logger: ctx.logger,
943
944
  }),
945
+ // v4.0.0 — open the Bizar Memory vault in Obsidian. Available to
946
+ // all agents; no serve child required.
947
+ bizar_open_kb: createOpenKbTool({
948
+ worktree: ctx.worktree,
949
+ logger: ctx.logger,
950
+ }),
944
951
  };
945
952
  const tools = bg.instanceManager
946
953
  ? {
@@ -265,6 +265,7 @@ function parseFlags(tokens: string[]): ParsedFlags {
265
265
  * - `/plan comments <slug> [id]` — tool_invocation: bizar_get_plan_comments
266
266
  * - `/plan wait <slug> [--timeout N]`
267
267
  * — deferred (returns response; no tool call)
268
+ * - `/kb` — open the Bizar Memory vault in Obsidian
268
269
  * - `/help` / `/commands` — help text
269
270
  */
270
271
  export function parseSlashCommand(
@@ -293,6 +294,8 @@ export function parseSlashCommand(
293
294
  return handlePlan(rest, ctx);
294
295
  case "bizar":
295
296
  return handleBizar(rest, ctx);
297
+ case "kb":
298
+ return handleKb(rest, ctx);
296
299
  case "help":
297
300
  case "commands":
298
301
  return helpResult();
@@ -1090,12 +1093,49 @@ function handleBizar(arg: string, ctx: ParseContext): SlashCommandResult {
1090
1093
  };
1091
1094
  }
1092
1095
 
1096
+ // --- /kb -----------------------------------------------------------------
1097
+
1098
+ /**
1099
+ * v4.0.0 — `/kb` opens the Bizar Memory vault in Obsidian.
1100
+ *
1101
+ * Behavior:
1102
+ * - Routes through the `bizar_open_kb` tool via tool_invocation.
1103
+ * - The tool resolves the vault path from .bizar/memory.json and
1104
+ * spawns Obsidian (or xdg-open as fallback).
1105
+ * - If memory isn't initialized, the tool returns a helpful message.
1106
+ *
1107
+ * No subcommands. With args, returns a help pointer.
1108
+ */
1109
+ function handleKb(arg: string, _ctx: ParseContext): SlashCommandResult {
1110
+ const trimmed = arg.trim();
1111
+
1112
+ if (trimmed !== "") {
1113
+ return {
1114
+ handled: true,
1115
+ response:
1116
+ `Unknown argument: \`${trimmed}\`. \`/kb\` takes no arguments.\n\n` +
1117
+ `Use \`/kb\` to open the Bizar Memory vault in Obsidian.\n` +
1118
+ `Use \`bizar memory status\` to see vault details.`,
1119
+ };
1120
+ }
1121
+
1122
+ return {
1123
+ handled: true,
1124
+ response: "Opening Bizar Memory vault in Obsidian…",
1125
+ sideEffect: {
1126
+ kind: "tool_invocation",
1127
+ toolName: "bizar_open_kb",
1128
+ args: {},
1129
+ },
1130
+ };
1131
+ }
1132
+
1093
1133
  // --- /help ----------------------------------------------------------------
1094
1134
 
1095
1135
  function helpResult(): SlashCommandResult {
1096
1136
  return {
1097
1137
  handled: true,
1098
- response: "Available commands: /visual-plan [on|off|status], /plan new <slug> [template], /plan list, /plan open <slug>, /plan get <slug>, /plan add <slug>, /plan update <slug> <id>, /plan delete <slug> <id>, /plan comment <slug> [id] \"text\", /plan comments <slug> [id], /plan status <slug> <status>, /plan wait <slug> [--timeout N], /bizar, /bizar <args>, /help. See the dialog for full descriptions.",
1138
+ response: "Available commands: /visual-plan [on|off|status], /plan new <slug> [template], /plan list, /plan open <slug>, /plan get <slug>, /plan add <slug>, /plan update <slug> <id>, /plan delete <slug> <id>, /plan comment <slug> [id] \"text\", /plan comments <slug> [id], /plan status <slug> <status>, /plan wait <slug> [--timeout N], /kb, /bizar, /bizar <args>, /help. See the dialog for full descriptions.",
1099
1139
  dialog: {
1100
1140
  id: generateId(),
1101
1141
  title: "Bizar Commands",
@@ -1117,6 +1157,7 @@ function helpResult(): SlashCommandResult {
1117
1157
  { cmd: "/plan wait <slug> [--timeout N]", desc: "Wait for feedback (deferred)" },
1118
1158
  { cmd: "/bizar", desc: "Launch the Bizar dashboard" },
1119
1159
  { cmd: "/bizar <args>", desc: "Route a request via the menu" },
1160
+ { cmd: "/kb", desc: "Open the Bizar Memory vault in Obsidian" },
1120
1161
  { cmd: "/help | /commands", desc: "Show this help" },
1121
1162
  ],
1122
1163
  templates: [...KNOWN_TEMPLATES],
@@ -0,0 +1,191 @@
1
+ /**
2
+ * open-kb.ts
3
+ *
4
+ * v4.0.0 — `bizar_open_kb` tool.
5
+ *
6
+ * Resolves the project's Bizar Memory vault from `.bizar/memory.json`
7
+ * and opens it in Obsidian. Falls back to printing the vault path if
8
+ * Obsidian isn't installed or running headlessly.
9
+ *
10
+ * Behavior:
11
+ * - Reads .bizar/memory.json from worktree
12
+ * - Calls resolveVault() to determine vault path
13
+ * - Spawns `obsidian <vaultPath>` as a detached child
14
+ * - On ENOENT, tries `xdg-open "obsidian://open?path=<vaultPath>"`
15
+ * - On no display server, prints the path so the user can `cd` to it
16
+ *
17
+ * No args. Returns a JSON string with { ok, vaultPath, opened, method }.
18
+ */
19
+
20
+ import { tool } from "@opencode-ai/plugin";
21
+ import { z } from "zod";
22
+ import { existsSync, readFileSync } from "node:fs";
23
+ import { join } from "node:path";
24
+ import { homedir } from "node:os";
25
+ import { spawn } from "node:child_process";
26
+
27
+ import type { Logger } from "../logger.js";
28
+
29
+ export interface OpenKbDeps {
30
+ worktree: string;
31
+ logger: Logger;
32
+ }
33
+
34
+ interface MemoryJson {
35
+ version?: number;
36
+ backend?: string;
37
+ projectId?: string;
38
+ memoryRepo?: {
39
+ mode?: string;
40
+ path?: string;
41
+ remote?: string | null;
42
+ branch?: string;
43
+ namespace?: string | null;
44
+ };
45
+ namespaces?: {
46
+ project?: string;
47
+ global?: string;
48
+ user?: string;
49
+ };
50
+ }
51
+
52
+ function loadMemoryJson(worktree: string): MemoryJson | null {
53
+ const p = join(worktree, ".bizar", "memory.json");
54
+ if (!existsSync(p)) return null;
55
+ try {
56
+ return JSON.parse(readFileSync(p, "utf8"));
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+
62
+ function resolveVaultPath(worktree: string, cfg: MemoryJson | null): string | null {
63
+ if (!cfg) return null;
64
+ const mr = cfg.memoryRepo || {};
65
+ const mode = mr.mode || "local-only";
66
+ const projectId = cfg.projectId || worktree.split("/").pop() || "unknown";
67
+
68
+ if (mode === "local-only") {
69
+ return join(worktree, ".obsidian");
70
+ }
71
+
72
+ // managed or linked
73
+ const rawPath = mr.path || "";
74
+ if (!rawPath) return null;
75
+ const expanded = rawPath.startsWith("~")
76
+ ? join(homedir(), rawPath.slice(1))
77
+ : rawPath;
78
+ return join(expanded, "projects", projectId);
79
+ }
80
+
81
+ export function createOpenKbTool(deps: OpenKbDeps) {
82
+ return tool({
83
+ description:
84
+ "Open the Bizar Memory vault in Obsidian. Resolves the vault path " +
85
+ "from .bizar/memory.json and spawns Obsidian with the path. " +
86
+ "If Obsidian isn't installed, prints the vault path so the user " +
87
+ "can open it manually. Available to all agents.",
88
+ args: {},
89
+ execute: async () => {
90
+ const cfg = loadMemoryJson(deps.worktree);
91
+
92
+ if (!cfg) {
93
+ return {
94
+ output: JSON.stringify({
95
+ ok: false,
96
+ error: "memory_not_initialized",
97
+ message:
98
+ "Run `bizar memory init` first to set up the Bizar Memory vault.",
99
+ }),
100
+ };
101
+ }
102
+
103
+ const vaultPath = resolveVaultPath(deps.worktree, cfg);
104
+
105
+ if (!vaultPath) {
106
+ return {
107
+ output: JSON.stringify({
108
+ ok: false,
109
+ error: "vault_path_unresolved",
110
+ message: "Could not resolve vault path from .bizar/memory.json",
111
+ config: cfg,
112
+ }),
113
+ };
114
+ }
115
+
116
+ if (!existsSync(vaultPath)) {
117
+ return {
118
+ output: JSON.stringify({
119
+ ok: false,
120
+ error: "vault_not_found",
121
+ vaultPath,
122
+ message: `Vault directory does not exist at ${vaultPath}. Run \`bizar memory init\` to create it.`,
123
+ }),
124
+ };
125
+ }
126
+
127
+ // Try `obsidian <path>` first
128
+ try {
129
+ const child = spawn("obsidian", [vaultPath], {
130
+ detached: true,
131
+ stdio: "ignore",
132
+ });
133
+ child.on("error", () => {
134
+ // Fall through to xdg-open attempt
135
+ });
136
+ child.unref();
137
+
138
+ // Give it a beat — if spawn succeeded, assume success
139
+ return {
140
+ output: JSON.stringify({
141
+ ok: true,
142
+ vaultPath,
143
+ opened: true,
144
+ method: "obsidian",
145
+ message: `Opened vault in Obsidian at ${vaultPath}`,
146
+ }),
147
+ };
148
+ } catch (err) {
149
+ deps.logger.warn(
150
+ `bizar: open-kb: obsidian spawn failed: ${err instanceof Error ? err.message : String(err)}`,
151
+ );
152
+ // fall through to xdg-open
153
+ }
154
+
155
+ // Fallback: xdg-open with obsidian:// URI
156
+ try {
157
+ const child = spawn(
158
+ "xdg-open",
159
+ [`obsidian://open?path=${encodeURIComponent(vaultPath)}`],
160
+ { detached: true, stdio: "ignore" },
161
+ );
162
+ child.on("error", (err) => {
163
+ deps.logger.warn(`bizar: open-kb: xdg-open failed: ${err.message}`);
164
+ });
165
+ child.unref();
166
+
167
+ return {
168
+ output: JSON.stringify({
169
+ ok: true,
170
+ vaultPath,
171
+ opened: true,
172
+ method: "xdg-open",
173
+ message: `Opened vault via xdg-open at ${vaultPath}`,
174
+ }),
175
+ };
176
+ } catch (err) {
177
+ // Last resort: just print the path
178
+ return {
179
+ output: JSON.stringify({
180
+ ok: false,
181
+ error: "no_obsidian",
182
+ vaultPath,
183
+ message:
184
+ `Could not launch Obsidian. Vault path: ${vaultPath}\n` +
185
+ `Install Obsidian (https://obsidian.md) and try again, or open the vault manually with \`obsidian ${vaultPath}\`.`,
186
+ }),
187
+ };
188
+ }
189
+ },
190
+ });
191
+ }
@@ -545,4 +545,40 @@ describe("parseSlashCommand — /help includes new subcommands", () => {
545
545
  expect(text).toContain(cmd);
546
546
  }
547
547
  });
548
+ });
549
+
550
+ // ===========================================================================
551
+ // Group 11 — v4.0.0 /kb
552
+ // ===========================================================================
553
+
554
+ describe("parseSlashCommand — /kb", () => {
555
+ test("parses '/kb' slash command", () => {
556
+ const result = parseSlashCommand("/kb", baseCtx);
557
+ expect(result).not.toBeNull();
558
+ expect(result!.handled).toBe(true);
559
+ expect(result!.sideEffect).toEqual({
560
+ kind: "tool_invocation",
561
+ toolName: "bizar_open_kb",
562
+ args: {},
563
+ });
564
+ });
565
+
566
+ test("'/kb' with args returns help", () => {
567
+ const result = parseSlashCommand("/kb foo", baseCtx);
568
+ expect(result).not.toBeNull();
569
+ expect(result!.handled).toBe(true);
570
+ expect(result!.sideEffect).toBeUndefined();
571
+ expect(result!.response).toContain("Unknown argument");
572
+ });
573
+
574
+ test("'/kb' case-insensitive", () => {
575
+ const r = parseSlashCommand("/KB", baseCtx);
576
+ expect(r).not.toBeNull();
577
+ expect(r!.handled).toBe(true);
578
+ expect(r!.sideEffect).toEqual({
579
+ kind: "tool_invocation",
580
+ toolName: "bizar_open_kb",
581
+ args: {},
582
+ });
583
+ });
548
584
  });