@polderlabs/bizar 4.9.0 → 5.0.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 (183) hide show
  1. package/bizar-dash/dist/assets/icons-Bo0iH9EC.js +701 -0
  2. package/bizar-dash/dist/assets/icons-Bo0iH9EC.js.map +1 -0
  3. package/bizar-dash/dist/assets/{index-DU61awG3.js → index-DmpSFPJY.js} +1 -1
  4. package/bizar-dash/dist/assets/{index-DU61awG3.js.map → index-DmpSFPJY.js.map} +1 -1
  5. package/bizar-dash/dist/assets/main-C1cpttnv.js +19 -0
  6. package/bizar-dash/dist/assets/main-C1cpttnv.js.map +1 -0
  7. package/bizar-dash/dist/assets/main-DTkNlLrw.css +1 -0
  8. package/bizar-dash/dist/assets/markdown-DIquRulQ.js +29 -0
  9. package/bizar-dash/dist/assets/markdown-DIquRulQ.js.map +1 -0
  10. package/bizar-dash/dist/assets/mobile-BtxQJftK.js +2 -0
  11. package/bizar-dash/dist/assets/mobile-BtxQJftK.js.map +1 -0
  12. package/bizar-dash/dist/assets/mobile-DD-FZrTC.js +1 -0
  13. package/bizar-dash/dist/assets/{mobile-CL5uUQEC.js.map → mobile-DD-FZrTC.js.map} +1 -1
  14. package/bizar-dash/dist/assets/react-vendor-DZRUXSPQ.js +40 -0
  15. package/bizar-dash/dist/assets/react-vendor-DZRUXSPQ.js.map +1 -0
  16. package/bizar-dash/dist/index.html +6 -3
  17. package/bizar-dash/dist/mobile.html +5 -2
  18. package/bizar-dash/node_modules/.package-lock.json +6 -0
  19. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  20. package/bizar-dash/package-lock.json +6 -0
  21. package/bizar-dash/skills/eval/SKILL.md +237 -0
  22. package/bizar-dash/src/server/api.mjs +35 -0
  23. package/bizar-dash/src/server/auth.mjs +155 -1
  24. package/bizar-dash/src/server/diagnostics-store.mjs +452 -2
  25. package/bizar-dash/src/server/eval-store.mjs +226 -0
  26. package/bizar-dash/src/server/eval.mjs +347 -0
  27. package/bizar-dash/src/server/memory-store.mjs +46 -0
  28. package/bizar-dash/src/server/ocr.mjs +55 -0
  29. package/bizar-dash/src/server/plugins/registry.mjs +363 -0
  30. package/bizar-dash/src/server/plugins/sandbox.mjs +655 -0
  31. package/bizar-dash/src/server/plugins/store.mjs +659 -0
  32. package/bizar-dash/src/server/providers-store.mjs +11 -4
  33. package/bizar-dash/src/server/routes/_shared.mjs +2 -2
  34. package/bizar-dash/src/server/routes/clipboard.mjs +173 -0
  35. package/bizar-dash/src/server/routes/doctor.mjs +71 -0
  36. package/bizar-dash/src/server/routes/env-vars.mjs +67 -1
  37. package/bizar-dash/src/server/routes/eval.mjs +147 -0
  38. package/bizar-dash/src/server/routes/memory.mjs +3 -0
  39. package/bizar-dash/src/server/routes/ocr.mjs +182 -0
  40. package/bizar-dash/src/server/routes/opencode-session-detail.mjs +61 -42
  41. package/bizar-dash/src/server/routes/plugins.mjs +220 -0
  42. package/bizar-dash/src/server/routes/schedules.mjs +55 -0
  43. package/bizar-dash/src/server/routes/users.mjs +84 -0
  44. package/bizar-dash/src/server/routes/voice.mjs +131 -0
  45. package/bizar-dash/src/server/routes/workspaces.mjs +204 -0
  46. package/bizar-dash/src/server/serve-info.mjs +172 -0
  47. package/bizar-dash/src/server/voice-store.mjs +202 -0
  48. package/bizar-dash/src/server/voice-transcribe.mjs +72 -0
  49. package/bizar-dash/src/server/workspaces.mjs +626 -0
  50. package/bizar-dash/src/web/App.tsx +48 -11
  51. package/bizar-dash/src/web/components/AutosaveField.tsx +50 -0
  52. package/bizar-dash/src/web/components/DoctorPanel.tsx +160 -0
  53. package/bizar-dash/src/web/components/EnvVarManager.tsx +245 -62
  54. package/bizar-dash/src/web/components/InviteDialog.tsx +205 -0
  55. package/bizar-dash/src/web/components/ScheduleTemplateCard.tsx +71 -0
  56. package/bizar-dash/src/web/components/ScreenshotCapture.tsx +138 -0
  57. package/bizar-dash/src/web/components/ScreenshotOCR.tsx +42 -0
  58. package/bizar-dash/src/web/components/SettingsNav.tsx +101 -0
  59. package/bizar-dash/src/web/components/Sidebar.tsx +25 -1
  60. package/bizar-dash/src/web/components/StatusBadge.tsx +33 -2
  61. package/bizar-dash/src/web/components/Topbar.tsx +11 -0
  62. package/bizar-dash/src/web/components/VoiceNotesPanel.tsx +182 -0
  63. package/bizar-dash/src/web/components/VoiceRecorder.tsx +104 -0
  64. package/bizar-dash/src/web/components/WorkspaceSelector.tsx +158 -0
  65. package/bizar-dash/src/web/components/chat/ChatInfoPanel.tsx +64 -0
  66. package/bizar-dash/src/web/components/chat/Composer.tsx +1 -1
  67. package/bizar-dash/src/web/components/chat/useChat.ts +118 -1
  68. package/bizar-dash/src/web/hooks/useAutosave.ts +107 -0
  69. package/bizar-dash/src/web/lib/types.ts +134 -0
  70. package/bizar-dash/src/web/styles/chat.css +8 -5
  71. package/bizar-dash/src/web/styles/main.css +77 -2
  72. package/bizar-dash/src/web/styles/memory.css +82 -0
  73. package/bizar-dash/src/web/styles/settings.css +265 -0
  74. package/bizar-dash/src/web/views/Chat.tsx +15 -1
  75. package/bizar-dash/src/web/views/Doctor.tsx +317 -0
  76. package/bizar-dash/src/web/views/Memory.tsx +17 -2
  77. package/bizar-dash/src/web/views/MiniMaxUsage.tsx +1 -0
  78. package/bizar-dash/src/web/views/Schedules.tsx +94 -12
  79. package/bizar-dash/src/web/views/Settings.tsx +78 -45
  80. package/bizar-dash/src/web/views/Workspace.tsx +294 -0
  81. package/bizar-dash/src/web/views/memory/ConfigPanel.tsx +114 -133
  82. package/bizar-dash/src/web/views/memory/FromScreenshotPanel.tsx +23 -0
  83. package/bizar-dash/src/web/views/memory/VaultFromClipboardPanel.tsx +101 -0
  84. package/bizar-dash/src/web/views/settings/AgentSection.tsx +23 -12
  85. package/bizar-dash/src/web/views/settings/EnvVarsSection.tsx +5 -6
  86. package/bizar-dash/src/web/views/settings/GeneralSection.tsx +38 -15
  87. package/bizar-dash/src/web/views/settings/MemorySection.tsx +92 -8
  88. package/bizar-dash/src/web/views/settings/WorkspacesSection.tsx +189 -0
  89. package/bizar-dash/tests/autosave.test.tsx +276 -0
  90. package/bizar-dash/tests/bundle-analysis.test.mjs +5 -2
  91. package/bizar-dash/tests/chat-composer.test.tsx +140 -0
  92. package/bizar-dash/tests/clipboard.test.mjs +147 -0
  93. package/bizar-dash/tests/components/doctor-panel.test.tsx +105 -0
  94. package/bizar-dash/tests/components/screenshot-ocr.test.tsx +75 -0
  95. package/bizar-dash/tests/components/workspace-selector.test.tsx +73 -0
  96. package/bizar-dash/tests/deploy-templates.test.mjs +100 -0
  97. package/bizar-dash/tests/diagnostics-store.test.mjs +206 -0
  98. package/bizar-dash/tests/env-vars-extended.test.mjs +190 -0
  99. package/bizar-dash/tests/eval/fixtures.test.mjs +141 -0
  100. package/bizar-dash/tests/eval/report.test.mjs +284 -0
  101. package/bizar-dash/tests/eval/runner.test.mjs +471 -0
  102. package/bizar-dash/tests/layout-spacing.test.mjs +101 -0
  103. package/bizar-dash/tests/memory-default-vault.test.mjs +98 -0
  104. package/bizar-dash/tests/minimax-bar.test.tsx +113 -0
  105. package/bizar-dash/tests/minimax-models.test.mjs +123 -0
  106. package/bizar-dash/tests/ocr.test.mjs +87 -0
  107. package/bizar-dash/tests/opencode-sessions-detail.test.mjs +158 -0
  108. package/bizar-dash/tests/plugins-registry.test.mjs +387 -0
  109. package/bizar-dash/tests/plugins-sandbox.test.mjs +374 -0
  110. package/bizar-dash/tests/plugins-store.test.mjs +455 -0
  111. package/bizar-dash/tests/routes-doctor.test.mjs +159 -0
  112. package/bizar-dash/tests/schedules-templates.test.mjs +144 -0
  113. package/bizar-dash/tests/settings-layout.test.tsx +129 -0
  114. package/bizar-dash/tests/settings-mode-wiring.test.tsx +151 -0
  115. package/bizar-dash/tests/settings-nav.test.tsx +126 -0
  116. package/bizar-dash/tests/users.test.mjs +108 -0
  117. package/bizar-dash/tests/voice-recorder.test.tsx +95 -0
  118. package/bizar-dash/tests/voice-store.test.mjs +148 -0
  119. package/bizar-dash/tests/voice-transcribe.test.mjs +87 -0
  120. package/bizar-dash/tests/workspaces.test.mjs +527 -0
  121. package/cli/bin.mjs +72 -2
  122. package/cli/commands/clip.mjs +146 -0
  123. package/cli/commands/deploy/cloudflare.mjs +250 -0
  124. package/cli/commands/deploy/docker.mjs +221 -0
  125. package/cli/commands/deploy/fly.mjs +161 -0
  126. package/cli/commands/deploy/vercel.mjs +225 -0
  127. package/cli/commands/deploy.mjs +240 -0
  128. package/cli/commands/eval.mjs +378 -0
  129. package/cli/commands/marketplace.mjs +64 -0
  130. package/cli/commands/minimax.mjs +5 -2
  131. package/cli/commands/ocr.mjs +165 -0
  132. package/cli/commands/plugin.mjs +358 -0
  133. package/cli/commands/voice.mjs +211 -0
  134. package/cli/commands/workspace.mjs +247 -0
  135. package/cli/tests/minimax-cli.test.mjs +79 -0
  136. package/config/agents/frigg.md +1 -1
  137. package/config/agents/heimdall.md +1 -1
  138. package/config/agents/mimir.md +1 -1
  139. package/config/agents/quick.md +1 -1
  140. package/config/agents/semble-search.md +1 -1
  141. package/config/agents/vor.md +1 -1
  142. package/config/opencode.json +28 -21
  143. package/config/opencode.json.template +8 -1
  144. package/package.json +12 -8
  145. package/plugins/bizar/index.ts +77 -0
  146. package/plugins/bizar/src/compaction.d.mts +48 -0
  147. package/plugins/bizar/src/compaction.mjs +192 -0
  148. package/plugins/bizar/tests/compaction.test.ts +264 -0
  149. package/templates/deploy/cloudflare/README.md +32 -0
  150. package/templates/deploy/cloudflare/functions-index.template.js +15 -0
  151. package/templates/deploy/cloudflare/wrangler.toml.template +9 -0
  152. package/templates/deploy/docker/.env.template +16 -0
  153. package/templates/deploy/docker/README.md +58 -0
  154. package/templates/deploy/docker/docker-compose.template.yml +23 -0
  155. package/templates/deploy/fly/README.md +35 -0
  156. package/templates/deploy/fly/fly.toml.template +28 -0
  157. package/templates/deploy/vercel/README.md +29 -0
  158. package/templates/deploy/vercel/api-index.template.js +18 -0
  159. package/templates/deploy/vercel/vercel.json.template +16 -0
  160. package/templates/eval-fixtures/README.md +58 -0
  161. package/templates/eval-fixtures/code-search-basic.json +28 -0
  162. package/templates/eval-fixtures/latency-bounds.json +16 -0
  163. package/templates/eval-fixtures/regression-suite.json +79 -0
  164. package/templates/eval-fixtures/response-format.json +30 -0
  165. package/templates/eval-fixtures/tool-call-correctness.json +24 -0
  166. package/templates/plugin-template/README.md +121 -0
  167. package/templates/plugin-template/index.js +66 -0
  168. package/templates/plugin-template/plugin.json +42 -0
  169. package/templates/plugin-template/tests/plugin.test.js +83 -0
  170. package/templates/schedules/daily-backup.json +12 -0
  171. package/templates/schedules/daily-cleanup.json +12 -0
  172. package/templates/schedules/hourly-health-check.json +12 -0
  173. package/templates/schedules/webhook-on-push.json +13 -0
  174. package/templates/schedules/weekly-digest.json +13 -0
  175. package/bizar-dash/dist/assets/main-DaC1Lc6q.js +0 -366
  176. package/bizar-dash/dist/assets/main-DaC1Lc6q.js.map +0 -1
  177. package/bizar-dash/dist/assets/main-DfmIfOUS.css +0 -1
  178. package/bizar-dash/dist/assets/mobile-CL5uUQEC.js +0 -1
  179. package/bizar-dash/dist/assets/mobile-D5WTWvuh.js +0 -338
  180. package/bizar-dash/dist/assets/mobile-D5WTWvuh.js.map +0 -1
  181. package/bizar-dash/src/web/views/settings/BackupSection.tsx +0 -16
  182. package/bizar-dash/src/web/views/settings/ProvidersSection.tsx +0 -16
  183. package/bizar-dash/src/web/views/settings/SkillsSection.tsx +0 -16
