@polderlabs/bizar 4.9.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/bizar-dash/dist/assets/icons-CFqu2M-c.js +656 -0
  2. package/bizar-dash/dist/assets/icons-CFqu2M-c.js.map +1 -0
  3. package/bizar-dash/dist/assets/{index-DU61awG3.js → index-DmpSFPJY.js} +1 -1
  4. package/bizar-dash/dist/assets/{index-DU61awG3.js.map → index-DmpSFPJY.js.map} +1 -1
  5. package/bizar-dash/dist/assets/main-Dl8yY5_H.js +16 -0
  6. package/bizar-dash/dist/assets/main-Dl8yY5_H.js.map +1 -0
  7. package/bizar-dash/dist/assets/{main-DfmIfOUS.css → main-ZAfGKENE.css} +1 -1
  8. package/bizar-dash/dist/assets/markdown-DIquRulQ.js +29 -0
  9. package/bizar-dash/dist/assets/markdown-DIquRulQ.js.map +1 -0
  10. package/bizar-dash/dist/assets/mobile-C2gysFOZ.js +2 -0
  11. package/bizar-dash/dist/assets/mobile-C2gysFOZ.js.map +1 -0
  12. package/bizar-dash/dist/assets/mobile-DHXXbn1A.js +1 -0
  13. package/bizar-dash/dist/assets/{mobile-CL5uUQEC.js.map → mobile-DHXXbn1A.js.map} +1 -1
  14. package/bizar-dash/dist/assets/react-vendor-DZRUXSPQ.js +40 -0
  15. package/bizar-dash/dist/assets/react-vendor-DZRUXSPQ.js.map +1 -0
  16. package/bizar-dash/dist/index.html +6 -3
  17. package/bizar-dash/dist/mobile.html +5 -2
  18. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  19. package/bizar-dash/skills/eval/SKILL.md +237 -0
  20. package/bizar-dash/src/server/api.mjs +28 -0
  21. package/bizar-dash/src/server/auth.mjs +155 -1
  22. package/bizar-dash/src/server/eval-store.mjs +226 -0
  23. package/bizar-dash/src/server/eval.mjs +347 -0
  24. package/bizar-dash/src/server/ocr.mjs +55 -0
  25. package/bizar-dash/src/server/plugins/registry.mjs +363 -0
  26. package/bizar-dash/src/server/plugins/sandbox.mjs +655 -0
  27. package/bizar-dash/src/server/plugins/store.mjs +659 -0
  28. package/bizar-dash/src/server/routes/clipboard.mjs +173 -0
  29. package/bizar-dash/src/server/routes/eval.mjs +147 -0
  30. package/bizar-dash/src/server/routes/ocr.mjs +182 -0
  31. package/bizar-dash/src/server/routes/plugins.mjs +220 -0
  32. package/bizar-dash/src/server/routes/users.mjs +84 -0
  33. package/bizar-dash/src/server/routes/voice.mjs +131 -0
  34. package/bizar-dash/src/server/routes/workspaces.mjs +204 -0
  35. package/bizar-dash/src/server/voice-store.mjs +202 -0
  36. package/bizar-dash/src/server/voice-transcribe.mjs +72 -0
  37. package/bizar-dash/src/server/workspaces.mjs +626 -0
  38. package/bizar-dash/src/web/components/InviteDialog.tsx +205 -0
  39. package/bizar-dash/src/web/components/ScreenshotCapture.tsx +138 -0
  40. package/bizar-dash/src/web/components/ScreenshotOCR.tsx +42 -0
  41. package/bizar-dash/src/web/components/VoiceNotesPanel.tsx +182 -0
  42. package/bizar-dash/src/web/components/VoiceRecorder.tsx +104 -0
  43. package/bizar-dash/src/web/components/WorkspaceSelector.tsx +158 -0
  44. package/bizar-dash/src/web/styles/memory.css +82 -0
  45. package/bizar-dash/src/web/views/Memory.tsx +17 -2
  46. package/bizar-dash/src/web/views/Settings.tsx +3 -0
  47. package/bizar-dash/src/web/views/Workspace.tsx +294 -0
  48. package/bizar-dash/src/web/views/memory/FromScreenshotPanel.tsx +23 -0
  49. package/bizar-dash/src/web/views/memory/VaultFromClipboardPanel.tsx +101 -0
  50. package/bizar-dash/src/web/views/settings/WorkspacesSection.tsx +189 -0
  51. package/bizar-dash/tests/bundle-analysis.test.mjs +5 -2
  52. package/bizar-dash/tests/clipboard.test.mjs +147 -0
  53. package/bizar-dash/tests/components/screenshot-ocr.test.tsx +75 -0
  54. package/bizar-dash/tests/components/workspace-selector.test.tsx +73 -0
  55. package/bizar-dash/tests/deploy-templates.test.mjs +100 -0
  56. package/bizar-dash/tests/eval/fixtures.test.mjs +141 -0
  57. package/bizar-dash/tests/eval/report.test.mjs +284 -0
  58. package/bizar-dash/tests/eval/runner.test.mjs +471 -0
  59. package/bizar-dash/tests/ocr.test.mjs +87 -0
  60. package/bizar-dash/tests/plugins-registry.test.mjs +387 -0
  61. package/bizar-dash/tests/plugins-sandbox.test.mjs +374 -0
  62. package/bizar-dash/tests/plugins-store.test.mjs +455 -0
  63. package/bizar-dash/tests/users.test.mjs +108 -0
  64. package/bizar-dash/tests/voice-recorder.test.tsx +95 -0
  65. package/bizar-dash/tests/voice-store.test.mjs +148 -0
  66. package/bizar-dash/tests/voice-transcribe.test.mjs +87 -0
  67. package/bizar-dash/tests/workspaces.test.mjs +527 -0
  68. package/cli/bin.mjs +72 -2
  69. package/cli/commands/clip.mjs +146 -0
  70. package/cli/commands/deploy/cloudflare.mjs +250 -0
  71. package/cli/commands/deploy/docker.mjs +221 -0
  72. package/cli/commands/deploy/fly.mjs +161 -0
  73. package/cli/commands/deploy/vercel.mjs +225 -0
  74. package/cli/commands/deploy.mjs +240 -0
  75. package/cli/commands/eval.mjs +378 -0
  76. package/cli/commands/marketplace.mjs +64 -0
  77. package/cli/commands/ocr.mjs +165 -0
  78. package/cli/commands/plugin.mjs +358 -0
  79. package/cli/commands/voice.mjs +211 -0
  80. package/cli/commands/workspace.mjs +247 -0
  81. package/package.json +12 -8
  82. package/templates/deploy/cloudflare/README.md +32 -0
  83. package/templates/deploy/cloudflare/functions-index.template.js +15 -0
  84. package/templates/deploy/cloudflare/wrangler.toml.template +9 -0
  85. package/templates/deploy/docker/.env.template +16 -0
  86. package/templates/deploy/docker/README.md +58 -0
  87. package/templates/deploy/docker/docker-compose.template.yml +23 -0
  88. package/templates/deploy/fly/README.md +35 -0
  89. package/templates/deploy/fly/fly.toml.template +28 -0
  90. package/templates/deploy/vercel/README.md +29 -0
  91. package/templates/deploy/vercel/api-index.template.js +18 -0
  92. package/templates/deploy/vercel/vercel.json.template +16 -0
  93. package/templates/eval-fixtures/README.md +58 -0
  94. package/templates/eval-fixtures/code-search-basic.json +28 -0
  95. package/templates/eval-fixtures/latency-bounds.json +16 -0
  96. package/templates/eval-fixtures/regression-suite.json +79 -0
  97. package/templates/eval-fixtures/response-format.json +30 -0
  98. package/templates/eval-fixtures/tool-call-correctness.json +24 -0
  99. package/templates/plugin-template/README.md +121 -0
  100. package/templates/plugin-template/index.js +66 -0
  101. package/templates/plugin-template/plugin.json +42 -0
  102. package/templates/plugin-template/tests/plugin.test.js +83 -0
  103. package/bizar-dash/dist/assets/main-DaC1Lc6q.js +0 -366
  104. package/bizar-dash/dist/assets/main-DaC1Lc6q.js.map +0 -1
  105. package/bizar-dash/dist/assets/mobile-CL5uUQEC.js +0 -1
  106. package/bizar-dash/dist/assets/mobile-D5WTWvuh.js +0 -338
  107. package/bizar-dash/dist/assets/mobile-D5WTWvuh.js.map +0 -1
