@polderlabs/bizar 4.9.0 → 5.0.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 (107) hide show
  1. package/bizar-dash/dist/assets/icons-CFqu2M-c.js +656 -0
  2. package/bizar-dash/dist/assets/icons-CFqu2M-c.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-Dl8yY5_H.js +16 -0
  6. package/bizar-dash/dist/assets/main-Dl8yY5_H.js.map +1 -0
  7. package/bizar-dash/dist/assets/{main-DfmIfOUS.css → main-ZAfGKENE.css} +1 -1
  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-C2gysFOZ.js +2 -0
  11. package/bizar-dash/dist/assets/mobile-C2gysFOZ.js.map +1 -0
  12. package/bizar-dash/dist/assets/mobile-DHXXbn1A.js +1 -0
  13. package/bizar-dash/dist/assets/{mobile-CL5uUQEC.js.map → mobile-DHXXbn1A.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/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  19. package/bizar-dash/skills/eval/SKILL.md +237 -0
  20. package/bizar-dash/src/server/api.mjs +28 -0
  21. package/bizar-dash/src/server/auth.mjs +155 -1
  22. package/bizar-dash/src/server/eval-store.mjs +226 -0
  23. package/bizar-dash/src/server/eval.mjs +347 -0
  24. package/bizar-dash/src/server/ocr.mjs +55 -0
  25. package/bizar-dash/src/server/plugins/registry.mjs +363 -0
  26. package/bizar-dash/src/server/plugins/sandbox.mjs +655 -0
  27. package/bizar-dash/src/server/plugins/store.mjs +659 -0
  28. package/bizar-dash/src/server/routes/clipboard.mjs +173 -0
  29. package/bizar-dash/src/server/routes/eval.mjs +147 -0
  30. package/bizar-dash/src/server/routes/ocr.mjs +182 -0
  31. package/bizar-dash/src/server/routes/plugins.mjs +220 -0
  32. package/bizar-dash/src/server/routes/users.mjs +84 -0
  33. package/bizar-dash/src/server/routes/voice.mjs +131 -0
  34. package/bizar-dash/src/server/routes/workspaces.mjs +204 -0
  35. package/bizar-dash/src/server/voice-store.mjs +202 -0
  36. package/bizar-dash/src/server/voice-transcribe.mjs +72 -0
  37. package/bizar-dash/src/server/workspaces.mjs +626 -0
  38. package/bizar-dash/src/web/components/InviteDialog.tsx +205 -0
  39. package/bizar-dash/src/web/components/ScreenshotCapture.tsx +138 -0
  40. package/bizar-dash/src/web/components/ScreenshotOCR.tsx +42 -0
  41. package/bizar-dash/src/web/components/VoiceNotesPanel.tsx +182 -0
  42. package/bizar-dash/src/web/components/VoiceRecorder.tsx +104 -0
  43. package/bizar-dash/src/web/components/WorkspaceSelector.tsx +158 -0
  44. package/bizar-dash/src/web/styles/memory.css +82 -0
  45. package/bizar-dash/src/web/views/Memory.tsx +17 -2
  46. package/bizar-dash/src/web/views/Settings.tsx +3 -0
  47. package/bizar-dash/src/web/views/Workspace.tsx +294 -0
  48. package/bizar-dash/src/web/views/memory/FromScreenshotPanel.tsx +23 -0
  49. package/bizar-dash/src/web/views/memory/VaultFromClipboardPanel.tsx +101 -0
  50. package/bizar-dash/src/web/views/settings/WorkspacesSection.tsx +189 -0
  51. package/bizar-dash/tests/bundle-analysis.test.mjs +5 -2
  52. package/bizar-dash/tests/clipboard.test.mjs +147 -0
  53. package/bizar-dash/tests/components/screenshot-ocr.test.tsx +75 -0
  54. package/bizar-dash/tests/components/workspace-selector.test.tsx +73 -0
  55. package/bizar-dash/tests/deploy-templates.test.mjs +100 -0
  56. package/bizar-dash/tests/eval/fixtures.test.mjs +141 -0
  57. package/bizar-dash/tests/eval/report.test.mjs +284 -0
  58. package/bizar-dash/tests/eval/runner.test.mjs +471 -0
  59. package/bizar-dash/tests/ocr.test.mjs +87 -0
  60. package/bizar-dash/tests/plugins-registry.test.mjs +387 -0
  61. package/bizar-dash/tests/plugins-sandbox.test.mjs +374 -0
  62. package/bizar-dash/tests/plugins-store.test.mjs +455 -0
  63. package/bizar-dash/tests/users.test.mjs +108 -0
  64. package/bizar-dash/tests/voice-recorder.test.tsx +95 -0
  65. package/bizar-dash/tests/voice-store.test.mjs +148 -0
  66. package/bizar-dash/tests/voice-transcribe.test.mjs +87 -0
  67. package/bizar-dash/tests/workspaces.test.mjs +527 -0
  68. package/cli/bin.mjs +72 -2
  69. package/cli/commands/clip.mjs +146 -0
  70. package/cli/commands/deploy/cloudflare.mjs +250 -0
  71. package/cli/commands/deploy/docker.mjs +221 -0
  72. package/cli/commands/deploy/fly.mjs +161 -0
  73. package/cli/commands/deploy/vercel.mjs +225 -0
  74. package/cli/commands/deploy.mjs +240 -0
  75. package/cli/commands/eval.mjs +378 -0
  76. package/cli/commands/marketplace.mjs +64 -0
  77. package/cli/commands/ocr.mjs +165 -0
  78. package/cli/commands/plugin.mjs +358 -0
  79. package/cli/commands/voice.mjs +211 -0
  80. package/cli/commands/workspace.mjs +247 -0
  81. package/package.json +12 -8
  82. package/templates/deploy/cloudflare/README.md +32 -0
  83. package/templates/deploy/cloudflare/functions-index.template.js +15 -0
  84. package/templates/deploy/cloudflare/wrangler.toml.template +9 -0
  85. package/templates/deploy/docker/.env.template +16 -0
  86. package/templates/deploy/docker/README.md +58 -0
  87. package/templates/deploy/docker/docker-compose.template.yml +23 -0
  88. package/templates/deploy/fly/README.md +35 -0
  89. package/templates/deploy/fly/fly.toml.template +28 -0
  90. package/templates/deploy/vercel/README.md +29 -0
  91. package/templates/deploy/vercel/api-index.template.js +18 -0
  92. package/templates/deploy/vercel/vercel.json.template +16 -0
  93. package/templates/eval-fixtures/README.md +58 -0
  94. package/templates/eval-fixtures/code-search-basic.json +28 -0
  95. package/templates/eval-fixtures/latency-bounds.json +16 -0
  96. package/templates/eval-fixtures/regression-suite.json +79 -0
  97. package/templates/eval-fixtures/response-format.json +30 -0
  98. package/templates/eval-fixtures/tool-call-correctness.json +24 -0
  99. package/templates/plugin-template/README.md +121 -0
  100. package/templates/plugin-template/index.js +66 -0
  101. package/templates/plugin-template/plugin.json +42 -0
  102. package/templates/plugin-template/tests/plugin.test.js +83 -0
  103. package/bizar-dash/dist/assets/main-DaC1Lc6q.js +0 -366
  104. package/bizar-dash/dist/assets/main-DaC1Lc6q.js.map +0 -1
  105. package/bizar-dash/dist/assets/mobile-CL5uUQEC.js +0 -1
  106. package/bizar-dash/dist/assets/mobile-D5WTWvuh.js +0 -338
  107. package/bizar-dash/dist/assets/mobile-D5WTWvuh.js.map +0 -1
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "4.9.0",
3
+ "version": "5.0.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": {
@@ -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"
@@ -0,0 +1,32 @@
1
+ # Cloudflare Deployment
2
+
3
+ This directory contains the scaffold files for deploying the Bizar dashboard
4
+ to Cloudflare Pages + Workers. Generated by `bizar deploy --to cloudflare`.
5
+
6
+ ## Files
7
+
8
+ - `wrangler.toml` — Cloudflare Pages project configuration
9
+ - `functions/index.js` — Workers function entry (handles SPA routing)
10
+ - `public/` — Static dashboard bundle (built from `npm run build`)
11
+
12
+ ## How it works
13
+
14
+ 1. The dashboard is built with `npm run build` → `dist/`
15
+ 2. Built assets are copied to `public/`
16
+ 3. `wrangler.toml` configures the Pages project
17
+ 4. `functions/index.js` handles SPA routing for non-asset paths
18
+ 5. The deployment is pushed to Cloudflare via wrangler CLI or REST API
19
+
20
+ ## Manual deployment
21
+
22
+ ```bash
23
+ # Install wrangler
24
+ npm i -g wrangler
25
+
26
+ # Login to Cloudflare
27
+ wrangler login
28
+
29
+ # Deploy from the scaffold directory
30
+ cd bizar-deploy/cloudflare
31
+ npx wrangler pages deploy . --project-name bizar-dash
32
+ ```
@@ -0,0 +1,15 @@
1
+ // Cloudflare Workers entry — Bizar dashboard
2
+ // This file becomes functions/index.js in the deployment.
3
+
4
+ export async function onRequest(context) {
5
+ const { request, env } = context;
6
+ const url = new URL(request.url);
7
+
8
+ // Serve static assets from Pages KV
9
+ if (url.pathname.startsWith("/assets/")) {
10
+ return env.ASSETS.fetch(request);
11
+ }
12
+
13
+ // SPA fallback — serve index.html for all non-asset routes
14
+ return env.ASSETS.fetch(new Request(url.origin + "/index.html", request));
15
+ }
@@ -0,0 +1,9 @@
1
+ name = "bizar-dash"
2
+ main = "functions/index.js"
3
+ compatibility_date = "2025-01-01"
4
+
5
+ [site]
6
+ bucket = "./public"
7
+
8
+ [env.production]
9
+ routes = ["bizar-dash.*.pages.dev/*"]
@@ -0,0 +1,16 @@
1
+ # Bizar dashboard environment variables
2
+ # Copy this file to .env and fill in your values.
3
+
4
+ # Dashboard
5
+ # BIZAR_DASHBOARD_PORT=4097
6
+ # BIZAR_DASHBOARD_HOST=0.0.0.0
7
+
8
+ # OpenTelemetry (optional)
9
+ # BIZAR_OTEL=1
10
+ # OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
11
+
12
+ # Tailscale (optional)
13
+ # TAILSCALE_AUTHKEY=tskey-auth-xxxxxxxxxxxxxxxxxxxx
14
+
15
+ # Headroom context compression (optional)
16
+ # HEADROOM_ENABLED=1
@@ -0,0 +1,58 @@
1
+ # Docker Deployment
2
+
3
+ This directory contains the scaffold files for deploying the Bizar dashboard
4
+ as a Docker container. Generated by `bizar deploy --to docker`.
5
+
6
+ ## Files
7
+
8
+ - `docker-compose.yml` — Docker Compose configuration for self-hosting
9
+ - `.env.template` — Environment variable template
10
+
11
+ ## Prerequisites
12
+
13
+ - Docker Engine 24+ (or Docker Desktop)
14
+ - Docker Compose v2 (`docker compose` plugin)
15
+ - A container registry account (GitHub Container Registry, Docker Hub, etc.)
16
+
17
+ ## How it works
18
+
19
+ The `bizar deploy --to docker` command:
20
+
21
+ 1. Builds the Docker image using the project's `Dockerfile`
22
+ 2. Tags it with `<registry>/<image-name>:<version>` and `:latest`
23
+ 3. Pushes both tags to the registry
24
+ 4. With `--compose`: generates `docker-compose.yml` + `.env.template`
25
+
26
+ ## Self-hosting with Docker Compose
27
+
28
+ ```bash
29
+ # Generate compose files
30
+ bizar deploy --to docker --compose
31
+
32
+ # Edit .env with your settings
33
+ cp .env.template .env
34
+ $EDITOR .env
35
+
36
+ # Start the dashboard
37
+ cd bizar-deploy/docker
38
+ docker compose up -d
39
+
40
+ # Check logs
41
+ docker compose logs -f
42
+
43
+ # Stop
44
+ docker compose down
45
+ ```
46
+
47
+ ## Manual build & push
48
+
49
+ ```bash
50
+ # Build
51
+ docker build -t ghcr.io/my-org/bizar-dash:latest .
52
+
53
+ # Push
54
+ docker push ghcr.io/my-org/bizar-dash:latest
55
+
56
+ # Run
57
+ docker run -d -p 4097:4097 ghcr.io/my-org/bizar-dash:latest
58
+ ```
@@ -0,0 +1,23 @@
1
+ version: "3.8"
2
+
3
+ services:
4
+ bizar-dash:
5
+ image: ghcr.io/your-org/bizar-dash:latest
6
+ container_name: bizar-dash
7
+ restart: unless-stopped
8
+ ports:
9
+ - "4097:4097"
10
+ - "4098:4098"
11
+ environment:
12
+ - BIZAR_DASHBOARD_PORT=4097
13
+ - BIZAR_DASHBOARD_HOST=0.0.0.0
14
+ - NODE_ENV=production
15
+ env_file:
16
+ - .env
17
+ volumes:
18
+ - bizar-config:/home/bizar/.config/bizar
19
+ - bizar-memory:/home/bizar/.local/share/bizar/memory
20
+
21
+ volumes:
22
+ bizar-config:
23
+ bizar-memory:
@@ -0,0 +1,35 @@
1
+ # Fly.io Deployment
2
+
3
+ This directory contains the scaffold file for deploying the Bizar dashboard
4
+ to Fly.io. Generated by `bizar deploy --to fly`.
5
+
6
+ ## Files
7
+
8
+ - `fly.toml` — Fly.io application configuration
9
+
10
+ ## Prerequisites
11
+
12
+ - [flyctl](https://fly.io/docs/getting-started/installing-flyctl/) installed
13
+ - A Fly.io account (sign up: `flyctl auth signup`)
14
+ - Dockerfile at the project root (already present in BizarHarness)
15
+
16
+ ## How it works
17
+
18
+ 1. `fly.toml` references the project's `Dockerfile` for building
19
+ 2. The app is launched on Fly.io (if not already created)
20
+ 3. `flyctl deploy` builds and deploys the container
21
+ 4. The dashboard is served at `https://<app-name>.fly.dev`
22
+
23
+ ## Manual deployment
24
+
25
+ ```bash
26
+ # Login
27
+ flyctl auth login
28
+
29
+ # Launch (first time)
30
+ cd bizar-deploy/fly
31
+ flyctl launch --name bizar-dash --region iad --now
32
+
33
+ # Deploy (subsequent times)
34
+ flyctl deploy --app bizar-dash
35
+ ```
@@ -0,0 +1,28 @@
1
+ # fly.toml — Bizar dashboard
2
+ # Generated by `bizar deploy --to fly`
3
+
4
+ app = "bizar-dash"
5
+ primary_region = "iad"
6
+
7
+ [build]
8
+ dockerfile = "../Dockerfile"
9
+
10
+ [http_service]
11
+ internal_port = 4097
12
+ force_https = true
13
+ auto_stop_machines = true
14
+ auto_start_machines = true
15
+ min_machines_running = 0
16
+
17
+ [[vm]]
18
+ memory = "512mb"
19
+ cpu_kind = "shared"
20
+ cpus = 1
21
+
22
+ [env]
23
+ BIZAR_DASHBOARD_PORT = "4097"
24
+ BIZAR_DASHBOARD_HOST = "0.0.0.0"
25
+ NODE_ENV = "production"
26
+
27
+ [deploy]
28
+ strategy = "rolling"
@@ -0,0 +1,29 @@
1
+ # Vercel Deployment
2
+
3
+ This directory contains the scaffold files for deploying the Bizar dashboard
4
+ to Vercel. Generated by `bizar deploy --to vercel`.
5
+
6
+ ## Files
7
+
8
+ - `vercel.json` — Vercel project configuration
9
+ - `api/index.js` — Serverless function entry (Express app that serves the SPA)
10
+ - `public/` — Static dashboard bundle (built from `npm run build`)
11
+
12
+ ## How it works
13
+
14
+ 1. The dashboard is built with `npm run build` → `dist/`
15
+ 2. Built assets are copied to `public/`
16
+ 3. `vercel.json` routes API calls to the serverless function and everything
17
+ else to the static SPA
18
+ 4. The deployment is pushed to Vercel via the REST API
19
+
20
+ ## Manual deployment
21
+
22
+ ```bash
23
+ # Install Vercel CLI
24
+ npm i -g vercel
25
+
26
+ # Deploy from the scaffold directory
27
+ cd bizar-deploy/vercel
28
+ vercel --prod
29
+ ```
@@ -0,0 +1,18 @@
1
+ // Vercel serverless entry — Bizar dashboard
2
+ // This file becomes api/index.js in the deployment.
3
+ import express from 'express';
4
+ import { join, dirname } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const app = express();
9
+
10
+ // Serve static files from the dashboard build
11
+ app.use(express.static(join(__dirname, '..', 'public')));
12
+
13
+ // Fallback to index.html for SPA routing
14
+ app.get('*', (_req, res) => {
15
+ res.sendFile(join(__dirname, '..', 'public', 'index.html'));
16
+ });
17
+
18
+ export default app;
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "bizar-dash",
3
+ "version": 2,
4
+ "buildCommand": "npm run build",
5
+ "outputDirectory": "public",
6
+ "routes": [
7
+ { "src": "/api/(.*)", "dest": "/api/index.js" },
8
+ { "src": "/(.*)", "dest": "/public/$1" }
9
+ ],
10
+ "functions": {
11
+ "api/index.js": {
12
+ "memory": 256,
13
+ "maxDuration": 30
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,58 @@
1
+ # Eval Fixtures
2
+
3
+ Example fixture suite for the BizarHarness eval framework.
4
+
5
+ ## Fixtures
6
+
7
+ | File | Description | Tags |
8
+ |------|-------------|------|
9
+ | `code-search-basic.json` | Verifies agent can find a function definition | smoke, code-search |
10
+ | `tool-call-correctness.json` | Verifies correct tool usage | smoke, tools |
11
+ | `response-format.json` | Verifies response is valid JSON with expected shape | smoke, format |
12
+ | `latency-bounds.json` | Verifies response arrives within latency budget | smoke, latency |
13
+ | `regression-suite.json` | Suite that runs all of the above | regression |
14
+
15
+ ## Running
16
+
17
+ ```bash
18
+ # Run all fixtures
19
+ bizar eval run ./templates/eval-fixtures
20
+
21
+ # Run with custom concurrency
22
+ bizar eval run ./templates/eval-fixtures --concurrency=3
23
+
24
+ # List recent runs
25
+ bizar eval list
26
+
27
+ # Compare two runs
28
+ bizar eval diff run_2026-07-05 run_2026-07-04
29
+ ```
30
+
31
+ ## Fixture Schema
32
+
33
+ ```json
34
+ {
35
+ "id": "unique-fixture-id",
36
+ "name": "Human Readable Name",
37
+ "description": "What this fixture verifies",
38
+ "agent": "thor",
39
+ "prompt": "The prompt to send to the agent",
40
+ "expected": {
41
+ "contains": ["expected substring 1", "expected substring 2"],
42
+ "notContains": ["forbidden substring"],
43
+ "regex": ["expected regex pattern"],
44
+ "jsonSchema": null,
45
+ "maxTokens": 2000,
46
+ "maxLatencyMs": 30000
47
+ },
48
+ "tags": ["smoke", "my-tag"]
49
+ }
50
+ ```
51
+
52
+ ## Writing Fixtures
53
+
54
+ 1. **Start simple** — begin with a `contains` check
55
+ 2. **Be specific** — match exact strings, not approximate ones
56
+ 3. **Use `notContains`** for things the agent should NOT do (e.g., TODO, FIXME, apologetic phrases)
57
+ 4. **Latency budgets** — set `maxLatencyMs` to catch regressions
58
+ 5. **Tag wisely** — use tags to filter fixtures: `bizar eval run --tag smoke`
@@ -0,0 +1,28 @@
1
+ {
2
+ "id": "code-search-basic",
3
+ "name": "Basic code search",
4
+ "description": "Verifies the agent can find a function definition using the sembe search tool",
5
+ "agent": "thor",
6
+ "prompt": "Find the function named 'add' in this repository. Show its signature and parameter names. Do not guess — use the code search tool to locate it first.",
7
+ "expected": {
8
+ "contains": [
9
+ "function add",
10
+ "a",
11
+ "b"
12
+ ],
13
+ "notContains": [
14
+ "TODO",
15
+ "FIXME",
16
+ "Not found",
17
+ "could not locate"
18
+ ],
19
+ "regex": [
20
+ "function\\s+add\\s*\\(",
21
+ "const\\s+add\\s*=\\s*\\("
22
+ ],
23
+ "jsonSchema": null,
24
+ "maxTokens": 500,
25
+ "maxLatencyMs": 30000
26
+ },
27
+ "tags": ["code-search", "smoke"]
28
+ }