@@ -0,0 +1,247 @@
1
+ /**
2
+ * cli/commands/workspace.mjs
3
+ *
4
+ * v5.0.0 — Workspace management CLI — talks to the running dashboard's HTTP API.
5
+ */
6
+ import chalk from 'chalk';
7
+ import { existsSync, readFileSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+ import { homedir } from 'node:os';
10
+
11
+ const HOME = homedir();
12
+
13
+ // ── Config dir helper ──────────────────────────────────────────────────────────
14
+
15
+ function getBizarConfigDir() {
16
+ if (process.platform === 'win32') {
17
+ return process.env.APPDATA
18
+ ? join(process.env.APPDATA, 'bizar')
19
+ : join(homedir(), '.config', 'bizar');
20
+ }
21
+ return process.env.XDG_CONFIG_HOME
22
+ ? join(process.env.XDG_CONFIG_HOME, 'bizar')
23
+ : join(homedir(), '.config', 'bizar');
24
+ }
25
+
26
+ function getDashboardPort() {
27
+ const portFile = join(getBizarConfigDir(), 'dashboard.port');
28
+ try {
29
+ const port = parseInt(readFileSync(portFile, 'utf8').trim(), 10);
30
+ if (Number.isFinite(port) && port > 0) return port;
31
+ } catch {
32
+ /* fall through */
33
+ }
34
+ return null;
35
+ }
36
+
37
+ // ── API helpers ───────────────────────────────────────────────────────────────
38
+
39
+ async function postJson(baseUrl, path, body) {
40
+ const res = await fetch(`${baseUrl}${path}`, {
41
+ method: 'POST',
42
+ headers: { 'Content-Type': 'application/json' },
43
+ body: JSON.stringify(body || {}),
44
+ });
45
+ const text = await res.text();
46
+ let json = null;
47
+ try { json = text ? JSON.parse(text) : null; } catch { /* keep raw */ }
48
+ if (!res.ok) {
49
+ const msg = json?.message || json?.error || text || `HTTP ${res.status}`;
50
+ throw new Error(`${path} failed: ${msg}`);
51
+ }
52
+ return json;
53
+ }
54
+
55
+ async function getJson(baseUrl, path) {
56
+ const res = await fetch(`${baseUrl}${path}`);
57
+ const text = await res.text();
58
+ let json = null;
59
+ try { json = text ? JSON.parse(text) : null; } catch { /* keep raw */ }
60
+ if (!res.ok) {
61
+ const msg = json?.message || json?.error || text || `HTTP ${res.status}`;
62
+ throw new Error(`${path} failed: ${msg}`);
63
+ }
64
+ return json;
65
+ }
66
+
67
+ async function delJson(baseUrl, path) {
68
+ const res = await fetch(`${baseUrl}${path}`, { method: 'DELETE' });
69
+ const text = await res.text();
70
+ let json = null;
71
+ try { json = text ? JSON.parse(text) : null; } catch { /* keep raw */ }
72
+ if (!res.ok) {
73
+ const msg = json?.message || json?.error || text || `HTTP ${res.status}`;
74
+ throw new Error(`${path} failed: ${msg}`);
75
+ }
76
+ return json;
77
+ }
78
+
79
+ // ── Help ───────────────────────────────────────────────────────────────────────
80
+
81
+ export function showWorkspaceHelp() {
82
+ console.log(`
83
+ bizar workspace — Manage workspaces (via the dashboard's HTTP API)
84
+
85
+ Usage:
86
+ bizar workspace list List all workspaces
87
+ bizar workspace create <name> Create a new workspace
88
+ bizar workspace switch <id> Set active workspace
89
+ bizar workspace invite <email> [role] Generate invite URL (role: admin|editor|viewer)
90
+ bizar workspace accept <token> Accept an invite
91
+ bizar workspace members List workspace members
92
+ bizar workspace remove-member <userId> Remove a member
93
+
94
+ Description:
95
+ Subcommands call the running dashboard's HTTP API. If no dashboard is
96
+ reachable, you'll be told to run \`bizar dash start\` first.
97
+
98
+ Workspaces let you share access with team members. Each workspace has
99
+ its own members, settings, and data.
100
+
101
+ Examples:
102
+ bizar workspace list
103
+ bizar workspace create "Engineering"
104
+ bizar workspace invite alice@example.com editor
105
+ bizar workspace accept abc123def456
106
+ bizar workspace members
107
+ bizar workspace remove-member usr_abc123
108
+ `);
109
+ }
110
+
111
+ // ── Command runner ───────────────────────────────────────────────────────────
112
+
113
+ export async function runWorkspaceCommand(args) {
114
+ const sub = args[0];
115
+ const positional = args.slice(1).filter((a) => !a.startsWith('-'));
116
+ const flags = args.slice(1).filter((a) => a.startsWith('-'));
117
+
118
+ if (!sub || sub === '--help' || sub === '-h' || flags.includes('--help') || flags.includes('-h')) {
119
+ showWorkspaceHelp();
120
+ return;
121
+ }
122
+
123
+ const port = getDashboardPort();
124
+ if (!port) {
125
+ console.error(chalk.red(' ✗ Dashboard is not running (no port file).'));
126
+ console.error(chalk.dim(' Start it first: `bizar dash start --bg`'));
127
+ process.exit(1);
128
+ }
129
+ const baseUrl = `http://127.0.0.1:${port}`;
130
+
131
+ try {
132
+ if (sub === 'list') {
133
+ const r = await getJson(baseUrl, '/api/workspaces');
134
+ const workspaces = r.workspaces || [];
135
+ if (workspaces.length === 0) {
136
+ console.log(chalk.dim(' (no workspaces)'));
137
+ return;
138
+ }
139
+ console.log(chalk.bold(' Workspaces:'));
140
+ for (const { workspace, role } of workspaces) {
141
+ const badge = role === 'admin' ? chalk.green('admin') : role === 'editor' ? chalk.yellow('editor') : chalk.blue('viewer');
142
+ console.log(` ${workspace.id.padEnd(20)} ${workspace.name.padEnd(30)} ${badge}`);
143
+ }
144
+ } else if (sub === 'create') {
145
+ const name = positional[0];
146
+ if (!name) {
147
+ console.error(chalk.red(' ✗ Missing workspace name. Usage: bizar workspace create <name>'));
148
+ process.exit(1);
149
+ }
150
+ const r = await postJson(baseUrl, '/api/workspaces', { name });
151
+ console.log(chalk.green(` ✓ Created workspace "${r.workspace.name}" (${r.workspace.id})`));
152
+ } else if (sub === 'switch') {
153
+ const wsId = positional[0];
154
+ if (!wsId) {
155
+ console.error(chalk.red(' ✗ Missing workspace id. Usage: bizar workspace switch <id>'));
156
+ process.exit(1);
157
+ }
158
+ // Verify workspace exists
159
+ try {
160
+ await getJson(baseUrl, `/api/workspaces/${wsId}`);
161
+ } catch {
162
+ console.error(chalk.red(` ✗ Workspace not found: ${wsId}`));
163
+ process.exit(1);
164
+ }
165
+ console.log(chalk.green(` ✓ Switched to workspace ${wsId}`));
166
+ console.log(chalk.dim(' Note: Run `bizar workspace list` to see workspace ids'));
167
+ } else if (sub === 'invite') {
168
+ const email = positional[0];
169
+ if (!email) {
170
+ console.error(chalk.red(' ✗ Missing email. Usage: bizar workspace invite <email> [role]'));
171
+ process.exit(1);
172
+ }
173
+ const role = positional[1] || 'editor';
174
+ if (!['admin', 'editor', 'viewer'].includes(role)) {
175
+ console.error(chalk.red(` ✗ Invalid role: ${role}. Must be admin, editor, or viewer.`));
176
+ process.exit(1);
177
+ }
178
+ // Get first workspace id (or use BIZAR_WORKSPACE_ID env)
179
+ const wsId = process.env.BIZAR_WORKSPACE_ID;
180
+ if (!wsId) {
181
+ console.error(chalk.red(' ✗ Set BIZAR_WORKSPACE_ID env var or run from a workspace context.'));
182
+ console.error(chalk.dim(' Run `bizar workspace list` to find your workspace id.'));
183
+ process.exit(1);
184
+ }
185
+ const r = await postJson(baseUrl, `/api/workspaces/${wsId}/invites`, { email, role });
186
+ console.log(chalk.green(` ✓ Invite created for ${email} as ${role}`));
187
+ console.log(chalk.dim(' Invite URL:'));
188
+ console.log(` ${r.url}`);
189
+ } else if (sub === 'accept') {
190
+ const token = positional[0];
191
+ if (!token) {
192
+ console.error(chalk.red(' ✗ Missing invite token. Usage: bizar workspace accept <token>'));
193
+ process.exit(1);
194
+ }
195
+ // For CLI acceptance, we need user info - prompt or use env
196
+ const email = process.env.BIZAR_USER_EMAIL;
197
+ const name = process.env.BIZAR_USER_NAME || 'CLI User';
198
+ if (!email) {
199
+ console.error(chalk.red(' ✗ Set BIZAR_USER_EMAIL env var with your email address.'));
200
+ process.exit(1);
201
+ }
202
+ const r = await postJson(baseUrl, `/api/invites/${token}/accept`, { email, name });
203
+ console.log(chalk.green(` ✓ Joined workspace "${r.workspace.name}" as ${r.role}`));
204
+ } else if (sub === 'members') {
205
+ const wsId = process.env.BIZAR_WORKSPACE_ID;
206
+ if (!wsId) {
207
+ console.error(chalk.red(' ✗ Set BIZAR_WORKSPACE_ID env var or run from a workspace context.'));
208
+ process.exit(1);
209
+ }
210
+ const r = await getJson(baseUrl, `/api/workspaces/${wsId}`);
211
+ const members = r.members || [];
212
+ if (members.length === 0) {
213
+ console.log(chalk.dim(' (no members)'));
214
+ return;
215
+ }
216
+ console.log(chalk.bold(` Members of ${r.workspace.name}:`));
217
+ for (const m of members) {
218
+ const badge = m.role === 'admin' ? chalk.green('admin') : m.role === 'editor' ? chalk.yellow('editor') : chalk.blue('viewer');
219
+ console.log(` ${m.userId.padEnd(20)} ${(m.name || m.email).padEnd(20)} ${badge}`);
220
+ }
221
+ } else if (sub === 'remove-member') {
222
+ const userId = positional[0];
223
+ if (!userId) {
224
+ console.error(chalk.red(' ✗ Missing user id. Usage: bizar workspace remove-member <userId>'));
225
+ process.exit(1);
226
+ }
227
+ const wsId = process.env.BIZAR_WORKSPACE_ID;
228
+ if (!wsId) {
229
+ console.error(chalk.red(' ✗ Set BIZAR_WORKSPACE_ID env var.'));
230
+ process.exit(1);
231
+ }
232
+ await delJson(baseUrl, `/api/workspaces/${wsId}/members/${userId}`);
233
+ console.log(chalk.green(` ✓ Removed member ${userId}`));
234
+ } else {
235
+ console.error(chalk.red(` ✗ Unknown workspace subcommand: ${sub}`));
236
+ showWorkspaceHelp();
237
+ process.exit(1);
238
+ }
239
+ } catch (err) {
240
+ console.error(chalk.red(` ✗ ${err.message}`));
241
+ process.exit(1);
242
+ }
243
+ }
244
+
245
+ export async function run(name, args, isHelpRequest) {
246
+ await runWorkspaceCommand(args);
247
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * cli/tests/minimax-cli.test.mjs
3
+ *
4
+ * Tests for MiniMax CLI bar rendering.
5
+ * Verifies bar shows CONSUMED quota as filled blocks (not remaining).
6
+ */
7
+
8
+ import { test, describe } from 'node:test';
9
+ import assert from 'node:assert';
10
+
11
+ // ── Bar rendering helper (mirrors minimax.mjs logic) ──────────────────────────
12
+
13
+ /**
14
+ * Render a quota bar string.
15
+ * @param {number} remainingPct - percentage of quota remaining (0-100)
16
+ * @returns {string} bar string like '██░░░░░░░░░░░░░░░░░' (20 chars total)
17
+ */
18
+ function renderBar(remainingPct) {
19
+ const consumed = 100 - remainingPct;
20
+ const filled = Math.round(consumed / 5);
21
+ const empty = 20 - filled;
22
+ return '█'.repeat(filled) + '░'.repeat(empty);
23
+ }
24
+
25
+ // ── Tests ───────────────────────────────────────────────────────────────────
26
+
27
+ describe('MiniMax CLI bar rendering', () => {
28
+
29
+ test('91% remaining → 9% consumed → 2 filled blocks (9/5 rounds to 2)', () => {
30
+ const bar = renderBar(91);
31
+ const filledCount = (bar.match(/█/g) || []).length;
32
+ assert.strictEqual(filledCount, 2, `Expected 2 filled blocks for 91% remaining, got ${filledCount}`);
33
+ assert.strictEqual(bar.length, 20, `Bar should be exactly 20 chars`);
34
+ });
35
+
36
+ test('100% remaining → 0% consumed → 0 filled blocks', () => {
37
+ const bar = renderBar(100);
38
+ const filledCount = (bar.match(/█/g) || []).length;
39
+ assert.strictEqual(filledCount, 0, `Expected 0 filled blocks for 100% remaining`);
40
+ });
41
+
42
+ test('0% remaining → 100% consumed → 20 filled blocks', () => {
43
+ const bar = renderBar(0);
44
+ const filledCount = (bar.match(/█/g) || []).length;
45
+ assert.strictEqual(filledCount, 20, `Expected 20 filled blocks for 0% remaining`);
46
+ });
47
+
48
+ test('75% remaining → 25% consumed → 5 filled blocks', () => {
49
+ const bar = renderBar(75);
50
+ const filledCount = (bar.match(/█/g) || []).length;
51
+ assert.strictEqual(filledCount, 5, `Expected 5 filled blocks for 75% remaining`);
52
+ });
53
+
54
+ test('25% remaining → 75% consumed → 15 filled blocks', () => {
55
+ const bar = renderBar(25);
56
+ const filledCount = (bar.match(/█/g) || []).length;
57
+ assert.strictEqual(filledCount, 15, `Expected 15 filled blocks for 25% remaining`);
58
+ });
59
+
60
+ test('bar is always exactly 20 characters', () => {
61
+ for (const remaining of [0, 10, 25, 50, 75, 90, 100]) {
62
+ const bar = renderBar(remaining);
63
+ assert.strictEqual(bar.length, 20, `Bar for ${remaining}% remaining should be 20 chars, got ${bar.length}`);
64
+ }
65
+ });
66
+
67
+ test('filled blocks increase as remaining decreases', () => {
68
+ const bars = [100, 91, 75, 50, 25, 10, 0].map(r => ({
69
+ remaining: r,
70
+ filled: (renderBar(r).match(/█/g) || []).length
71
+ }));
72
+ for (let i = 1; i < bars.length; i++) {
73
+ assert.ok(
74
+ bars[i].filled >= bars[i-1].filled,
75
+ `${bars[i].remaining}% remaining (${bars[i].filled} blocks) should have >= filled blocks than ${bars[i-1].remaining}% remaining (${bars[i-1].filled} blocks)`
76
+ );
77
+ }
78
+ });
79
+ });
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Frigg — Read-only codebase Q&A. Answers questions about the project with file references, never modifies anything. Routes to no one.
3
3
  mode: subagent
4
- model: opencode/deepseek-v4-flash-free
4
+ model: minimax/MiniMax-M2.7
5
5
  color: "#f472b6"
6
6
  permission:
7
7
  read: allow
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Heimdall — Simple, routine, and deterministic tasks using DeepSeek. Quick edits, mechanical work, file operations. The ever-watchful guardian.
3
3
  mode: subagent
4
- model: opencode/deepseek-v4-flash-free
4
+ model: minimax/MiniMax-M2.7
5
5
  color: "#10b981"
6
6
  permission:
7
7
  read: allow
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Mimir — Deep codebase research and exploration. Uses Semble as primary search tool. Architecture analysis, pattern discovery, documentation research, and project initialization.
3
3
  mode: subagent
4
- model: opencode/deepseek-v4-flash-free
4
+ model: minimax/MiniMax-M2.7
5
5
  color: "#06b6d4"
6
6
  permission:
7
7
  read: allow
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Quick (quick) — fast single-shot tasks. No delegation, no parallel streams. Use for small edits, mechanical changes, one-shot questions. Routes to no one.
3
3
  mode: primary
4
- model: minimax/MiniMax-M2.7
4
+ model: minimax/MiniMax-M2.7-Flash
5
5
  color: "#22d3ee"
6
6
  permission:
7
7
  read: allow
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Code search agent for exploring any codebase. Use for finding code by intent, locating implementations, understanding how something works, or discovering related code. Prefer over Bash/Read for any semantic or exploratory question.
3
3
  mode: subagent
4
- model: opencode/deepseek-v4-flash-free
4
+ model: minimax/MiniMax-M2.7
5
5
  color: "#64748b"
6
6
  permission:
7
7
  read: allow
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Vör — Asks clarifying questions for ambiguous or incomplete requests. Reads project context first, then asks one targeted, project-specific question.
3
3
  mode: subagent
4
- model: opencode/deepseek-v4-flash-free
4
+ model: minimax/MiniMax-M2.7
5
5
  color: "#a78bfa"
6
6
  permission:
7
7
  read: allow
@@ -37,7 +37,14 @@
37
37
  "loopThresholdWarn": 5,
38
38
  "loopThresholdEscalate": 8,
39
39
  "loopThresholdBlock": 12,
40
- "loopWindowSize": 10
40
+ "loopWindowSize": 10,
41
+ "compaction": {
42
+ "auto": true,
43
+ "threshold": 0.5,
44
+ "preserve_recent": 10,
45
+ "strategy": "summarize",
46
+ "model": "minimax/MiniMax-M3-Reasoning"
47
+ }
41
48
  }]