@@ -0,0 +1,202 @@
1
+ /**
2
+ * src/server/voice-store.mjs
3
+ *
4
+ * v5.0.0 — Voice note persistence layer.
5
+ *
6
+ * Audio files are stored at:
7
+ * ~/.local/share/bizar/voice-notes/<id>.webm
8
+ *
9
+ * Markdown transcripts are written to the user's vault at:
10
+ * <vaultPath>/.obsidian/voice-notes/<id>.md
11
+ * or (if no vaultPath is set) to the default vault:
12
+ * ~/.local/share/bizar/voice-notes/transcripts/<id>.md
13
+ *
14
+ * Metadata index is at:
15
+ * ~/.local/share/bizar/voice-notes/index.json
16
+ */
17
+
18
+ import {
19
+ existsSync,
20
+ readFileSync,
21
+ writeFileSync,
22
+ renameSync,
23
+ mkdirSync,
24
+ readdirSync,
25
+ unlinkSync,
26
+ } from 'node:fs';
27
+ import { join, dirname } from 'node:path';
28
+ import { homedir } from 'node:os';
29
+ import { randomBytes } from 'node:crypto';
30
+ import { info, warn, child } from './logger.mjs';
31
+
32
+ const log = child({ module: 'voice-store' });
33
+
34
+ const HOME = homedir();
35
+ const VOICE_DIR = join(HOME, '.local', 'share', 'bizar', 'voice-notes');
36
+ const INDEX_FILE = join(VOICE_DIR, 'index.json');
37
+ const TRANSCRIPT_DIR = join(VOICE_DIR, 'transcripts');
38
+
39
+ // ── Paths ───────────────────────────────────────────────────────────────────
40
+
41
+ function audioPath(id) {
42
+ return join(VOICE_DIR, `${id}.webm`);
43
+ }
44
+
45
+ function transcriptPath(id, vaultPath) {
46
+ if (vaultPath) {
47
+ const dir = join(vaultPath, '.obsidian', 'voice-notes');
48
+ return join(dir, `${id}.md`);
49
+ }
50
+ return join(TRANSCRIPT_DIR, `${id}.md`);
51
+ }
52
+
53
+ // ── Atomic helpers ──────────────────────────────────────────────────────────
54
+
55
+ function atomicWriteJson(filePath, data) {
56
+ const tmp = `${filePath}.tmp.${process.pid}`;
57
+ writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', 'utf8');
58
+ renameSync(tmp, filePath);
59
+ }
60
+
61
+ // ── Index ───────────────────────────────────────────────────────────────────
62
+
63
+ /**
64
+ * @typedef {Object} VoiceNote
65
+ * @property {string} id
66
+ * @property {string} audioPath
67
+ * @property {string|null} notePath - markdown transcript path (may not exist yet)
68
+ * @property {string|null} transcript
69
+ * @property {number|null} durationSec
70
+ * @property {string|null} vaultPath
71
+ * @property {number} createdAtMs
72
+ * @property {number} updatedAtMs
73
+ */
74
+
75
+ /** @returns {VoiceNote[]} */
76
+ function loadIndex() {
77
+ try {
78
+ if (!existsSync(INDEX_FILE)) return [];
79
+ const text = readFileSync(INDEX_FILE, 'utf8');
80
+ if (!text.trim()) return [];
81
+ return JSON.parse(text);
82
+ } catch {
83
+ return [];
84
+ }
85
+ }
86
+
87
+ function saveIndex(notes) {
88
+ mkdirSync(dirname(INDEX_FILE), { recursive: true });
89
+ atomicWriteJson(INDEX_FILE, notes);
90
+ }
91
+
92
+ // ── Public API ───────────────────────────────────────────────────────────────
93
+
94
+ /**
95
+ * Save a voice note: write audio file + markdown transcript + update index.
96
+ *
97
+ * @param {{ audioBuffer: Buffer|Uint8Array, transcript: string|null, durationSec: number|null, vaultPath: string|null }} opts
98
+ * @returns {Promise<{ notePath: string, audioPath: string, id: string }>}
99
+ */
100
+ export async function saveVoiceNote({ audioBuffer, transcript, durationSec, vaultPath }) {
101
+ const id = randomBytes(8).toString('hex');
102
+ const ap = audioPath(id);
103
+
104
+ // 1. Write audio (binary, no atomic needed for raw bytes — just ensure dir)
105
+ mkdirSync(dirname(ap), { recursive: true });
106
+ writeFileSync(ap, Buffer.from(audioBuffer));
107
+
108
+ // 2. Write markdown transcript if we have one
109
+ let notePath = null;
110
+ if (transcript) {
111
+ const tp = transcriptPath(id, vaultPath);
112
+ const tpDir = dirname(tp);
113
+ mkdirSync(tpDir, { recursive: true });
114
+ const md = [
115
+ '---',
116
+ `title: Voice Note ${new Date().toISOString().slice(0, 16).replace('T', ' ')}`,
117
+ `date: ${new Date().toISOString()}`,
118
+ `type: voice-note`,
119
+ `audio: ${id}.webm`,
120
+ `duration: ${durationSec ?? '?'}`,
121
+ '---',
122
+ '',
123
+ `# ${new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}`,
124
+ '',
125
+ transcript,
126
+ ].join('\n');
127
+ writeFileSync(tp, md, 'utf8');
128
+ notePath = tp;
129
+ }
130
+
131
+ // 3. Update index
132
+ const notes = loadIndex();
133
+ /** @type {VoiceNote} */
134
+ const entry = {
135
+ id,
136
+ audioPath: ap,
137
+ notePath,
138
+ transcript: transcript || null,
139
+ durationSec: durationSec || null,
140
+ vaultPath: vaultPath || null,
141
+ createdAtMs: Date.now(),
142
+ updatedAtMs: Date.now(),
143
+ };
144
+ notes.unshift(entry); // newest first
145
+ saveIndex(notes);
146
+
147
+ log.info('voice note saved', { id, notePath, audioPath: ap });
148
+ return { notePath, audioPath: ap, id };
149
+ }
150
+
151
+ /**
152
+ * @param {{ vaultPath?: string|null, limit?: number }} opts
153
+ * @returns {VoiceNote[]}
154
+ */
155
+ export function listVoiceNotes({ vaultPath = null, limit = 50 } = {}) {
156
+ const notes = loadIndex();
157
+ const filtered = vaultPath
158
+ ? notes.filter((n) => n.vaultPath === vaultPath)
159
+ : notes;
160
+ return filtered.slice(0, limit);
161
+ }
162
+
163
+ /**
164
+ * @param {string} id
165
+ * @returns {VoiceNote|undefined}
166
+ */
167
+ export function getVoiceNote(id) {
168
+ return loadIndex().find((n) => n.id === id);
169
+ }
170
+
171
+ /**
172
+ * @param {string} id
173
+ * @returns {boolean}
174
+ */
175
+ export function deleteVoiceNote(id) {
176
+ const notes = loadIndex();
177
+ const idx = notes.findIndex((n) => n.id === id);
178
+ if (idx === -1) return false;
179
+
180
+ const { audioPath: ap, notePath: tp } = notes[idx];
181
+
182
+ // Remove audio file
183
+ try {
184
+ if (existsSync(ap)) unlinkSync(ap);
185
+ } catch (err) {
186
+ warn('failed to remove audio file', { id, ap, err: err.message });
187
+ }
188
+
189
+ // Remove transcript file
190
+ if (tp) {
191
+ try {
192
+ if (existsSync(tp)) unlinkSync(tp);
193
+ } catch (err) {
194
+ warn('failed to remove transcript file', { id, tp, err: err.message });
195
+ }
196
+ }
197
+
198
+ notes.splice(idx, 1);
199
+ saveIndex(notes);
200
+ log.info('voice note deleted', { id });
201
+ return true;
202
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * src/server/voice-transcribe.mjs
3
+ *
4
+ * v5.0.0 — Whisper transcription for voice notes.
5
+ *
6
+ * Uses OpenAI Whisper API (or a compatible BIZAR_WHISPER_ENDPOINT).
7
+ * Returns null on failure so callers can degrade gracefully.
8
+ */
9
+
10
+ import { info, warn, child } from './logger.mjs';
11
+
12
+ const log = child({ module: 'voice-transcribe' });
13
+
14
+ /**
15
+ * Transcribe an audio buffer via Whisper.
16
+ *
17
+ * @param {Buffer|Uint8Array} audioBuffer - raw audio bytes
18
+ * @param {{ mimeType?: string }} opts
19
+ * @returns {Promise<string|null>} transcript text or null
20
+ */
21
+ export async function transcribe(audioBuffer, { mimeType = 'audio/webm' } = {}) {
22
+ if (!process.env.OPENAI_API_KEY && !process.env.BIZAR_WHISPER_ENDPOINT) {
23
+ log.debug('no whisper credentials configured, skipping transcription');
24
+ return null;
25
+ }
26
+
27
+ const endpoint =
28
+ process.env.BIZAR_WHISPER_ENDPOINT || 'https://api.openai.com/v1/audio/transcriptions';
29
+
30
+ const body = new FormData();
31
+ body.append(
32
+ 'file',
33
+ new Blob([audioBuffer], { type: mimeType }),
34
+ 'audio.webm',
35
+ );
36
+ body.append('model', 'whisper-1');
37
+
38
+ const headers = {};
39
+ if (process.env.OPENAI_API_KEY) {
40
+ headers['Authorization'] = `Bearer ${process.env.OPENAI_API_KEY}`;
41
+ }
42
+ // BIZAR_WHISPER_ENDPOINT may be a local proxy that doesn't need auth
43
+ // but may also support Authorization header — only set if key exists
44
+
45
+ log.info('submitting audio to whisper', { endpoint, sizeBytes: audioBuffer.byteLength });
46
+
47
+ let resp;
48
+ try {
49
+ resp = await fetch(endpoint, { method: 'POST', body, headers });
50
+ } catch (err) {
51
+ warn('whisper fetch failed', { err: err.message });
52
+ return null;
53
+ }
54
+
55
+ if (!resp.ok) {
56
+ const text = await resp.text().catch(() => '');
57
+ warn('whisper returned error', { status: resp.status, body: text });
58
+ return null;
59
+ }
60
+
61
+ let data;
62
+ try {
63
+ data = await resp.json();
64
+ } catch {
65
+ warn('whisper returned non-JSON');
66
+ return null;
67
+ }
68
+
69
+ const text = typeof data.text === 'string' ? data.text.trim() : null;
70
+ log.info('transcription complete', { textLength: text?.length ?? 0 });
71
+ return text;
72
+ }