@polderlabs/bizar 4.8.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 (129) 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-DmpSFPJY.js +9 -0
  4. package/bizar-dash/dist/assets/index-DmpSFPJY.js.map +1 -0
  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-DX_Jh8Wc.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-Chvf9u_B.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/memory-lightrag.mjs +109 -0
  25. package/bizar-dash/src/server/memory-store.mjs +121 -0
  26. package/bizar-dash/src/server/ocr.mjs +55 -0
  27. package/bizar-dash/src/server/otel.mjs +133 -0
  28. package/bizar-dash/src/server/plugins/registry.mjs +363 -0
  29. package/bizar-dash/src/server/plugins/sandbox.mjs +655 -0
  30. package/bizar-dash/src/server/plugins/store.mjs +659 -0
  31. package/bizar-dash/src/server/routes/chat.mjs +246 -170
  32. package/bizar-dash/src/server/routes/clipboard.mjs +173 -0
  33. package/bizar-dash/src/server/routes/eval.mjs +147 -0
  34. package/bizar-dash/src/server/routes/memory.mjs +46 -0
  35. package/bizar-dash/src/server/routes/ocr.mjs +182 -0
  36. package/bizar-dash/src/server/routes/opencode-sessions.mjs +82 -48
  37. package/bizar-dash/src/server/routes/plugins.mjs +220 -0
  38. package/bizar-dash/src/server/routes/users.mjs +84 -0
  39. package/bizar-dash/src/server/routes/voice.mjs +131 -0
  40. package/bizar-dash/src/server/routes/workspaces.mjs +204 -0
  41. package/bizar-dash/src/server/server.mjs +40 -0
  42. package/bizar-dash/src/server/voice-store.mjs +202 -0
  43. package/bizar-dash/src/server/voice-transcribe.mjs +72 -0
  44. package/bizar-dash/src/server/workspaces.mjs +626 -0
  45. package/bizar-dash/src/web/components/InviteDialog.tsx +205 -0
  46. package/bizar-dash/src/web/components/ScreenshotCapture.tsx +138 -0
  47. package/bizar-dash/src/web/components/ScreenshotOCR.tsx +42 -0
  48. package/bizar-dash/src/web/components/SettingsSearch.tsx +204 -89
  49. package/bizar-dash/src/web/components/VoiceNotesPanel.tsx +182 -0
  50. package/bizar-dash/src/web/components/VoiceRecorder.tsx +104 -0
  51. package/bizar-dash/src/web/components/WorkspaceSelector.tsx +158 -0
  52. package/bizar-dash/src/web/lib/search.ts +115 -0
  53. package/bizar-dash/src/web/mobile/views/MobileSettings.tsx +10 -35
  54. package/bizar-dash/src/web/mobile/views/QrCodePanel.tsx +69 -0
  55. package/bizar-dash/src/web/styles/memory.css +166 -1
  56. package/bizar-dash/src/web/styles/settings.css +80 -0
  57. package/bizar-dash/src/web/views/Memory.tsx +22 -2
  58. package/bizar-dash/src/web/views/Settings.tsx +99 -0
  59. package/bizar-dash/src/web/views/Workspace.tsx +294 -0
  60. package/bizar-dash/src/web/views/memory/FromScreenshotPanel.tsx +23 -0
  61. package/bizar-dash/src/web/views/memory/MemoryGraphLegend.tsx +29 -0
  62. package/bizar-dash/src/web/views/memory/MemoryGraphPanel.tsx +192 -0
  63. package/bizar-dash/src/web/views/memory/MemoryGraphView.tsx +336 -0
  64. package/bizar-dash/src/web/views/memory/VaultFromClipboardPanel.tsx +101 -0
  65. package/bizar-dash/src/web/views/settings/WorkspacesSection.tsx +189 -0
  66. package/bizar-dash/tests/bundle-analysis.test.mjs +73 -0
  67. package/bizar-dash/tests/clipboard.test.mjs +147 -0
  68. package/bizar-dash/tests/components/screenshot-ocr.test.tsx +75 -0
  69. package/bizar-dash/tests/components/settings-search.test.tsx +180 -0
  70. package/bizar-dash/tests/components/workspace-selector.test.tsx +73 -0
  71. package/bizar-dash/tests/deploy-templates.test.mjs +100 -0
  72. package/bizar-dash/tests/docker-build.test.mjs +96 -0
  73. package/bizar-dash/tests/eval/fixtures.test.mjs +141 -0
  74. package/bizar-dash/tests/eval/report.test.mjs +284 -0
  75. package/bizar-dash/tests/eval/runner.test.mjs +471 -0
  76. package/bizar-dash/tests/lib/search-fuzzy.test.ts +149 -0
  77. package/bizar-dash/tests/memory-graph-view.test.tsx +69 -0
  78. package/bizar-dash/tests/memory-graph.test.mjs +95 -0
  79. package/bizar-dash/tests/ocr.test.mjs +87 -0
  80. package/bizar-dash/tests/otel.test.mjs +188 -0
  81. package/bizar-dash/tests/plugins-registry.test.mjs +387 -0
  82. package/bizar-dash/tests/plugins-sandbox.test.mjs +374 -0
  83. package/bizar-dash/tests/plugins-store.test.mjs +455 -0
  84. package/bizar-dash/tests/users.test.mjs +108 -0
  85. package/bizar-dash/tests/voice-recorder.test.tsx +95 -0
  86. package/bizar-dash/tests/voice-store.test.mjs +148 -0
  87. package/bizar-dash/tests/voice-transcribe.test.mjs +87 -0
  88. package/bizar-dash/tests/workspaces.test.mjs +527 -0
  89. package/cli/bin.mjs +72 -2
  90. package/cli/commands/clip.mjs +146 -0
  91. package/cli/commands/dash.mjs +6 -0
  92. package/cli/commands/deploy/cloudflare.mjs +250 -0
  93. package/cli/commands/deploy/docker.mjs +221 -0
  94. package/cli/commands/deploy/fly.mjs +161 -0
  95. package/cli/commands/deploy/vercel.mjs +225 -0
  96. package/cli/commands/deploy.mjs +240 -0
  97. package/cli/commands/eval.mjs +378 -0
  98. package/cli/commands/marketplace.mjs +64 -0
  99. package/cli/commands/ocr.mjs +165 -0
  100. package/cli/commands/plugin.mjs +358 -0
  101. package/cli/commands/voice.mjs +211 -0
  102. package/cli/commands/workspace.mjs +247 -0
  103. package/package.json +12 -2
  104. package/templates/deploy/cloudflare/README.md +32 -0
  105. package/templates/deploy/cloudflare/functions-index.template.js +15 -0
  106. package/templates/deploy/cloudflare/wrangler.toml.template +9 -0
  107. package/templates/deploy/docker/.env.template +16 -0
  108. package/templates/deploy/docker/README.md +58 -0
  109. package/templates/deploy/docker/docker-compose.template.yml +23 -0
  110. package/templates/deploy/fly/README.md +35 -0
  111. package/templates/deploy/fly/fly.toml.template +28 -0
  112. package/templates/deploy/vercel/README.md +29 -0
  113. package/templates/deploy/vercel/api-index.template.js +18 -0
  114. package/templates/deploy/vercel/vercel.json.template +16 -0
  115. package/templates/eval-fixtures/README.md +58 -0
  116. package/templates/eval-fixtures/code-search-basic.json +28 -0
  117. package/templates/eval-fixtures/latency-bounds.json +16 -0
  118. package/templates/eval-fixtures/regression-suite.json +79 -0
  119. package/templates/eval-fixtures/response-format.json +30 -0
  120. package/templates/eval-fixtures/tool-call-correctness.json +24 -0
  121. package/templates/plugin-template/README.md +121 -0
  122. package/templates/plugin-template/index.js +66 -0
  123. package/templates/plugin-template/plugin.json +42 -0
  124. package/templates/plugin-template/tests/plugin.test.js +83 -0
  125. package/bizar-dash/dist/assets/main-DHZmbnxQ.js +0 -361
  126. package/bizar-dash/dist/assets/main-DHZmbnxQ.js.map +0 -1
  127. package/bizar-dash/dist/assets/mobile-BK8-ythT.js +0 -351
  128. package/bizar-dash/dist/assets/mobile-BK8-ythT.js.map +0 -1
  129. package/bizar-dash/dist/assets/mobile-Chvf9u_B.js +0 -1