42
49
  ],
43
50
  "tools": {
@@ -66,9 +73,9 @@
66
73
  }
67
74
  },
68
75
  "vor": {
69
- "description": "Vör — Tier 2 clarifier. Reads project context first and asks targeted, project-specific questions when requests are ambiguous. Backed by DeepSeek V4 Flash. Operates inside BizarHarness alongside 13 sibling agents.",
76
+ "description": "Vör — Tier 2 clarifier. Reads project context first and asks targeted, project-specific questions when requests are ambiguous. Backed by MiniMax M2.7. Operates inside BizarHarness alongside 13 sibling agents.",
70
77
  "mode": "subagent",
71
- "model": "opencode/deepseek-v4-flash-free",
78
+ "model": "minimax/MiniMax-M2.7",
72
79
  "color": "#8b5cf6",
73
80
  "permission": {
74
81
  "read": "allow",
@@ -77,9 +84,9 @@
77
84
  }
78
85
  },
79
86
  "frigg": {
80
- "description": "Frigg — Tier 1 primary. Read-only Q&A agent that answers codebase questions with file:line citations, never edits. Backed by DeepSeek V4 Flash. Operates inside BizarHarness alongside 13 sibling agents.",
87
+ "description": "Frigg — Tier 1 primary. Read-only Q&A agent that answers codebase questions with file:line citations, never edits. Backed by MiniMax M2.7. Operates inside BizarHarness alongside 13 sibling agents.",
81
88
  "mode": "primary",
82
- "model": "opencode/deepseek-v4-flash-free",
89
+ "model": "minimax/MiniMax-M2.7",
83
90
  "color": "#06b6d4",
84
91
  "permission": {
85
92
  "read": "allow",
@@ -93,9 +100,9 @@
93
100
  }
94
101
  },
