@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.
- package/bizar-dash/dist/assets/icons-CFqu2M-c.js +656 -0
- package/bizar-dash/dist/assets/icons-CFqu2M-c.js.map +1 -0
- package/bizar-dash/dist/assets/index-DmpSFPJY.js +9 -0
- package/bizar-dash/dist/assets/index-DmpSFPJY.js.map +1 -0
- package/bizar-dash/dist/assets/main-Dl8yY5_H.js +16 -0
- package/bizar-dash/dist/assets/main-Dl8yY5_H.js.map +1 -0
- package/bizar-dash/dist/assets/{main-DX_Jh8Wc.css → main-ZAfGKENE.css} +1 -1
- package/bizar-dash/dist/assets/markdown-DIquRulQ.js +29 -0
- package/bizar-dash/dist/assets/markdown-DIquRulQ.js.map +1 -0
- package/bizar-dash/dist/assets/mobile-C2gysFOZ.js +2 -0
- package/bizar-dash/dist/assets/mobile-C2gysFOZ.js.map +1 -0
- package/bizar-dash/dist/assets/mobile-DHXXbn1A.js +1 -0
- package/bizar-dash/dist/assets/{mobile-Chvf9u_B.js.map → mobile-DHXXbn1A.js.map} +1 -1
- package/bizar-dash/dist/assets/react-vendor-DZRUXSPQ.js +40 -0
- package/bizar-dash/dist/assets/react-vendor-DZRUXSPQ.js.map +1 -0
- package/bizar-dash/dist/index.html +6 -3
- package/bizar-dash/dist/mobile.html +5 -2
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/bizar-dash/skills/eval/SKILL.md +237 -0
- package/bizar-dash/src/server/api.mjs +28 -0
- package/bizar-dash/src/server/auth.mjs +155 -1
- package/bizar-dash/src/server/eval-store.mjs +226 -0
- package/bizar-dash/src/server/eval.mjs +347 -0
- package/bizar-dash/src/server/memory-lightrag.mjs +109 -0
- package/bizar-dash/src/server/memory-store.mjs +121 -0
- package/bizar-dash/src/server/ocr.mjs +55 -0
- package/bizar-dash/src/server/otel.mjs +133 -0
- package/bizar-dash/src/server/plugins/registry.mjs +363 -0
- package/bizar-dash/src/server/plugins/sandbox.mjs +655 -0
- package/bizar-dash/src/server/plugins/store.mjs +659 -0
- package/bizar-dash/src/server/routes/chat.mjs +246 -170
- package/bizar-dash/src/server/routes/clipboard.mjs +173 -0
- package/bizar-dash/src/server/routes/eval.mjs +147 -0
- package/bizar-dash/src/server/routes/memory.mjs +46 -0
- package/bizar-dash/src/server/routes/ocr.mjs +182 -0
- package/bizar-dash/src/server/routes/opencode-sessions.mjs +82 -48
- package/bizar-dash/src/server/routes/plugins.mjs +220 -0
- package/bizar-dash/src/server/routes/users.mjs +84 -0
- package/bizar-dash/src/server/routes/voice.mjs +131 -0
- package/bizar-dash/src/server/routes/workspaces.mjs +204 -0
- package/bizar-dash/src/server/server.mjs +40 -0
- package/bizar-dash/src/server/voice-store.mjs +202 -0
- package/bizar-dash/src/server/voice-transcribe.mjs +72 -0
- package/bizar-dash/src/server/workspaces.mjs +626 -0
- package/bizar-dash/src/web/components/InviteDialog.tsx +205 -0
- package/bizar-dash/src/web/components/ScreenshotCapture.tsx +138 -0
- package/bizar-dash/src/web/components/ScreenshotOCR.tsx +42 -0
- package/bizar-dash/src/web/components/SettingsSearch.tsx +204 -89
- package/bizar-dash/src/web/components/VoiceNotesPanel.tsx +182 -0
- package/bizar-dash/src/web/components/VoiceRecorder.tsx +104 -0
- package/bizar-dash/src/web/components/WorkspaceSelector.tsx +158 -0
- package/bizar-dash/src/web/lib/search.ts +115 -0
- package/bizar-dash/src/web/mobile/views/MobileSettings.tsx +10 -35
- package/bizar-dash/src/web/mobile/views/QrCodePanel.tsx +69 -0
- package/bizar-dash/src/web/styles/memory.css +166 -1
- package/bizar-dash/src/web/styles/settings.css +80 -0
- package/bizar-dash/src/web/views/Memory.tsx +22 -2
- package/bizar-dash/src/web/views/Settings.tsx +99 -0
- package/bizar-dash/src/web/views/Workspace.tsx +294 -0
- package/bizar-dash/src/web/views/memory/FromScreenshotPanel.tsx +23 -0
- package/bizar-dash/src/web/views/memory/MemoryGraphLegend.tsx +29 -0
- package/bizar-dash/src/web/views/memory/MemoryGraphPanel.tsx +192 -0
- package/bizar-dash/src/web/views/memory/MemoryGraphView.tsx +336 -0
- package/bizar-dash/src/web/views/memory/VaultFromClipboardPanel.tsx +101 -0
- package/bizar-dash/src/web/views/settings/WorkspacesSection.tsx +189 -0
- package/bizar-dash/tests/bundle-analysis.test.mjs +73 -0
- package/bizar-dash/tests/clipboard.test.mjs +147 -0
- package/bizar-dash/tests/components/screenshot-ocr.test.tsx +75 -0
- package/bizar-dash/tests/components/settings-search.test.tsx +180 -0
- package/bizar-dash/tests/components/workspace-selector.test.tsx +73 -0
- package/bizar-dash/tests/deploy-templates.test.mjs +100 -0
- package/bizar-dash/tests/docker-build.test.mjs +96 -0
- package/bizar-dash/tests/eval/fixtures.test.mjs +141 -0
- package/bizar-dash/tests/eval/report.test.mjs +284 -0
- package/bizar-dash/tests/eval/runner.test.mjs +471 -0
- package/bizar-dash/tests/lib/search-fuzzy.test.ts +149 -0
- package/bizar-dash/tests/memory-graph-view.test.tsx +69 -0
- package/bizar-dash/tests/memory-graph.test.mjs +95 -0
- package/bizar-dash/tests/ocr.test.mjs +87 -0
- package/bizar-dash/tests/otel.test.mjs +188 -0
- package/bizar-dash/tests/plugins-registry.test.mjs +387 -0
- package/bizar-dash/tests/plugins-sandbox.test.mjs +374 -0
- package/bizar-dash/tests/plugins-store.test.mjs +455 -0
- package/bizar-dash/tests/users.test.mjs +108 -0
- package/bizar-dash/tests/voice-recorder.test.tsx +95 -0
- package/bizar-dash/tests/voice-store.test.mjs +148 -0
- package/bizar-dash/tests/voice-transcribe.test.mjs +87 -0
- package/bizar-dash/tests/workspaces.test.mjs +527 -0
- package/cli/bin.mjs +72 -2
- package/cli/commands/clip.mjs +146 -0
- package/cli/commands/dash.mjs +6 -0
- package/cli/commands/deploy/cloudflare.mjs +250 -0
- package/cli/commands/deploy/docker.mjs +221 -0
- package/cli/commands/deploy/fly.mjs +161 -0
- package/cli/commands/deploy/vercel.mjs +225 -0
- package/cli/commands/deploy.mjs +240 -0
- package/cli/commands/eval.mjs +378 -0
- package/cli/commands/marketplace.mjs +64 -0
- package/cli/commands/ocr.mjs +165 -0
- package/cli/commands/plugin.mjs +358 -0
- package/cli/commands/voice.mjs +211 -0
- package/cli/commands/workspace.mjs +247 -0
- package/package.json +12 -2
- package/templates/deploy/cloudflare/README.md +32 -0
- package/templates/deploy/cloudflare/functions-index.template.js +15 -0
- package/templates/deploy/cloudflare/wrangler.toml.template +9 -0
- package/templates/deploy/docker/.env.template +16 -0
- package/templates/deploy/docker/README.md +58 -0
- package/templates/deploy/docker/docker-compose.template.yml +23 -0
- package/templates/deploy/fly/README.md +35 -0
- package/templates/deploy/fly/fly.toml.template +28 -0
- package/templates/deploy/vercel/README.md +29 -0
- package/templates/deploy/vercel/api-index.template.js +18 -0
- package/templates/deploy/vercel/vercel.json.template +16 -0
- package/templates/eval-fixtures/README.md +58 -0
- package/templates/eval-fixtures/code-search-basic.json +28 -0
- package/templates/eval-fixtures/latency-bounds.json +16 -0
- package/templates/eval-fixtures/regression-suite.json +79 -0
- package/templates/eval-fixtures/response-format.json +30 -0
- package/templates/eval-fixtures/tool-call-correctness.json +24 -0
- package/templates/plugin-template/README.md +121 -0
- package/templates/plugin-template/index.js +66 -0
- package/templates/plugin-template/plugin.json +42 -0
- package/templates/plugin-template/tests/plugin.test.js +83 -0
- package/bizar-dash/dist/assets/main-DHZmbnxQ.js +0 -361
- package/bizar-dash/dist/assets/main-DHZmbnxQ.js.map +0 -1
- package/bizar-dash/dist/assets/mobile-BK8-ythT.js +0 -351
- package/bizar-dash/dist/assets/mobile-BK8-ythT.js.map +0 -1
- 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
|
+
}
|