@polderlabs/bizar 4.9.0 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/bizar-dash/dist/assets/icons-Bo0iH9EC.js +701 -0
  2. package/bizar-dash/dist/assets/icons-Bo0iH9EC.js.map +1 -0
  3. package/bizar-dash/dist/assets/{index-DU61awG3.js → index-DmpSFPJY.js} +1 -1
  4. package/bizar-dash/dist/assets/{index-DU61awG3.js.map → index-DmpSFPJY.js.map} +1 -1
  5. package/bizar-dash/dist/assets/main-C1cpttnv.js +19 -0
  6. package/bizar-dash/dist/assets/main-C1cpttnv.js.map +1 -0
  7. package/bizar-dash/dist/assets/main-DTkNlLrw.css +1 -0
  8. package/bizar-dash/dist/assets/markdown-DIquRulQ.js +29 -0
  9. package/bizar-dash/dist/assets/markdown-DIquRulQ.js.map +1 -0
  10. package/bizar-dash/dist/assets/mobile-BtxQJftK.js +2 -0
  11. package/bizar-dash/dist/assets/mobile-BtxQJftK.js.map +1 -0
  12. package/bizar-dash/dist/assets/mobile-DD-FZrTC.js +1 -0
  13. package/bizar-dash/dist/assets/{mobile-CL5uUQEC.js.map → mobile-DD-FZrTC.js.map} +1 -1
  14. package/bizar-dash/dist/assets/react-vendor-DZRUXSPQ.js +40 -0
  15. package/bizar-dash/dist/assets/react-vendor-DZRUXSPQ.js.map +1 -0
  16. package/bizar-dash/dist/index.html +6 -3
  17. package/bizar-dash/dist/mobile.html +5 -2
  18. package/bizar-dash/node_modules/.package-lock.json +6 -0
  19. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  20. package/bizar-dash/package-lock.json +6 -0
  21. package/bizar-dash/skills/eval/SKILL.md +237 -0
  22. package/bizar-dash/src/server/api.mjs +35 -0
  23. package/bizar-dash/src/server/auth.mjs +155 -1
  24. package/bizar-dash/src/server/diagnostics-store.mjs +452 -2
  25. package/bizar-dash/src/server/eval-store.mjs +226 -0
  26. package/bizar-dash/src/server/eval.mjs +347 -0
  27. package/bizar-dash/src/server/memory-store.mjs +46 -0
  28. package/bizar-dash/src/server/ocr.mjs +55 -0
  29. package/bizar-dash/src/server/plugins/registry.mjs +363 -0
  30. package/bizar-dash/src/server/plugins/sandbox.mjs +655 -0
  31. package/bizar-dash/src/server/plugins/store.mjs +659 -0
  32. package/bizar-dash/src/server/providers-store.mjs +11 -4
  33. package/bizar-dash/src/server/routes/_shared.mjs +2 -2
  34. package/bizar-dash/src/server/routes/clipboard.mjs +173 -0
  35. package/bizar-dash/src/server/routes/doctor.mjs +71 -0
  36. package/bizar-dash/src/server/routes/env-vars.mjs +67 -1
  37. package/bizar-dash/src/server/routes/eval.mjs +147 -0
  38. package/bizar-dash/src/server/routes/memory.mjs +3 -0
  39. package/bizar-dash/src/server/routes/ocr.mjs +182 -0
  40. package/bizar-dash/src/server/routes/opencode-session-detail.mjs +61 -42
  41. package/bizar-dash/src/server/routes/plugins.mjs +220 -0
  42. package/bizar-dash/src/server/routes/schedules.mjs +55 -0
  43. package/bizar-dash/src/server/routes/users.mjs +84 -0
  44. package/bizar-dash/src/server/routes/voice.mjs +131 -0
  45. package/bizar-dash/src/server/routes/workspaces.mjs +204 -0
  46. package/bizar-dash/src/server/serve-info.mjs +172 -0
  47. package/bizar-dash/src/server/voice-store.mjs +202 -0
  48. package/bizar-dash/src/server/voice-transcribe.mjs +72 -0
  49. package/bizar-dash/src/server/workspaces.mjs +626 -0
  50. package/bizar-dash/src/web/App.tsx +48 -11
  51. package/bizar-dash/src/web/components/AutosaveField.tsx +50 -0
  52. package/bizar-dash/src/web/components/DoctorPanel.tsx +160 -0
  53. package/bizar-dash/src/web/components/EnvVarManager.tsx +245 -62
  54. package/bizar-dash/src/web/components/InviteDialog.tsx +205 -0
  55. package/bizar-dash/src/web/components/ScheduleTemplateCard.tsx +71 -0
  56. package/bizar-dash/src/web/components/ScreenshotCapture.tsx +138 -0
  57. package/bizar-dash/src/web/components/ScreenshotOCR.tsx +42 -0
  58. package/bizar-dash/src/web/components/SettingsNav.tsx +101 -0
  59. package/bizar-dash/src/web/components/Sidebar.tsx +25 -1
  60. package/bizar-dash/src/web/components/StatusBadge.tsx +33 -2
  61. package/bizar-dash/src/web/components/Topbar.tsx +11 -0
  62. package/bizar-dash/src/web/components/VoiceNotesPanel.tsx +182 -0
  63. package/bizar-dash/src/web/components/VoiceRecorder.tsx +104 -0
  64. package/bizar-dash/src/web/components/WorkspaceSelector.tsx +158 -0
  65. package/bizar-dash/src/web/components/chat/ChatInfoPanel.tsx +64 -0
  66. package/bizar-dash/src/web/components/chat/Composer.tsx +1 -1
  67. package/bizar-dash/src/web/components/chat/useChat.ts +118 -1
  68. package/bizar-dash/src/web/hooks/useAutosave.ts +107 -0
  69. package/bizar-dash/src/web/lib/types.ts +134 -0
  70. package/bizar-dash/src/web/styles/chat.css +8 -5
  71. package/bizar-dash/src/web/styles/main.css +77 -2
  72. package/bizar-dash/src/web/styles/memory.css +82 -0
  73. package/bizar-dash/src/web/styles/settings.css +265 -0
  74. package/bizar-dash/src/web/views/Chat.tsx +15 -1
  75. package/bizar-dash/src/web/views/Doctor.tsx +317 -0
  76. package/bizar-dash/src/web/views/Memory.tsx +17 -2
  77. package/bizar-dash/src/web/views/MiniMaxUsage.tsx +1 -0
  78. package/bizar-dash/src/web/views/Schedules.tsx +94 -12
  79. package/bizar-dash/src/web/views/Settings.tsx +78 -45
  80. package/bizar-dash/src/web/views/Workspace.tsx +294 -0
  81. package/bizar-dash/src/web/views/memory/ConfigPanel.tsx +114 -133
  82. package/bizar-dash/src/web/views/memory/FromScreenshotPanel.tsx +23 -0
  83. package/bizar-dash/src/web/views/memory/VaultFromClipboardPanel.tsx +101 -0
  84. package/bizar-dash/src/web/views/settings/AgentSection.tsx +23 -12
  85. package/bizar-dash/src/web/views/settings/EnvVarsSection.tsx +5 -6
  86. package/bizar-dash/src/web/views/settings/GeneralSection.tsx +38 -15
  87. package/bizar-dash/src/web/views/settings/MemorySection.tsx +92 -8
  88. package/bizar-dash/src/web/views/settings/WorkspacesSection.tsx +189 -0
  89. package/bizar-dash/tests/autosave.test.tsx +276 -0
  90. package/bizar-dash/tests/bundle-analysis.test.mjs +5 -2
  91. package/bizar-dash/tests/chat-composer.test.tsx +140 -0
  92. package/bizar-dash/tests/clipboard.test.mjs +147 -0
  93. package/bizar-dash/tests/components/doctor-panel.test.tsx +105 -0
  94. package/bizar-dash/tests/components/screenshot-ocr.test.tsx +75 -0
  95. package/bizar-dash/tests/components/workspace-selector.test.tsx +73 -0
  96. package/bizar-dash/tests/deploy-templates.test.mjs +100 -0
  97. package/bizar-dash/tests/diagnostics-store.test.mjs +206 -0
  98. package/bizar-dash/tests/env-vars-extended.test.mjs +190 -0
  99. package/bizar-dash/tests/eval/fixtures.test.mjs +141 -0
  100. package/bizar-dash/tests/eval/report.test.mjs +284 -0
  101. package/bizar-dash/tests/eval/runner.test.mjs +471 -0
  102. package/bizar-dash/tests/layout-spacing.test.mjs +101 -0
  103. package/bizar-dash/tests/memory-default-vault.test.mjs +98 -0
  104. package/bizar-dash/tests/minimax-bar.test.tsx +113 -0
  105. package/bizar-dash/tests/minimax-models.test.mjs +123 -0
  106. package/bizar-dash/tests/ocr.test.mjs +87 -0
  107. package/bizar-dash/tests/opencode-sessions-detail.test.mjs +158 -0
  108. package/bizar-dash/tests/plugins-registry.test.mjs +387 -0
  109. package/bizar-dash/tests/plugins-sandbox.test.mjs +374 -0
  110. package/bizar-dash/tests/plugins-store.test.mjs +455 -0
  111. package/bizar-dash/tests/routes-doctor.test.mjs +159 -0
  112. package/bizar-dash/tests/schedules-templates.test.mjs +144 -0
  113. package/bizar-dash/tests/settings-layout.test.tsx +129 -0
  114. package/bizar-dash/tests/settings-mode-wiring.test.tsx +151 -0
  115. package/bizar-dash/tests/settings-nav.test.tsx +126 -0
  116. package/bizar-dash/tests/users.test.mjs +108 -0
  117. package/bizar-dash/tests/voice-recorder.test.tsx +95 -0
  118. package/bizar-dash/tests/voice-store.test.mjs +148 -0
  119. package/bizar-dash/tests/voice-transcribe.test.mjs +87 -0
  120. package/bizar-dash/tests/workspaces.test.mjs +527 -0
  121. package/cli/bin.mjs +72 -2
  122. package/cli/commands/clip.mjs +146 -0
  123. package/cli/commands/deploy/cloudflare.mjs +250 -0
  124. package/cli/commands/deploy/docker.mjs +221 -0
  125. package/cli/commands/deploy/fly.mjs +161 -0
  126. package/cli/commands/deploy/vercel.mjs +225 -0
  127. package/cli/commands/deploy.mjs +240 -0
  128. package/cli/commands/eval.mjs +378 -0
  129. package/cli/commands/marketplace.mjs +64 -0
  130. package/cli/commands/minimax.mjs +5 -2
  131. package/cli/commands/ocr.mjs +165 -0
  132. package/cli/commands/plugin.mjs +358 -0
  133. package/cli/commands/voice.mjs +211 -0
  134. package/cli/commands/workspace.mjs +247 -0
  135. package/cli/tests/minimax-cli.test.mjs +79 -0
  136. package/config/agents/frigg.md +1 -1
  137. package/config/agents/heimdall.md +1 -1
  138. package/config/agents/mimir.md +1 -1
  139. package/config/agents/quick.md +1 -1
  140. package/config/agents/semble-search.md +1 -1
  141. package/config/agents/vor.md +1 -1
  142. package/config/opencode.json +28 -21
  143. package/config/opencode.json.template +8 -1
  144. package/package.json +12 -8
  145. package/plugins/bizar/index.ts +77 -0
  146. package/plugins/bizar/src/compaction.d.mts +48 -0
  147. package/plugins/bizar/src/compaction.mjs +192 -0
  148. package/plugins/bizar/tests/compaction.test.ts +264 -0
  149. package/templates/deploy/cloudflare/README.md +32 -0
  150. package/templates/deploy/cloudflare/functions-index.template.js +15 -0
  151. package/templates/deploy/cloudflare/wrangler.toml.template +9 -0
  152. package/templates/deploy/docker/.env.template +16 -0
  153. package/templates/deploy/docker/README.md +58 -0
  154. package/templates/deploy/docker/docker-compose.template.yml +23 -0
  155. package/templates/deploy/fly/README.md +35 -0
  156. package/templates/deploy/fly/fly.toml.template +28 -0
  157. package/templates/deploy/vercel/README.md +29 -0
  158. package/templates/deploy/vercel/api-index.template.js +18 -0
  159. package/templates/deploy/vercel/vercel.json.template +16 -0
  160. package/templates/eval-fixtures/README.md +58 -0
  161. package/templates/eval-fixtures/code-search-basic.json +28 -0
  162. package/templates/eval-fixtures/latency-bounds.json +16 -0
  163. package/templates/eval-fixtures/regression-suite.json +79 -0
  164. package/templates/eval-fixtures/response-format.json +30 -0
  165. package/templates/eval-fixtures/tool-call-correctness.json +24 -0
  166. package/templates/plugin-template/README.md +121 -0
  167. package/templates/plugin-template/index.js +66 -0
  168. package/templates/plugin-template/plugin.json +42 -0
  169. package/templates/plugin-template/tests/plugin.test.js +83 -0
  170. package/templates/schedules/daily-backup.json +12 -0
  171. package/templates/schedules/daily-cleanup.json +12 -0
  172. package/templates/schedules/hourly-health-check.json +12 -0
  173. package/templates/schedules/webhook-on-push.json +13 -0
  174. package/templates/schedules/weekly-digest.json +13 -0
  175. package/bizar-dash/dist/assets/main-DaC1Lc6q.js +0 -366
  176. package/bizar-dash/dist/assets/main-DaC1Lc6q.js.map +0 -1
  177. package/bizar-dash/dist/assets/main-DfmIfOUS.css +0 -1
  178. package/bizar-dash/dist/assets/mobile-CL5uUQEC.js +0 -1
  179. package/bizar-dash/dist/assets/mobile-D5WTWvuh.js +0 -338
  180. package/bizar-dash/dist/assets/mobile-D5WTWvuh.js.map +0 -1
  181. package/bizar-dash/src/web/views/settings/BackupSection.tsx +0 -16
  182. package/bizar-dash/src/web/views/settings/ProvidersSection.tsx +0 -16
  183. package/bizar-dash/src/web/views/settings/SkillsSection.tsx +0 -16