95
102
  "quick": {
96
- "description": "Quick — Tier 1 primary. Fast single-shot escape hatch for small edits, mechanical changes, and one-shot questions. No delegation, no parallel streams. Backed by MiniMax M2.7. Operates inside BizarHarness alongside 13 sibling agents.",
103
+ "description": "Quick — Tier 1 primary. Fast single-shot escape hatch for small edits, mechanical changes, and one-shot questions. No delegation, no parallel streams. Backed by MiniMax M2.7 Flash. Operates inside BizarHarness alongside 13 sibling agents.",
97
104
  "mode": "primary",
98
- "model": "minimax/MiniMax-M2.7",
105
+ "model": "minimax/MiniMax-M2.7-Flash",
99
106
  "color": "#22d3ee",
100
107
  "permission": {
101
108
  "read": "allow",
@@ -112,9 +119,9 @@
112
119
  }
113
120
  },
114
121
  "mimir": {
115
- "description": "Mimir — Tier 2 research. Dedicated codebase exploration agent who uses Semble as primary search tool for deep analysis, pattern discovery, and documentation research. Backed by DeepSeek V4 Flash. Operates inside BizarHarness alongside 13 sibling agents.",
122
+ "description": "Mimir — Tier 2 research. Dedicated codebase exploration agent who uses Semble as primary search tool for deep analysis, pattern discovery, and documentation research. Backed by MiniMax M2.7. Operates inside BizarHarness alongside 13 sibling agents.",
116
123
  "mode": "subagent",
117
- "model": "opencode/deepseek-v4-flash-free",
124
+ "model": "minimax/MiniMax-M2.7",
118
125
  "color": "#0ea5e9",
119
126
  "permission": {
120
127
  "read": "allow",
@@ -130,9 +137,9 @@
130
137
  }
131
138
  },
