@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,205 @@
1
+ // src/web/components/InviteDialog.tsx — v5.0.0 — modal to create and manage workspace invites.
2
+ import { useState, useEffect } from 'react';
3
+ import { Copy, Check, Trash2, Mail, Link2 } from 'lucide-react';
4
+ import { useModal } from './Modal';
5
+ import { Button } from './Button';
6
+ import { api } from '../lib/api';
7
+ import { cn } from '../lib/utils';
8
+
9
+ export type InviteInfo = {
10
+ token: string;
11
+ email: string;
12
+ role: 'admin' | 'editor' | 'viewer';
13
+ invitedBy: string;
14
+ expiresAt: string;
15
+ };
16
+
17
+ type Props = {
18
+ workspaceId: string;
19
+ onInviteCreated?: () => void;
20
+ };
21
+
22
+ export function InviteDialog({ workspaceId, onInviteCreated }: Props) {
23
+ const modal = useModal();
24
+ const [email, setEmail] = useState('');
25
+ const [role, setRole] = useState<'admin' | 'editor' | 'viewer'>('editor');
26
+ const [invites, setInvites] = useState<InviteInfo[]>([]);
27
+ const [loading, setLoading] = useState(false);
28
+ const [creating, setCreating] = useState(false);
29
+ const [copied, setCopied] = useState<string | null>(null);
30
+ const [error, setError] = useState<string | null>(null);
31
+
32
+ useEffect(() => {
33
+ loadInvites();
34
+ }, [workspaceId]);
35
+
36
+ async function loadInvites() {
37
+ setLoading(true);
38
+ try {
39
+ const r = await api.get<{ invites: InviteInfo[] }>(`/workspaces/${workspaceId}/invites`);
40
+ setInvites(r.invites || []);
41
+ } catch {
42
+ // Ignore load errors
43
+ } finally {
44
+ setLoading(false);
45
+ }
46
+ }
47
+
48
+ async function handleCreate(e: React.FormEvent) {
49
+ e.preventDefault();
50
+ if (!email.trim()) return;
51
+ setCreating(true);
52
+ setError(null);
53
+ try {
54
+ const result = await api.post<{ token: string; url: string }>(`/workspaces/${workspaceId}/invites`, {
55
+ email: email.trim(),
56
+ role,
57
+ });
58
+ setEmail('');
59
+ await loadInvites();
60
+ onInviteCreated?.();
61
+
62
+ // Show the invite URL in a copy dialog
63
+ modal.open({
64
+ title: 'Invite Link Created',
65
+ children: (
66
+ <div className="invite-link-dialog">
67
+ <p>Share this link with <strong>{email}</strong> to invite them as <strong>{role}</strong>:</p>
68
+ <div className="invite-url-box">
69
+ <Link2 size={14} />
70
+ <input
71
+ type="text"
72
+ readOnly
73
+ value={result.url}
74
+ onClick={(e) => (e.target as HTMLInputElement).select()}
75
+ />
76
+ <button
77
+ type="button"
78
+ className="invite-copy-btn"
79
+ onClick={() => {
80
+ navigator.clipboard.writeText(result.url).catch(() => undefined);
81
+ setCopied(result.token);
82
+ setTimeout(() => setCopied(null), 2000);
83
+ }}
84
+ >
85
+ {copied === result.token ? <Check size={14} /> : <Copy size={14} />}
86
+ </button>
87
+ </div>
88
+ <p className="invite-url-note">This link expires in 7 days.</p>
89
+ </div>
90
+ ),
91
+ footer: (
92
+ <Button variant="primary" size="sm" onClick={() => modal.close()}>
93
+ Done
94
+ </Button>
95
+ ),
96
+ });
97
+ } catch (err: unknown) {
98
+ setError((err as Error).message || 'Failed to create invite');
99
+ } finally {
100
+ setCreating(false);
101
+ }
102
+ }
103
+
104
+ async function handleRevoke(token: string) {
105
+ if (!confirm('Revoke this invite?')) return;
106
+ try {
107
+ await api.del(`/workspaces/${workspaceId}/invites/${token}`);
108
+ await loadInvites();
109
+ } catch (err: unknown) {
110
+ setError((err as Error).message || 'Failed to revoke invite');
111
+ }
112
+ }
113
+
114
+ function copyToClipboard(text: string, key: string) {
115
+ navigator.clipboard.writeText(text).catch(() => undefined);
116
+ setCopied(key);
117
+ setTimeout(() => setCopied(null), 2000);
118
+ }
119
+
120
+ const roleColors: Record<string, string> = {
121
+ admin: 'role-admin',
122
+ editor: 'role-editor',
123
+ viewer: 'role-viewer',
124
+ };
125
+
126
+ return (
127
+ <div className="invite-dialog">
128
+ <form className="invite-form" onSubmit={handleCreate}>
129
+ <div className="invite-form-row">
130
+ <div className="invite-form-field">
131
+ <label htmlFor="invite-email">Email address</label>
132
+ <input
133
+ id="invite-email"
134
+ type="email"
135
+ value={email}
136
+ onChange={(e) => setEmail(e.target.value)}
137
+ placeholder="colleague@example.com"
138
+ required
139
+ />
140
+ </div>
141
+ <div className="invite-form-field invite-form-field-role">
142
+ <label htmlFor="invite-role">Role</label>
143
+ <select
144
+ id="invite-role"
145
+ value={role}
146
+ onChange={(e) => setRole(e.target.value as 'admin' | 'editor' | 'viewer')}
147
+ >
148
+ <option value="viewer">Viewer</option>
149
+ <option value="editor">Editor</option>
150
+ <option value="admin">Admin</option>
151
+ </select>
152
+ </div>
153
+ <Button
154
+ type="submit"
155
+ variant="primary"
156
+ size="sm"
157
+ loading={creating}
158
+ disabled={!email.trim()}
159
+ >
160
+ <Mail size={13} />
161
+ Invite
162
+ </Button>
163
+ </div>
164
+ {error && <p className="invite-error">{error}</p>}
165
+ </form>
166
+
167
+ <div className="invite-list">
168
+ <h4 className="invite-list-title">Pending Invites</h4>
169
+ {loading && <p className="invite-loading">Loading...</p>}
170
+ {!loading && invites.length === 0 && (
171
+ <p className="invite-empty">No pending invites</p>
172
+ )}
173
+ {invites.map((inv) => (
174
+ <div key={inv.token} className="invite-item">
175
+ <div className="invite-item-info">
176
+ <span className="invite-item-email">{inv.email}</span>
177
+ <span className={cn('invite-item-role', roleColors[inv.role])}>{inv.role}</span>
178
+ </div>
179
+ <div className="invite-item-actions">
180
+ <button
181
+ type="button"
182
+ className="invite-action-btn"
183
+ title="Copy invite link"
184
+ onClick={() => {
185
+ const url = `${window.location.origin}/accept-invite?token=${inv.token}`;
186
+ copyToClipboard(url, inv.token);
187
+ }}
188
+ >
189
+ {copied === inv.token ? <Check size={13} /> : <Copy size={13} />}
190
+ </button>
191
+ <button
192
+ type="button"
193
+ className="invite-action-btn invite-action-revoke"
194
+ title="Revoke invite"
195
+ onClick={() => handleRevoke(inv.token)}
196
+ >
197
+ <Trash2 size={13} />
198
+ </button>
199
+ </div>
200
+ </div>
201
+ ))}
202
+ </div>
203
+ </div>
204
+ );
205
+ }
@@ -0,0 +1,138 @@
1
+ // src/web/components/ScreenshotCapture.tsx — v5.0.0
2
+ //
3
+ // Uses browser's getDisplayMedia() to capture a screen, window, or browser tab.
4
+ // Returns a blob URL for preview and uploads to /api/ocr/process for text extraction.
5
+
6
+ import { useState, useCallback } from 'react';
7
+ import { Camera, Loader2 } from 'lucide-react';
8
+ import { Button } from './Button';
9
+ import { Card, CardTitle } from './Card';
10
+ import { useToast } from './Toast';
11
+ import { api } from '../lib/api';
12
+
13
+ export type ScreenshotCaptureProps = {
14
+ onTextExtracted?: (text: string, notePath: string) => void;
15
+ };
16
+
17
+ export function ScreenshotCapture({ onTextExtracted }: ScreenshotCaptureProps) {
18
+ const toast = useToast();
19
+ const [imageUrl, setImageUrl] = useState<string | null>(null);
20
+ const [processing, setProcessing] = useState(false);
21
+ const [capturing, setCapturing] = useState(false);
22
+
23
+ const capture = useCallback(async () => {
24
+ setCapturing(true);
25
+ try {
26
+ const stream = await navigator.mediaDevices.getDisplayMedia({
27
+ video: { mediaSource: 'screen' } as MediaTrackConstraints,
28
+ });
29
+ const video = document.createElement('video');
30
+ video.srcObject = stream;
31
+ await video.play();
32
+
33
+ const canvas = document.createElement('canvas');
34
+ canvas.width = video.videoWidth;
35
+ canvas.height = video.videoHeight;
36
+ const ctx = canvas.getContext('2d');
37
+ if (!ctx) {
38
+ toast.error('Failed to get canvas context');
39
+ return;
40
+ }
41
+ ctx.drawImage(video, 0, 0);
42
+
43
+ // Stop all tracks
44
+ stream.getTracks().forEach((t) => t.stop());
45
+ video.remove();
46
+
47
+ canvas.toBlob(async (blob) => {
48
+ if (!blob) {
49
+ toast.error('Failed to capture screenshot');
50
+ return;
51
+ }
52
+ const url = URL.createObjectURL(blob);
53
+ setImageUrl(url);
54
+ await processImage(blob);
55
+ }, 'image/png');
56
+ } catch (err) {
57
+ if ((err as Error).name !== 'NotAllowedError') {
58
+ toast.error(`Capture failed: ${(err as Error).message}`);
59
+ }
60
+ // NotAllowedError = user cancelled the picker — no toast needed
61
+ } finally {
62
+ setCapturing(false);
63
+ }
64
+ }, [toast]);
65
+
66
+ const processImage = useCallback(async (blob: Blob) => {
67
+ setProcessing(true);
68
+ try {
69
+ const reader = new FileReader();
70
+ const base64 = await new Promise<string>((resolve, reject) => {
71
+ reader.onload = () => {
72
+ const result = reader.result as string;
73
+ // Strip data:image/png;base64, prefix for API
74
+ const b64 = result.includes(',') ? result.split(',')[1] : result;
75
+ resolve(b64);
76
+ };
77
+ reader.onerror = () => reject(new Error('FileReader failed'));
78
+ reader.readAsDataURL(blob);
79
+ });
80
+
81
+ const r = await api.post<{ ok: boolean; text: string; notePath: string }>('/ocr/process', {
82
+ image: base64,
83
+ lang: 'eng',
84
+ });
85
+
86
+ if (r.ok) {
87
+ toast.success('Text extracted from screenshot');
88
+ onTextExtracted?.(r.text, r.notePath);
89
+ }
90
+ } catch (err) {
91
+ toast.error(`OCR failed: ${(err as Error).message}`);
92
+ } finally {
93
+ setProcessing(false);
94
+ }
95
+ }, [toast, onTextExtracted]);
96
+
97
+ const dismiss = useCallback(() => {
98
+ setImageUrl(null);
99
+ }, []);
100
+
101
+ return (
102
+ <Card variant="outlined" className="screenshot-capture-card">
103
+ <CardTitle><Camera size={14} /> Screenshot Capture</CardTitle>
104
+
105
+ <div className="screenshot-capture-body">
106
+ {!imageUrl ? (
107
+ <div className="screenshot-capture-empty">
108
+ <Camera size={32} className="muted" />
109
+ <p className="muted text-sm">Capture your screen to extract text via OCR.</p>
110
+ <Button
111
+ variant="primary"
112
+ size="sm"
113
+ onClick={capture}
114
+ disabled={capturing}
115
+ >
116
+ {capturing ? <Loader2 size={12} className="spinner" /> : <Camera size={12} />}
117
+ {capturing ? 'Selecting…' : 'Capture Screen'}
118
+ </Button>
119
+ </div>
120
+ ) : (
121
+ <div className="screenshot-capture-preview">
122
+ <img src={imageUrl} alt="Captured screenshot" className="screenshot-capture-img" />
123
+ <div className="screenshot-capture-actions">
124
+ {processing && (
125
+ <span className="text-sm muted">
126
+ <Loader2 size={12} className="spinner" /> Extracting text…
127
+ </span>
128
+ )}
129
+ <Button variant="ghost" size="sm" onClick={dismiss} disabled={processing}>
130
+ Dismiss
131
+ </Button>
132
+ </div>
133
+ </div>
134
+ )}
135
+ </div>
136
+ </Card>
137
+ );
138
+ }
@@ -0,0 +1,42 @@
1
+ // src/web/components/ScreenshotOCR.tsx — v5.0.0
2
+ //
3
+ // Combines ScreenshotCapture + extracted text display in one panel.
4
+ // Shows the screenshot preview and the OCR-extracted text side by side.
5
+
6
+ import { useState } from 'react';
7
+ import { FileText } from 'lucide-react';
8
+ import { ScreenshotCapture } from './ScreenshotCapture';
9
+ import { Card, CardTitle, CardMeta } from './Card';
10
+
11
+ export function ScreenshotOCR() {
12
+ const [ocrText, setOcrText] = useState<string | null>(null);
13
+ const [notePath, setNotePath] = useState<string | null>(null);
14
+
15
+ const handleTextExtracted = (text: string, path: string) => {
16
+ setOcrText(text);
17
+ setNotePath(path);
18
+ };
19
+
20
+ return (
21
+ <div className="screenshot-ocr-panel">
22
+ <ScreenshotCapture onTextExtracted={handleTextExtracted} />
23
+
24
+ {ocrText !== null && (
25
+ <Card variant="outlined" className="screenshot-ocr-result-card">
26
+ <CardTitle>
27
+ <FileText size={14} /> Extracted Text
28
+ </CardTitle>
29
+ {notePath && <CardMeta>Saved to: {notePath}</CardMeta>}
30
+
31
+ <div className="screenshot-ocr-text">
32
+ {ocrText ? (
33
+ <pre className="screenshot-ocr-pre mono text-sm">{ocrText}</pre>
34
+ ) : (
35
+ <p className="muted text-sm">No text was found in the captured image.</p>
36
+ )}
37
+ </div>
38
+ </Card>
39
+ )}
40
+ </div>
41
+ );
42
+ }