@@ -0,0 +1,378 @@
1
+ /**
2
+ * cli/commands/eval.mjs
3
+ *
4
+ * v5.0.0 — Eval framework CLI.
5
+ *
6
+ * Subcommands:
7
+ * bizar eval list [--limit N] List recent runs
8
+ * bizar eval run <suite-path> [--concurrency N] [--agent thor] Run a suite
9
+ * bizar eval show <run-id> Show run details
10
+ * bizar eval diff <run-id-1> <run-id-2> Compare two runs
11
+ * bizar eval init <path> Scaffold a new fixture template
12
+ * bizar eval validate <fixture-path> Validate fixture JSON
13
+ */
14
+ import chalk from 'chalk';
15
+ import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
16
+ import { join, dirname } from 'node:path';
17
+ import { homedir } from 'node:os';
18
+ import { readDashboardConn } from './headroom.mjs';
19
+
20
+ const HOME = homedir();
21
+ const TEMPLATES_DIR = join(HOME, '.config', 'bizar', 'templates', 'eval-fixtures');
22
+
23
+ // ── API helpers ────────────────────────────────────────────────────────────────
24
+
25
+ async function apiGet(path) {
26
+ const { port, secret } = readDashboardConn();
27
+ const baseUrl = `http://127.0.0.1:${port}`;
28
+ const url = `${baseUrl}${path}`;
29
+ const headers = { accept: 'application/json' };
30
+ if (secret) headers.authorization = `Basic ${Buffer.from(`opencode:${secret}`).toString('base64')}`;
31
+ const res = await fetch(url, { method: 'GET', headers });
32
+ const text = await res.text();
33
+ let data = null;
34
+ try { data = text ? JSON.parse(text) : null; } catch { /* ignore */ }
35
+ if (!res.ok) throw new Error(`${path}: ${data?.message || data?.error || res.statusText}`);
36
+ return data;
37
+ }
38
+
39
+ async function apiPost(path, body = {}) {
40
+ const { port, secret } = readDashboardConn();
41
+ const baseUrl = `http://127.0.0.1:${port}`;
42
+ const url = `${baseUrl}${path}`;
43
+ const headers = { 'content-type': 'application/json', accept: 'application/json' };
44
+ if (secret) headers.authorization = `Basic ${Buffer.from(`opencode:${secret}`).toString('base64')}`;
45
+ const res = await fetch(url, { method: 'POST', headers, body: JSON.stringify(body) });
46
+ const text = await res.text();
47
+ let data = null;
48
+ try { data = text ? JSON.parse(text) : null; } catch { /* ignore */ }
49
+ if (!res.ok) throw new Error(`${path}: ${data?.message || data?.error || res.statusText}`);
50
+ return data;
51
+ }
52
+
53
+ // ── Help ──────────────────────────────────────────────────────────────────────
54
+
55
+ export function showEvalHelp() {
56
+ console.log(`
57
+ bizar eval — Evaluate AI agent outputs against golden fixtures
58
+
59
+ Usage:
60
+ bizar eval list [--limit N] List recent runs (default limit 20)
61
+ bizar eval run <suite-path> [--concurrency N] [--agent thor]
62
+ Run a fixture suite
63
+ bizar eval show <run-id> Show run details + results
64
+ bizar eval diff <run-id-1> <run-id-2> Diff two runs
65
+ bizar eval init <path> Scaffold a new fixture template
66
+ bizar eval validate <fixture-path> Validate fixture JSON
67
+
68
+ Examples:
69
+ bizar eval list
70
+ bizar eval run ./templates/eval-fixtures
71
+ bizar eval show run_2026-07-05
72
+ bizar eval diff run_2026-07-05 run_2026-07-04
73
+ `);
74
+ }
75
+
76
+ // ── Subcommand handlers ────────────────────────────────────────────────────────
77
+
78
+ async function listRuns(args) {
79
+ const limit = parseInt(args.find((a) => a.startsWith('--limit='))?.split('=')[1] || '20', 10);
80
+ const data = await apiGet(`/api/eval/runs?limit=${limit}`);
81
+ if (!data.runs || data.runs.length === 0) {
82
+ console.log(' No eval runs found.');
83
+ return;
84
+ }
85
+ console.log('');
86
+ console.log(chalk.bold(' Recent Eval Runs'));
87
+ console.log('');
88
+ for (const run of data.runs) {
89
+ const date = new Date(run.startedAt).toLocaleString();
90
+ const passPct = run.total > 0 ? Math.round((run.passed / run.total) * 100) : 0;
91
+ const ok = run.failed === 0;
92
+ console.log(` ${chalk[ok ? 'green' : 'red']('●')} ${chalk.bold(run.id)}`);
93
+ console.log(` ${date} ${run.passed}/${run.total} passed (${passPct}%) [${run.suitePath}]`);
94
+ console.log('');
95
+ }
96
+ }
97
+
98
+ async function runSuite(args) {
99
+ const positional = args.filter((a) => !a.startsWith('-'));
100
+ const suitePath = positional[0];
101
+ if (!suitePath) {
102
+ console.error(chalk.red(' Error: suite-path is required'));
103
+ console.error(' Usage: bizar eval run <suite-path> [--concurrency N] [--agent thor]');
104
+ process.exit(1);
105
+ }
106
+
107
+ const concurrency = parseInt(args.find((a) => a.startsWith('--concurrency='))?.split('=')[1] || '5', 10);
108
+ const agent = args.find((a) => a.startsWith('--agent='))?.split('=')[1] || 'thor';
109
+
110
+ console.log(chalk.bold(` Running eval suite: ${suitePath}`));
111
+ console.log(` Concurrency: ${concurrency} Agent: ${agent}`);
112
+ console.log('');
113
+
114
+ try {
115
+ const result = await apiPost('/api/eval/run', { suitePath, concurrency, agent });
116
+ const passPct = result.total > 0 ? Math.round((result.passed / result.total) * 100) : 0;
117
+ console.log('');
118
+ if (result.failed === 0) {
119
+ console.log(chalk.green(` ✓ All ${result.total} fixtures passed (${passPct}%)`));
120
+ } else {
121
+ console.log(chalk.red(` ✗ ${result.failed}/${result.total} fixtures failed (${passPct}% passed)`));
122
+ // Print failures
123
+ for (const r of result.results) {
124
+ if (!r.ok) {
125
+ console.log('');
126
+ console.log(` ${chalk.red('✗')} ${chalk.bold(r.fixtureId)}`);
127
+ for (const c of r.checks) {
128
+ if (!c.ok) {
129
+ console.log(` ${chalk.red(' FAIL')} ${c.kind}: ${c.message}`);
130
+ }
131
+ }
132
+ }
133
+ }
134
+ }
135
+ console.log('');
136
+ console.log(` Run ID: ${result.id}`);
137
+ console.log(` Duration: ${result.finishedAt && result.startedAt ? Math.round((new Date(result.finishedAt) - new Date(result.startedAt)) / 1000) + 's' : '—'}`);
138
+ console.log('');
139
+ } catch (err) {
140
+ console.error(chalk.red(` ✗ Failed to run suite: ${err.message}`));
141
+ process.exit(1);
142
+ }
143
+ }
144
+
145
+ async function showRun(runId) {
146
+ if (!runId) {
147
+ console.error(chalk.red(' Error: run-id is required'));
148
+ console.error(' Usage: bizar eval show <run-id>');
149
+ process.exit(1);
150
+ }
151
+ try {
152
+ const result = await apiGet(`/api/eval/runs/${encodeURIComponent(runId)}`);
153
+ console.log('');
154
+ console.log(chalk.bold(` Eval Run: ${result.id}`));
155
+ console.log('');
156
+ console.log(` Suite: ${result.suitePath}`);
157
+ console.log(` Started: ${new Date(result.startedAt).toLocaleString()}`);
158
+ console.log(` Finished: ${new Date(result.finishedAt).toLocaleString()}`);
159
+ console.log(` Result: ${chalk[result.failed === 0 ? 'green' : 'red'](`${result.passed}/${result.total} passed`)}`);
160
+ console.log('');
161
+ console.log(chalk.bold(' Results:'));
162
+ console.log('');
163
+ for (const r of result.results) {
164
+ const icon = r.ok ? chalk.green('✓') : chalk.red('✗');
165
+ const latency = r.latencyMs ? `${Math.round(r.latencyMs)}ms` : '—';
166
+ console.log(` ${icon} ${chalk.bold(r.fixtureId)} [${latency}]`);
167
+ if (!r.ok) {
168
+ for (const c of r.checks) {
169
+ if (!c.ok) {
170
+ console.log(` ${chalk.red('FAIL')} ${c.kind}: ${c.message}`);
171
+ }
172
+ }
173
+ }
174
+ }
175
+ console.log('');
176
+ } catch (err) {
177
+ if (err.message.includes('404')) {
178
+ console.error(chalk.red(` Error: run ${runId} not found`));
179
+ } else {
180
+ console.error(chalk.red(` ✗ Failed to show run: ${err.message}`));
181
+ }
182
+ process.exit(1);
183
+ }
184
+ }
185
+
186
+ async function diffRuns(args) {
187
+ const [id1, id2] = args;
188
+ if (!id1 || !id2) {
189
+ console.error(chalk.red(' Error: two run IDs are required'));
190
+ console.error(' Usage: bizar eval diff <run-id-1> <run-id-2>');
191
+ process.exit(1);
192
+ }
193
+ try {
194
+ const diff = await apiGet(`/api/eval/runs/${encodeURIComponent(id1)}/compare/${encodeURIComponent(id2)}`);
195
+ console.log('');
196
+ console.log(chalk.bold(` Diff: ${id1} vs ${id2}`));
197
+ console.log('');
198
+
199
+ if (diff.improved?.length > 0) {
200
+ console.log(chalk.green(' Improved:'));
201
+ for (const item of diff.improved) {
202
+ console.log(` ${chalk.green('↑')} ${item.fixtureId} (was ${item.run1Ok ? 'pass' : 'fail'}, now ${item.run2Ok ? 'pass' : 'fail'})`);
203
+ }
204
+ console.log('');
205
+ }
206
+
207
+ if (diff.regressed?.length > 0) {
208
+ console.log(chalk.red(' Regressed:'));
209
+ for (const item of diff.regressed) {
210
+ console.log(` ${chalk.red('↓')} ${item.fixtureId} (was ${item.run1Ok ? 'pass' : 'fail'}, now ${item.run2Ok ? 'pass' : 'fail'})`);
211
+ }
212
+ console.log('');
213
+ }
214
+
215
+ if (diff.unchanged?.length > 0) {
216
+ console.log(chalk.dim(' Unchanged:'));
217
+ for (const item of diff.unchanged) {
218
+ console.log(` ${chalk.dim('→')} ${item.fixtureId} (${item.run1Ok ? 'pass' : 'fail'})`);
219
+ }
220
+ console.log('');
221
+ }
222
+
223
+ if (diff.improved?.length === 0 && diff.regressed?.length === 0 && diff.unchanged?.length === 0) {
224
+ console.log(' No fixtures to compare.');
225
+ console.log('');
226
+ }
227
+ } catch (err) {
228
+ console.error(chalk.red(` ✗ Failed to diff runs: ${err.message}`));
229
+ process.exit(1);
230
+ }
231
+ }
232
+
233
+ function initFixture(path) {
234
+ if (!path) {
235
+ console.error(chalk.red(' Error: path is required'));
236
+ console.error(' Usage: bizar eval init <path>');
237
+ process.exit(1);
238
+ }
239
+ mkdirSync(path, { recursive: true });
240
+
241
+ const template = {
242
+ id: 'my-fixture-id',
243
+ name: 'My Fixture',
244
+ description: 'Describe what this fixture verifies',
245
+ agent: 'thor',
246
+ prompt: 'What should the agent do?',
247
+ expected: {
248
+ contains: ['expected output fragment'],
249
+ notContains: ['unexpected output fragment'],
250
+ regex: ['expected regex pattern'],
251
+ jsonSchema: null,
252
+ maxTokens: 2000,
253
+ maxLatencyMs: 30000,
254
+ },
255
+ tags: ['smoke', 'my-tag'],
256
+ };
257
+
258
+ const fixturePath = join(path, 'my-fixture.json');
259
+ writeFileSync(fixturePath, JSON.stringify(template, null, 2) + '\n', 'utf8');
260
+
261
+ const readmeContent = `# Eval Fixtures
262
+
263
+ This directory contains fixture definitions for the BizarHarness eval framework.
264
+
265
+ ## Structure
266
+
267
+ Each \`.json\` file is a fixture with:
268
+ - \`id\` — unique identifier
269
+ - \`name\` — human-readable name
270
+ - \`description\` — what the fixture verifies
271
+ - \`agent\` — agent to use (thor, tyr, etc.)
272
+ - \`prompt\` — the prompt to send
273
+ - \`expected\` — validation rules (contains, notContains, regex, jsonSchema, maxTokens, maxLatencyMs)
274
+ - \`tags\` — optional tags for filtering
275
+
276
+ ## Running
277
+
278
+ \`\`\`bash
279
+ bizar eval run ./path/to/fixtures
280
+ \`\`\`
281
+ `;
282
+ const readmePath = join(path, 'README.md');
283
+ writeFileSync(readmePath, readmeContent, 'utf8');
284
+
285
+ console.log(chalk.green(` ✓ Fixture scaffold created at ${path}`));
286
+ console.log(` Created: ${fixturePath}`);
287
+ console.log(` Created: ${readmePath}`);
288
+ console.log('');
289
+ }
290
+
291
+ function validateFixture(path) {
292
+ if (!path) {
293
+ console.error(chalk.red(' Error: fixture path is required'));
294
+ console.error(' Usage: bizar eval validate <fixture-path>');
295
+ process.exit(1);
296
+ }
297
+ if (!existsSync(path)) {
298
+ console.error(chalk.red(` ✗ File not found: ${path}`));
299
+ process.exit(1);
300
+ }
301
+ try {
302
+ const raw = readFileSync(path, 'utf8');
303
+ const fixture = JSON.parse(raw);
304
+
305
+ // Validate required fields
306
+ const required = ['id', 'name', 'description', 'agent', 'prompt', 'expected'];
307
+ const missing = required.filter((f) => !fixture[f]);
308
+ if (missing.length > 0) {
309
+ console.error(chalk.red(` ✗ Validation failed. Missing required fields: ${missing.join(', ')}`));
310
+ process.exit(1);
311
+ }
312
+
313
+ // Validate expected shape
314
+ const expected = fixture.expected;
315
+ if (typeof expected !== 'object') {
316
+ console.error(chalk.red(' ✗ Validation failed: expected must be an object'));
317
+ process.exit(1);
318
+ }
319
+
320
+ console.log(chalk.green(` ✓ Fixture is valid: ${fixture.id}`));
321
+ console.log('');
322
+ console.log(` ID: ${fixture.id}`);
323
+ console.log(` Name: ${fixture.name}`);
324
+ console.log(` Agent: ${fixture.agent}`);
325
+ console.log(` Tags: ${fixture.tags?.join(', ') || 'none'}`);
326
+ console.log(` Max Tokens: ${expected.maxTokens ?? 'unlimited'}`);
327
+ console.log(` Max Latency: ${expected.maxLatencyMs ? `${expected.maxLatencyMs}ms` : 'unlimited'}`);
328
+ console.log(` Contains: ${expected.contains?.length ?? 0} patterns`);
329
+ console.log(` Not Contains: ${expected.notContains?.length ?? 0} patterns`);
330
+ console.log(` Regex: ${expected.regex?.length ?? 0} patterns`);
331
+ console.log(` JSON Schema: ${expected.jsonSchema ? 'yes' : 'no'}`);
332
+ console.log('');
333
+ } catch (err) {
334
+ if (err.message.includes('JSON')) {
335
+ console.error(chalk.red(` ✗ Invalid JSON: ${err.message}`));
336
+ } else {
337
+ console.error(chalk.red(` ✗ Validation failed: ${err.message}`));
338
+ }
339
+ process.exit(1);
340
+ }
341
+ }
342
+
343
+ // ── Main dispatcher ────────────────────────────────────────────────────────────
344
+
345
+ export async function run(name, args, isHelpRequest) {
346
+ const sub = args[0];
347
+ const subArgs = args.slice(1);
348
+
349
+ if (!sub || sub === '--help' || sub === '-h' || isHelpRequest) {
350
+ showEvalHelp();
351
+ return;
352
+ }
353
+
354
+ switch (sub) {
355
+ case 'list':
356
+ await listRuns(subArgs);
357
+ break;
358
+ case 'run':
359
+ await runSuite(subArgs);
360
+ break;
361
+ case 'show':
362
+ await showRun(subArgs[0]);
363
+ break;
364
+ case 'diff':
365
+ await diffRuns(subArgs);
366
+ break;
367
+ case 'init':
368
+ initFixture(subArgs[0]);
369
+ break;
370
+ case 'validate':
371
+ validateFixture(subArgs[0]);
372
+ break;
373
+ default:
374
+ console.error(chalk.red(` ✗ Unknown subcommand: ${sub}`));
375
+ showEvalHelp();
376
+ process.exit(1);
377
+ }
378
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * cli/commands/marketplace.mjs
3
+ *
4
+ * v5.0.0 — Thin alias for `bizar marketplace <subcommand>` that
5
+ * forwards to the plugin command. Keeps the historical marketplace
6
+ * name alive for users who learned it first.
7
+ *
8
+ * bizar marketplace search <query> → bizar plugin search <query>
9
+ * bizar marketplace install <id> → bizar plugin install <id>
10
+ * bizar marketplace list → bizar plugin list
11
+ * bizar marketplace → bizar plugin list
12
+ *
13
+ * Anything else falls through to `plugin help` so the user gets the
14
+ * full subcommand reference.
15
+ */
16
+ import chalk from 'chalk';
17
+ import { runPluginCommand, showPluginHelp } from './plugin.mjs';
18
+
19
+ export function showMarketplaceHelp() {
20
+ console.log(`
21
+ bizar marketplace — Browse and install plugins from the public marketplace
22
+
23
+ Usage:
24
+ bizar marketplace List installed plugins
25
+ bizar marketplace list Same as \`bizar plugin list\`
26
+ bizar marketplace search <query> Same as \`bizar plugin search <query>\`
27
+ bizar marketplace install <id> Same as \`bizar plugin install <id>\`
28
+
29
+ Description:
30
+ The marketplace is the public registry of plugins hosted at
31
+ ${process.env.BIZAR_REGISTRY_URL ||
32
+ 'https://raw.githubusercontent.com/DrB0rk/bizar-plugins/main/registry.json'}.
33
+
34
+ This command is a thin alias for the \`plugin\` command — run
35
+ \`bizar plugin --help\` for the full surface (config, update, invoke, ...).
36
+ `);
37
+ }
38
+
39
+ export async function runMarketplaceCommand(marketplaceArgs) {
40
+ const sub = marketplaceArgs[0];
41
+ if (!sub || sub === '--help' || sub === '-h') {
42
+ showMarketplaceHelp();
43
+ return;
44
+ }
45
+ // Only a handful of subcommands are aliased — everything else falls
46
+ // through to the plugin command's help so the user isn't confused
47
+ // by a half-implemented alias.
48
+ const aliased = new Set(['search', 'install', 'list']);
49
+ if (!aliased.has(sub)) {
50
+ console.error(chalk.red(` ✗ Unknown marketplace subcommand: ${sub}`));
51
+ console.error(chalk.dim(' marketplace is an alias for `plugin`; try one of:'));
52
+ console.error(chalk.dim(' marketplace search <query>'));
53
+ console.error(chalk.dim(' marketplace install <id>'));
54
+ console.error(chalk.dim(' marketplace list'));
55
+ console.error(chalk.dim(' or run `bizar plugin --help` for the full surface'));
56
+ showPluginHelp();
57
+ process.exit(1);
58
+ }
59
+ await runPluginCommand(marketplaceArgs);
60
+ }
61
+
62
+ export async function run(name, args, isHelpRequest) {
63
+ await runMarketplaceCommand(args);
64
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * cli/commands/ocr.mjs
3
+ *
4
+ * v5.0.0 — OCR CLI subcommand.
5
+ *
6
+ * Subcommands:
7
+ * bizar ocr list List recent OCR operations
8
+ * bizar ocr process <file> Run OCR on a local image file
9
+ * bizar ocr configure Show current dashboard connection
10
+ */
11
+
12
+ import chalk from 'chalk';
13
+ import { readFileSync, existsSync } from 'node:fs';
14
+ import { readDashboardConn } from './headroom.mjs';
15
+
16
+ // ── API helpers ────────────────────────────────────────────────────────────────
17
+
18
+ async function apiGet(path) {
19
+ const { port, secret } = readDashboardConn();
20
+ const baseUrl = `http://127.0.0.1:${port}`;
21
+ const url = `${baseUrl}${path}`;
22
+ const headers = { accept: 'application/json' };
23
+ if (secret) headers.authorization = `Basic ${Buffer.from(`opencode:${secret}`).toString('base64')}`;
24
+ const res = await fetch(url, { method: 'GET', headers });
25
+ const text = await res.text();
26
+ let data = null;
27
+ try { data = text ? JSON.parse(text) : null; } catch { /* ignore */ }
28
+ if (!res.ok) throw new Error(`${path}: ${data?.message || data?.error || res.statusText}`);
29
+ return data;
30
+ }
31
+
32
+ async function apiPost(path, body = {}) {
33
+ const { port, secret } = readDashboardConn();
34
+ const baseUrl = `http://127.0.0.1:${port}`;
35
+ const url = `${baseUrl}${path}`;
36
+ const headers = { 'content-type': 'application/json', accept: 'application/json' };
37
+ if (secret) headers.authorization = `Basic ${Buffer.from(`opencode:${secret}`).toString('base64')}`;
38
+ const res = await fetch(url, { method: 'POST', headers, body: JSON.stringify(body) });
39
+ const text = await res.text();
40
+ let data = null;
41
+ try { data = text ? JSON.parse(text) : null; } catch { /* ignore */ }
42
+ if (!res.ok) throw new Error(`${path}: ${data?.message || data?.error || res.statusText}`);
43
+ return data;
44
+ }
45
+
46
+ // ── Help ──────────────────────────────────────────────────────────────────────
47
+
48
+ export function showOcrHelp() {
49
+ console.log(`
50
+ bizar ocr — OCR (Optical Character Recognition) operations
51
+
52
+ Usage:
53
+ bizar ocr list List recent OCR operations
54
+ bizar ocr process <file> Run OCR on a local image file and save result
55
+ bizar ocr configure Show the current dashboard connection
56
+
57
+ Examples:
58
+ bizar ocr list
59
+ bizar ocr process screenshot.png
60
+ `);
61
+ }
62
+
63
+ // ── Subcommand handlers ────────────────────────────────────────────────────────
64
+
65
+ async function listOcr() {
66
+ try {
67
+ const data = await apiGet('/api/ocr/list');
68
+ const entries = data.entries || [];
69
+ if (entries.length === 0) {
70
+ console.log(chalk.dim(' No OCR entries yet.'));
71
+ return;
72
+ }
73
+ console.log('');
74
+ console.log(chalk.bold(' Recent OCR Operations'));
75
+ console.log('');
76
+ for (const e of entries) {
77
+ const date = e.savedAt ? new Date(e.savedAt).toLocaleString() : '—';
78
+ console.log(` ${chalk.bold(e.id)}`);
79
+ console.log(` Date: ${date}`);
80
+ console.log(` Path: ${e.notePath || '—'}`);
81
+ console.log(` Length: ${e.textLength || 0} chars`);
82
+ console.log('');
83
+ }
84
+ } catch (err) {
85
+ console.error(chalk.red(` ✗ Failed to list OCR entries: ${err.message}`));
86
+ process.exit(1);
87
+ }
88
+ }
89
+
90
+ async function processImage(filePath) {
91
+ if (!filePath) {
92
+ console.error(chalk.red(' Error: image file path is required'));
93
+ console.error(' Usage: bizar ocr process <file>');
94
+ process.exit(1);
95
+ }
96
+
97
+ if (!existsSync(filePath)) {
98
+ console.error(chalk.red(` ✗ File not found: ${filePath}`));
99
+ process.exit(1);
100
+ }
101
+
102
+ console.log(chalk.bold(` Processing: ${filePath}`));
103
+
104
+ try {
105
+ const imageBuffer = readFileSync(filePath);
106
+ const base64 = imageBuffer.toString('base64');
107
+
108
+ console.log(chalk.dim(' Sending to OCR endpoint…'));
109
+
110
+ const result = await apiPost('/api/ocr/process', { image: base64, lang: 'eng' });
111
+ console.log('');
112
+ console.log(chalk.green(' ✓ OCR complete'));
113
+ console.log(` Note: ${result.notePath}`);
114
+ console.log(` Chars: ${result.text?.length || 0}`);
115
+ console.log('');
116
+ console.log(chalk.bold(' Extracted Text:'));
117
+ console.log('');
118
+ console.log(` ${(result.text || '(no text found)').substring(0, 2000)}`);
119
+ if ((result.text?.length || 0) > 2000) {
120
+ console.log(chalk.dim(` … (truncated, ${result.text.length} total chars)`));
121
+ }
122
+ console.log('');
123
+ } catch (err) {
124
+ console.error(chalk.red(` ✗ OCR failed: ${err.message}`));
125
+ process.exit(1);
126
+ }
127
+ }
128
+
129
+ function showConfigure() {
130
+ const conn = readDashboardConn();
131
+ console.log('');
132
+ console.log(chalk.bold(' Dashboard Connection'));
133
+ console.log('');
134
+ console.log(` URL: http://127.0.0.1:${conn.port}`);
135
+ console.log(` Auth: ${conn.secret ? 'enabled' : 'disabled'}`);
136
+ console.log('');
137
+ }
138
+
139
+ // ── Main dispatcher ────────────────────────────────────────────────────────────
140
+
141
+ export async function run(name, args, isHelpRequest) {
142
+ const sub = args[0];
143
+ const subArgs = args.slice(1);
144
+
145
+ if (!sub || sub === '--help' || sub === '-h' || isHelpRequest) {
146
+ showOcrHelp();
147
+ return;
148
+ }
149
+
150
+ switch (sub) {
151
+ case 'list':
152
+ await listOcr();
153
+ break;
154
+ case 'process':
155
+ await processImage(subArgs[0]);
156
+ break;
157
+ case 'configure':
158
+ showConfigure();
159
+ break;
160
+ default:
161
+ console.error(chalk.red(` ✗ Unknown subcommand: ${sub}`));
162
+ showOcrHelp();
163
+ process.exit(1);
164
+ }
165
+ }