132
139
  "heimdall": {
133
- "description": "Heimdall — Tier 2 mechanical. Handles simple, routine, deterministic engineering tasks with speed and precision using DeepSeek V4 Flash. The ever-watchful guardian. Operates inside BizarHarness alongside 13 sibling agents.",
140
+ "description": "Heimdall — Tier 2 mechanical. Handles simple, routine, deterministic engineering tasks with speed and precision using MiniMax M2.7. The ever-watchful guardian. Operates inside BizarHarness alongside 13 sibling agents.",
134
141
  "mode": "subagent",
135
- "model": "opencode/deepseek-v4-flash-free",
142
+ "model": "minimax/MiniMax-M2.7",
136
143
  "color": "#10b981",
137
144
  "permission": {
138
145
  "read": "allow",
@@ -249,9 +256,9 @@
249
256
  }
250
257
  },
251
258
  "semble-search": {
252
- "description": "semble-search — Tier 2 research. Code search agent for exploring any codebase using Semble's semantic search. For finding code by intent, locating implementations, and understanding how things work. Backed by DeepSeek V4 Flash. Operates inside BizarHarness alongside 13 sibling agents.",
259
+ "description": "semble-search — Tier 2 research. Code search agent for exploring any codebase using Semble's semantic search. For finding code by intent, locating implementations, and understanding how things work. Backed by MiniMax M2.7. Operates inside BizarHarness alongside 13 sibling agents.",
253
260
  "mode": "subagent",
254
- "model": "opencode/deepseek-v4-flash-free",
261
+ "model": "minimax/MiniMax-M2.7",
255
262
  "color": "#0ea5e9",
256
263
  "permission": {
257
264
  "bash": "allow",
@@ -312,15 +319,15 @@
312
319
  },
313
320
  "provider": {
314
321
  "minimax": {
322
+ "options": {
323
+ "baseURL": "https://api.minimax.io/v1",
324
+ "apiKey": "{env:MiniMax_API_KEY}"
325
+ },
315
326
  "models": {
316
- "MiniMax-M3": {
317
- "interleaved": { "field": "reasoning_details" },
318
- "reasoning": true
319
- },
320
- "MiniMax-M2.7": {
321
- "interleaved": { "field": "reasoning_details" },
322
- "reasoning": true
323
- }
327
+ "MiniMax-M2.7-Flash": { "name": "MiniMax M2.7 Flash", "interleaved": { "field": "reasoning_details" }, "reasoning": true },
328
+ "MiniMax-M2.7": { "name": "MiniMax M2.7", "interleaved": { "field": "reasoning_details" }, "reasoning": true },
329
+ "MiniMax-M3": { "name": "MiniMax M3", "interleaved": { "field": "reasoning_details" }, "reasoning": true },
330
+ "MiniMax-M3-Reasoning": { "name": "MiniMax M3 Reasoning", "interleaved": { "field": "reasoning_details" }, "reasoning": true }
324
331
  }
325
332
  }
326
333
  }
@@ -28,7 +28,14 @@
28
28
  "loopThresholdWarn": 5,
29
29
  "loopThresholdEscalate": 8,
30
30
  "loopThresholdBlock": 12,
31
- "loopWindowSize": 10
31
+ "loopWindowSize": 10,
32
+ "compaction": {
33
+ "auto": true,
34
+ "threshold": 0.5,
35
+ "preserve_recent": 10,
36
+ "strategy": "summarize",
37
+ "model": "minimax/MiniMax-M3-Reasoning"
38
+ }
32
39
  }]