@@ -0,0 +1,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
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "id": "latency-bounds",
3
+ "name": "Latency bounds",
4
+ "description": "Verifies the agent responds within a strict latency budget",
5
+ "agent": "thor",
6
+ "prompt": "Respond with exactly the word 'ok' and nothing else.",
7
+ "expected": {
8
+ "contains": ["ok"],
9
+ "notContains": [],
10
+ "regex": [],
11
+ "jsonSchema": null,
12
+ "maxTokens": 50,
13
+ "maxLatencyMs": 10000
14
+ },
15
+ "tags": ["latency", "performance"]
16
+ }
@@ -0,0 +1,79 @@
1
+ {
2
+ "id": "regression-suite",
3
+ "name": "Full regression suite",
4
+ "description": "Runs all smoke tests to catch regressions",
5
+ "fixtures": [
6
+ {
7
+ "id": "code-search-basic",
8
+ "name": "Basic code search",
9
+ "description": "Verifies the agent can find a function definition",
10
+ "agent": "thor",
11
+ "prompt": "Find the function named 'add' in this repository. Show its signature and parameter names.",
12
+ "expected": {
13
+ "contains": ["function add", "a", "b"],
14
+ "notContains": ["TODO", "FIXME"],
15
+ "regex": ["function\\s+add\\s*\\("],
16
+ "jsonSchema": null,
17
+ "maxTokens": 500,
18
+ "maxLatencyMs": 30000
19
+ },
20
+ "tags": ["code-search", "smoke"]
21
+ },
22
+ {
23
+ "id": "tool-call-correctness",
24
+ "name": "Tool call correctness",
25
+ "description": "Verifies correct tool usage",
26
+ "agent": "thor",
27
+ "prompt": "Create a new file called /tmp/eval-test.txt with the content 'eval test output'.",
28
+ "expected": {
29
+ "contains": ["write", "/tmp/eval-test.txt"],
30
+ "notContains": ["error", "failed"],
31
+ "regex": [],
32
+ "jsonSchema": null,
33
+ "maxTokens": 500,
34
+ "maxLatencyMs": 30000
35
+ },
36
+ "tags": ["tools", "smoke"]
37
+ },
38
+ {
39
+ "id": "response-format",
40
+ "name": "Response format validation",
41
+ "description": "Verifies JSON output with schema",
42
+ "agent": "thor",
43
+ "prompt": "Return JSON: { \"status\": \"ok\", \"version\": \"1.0.0\", \"data\": { \"count\": 42 } }. No markdown.",
44
+ "expected": {
45
+ "contains": [],
46
+ "notContains": [],
47
+ "regex": [],
48
+ "jsonSchema": {
49
+ "type": "object",
50
+ "required": ["status", "version", "data"],
51
+ "properties": {
52
+ "status": { "type": "string" },
53
+ "version": { "type": "string" },
54
+ "data": { "type": "object", "required": ["count"], "properties": { "count": { "type": "number" } } }
55
+ }
56
+ },
57
+ "maxTokens": 300,
58
+ "maxLatencyMs": 20000
59
+ },
60
+ "tags": ["format", "json", "smoke"]
61
+ },
62
+ {
63
+ "id": "latency-bounds",
64
+ "name": "Latency bounds",
65
+ "description": "Verifies response within latency budget",
66
+ "agent": "thor",
67
+ "prompt": "Respond with exactly the word 'ok' and nothing else.",
68
+ "expected": {
69
+ "contains": ["ok"],
70
+ "notContains": [],
71
+ "regex": [],
72
+ "jsonSchema": null,
73
+ "maxTokens": 50,
74
+ "maxLatencyMs": 10000
75
+ },
76
+ "tags": ["latency", "performance"]
77
+ }
78
+ ]
79
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "id": "response-format",
3
+ "name": "Response format validation",
4
+ "description": "Verifies the agent returns valid JSON with a known schema",
5
+ "agent": "thor",
6
+ "prompt": "Return a JSON object with exactly these fields: { \"status\": \"ok\", \"version\": \"1.0.0\", \"data\": { \"count\": 42 } }. No extra fields. Valid JSON only, no markdown fences.",
7
+ "expected": {
8
+ "contains": [],
9
+ "notContains": [],
10
+ "regex": [],
11
+ "jsonSchema": {
12
+ "type": "object",
13
+ "required": ["status", "version", "data"],
14
+ "properties": {
15
+ "status": { "type": "string" },
16
+ "version": { "type": "string" },
17
+ "data": {
18
+ "type": "object",
19
+ "required": ["count"],
20
+ "properties": {
21
+ "count": { "type": "number" }
22
+ }
23
+ }
24
+ }
25
+ },
26
+ "maxTokens": 300,
27
+ "maxLatencyMs": 20000
28
+ },
29
+ "tags": ["format", "json", "smoke"]
30
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "id": "tool-call-correctness",
3
+ "name": "Tool call correctness",
4
+ "description": "Verifies the agent uses the correct tool for a file system operation",
5
+ "agent": "thor",
6
+ "prompt": "Create a new file called /tmp/eval-test.txt with the content 'eval test output' using the write tool.",
7
+ "expected": {
8
+ "contains": [
9
+ "write",
10
+ "/tmp/eval-test.txt",
11
+ "eval test output"
12
+ ],
13
+ "notContains": [
14
+ "error",
15
+ "failed",
16
+ "could not"
17
+ ],
18
+ "regex": [],
19
+ "jsonSchema": null,
20
+ "maxTokens": 500,
21
+ "maxLatencyMs": 30000
22
+ },
23
+ "tags": ["tools", "smoke", "file-ops"]
24
+ }
@@ -0,0 +1,121 @@
1
+ # Echo Plugin Template
2
+
3
+ The smallest possible Bizar marketplace plugin. Use it as a starting
4
+ point for your own — copy this folder, rename the fields in
5
+ `plugin.json`, replace the body of `index.js`, and ship it as a
6
+ `.tar.gz`.
7
+
8
+ ## What you get
9
+
10
+ ```
11
+ plugin-template/
12
+ ├── plugin.json # manifest (id, name, version, main, exports, permissions)
13
+ ├── index.js # the plugin's main entry — exports { init, echo, shout }
14
+ ├── README.md # you are here
15
+ └── tests/
16
+ └── plugin.test.js # node:test smoke tests
17
+ ```
18
+
19
+ ## Trying it locally
20
+
21
+ You can sanity-check the template without publishing it: install it
22
+ from a local path (or just point the host at this folder directly):
23
+
24
+ ```bash
25
+ # From inside your dashboard's project:
26
+ bizar plugin install <path-to-plugin-template>
27
+ bizar plugin info echo
28
+ bizar plugin invoke echo echo "hello world"
29
+ # → echo: hello world
30
+
31
+ bizar plugin config echo greeting "yo"
32
+ bizar plugin invoke echo shout "hello world"
33
+ # → SHOUT: HELLO WORLD
34
+
35
+ bizar plugin uninstall echo
36
+ ```
37
+
38
+ ## Publishing
39
+
40
+ 1. **Pick an id.** It must match `/^[a-z0-9][a-z0-9-]*$/`. Lowercase
41
+ kebab-case is the convention (`vercel-deploy`, `github-pr-watcher`).
42
+
43
+ 2. **Edit `plugin.json`** — at minimum:
44
+ - `id`, `name`, `version`, `description`
45
+ - `main` (relative to the tarball root)
46
+ - `exports` (array of method descriptors; drives the dashboard UI)
47
+ - `permissions` (every `api.*` method you'll use, except `console`,
48
+ `api.config.*`, and `api.log` which are always available)
49
+
50
+ 3. **Write the plugin code** in `index.js`. Use `module.exports = { ... }`
51
+ and export plain async functions. Every async method is awaited by
52
+ the host; a thrown error becomes `{ ok: false, error, code }` on the
53
+ wire.
54
+
55
+ 4. **Package the tarball.** Standard convention:
56
+
57
+ ```bash
58
+ tar czf echo-0.1.0.tar.gz plugin.json index.js tests
59
+ ```
60
+
61
+ The tarball root must contain `plugin.json` (either at the very
62
+ top of the archive or inside a single top-level directory — both
63
+ work).
64
+
65
+ 5. **Compute the SHA-256** of the tarball:
66
+
67
+ ```bash
68
+ sha256sum echo-0.1.0.tar.gz
69
+ # → 8a3f... (64 hex chars)
70
+ ```
71
+
72
+ 6. **Submit to the registry** by opening a PR against
73
+ [DrB0rk/bizar-plugins](https://github.com/DrB0rk/bizar-plugins).
74
+ Add your entry under `plugins:` in `registry.json`:
75
+
76
+ ```json
77
+ {
78
+ "id": "echo",
79
+ "name": "Echo",
80
+ "version": "0.1.0",
81
+ "description": "Echo plugin",
82
+ "author": "you",
83
+ "category": "utility",
84
+ "tags": ["example", "template"],
85
+ "tarball": "https://github.com/.../echo-0.1.0.tar.gz",
86
+ "checksum": "sha256:8a3f...",
87
+ "permissions": [],
88
+ "minBizarVersion": "4.9.0"
89
+ }
90
+ ```
91
+
92
+ The host verifies the checksum before extracting — a tampered
93
+ tarball is rejected outright.
94
+
95
+ 7. **Install.** Once merged:
96
+
97
+ ```bash
98
+ bizar plugin install echo
99
+ ```
100
+
101
+ ## Sandbox permissions
102
+
103
+ | Permission | What it unlocks |
104
+ |--------------|------------------------------------------------------|
105
+ | `net` | `api.http.get(url)`, `api.http.post(url, body)` |
106
+ | `fs:read` | `api.fs.read(path)` — sandboxed to the plugin root |
107
+ | *(none)* | `api.config.*`, `api.log`, `console.*` are free |
108
+
109
+ Anything else (`process`, `child_process`, raw `fs`, `net`) is
110
+ stripped from the plugin's global scope by `node:vm`. A plugin that
111
+ tries `require('node:fs')` will throw `require is not defined`.
112
+
113
+ ## Versioning
114
+
115
+ Bizar plugins use [semver](https://semver.org/). Bump the major
116
+ version for any breaking change to `plugin.json`'s `exports[]`; minor
117
+ for new methods; patch for bugfixes.
118
+
119
+ ## License
120
+
121
+ MIT, same as the rest of BizarHarness.
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Echo plugin — the minimal marketplace plugin.
3
+ *
4
+ * Plugins export their public methods via `module.exports`. Each
5
+ * method receives `(args..., { config, api, plugin })`. The host
6
+ * (bizar-dash's plugin store) handles sandboxing, timeouts, and
7
+ * permission enforcement — your code just needs to focus on the
8
+ * domain logic.
9
+ *
10
+ * What you CAN do from inside a plugin:
11
+ * - console.log / console.error → routed through the dashboard's logger
12
+ * - api.http.get(url) → only if you declare "net" permission
13
+ * - api.fs.read(path) → only if you declare "fs:read" permission
14
+ * - api.config.get(key) / set(key, value) → in-memory + persisted to disk
15
+ * - api.log(level, msg) → structured logger with your plugin id
16
+ *
17
+ * What you CANNOT do (the sandbox strips these globals):
18
+ * - require('node:fs') directly → use api.fs.read instead
19
+ * - require('node:child_process') → not allowed in v1
20
+ * - process.exit / process.kill → use api.log instead
21
+ * - require('node:net') → use api.http instead
22
+ */
23
+ 'use strict';
24
+
25
+ /**
26
+ * The plugin's `init` hook. Called once after install and on every
27
+ * dashboard start (the host caches the loaded plugin). Use it to
28
+ * warm caches, validate config, or subscribe to events.
29
+ *
30
+ * Returning a Promise is fine — the host awaits it.
31
+ */
32
+ async function init() {
33
+ // Pull a config value the user may have set.
34
+ const greeting = api.config.get('greeting') || 'echo';
35
+ api.log('info', `${plugin.id} v${plugin.version} initialized (greeting="${greeting}")`);
36
+ }
37
+
38
+ /**
39
+ * The simplest possible plugin method: return the input verbatim,
40
+ * prefixed with the configured greeting.
41
+ *
42
+ * @param {string} message
43
+ */
44
+ async function echo(message) {
45
+ if (typeof message !== 'string') {
46
+ throw new Error(`echo: expected string, got ${typeof message}`);
47
+ }
48
+ const greeting = api.config.get('greeting') || 'echo';
49
+ return `${greeting}: ${message}`;
50
+ }
51
+
52
+ /**
53
+ * Upper-cased variant — demonstrates that async / await / Promises
54
+ * all work transparently inside the sandbox.
55
+ *
56
+ * @param {string} message
57
+ */
58
+ async function shout(message) {
59
+ return `SHOUT: ${String(message).toUpperCase()}`;
60
+ }
61
+
62
+ module.exports = {
63
+ init,
64
+ echo,
65
+ shout,
66
+ };
@@ -0,0 +1,42 @@
1
+ {
2
+ "id": "echo",
3
+ "name": "Echo",
4
+ "version": "0.1.0",
5
+ "description": "A minimal example plugin — echoes whatever you send it. Use this as a starting template for your own marketplace plugin.",
6
+ "author": "DrB0rk",
7
+ "main": "index.js",
8
+ "exports": [
9
+ {
10
+ "name": "echo",
11
+ "args": [
12
+ { "name": "message", "type": "string" }
13
+ ],
14
+ "returns": "string",
15
+ "description": "Return the input message prefixed with 'echo:'."
16
+ },
17
+ {
18
+ "name": "shout",
19
+ "args": [
20
+ { "name": "message", "type": "string" }
21
+ ],
22
+ "returns": "string",
23
+ "description": "Return the input message upper-cased and prefixed with 'SHOUT:'."
24
+ },
25
+ {
26
+ "name": "init",
27
+ "args": [],
28
+ "returns": "void",
29
+ "description": "Optional setup hook — called once after install / dashboard start."
30
+ }
31
+ ],
32
+ "permissions": [],
33
+ "configSchema": {
34
+ "greeting": {
35
+ "type": "string",
36
+ "required": false,
37
+ "default": "echo"
38
+ }
39
+ },
40
+ "homepage": "https://github.com/DrB0rk/bizar-plugins",
41
+ "minBizarVersion": "4.9.0"
42
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * tests/plugin.test.js
3
+ *
4
+ * Smoke tests for the Echo plugin template.
5
+ *
6
+ * These run with `node --test tests/plugin.test.js` from the
7
+ * template folder. They exercise the plugin by loading it directly
8
+ * through the host's `sandbox.mjs` (so the same code path that runs
9
+ * in production is exercised here), not by re-implementing the
10
+ * loading logic.
11
+ *
12
+ * If you copy this template for your own plugin, replace the
13
+ * expectations with real ones for your plugin's behaviour.
14
+ */
15
+ import { test } from 'node:test';
16
+ import assert from 'node:assert/strict';
17
+ import { fileURLToPath } from 'node:url';
18
+ import { dirname, resolve } from 'node:path';
19
+ import { readFileSync } from 'node:fs';
20
+
21
+ const HERE = dirname(fileURLToPath(import.meta.url));
22
+ const TEMPLATE_ROOT = resolve(HERE, '..');
23
+
24
+ test('plugin.json is valid JSON with required fields', () => {
25
+ const raw = readFileSync(resolve(TEMPLATE_ROOT, 'plugin.json'), 'utf8');
26
+ const m = JSON.parse(raw);
27
+ assert.equal(typeof m.id, 'string');
28
+ assert.ok(m.id, 'id must be non-empty');
29
+ assert.equal(typeof m.version, 'string');
30
+ assert.ok(m.version, 'version must be non-empty');
31
+ assert.equal(typeof m.main, 'string');
32
+ assert.ok(m.main, 'main must be non-empty');
33
+ assert.ok(Array.isArray(m.exports), 'exports must be an array');
34
+ assert.ok(Array.isArray(m.permissions), 'permissions must be an array');
35
+ });
36
+
37
+ test('index.js exists and is non-empty', () => {
38
+ const src = readFileSync(resolve(TEMPLATE_ROOT, 'index.js'), 'utf8');
39
+ assert.ok(src.length > 100, 'index.js should be a real file');
40
+ assert.match(src, /module\.exports\s*=/, 'must use module.exports');
41
+ });
42
+
43
+ test('plugin exports echo, shout, and init', async () => {
44
+ // We exercise the actual sandbox so this test fails if the plugin
45
+ // ever drifts from the host's API surface.
46
+ const sandboxUrl = new URL('../../../bizar-dash/src/server/plugins/sandbox.mjs',
47
+ import.meta.url);
48
+ const { loadPlugin, safeInvoke } = await import(sandboxUrl);
49
+ const loaded = await loadPlugin({
50
+ mainFile: resolve(TEMPLATE_ROOT, 'index.js'),
51
+ config: { greeting: 'test' },
52
+ permissions: [],
53
+ pluginId: 'echo',
54
+ pluginRoot: TEMPLATE_ROOT,
55
+ });
56
+ assert.equal(typeof loaded.exports.echo, 'function');
57
+ assert.equal(typeof loaded.exports.shout, 'function');
58
+ assert.equal(typeof loaded.exports.init, 'function');
59
+
60
+ const r1 = await safeInvoke(loaded, 'echo', ['hello']);
61
+ assert.equal(r1.ok, true);
62
+ assert.equal(r1.result, 'test: hello');
63
+
64
+ const r2 = await safeInvoke(loaded, 'shout', ['hi']);
65
+ assert.equal(r2.ok, true);
66
+ assert.equal(r2.result, 'SHOUT: HI');
67
+ });
68
+
69
+ test('echo validates its argument', async () => {
70
+ const sandboxUrl = new URL('../../../bizar-dash/src/server/plugins/sandbox.mjs',
71
+ import.meta.url);
72
+ const { loadPlugin, safeInvoke } = await import(sandboxUrl);
73
+ const loaded = await loadPlugin({
74
+ mainFile: resolve(TEMPLATE_ROOT, 'index.js'),
75
+ config: {},
76
+ permissions: [],
77
+ pluginId: 'echo',
78
+ pluginRoot: TEMPLATE_ROOT,
79
+ });
80
+ const r = await safeInvoke(loaded, 'echo', [42]);
81
+ assert.equal(r.ok, false);
82
+ assert.match(r.error, /expected string/);
83
+ });
@@ -0,0 +1,12 @@
1
+ {
2
+ "id": "template-daily-backup",
3
+ "name": "Daily backup",
4
+ "description": "Create a backup every day at 03:00",
5
+ "type": "cron",
6
+ "schedule": "0 3 * * *",
7
+ "action": {
8
+ "type": "command",
9
+ "target": "bizar backup --label daily"
10
+ },
11
+ "tags": ["backup", "maintenance"]
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "id": "template-daily-cleanup",
3
+ "name": "Daily cleanup",
4
+ "description": "Clean up old backups and logs every day at 04:00",
5
+ "type": "cron",
6
+ "schedule": "0 4 * * *",
7
+ "action": {
8
+ "type": "command",
9
+ "target": "bizar backup delete --older-than 30d"
10
+ },
11
+ "tags": ["backup", "maintenance", "cleanup"]
12
+ }