33
40
  ],
34
41
  "tools": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "4.9.0",
3
+ "version": "5.0.1",
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": {
@@ -55,6 +55,7 @@
55
55
  "cors": "^2.8.6",
56
56
  "croner": "^10.0.1",
57
57
  "express": "^4.22.0",
58
+ "formidable": "^3.5.0",
58
59
  "fuse.js": "^7.0.0",
59
60
  "inquirer": "^12.0.0",
60
61
  "lucide-react": "^0.460.0",
@@ -65,8 +66,15 @@
65
66
  "react-dom": "^18.3.0",
66
67
  "react-markdown": "^9.0.0",
67
68
  "remark-gfm": "^4.0.0",
69
+ "tesseract.js": "^5.1.1",
68
70
  "ws": "^8.18.0",
69
- "zod": "4.1.8"
71
+ "zod": "4.1.8",
72
+ "@opentelemetry/api": "^1.9.1",
73
+ "@opentelemetry/exporter-trace-otlp-http": "^0.52.1",
74
+ "@opentelemetry/resources": "^1.30.1",
75
+ "@opentelemetry/sdk-node": "^0.52.1",
76
+ "@opentelemetry/sdk-trace-node": "^1.30.1",
77
+ "@opentelemetry/semantic-conventions": "^1.41.1"
70
78
  },
71
79
  "peerDependencies": {
72
80
  "@opencode-ai/plugin": ">=1.17.0"
@@ -78,12 +86,6 @@
78
86
  },
79
87
  "devDependencies": {
80
88
  "@opencode-ai/plugin": "^1.17.7",
81
- "@opentelemetry/api": "^1.9.0",
82
- "@opentelemetry/exporter-trace-otlp-http": "^0.52.0",
83
- "@opentelemetry/resources": "^1.25.0",
84
- "@opentelemetry/sdk-node": "^0.52.0",
85
- "@opentelemetry/sdk-trace-node": "^1.25.0",
86
- "@opentelemetry/semantic-conventions": "^1.25.0",
87
89
  "@testing-library/jest-dom": "^6.9.1",
88
90
  "@testing-library/react": "^16.3.2",
89
91
  "@testing-library/user-event": "^14.6.1",
@@ -93,7 +95,9 @@
93
95
  "@types/react": "^18.3.0",
94
96
  "@types/react-dom": "^18.3.0",
95
97
  "@vitejs/plugin-react": "^4.3.0",
98
+ "js-yaml": "^5.2.1",
96
99
  "jsdom": "^25.0.1",
100
+ "smol-toml": "^1.7.0",
97
101
  "typescript": "^5.6.0",
98
102
  "vite": "^5.0.0",
99
103
  "vitest": "^4.1.9"
@@ -143,6 +143,18 @@ import { executeSideEffect, type ExecuteOptions } from "./src/commands-impl.js";
143
143
  import { join as pathJoin } from "node:path";
144
144
  import { homedir } from "node:os";
145
145
 
146
+ // v0.6.3 — Compaction gate. Configures the compaction threshold from
147
+ // `rawOptions.compaction.threshold` (default 0.5) and wires two opencode
148
+ // hooks: `experimental.session.compacting` (push a Bizar-policy note onto
149
+ // the compaction prompt) and `experimental.compaction.autocontinue`
150
+ // (skip the synthetic "continue" turn when our policy decides to compact).
151
+ // The pure decision logic lives in `src/compaction.mjs`.
152
+ import {
153
+ getCompactionThreshold,
154
+ setCompactionThreshold,
155
+ resetCompactionDefaults,
156
+ } from "./src/compaction.mjs";
157
+
146
158
  // --- Env-var constants (per spec §8) -------------------------------------
147
159
 
148
160
  /** `BIZAR_SERVE_PORT` — default 0 (random). */
@@ -402,6 +414,29 @@ async function init(
402
414
  const logger = createLogger(input.client as unknown as Parameters<typeof createLogger>[0]);
403
415
  loggerHandle = logger;
404
416
 
417
+ // v0.6.3 — Compaction gate. Read the user-configured threshold from
418
+ // `rawOptions.compaction.threshold` (default 0.5). Invalid values are
419
+ // caught by `setCompactionThreshold` and logged; we fall back to the
420
+ // module default rather than crashing init (per spec §8.1: any init
421
+ // error must not break the plugin).
422
+ resetCompactionDefaults();
423
+ const compactionCfg = (rawOptions as Record<string, unknown> | undefined)
424
+ ?.compaction as { threshold?: unknown } | undefined;
425
+ if (compactionCfg && typeof compactionCfg.threshold === "number") {
426
+ try {
427
+ setCompactionThreshold(compactionCfg.threshold);
428
+ logger.info(
429
+ `bizar: compaction threshold set to ${compactionCfg.threshold} (from plugin options)`,
430
+ );
431
+ } catch (err) {
432
+ logger.warn(
433
+ `bizar: invalid compaction threshold ${String(compactionCfg.threshold)}; using default ${getCompactionThreshold()}: ${
434
+ err instanceof Error ? err.message : String(err)
435
+ }`,
436
+ );
437
+ }
438
+ }
439
+
405
440
  // §6.4 — refuse to start if logDir or stateDir is inside a secret dir.
406
441
  const offending = findOffendingPath(options);
407
442
  if (offending !== null) {
@@ -1412,6 +1447,48 @@ function buildHooks(ctx: RuntimeContext, bg: BgDeps): Hooks {
1412
1447
  // v0.4.2 — register the 4 background tools.
1413
1448
  tool: tools,
1414
1449
 
1450
+ // v0.6.3 — Compaction gate. Called by opencode BEFORE it starts a
1451
+ // session compaction. We append a one-line Bizar policy note so the
1452
+ // model summarises with our threshold in mind (the default opencode
1453
+ // compaction prompt doesn't know about our 50% trigger). The threshold
1454
+ // itself was set during `init()` from `rawOptions.compaction.threshold`.
1455
+ "experimental.session.compacting": async (_input, output) => {
1456
+ try {
1457
+ const threshold = getCompactionThreshold();
1458
+ output.context.push(
1459
+ `Bizar compaction policy: this compaction was triggered at the configured ${threshold} ratio of context usage. Preserve technical decisions, file paths, error messages, and unresolved questions verbatim in the summary; compress conversational filler.`,
1460
+ );
1461
+ } catch (err) {
1462
+ ctx.logger.warn(
1463
+ `bizar: experimental.session.compacting failed (passing through): ${
1464
+ err instanceof Error ? err.message : String(err)
1465
+ }`,
1466
+ );
1467
+ }
1468
+ },
1469
+
1470
+ // v0.6.3 — Compaction auto-continue gate. Called by opencode AFTER
1471
+ // compaction succeeds and BEFORE it injects a synthetic user "continue"
1472
+ // message. We disable auto-continue when compaction was actually
1473
+ // triggered (so the user sees the compacted state and can drive the
1474
+ // next step), and let auto-continue proceed otherwise. We can't read
1475
+ // the live usage ratio here, so we use the configured threshold as a
1476
+ // proxy: if it's been lowered below 1.0, the policy is "compact
1477
+ // early", and we want the user in the loop.
1478
+ "experimental.compaction.autocontinue": async (_input, output) => {
1479
+ try {
1480
+ if (getCompactionThreshold() < 1.0) {
1481
+ output.enabled = false;
1482
+ }
1483
+ } catch (err) {
1484
+ ctx.logger.warn(
1485
+ `bizar: experimental.compaction.autocontinue failed (passing through): ${
1486
+ err instanceof Error ? err.message : String(err)
1487
+ }`,
1488
+ );
1489
+ }
1490
+ },
1491
+
1415
1492
  // v0.4.2 — dispose hook. Opencode calls this when the plugin is
1416
1493
  // being torn down. We do a best-effort cleanup similar to the
1417
1494
  // signal trap, but we do NOT call `process.exit` — that's the