@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,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/commands/plugin.mjs
|
|
3
|
+
*
|
|
4
|
+
* v5.0.0 — Plugin marketplace CLI.
|
|
5
|
+
*
|
|
6
|
+
* Talks to the running dashboard's HTTP API (which in turn uses
|
|
7
|
+
* `bizar-dash/src/server/plugins/{registry,store,sandbox}.mjs`).
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* bizar plugin search <query> — search the registry
|
|
11
|
+
* bizar plugin install <id> — install from registry
|
|
12
|
+
* bizar plugin list — list installed
|
|
13
|
+
* bizar plugin info <id> — show details + config schema
|
|
14
|
+
* bizar plugin config <id> [key] [value] — get/set config (dot-path key)
|
|
15
|
+
* bizar plugin update <id> — update to latest
|
|
16
|
+
* bizar plugin uninstall <id> — remove
|
|
17
|
+
* bizar plugin invoke <id> <method> [...args] — run a plugin method
|
|
18
|
+
*/
|
|
19
|
+
import chalk from 'chalk';
|
|
20
|
+
import { readFileSync } from 'node:fs';
|
|
21
|
+
import { homedir } from 'node:os';
|
|
22
|
+
import { join } from 'node:path';
|
|
23
|
+
|
|
24
|
+
// ── Config dir helper (same shape as cli/commands/mod.mjs) ────────────────
|
|
25
|
+
|
|
26
|
+
function getBizarConfigDir() {
|
|
27
|
+
if (process.platform === 'win32') {
|
|
28
|
+
return process.env.APPDATA
|
|
29
|
+
? join(process.env.APPDATA, 'bizar')
|
|
30
|
+
: join(homedir(), '.config', 'bizar');
|
|
31
|
+
}
|
|
32
|
+
return process.env.XDG_CONFIG_HOME
|
|
33
|
+
? join(process.env.XDG_CONFIG_HOME, 'bizar')
|
|
34
|
+
: join(homedir(), '.config', 'bizar');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ── Help ───────────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
export function showPluginHelp() {
|
|
40
|
+
console.log(`
|
|
41
|
+
bizar plugin — Manage marketplace plugins (via the dashboard's HTTP API)
|
|
42
|
+
|
|
43
|
+
Usage:
|
|
44
|
+
bizar plugin search <query> Search the public registry
|
|
45
|
+
bizar plugin install <id> Install a plugin from the registry
|
|
46
|
+
bizar plugin list List installed plugins
|
|
47
|
+
bizar plugin info <id> Show details + config schema
|
|
48
|
+
bizar plugin config <id> [key] [value] Get or set a config value
|
|
49
|
+
bizar plugin update <id> Update to the latest version
|
|
50
|
+
bizar plugin uninstall <id> Remove a plugin
|
|
51
|
+
bizar plugin invoke <id> <method> [...args] Invoke a plugin method
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
bizar plugin search deploy
|
|
55
|
+
bizar plugin install vercel-deploy
|
|
56
|
+
bizar plugin info vercel-deploy
|
|
57
|
+
bizar plugin config vercel-deploy apiKey sk-xxx
|
|
58
|
+
bizar plugin update vercel-deploy
|
|
59
|
+
bizar plugin invoke vercel-deploy deploy ./my-project
|
|
60
|
+
|
|
61
|
+
Description:
|
|
62
|
+
Subcommands call the running dashboard's HTTP API. If no dashboard is
|
|
63
|
+
reachable, you'll be told to run \`bizar dash start --bg\` first.
|
|
64
|
+
|
|
65
|
+
\`bizar plugin config <id> <key> <value>\` sets a single key at the
|
|
66
|
+
given dot-path. To replace the entire config object, use the dashboard
|
|
67
|
+
UI or the REST API directly.
|
|
68
|
+
`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── API helpers (mirror cli/commands/mod.mjs) ──────────────────────────────
|
|
72
|
+
|
|
73
|
+
async function request(baseUrl, method, path, body) {
|
|
74
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
75
|
+
method,
|
|
76
|
+
headers: body ? { 'Content-Type': 'application/json' } : {},
|
|
77
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
78
|
+
});
|
|
79
|
+
const text = await res.text();
|
|
80
|
+
let json = null;
|
|
81
|
+
try { json = text ? JSON.parse(text) : null; } catch { /* keep raw */ }
|
|
82
|
+
if (!res.ok) {
|
|
83
|
+
const msg = json?.message || json?.error || text || `HTTP ${res.status}`;
|
|
84
|
+
const err = new Error(`${method} ${path} failed: ${msg}`);
|
|
85
|
+
err.status = res.status;
|
|
86
|
+
err.code = json?.error;
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
return { status: res.status, json };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function readDashboardUrl() {
|
|
93
|
+
const portFile = join(getBizarConfigDir(), 'dashboard.port');
|
|
94
|
+
let port = null;
|
|
95
|
+
try {
|
|
96
|
+
port = parseInt(readFileSync(portFile, 'utf8').trim(), 10);
|
|
97
|
+
if (!Number.isFinite(port) || port <= 0) port = null;
|
|
98
|
+
} catch {
|
|
99
|
+
port = null;
|
|
100
|
+
}
|
|
101
|
+
if (!port) {
|
|
102
|
+
console.error(
|
|
103
|
+
chalk.red(' ✗ Dashboard is not running (no port file at ' + portFile + ').'),
|
|
104
|
+
);
|
|
105
|
+
console.error(chalk.dim(' Start it first: `bizar dash start --bg`'));
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
return `http://127.0.0.1:${port}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── Subcommand implementations ─────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
async function cmdSearch(args, baseUrl) {
|
|
114
|
+
const query = args.join(' ').trim();
|
|
115
|
+
const r = await request(baseUrl, 'GET',
|
|
116
|
+
`/api/plugins/registry?q=${encodeURIComponent(query)}`);
|
|
117
|
+
const { plugins = [], registry: reg } = r.json;
|
|
118
|
+
console.log(chalk.dim(` Source: ${reg?.source || '(unknown)'}`));
|
|
119
|
+
console.log('');
|
|
120
|
+
if (plugins.length === 0) {
|
|
121
|
+
console.log(chalk.dim(' (no plugins matched)'));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
for (const p of plugins) {
|
|
125
|
+
const installed = p.installed
|
|
126
|
+
? chalk.green(`installed v${p.installedVersion || '?'}`)
|
|
127
|
+
: chalk.dim('not installed');
|
|
128
|
+
const upgrade = p.upgradeAvailable
|
|
129
|
+
? chalk.yellow(` ↑ v${p.upgradeAvailable} available`)
|
|
130
|
+
: '';
|
|
131
|
+
console.log(
|
|
132
|
+
` ${p.id.padEnd(24)} v${(p.version || '?').padEnd(10)} ${installed}${upgrade} ${p.name || ''}`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function cmdInstall(args, baseUrl) {
|
|
138
|
+
const id = args[0];
|
|
139
|
+
if (!id) {
|
|
140
|
+
console.error(chalk.red(' ✗ Missing plugin id. Usage: bizar plugin install <id>'));
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
const force = args.includes('--force') || args.includes('-f');
|
|
144
|
+
const r = await request(baseUrl, 'POST', '/api/plugins/install',
|
|
145
|
+
{ pluginId: id, force });
|
|
146
|
+
console.log(chalk.green(` ✓ Installed "${r.json.id}" v${r.json.version}`));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function cmdList(_args, baseUrl) {
|
|
150
|
+
const r = await request(baseUrl, 'GET', '/api/plugins/installed');
|
|
151
|
+
const plugins = r.json.plugins || [];
|
|
152
|
+
if (plugins.length === 0) {
|
|
153
|
+
console.log(chalk.dim(' (no plugins installed)'));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
for (const p of plugins) {
|
|
157
|
+
console.log(` ${p.id.padEnd(24)} v${(p.version || '?').padEnd(10)} ${p.name || ''}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function cmdInfo(args, baseUrl) {
|
|
162
|
+
const id = args[0];
|
|
163
|
+
if (!id) {
|
|
164
|
+
console.error(chalk.red(' ✗ Missing plugin id. Usage: bizar plugin info <id>'));
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
// Try registry first (gets description + schema); fall back to
|
|
168
|
+
// installed lookup if not in registry.
|
|
169
|
+
let plugin;
|
|
170
|
+
try {
|
|
171
|
+
const r = await request(baseUrl, 'GET',
|
|
172
|
+
`/api/plugins/registry/${encodeURIComponent(id)}`);
|
|
173
|
+
plugin = r.json;
|
|
174
|
+
} catch (err) {
|
|
175
|
+
if (err.status !== 404) throw err;
|
|
176
|
+
plugin = null;
|
|
177
|
+
}
|
|
178
|
+
const installed = await safeGetInstalled(id, baseUrl);
|
|
179
|
+
const merged = { ...(plugin || {}), ...(installed || {}) };
|
|
180
|
+
if (!merged.id) {
|
|
181
|
+
console.error(chalk.red(` ✗ Plugin "${id}" not found in registry or installed list`));
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
console.log(` ${chalk.bold(merged.name || merged.id)} ${chalk.dim('v' + (merged.version || '?'))}`);
|
|
185
|
+
if (merged.description) console.log(` ${chalk.dim(merged.description)}`);
|
|
186
|
+
if (merged.author) console.log(` ${chalk.dim('author:')} ${merged.author}`);
|
|
187
|
+
if (merged.category) console.log(` ${chalk.dim('category:')} ${merged.category}`);
|
|
188
|
+
if (Array.isArray(merged.tags) && merged.tags.length) {
|
|
189
|
+
console.log(` ${chalk.dim('tags:')} ${merged.tags.join(', ')}`);
|
|
190
|
+
}
|
|
191
|
+
if (merged.homepage) console.log(` ${chalk.dim('homepage:')} ${merged.homepage}`);
|
|
192
|
+
if (Array.isArray(merged.permissions) && merged.permissions.length) {
|
|
193
|
+
console.log(` ${chalk.dim('permissions:')} ${merged.permissions.join(', ')}`);
|
|
194
|
+
}
|
|
195
|
+
if (installed) {
|
|
196
|
+
console.log('');
|
|
197
|
+
console.log(` ${chalk.dim('install path:')} ${installed.path}`);
|
|
198
|
+
console.log(` ${chalk.dim('installed:')} ${installed.installedAt}`);
|
|
199
|
+
console.log(` ${chalk.dim('config:')}`);
|
|
200
|
+
if (!installed.config || Object.keys(installed.config).length === 0) {
|
|
201
|
+
console.log(chalk.dim(' (empty)'));
|
|
202
|
+
} else {
|
|
203
|
+
for (const [k, v] of Object.entries(installed.config)) {
|
|
204
|
+
const display = typeof v === 'string' && v.length > 40
|
|
205
|
+
? v.slice(0, 37) + '...'
|
|
206
|
+
: JSON.stringify(v);
|
|
207
|
+
console.log(` ${chalk.cyan(k)}: ${display}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function safeGetInstalled(id, baseUrl) {
|
|
214
|
+
try {
|
|
215
|
+
const r = await request(baseUrl, 'GET', '/api/plugins/installed');
|
|
216
|
+
return (r.json.plugins || []).find((p) => p.id === id) || null;
|
|
217
|
+
} catch {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function cmdConfig(args, baseUrl) {
|
|
223
|
+
const id = args[0];
|
|
224
|
+
if (!id) {
|
|
225
|
+
console.error(chalk.red(
|
|
226
|
+
' ✗ Missing plugin id. Usage: bizar plugin config <id> [key] [value]'));
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
const installed = await safeGetInstalled(id, baseUrl);
|
|
230
|
+
if (!installed) {
|
|
231
|
+
console.error(chalk.red(` ✗ Plugin "${id}" is not installed`));
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
if (args.length === 1) {
|
|
235
|
+
// No key — print full config.
|
|
236
|
+
console.log(JSON.stringify(installed.config || {}, null, 2));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const key = args[1];
|
|
240
|
+
if (args.length === 2) {
|
|
241
|
+
// Just key — print that value.
|
|
242
|
+
const v = getDotPath(installed.config || {}, key);
|
|
243
|
+
console.log(JSON.stringify(v, null, 2));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// key + value — replace full config with the new merged value.
|
|
247
|
+
// We merge to avoid wiping sibling keys the user didn't mention.
|
|
248
|
+
const newConfig = JSON.parse(JSON.stringify(installed.config || {}));
|
|
249
|
+
setDotPath(newConfig, key, args.slice(2).join(' '));
|
|
250
|
+
await request(baseUrl, 'PUT',
|
|
251
|
+
`/api/plugins/${encodeURIComponent(id)}`,
|
|
252
|
+
{ action: 'config', config: newConfig });
|
|
253
|
+
console.log(chalk.green(` ✓ Set ${id}.${key}`));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function getDotPath(obj, path) {
|
|
257
|
+
const parts = path.split('.');
|
|
258
|
+
let cur = obj;
|
|
259
|
+
for (const p of parts) {
|
|
260
|
+
if (cur == null || typeof cur !== 'object') return undefined;
|
|
261
|
+
cur = cur[p];
|
|
262
|
+
}
|
|
263
|
+
return cur;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function setDotPath(obj, path, value) {
|
|
267
|
+
const parts = path.split('.');
|
|
268
|
+
let cur = obj;
|
|
269
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
270
|
+
const p = parts[i];
|
|
271
|
+
if (cur[p] == null || typeof cur[p] !== 'object') cur[p] = {};
|
|
272
|
+
cur = cur[p];
|
|
273
|
+
}
|
|
274
|
+
cur[parts[parts.length - 1]] = value;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function cmdUpdate(args, baseUrl) {
|
|
278
|
+
const id = args[0];
|
|
279
|
+
if (!id) {
|
|
280
|
+
console.error(chalk.red(' ✗ Missing plugin id. Usage: bizar plugin update <id>'));
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
const r = await request(baseUrl, 'PUT',
|
|
284
|
+
`/api/plugins/${encodeURIComponent(id)}`, { action: 'update' });
|
|
285
|
+
const note = r.json.from === r.json.to
|
|
286
|
+
? chalk.dim(' (already at latest)')
|
|
287
|
+
: '';
|
|
288
|
+
console.log(chalk.green(` ✓ Updated "${id}" v${r.json.from} → v${r.json.to}`) + note);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function cmdUninstall(args, baseUrl) {
|
|
292
|
+
const id = args[0];
|
|
293
|
+
if (!id) {
|
|
294
|
+
console.error(chalk.red(' ✗ Missing plugin id. Usage: bizar plugin uninstall <id>'));
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
await request(baseUrl, 'DELETE', `/api/plugins/${encodeURIComponent(id)}`);
|
|
298
|
+
console.log(chalk.green(` ✓ Uninstalled "${id}"`));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function cmdInvoke(args, baseUrl) {
|
|
302
|
+
const [id, method, ...rest] = args;
|
|
303
|
+
if (!id || !method) {
|
|
304
|
+
console.error(chalk.red(
|
|
305
|
+
' ✗ Usage: bizar plugin invoke <id> <method> [...args]'));
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
const r = await request(baseUrl, 'POST',
|
|
309
|
+
`/api/plugins/${encodeURIComponent(id)}/invoke`,
|
|
310
|
+
{ method, args: rest });
|
|
311
|
+
if (r.json.ok) {
|
|
312
|
+
console.log(JSON.stringify(r.json.result, null, 2));
|
|
313
|
+
} else {
|
|
314
|
+
console.error(chalk.red(` ✗ ${r.json.error}`));
|
|
315
|
+
if (r.json.code) console.error(chalk.dim(` code: ${r.json.code}`));
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ── Dispatcher ─────────────────────────────────────────────────────────────
|
|
321
|
+
|
|
322
|
+
export async function runPluginCommand(pluginArgs) {
|
|
323
|
+
const sub = pluginArgs[0];
|
|
324
|
+
const rest = pluginArgs.slice(1);
|
|
325
|
+
|
|
326
|
+
if (!sub || sub === '--help' || sub === '-h') {
|
|
327
|
+
showPluginHelp();
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const baseUrl = await readDashboardUrl();
|
|
332
|
+
const handlers = {
|
|
333
|
+
search: cmdSearch,
|
|
334
|
+
install: cmdInstall,
|
|
335
|
+
list: cmdList,
|
|
336
|
+
info: cmdInfo,
|
|
337
|
+
config: cmdConfig,
|
|
338
|
+
update: cmdUpdate,
|
|
339
|
+
uninstall: cmdUninstall,
|
|
340
|
+
invoke: cmdInvoke,
|
|
341
|
+
};
|
|
342
|
+
const handler = handlers[sub];
|
|
343
|
+
if (!handler) {
|
|
344
|
+
console.error(chalk.red(` ✗ Unknown plugin subcommand: ${sub}`));
|
|
345
|
+
showPluginHelp();
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
await handler(rest, baseUrl);
|
|
350
|
+
} catch (err) {
|
|
351
|
+
console.error(chalk.red(` ✗ ${err.message}`));
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export async function run(name, args, isHelpRequest) {
|
|
357
|
+
await runPluginCommand(args);
|
|
358
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/commands/voice.mjs
|
|
3
|
+
*
|
|
4
|
+
* v5.0.0 — Voice notes CLI — talks to the running dashboard's HTTP API.
|
|
5
|
+
*/
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
10
|
+
|
|
11
|
+
const HOME = homedir();
|
|
12
|
+
|
|
13
|
+
// ── Config dir helper ──────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
function getBizarConfigDir() {
|
|
16
|
+
if (process.platform === 'win32') {
|
|
17
|
+
return process.env.APPDATA
|
|
18
|
+
? join(process.env.APPDATA, 'bizar')
|
|
19
|
+
: join(homedir(), '.config', 'bizar');
|
|
20
|
+
}
|
|
21
|
+
return process.env.XDG_CONFIG_HOME
|
|
22
|
+
? join(process.env.XDG_CONFIG_HOME, 'bizar')
|
|
23
|
+
: join(homedir(), '.config', 'bizar');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getDashboardPort() {
|
|
27
|
+
const portFile = join(getBizarConfigDir(), 'dashboard.port');
|
|
28
|
+
try {
|
|
29
|
+
const port = parseInt(readFileSync(portFile, 'utf8').trim(), 10);
|
|
30
|
+
if (Number.isFinite(port) && port > 0) return port;
|
|
31
|
+
} catch {
|
|
32
|
+
/* fall through */
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ── API helpers ───────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
async function getJson(baseUrl, path) {
|
|
40
|
+
const res = await fetch(`${baseUrl}${path}`);
|
|
41
|
+
const text = await res.text();
|
|
42
|
+
let json = null;
|
|
43
|
+
try { json = text ? JSON.parse(text) : null; } catch { /* keep raw */ }
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
const msg = json?.message || json?.error || text || `HTTP ${res.status}`;
|
|
46
|
+
throw new Error(`${path} failed: ${msg}`);
|
|
47
|
+
}
|
|
48
|
+
return json;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function delJson(baseUrl, path) {
|
|
52
|
+
const res = await fetch(`${baseUrl}${path}`, { method: 'DELETE' });
|
|
53
|
+
if (!res.ok && res.status !== 204) {
|
|
54
|
+
const text = await res.text();
|
|
55
|
+
let json = null;
|
|
56
|
+
try { json = text ? JSON.parse(text) : null; } catch { /* keep raw */ }
|
|
57
|
+
const msg = json?.message || json?.error || text || `HTTP ${res.status}`;
|
|
58
|
+
throw new Error(`${path} failed: ${msg}`);
|
|
59
|
+
}
|
|
60
|
+
return json;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Help ───────────────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
export function showVoiceHelp() {
|
|
66
|
+
console.log(`
|
|
67
|
+
bizar voice — Manage voice notes (via the dashboard's HTTP API)
|
|
68
|
+
|
|
69
|
+
Usage:
|
|
70
|
+
bizar voice list [vaultPath] List voice notes (optionally scoped to vault)
|
|
71
|
+
bizar voice delete <id> Delete a voice note by id
|
|
72
|
+
bizar voice configure --api-key <key> Set Whisper API key
|
|
73
|
+
bizar voice transcribe <audio-path> Transcribe an audio file (CLI-side)
|
|
74
|
+
|
|
75
|
+
Description:
|
|
76
|
+
Voice notes are recorded in the browser (MediaRecorder API) and uploaded
|
|
77
|
+
to the dashboard, which saves audio and transcribes via Whisper.
|
|
78
|
+
|
|
79
|
+
Subcommands call the running dashboard's HTTP API. If no dashboard is
|
|
80
|
+
reachable, you'll be told to run \`bizar dash start\` first.
|
|
81
|
+
|
|
82
|
+
Examples:
|
|
83
|
+
bizar voice list
|
|
84
|
+
bizar voice list ~/vault
|
|
85
|
+
bizar voice delete abc123def456
|
|
86
|
+
bizar voice configure --api-key sk-...
|
|
87
|
+
`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── Config helpers ─────────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
const CONFIG_FILE = join(getBizarConfigDir(), 'voice-config.json');
|
|
93
|
+
|
|
94
|
+
function loadConfig() {
|
|
95
|
+
try {
|
|
96
|
+
if (!existsSync(CONFIG_FILE)) return {};
|
|
97
|
+
return JSON.parse(readFileSync(CONFIG_FILE, 'utf8'));
|
|
98
|
+
} catch {
|
|
99
|
+
return {};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function saveConfig(cfg) {
|
|
104
|
+
const dir = join(getBizarConfigDir());
|
|
105
|
+
if (!existsSync(dir)) {
|
|
106
|
+
require('node:fs').mkdirSync(dir, { recursive: true });
|
|
107
|
+
}
|
|
108
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), 'utf8');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── Command runner ────────────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
export async function runVoiceCommand(args) {
|
|
114
|
+
const sub = args[0];
|
|
115
|
+
const positional = args.slice(1).filter((a) => !a.startsWith('-'));
|
|
116
|
+
const flags = args.slice(1).filter((a) => a.startsWith('-'));
|
|
117
|
+
|
|
118
|
+
if (!sub || sub === '--help' || sub === '-h' || flags.includes('--help') || flags.includes('-h')) {
|
|
119
|
+
showVoiceHelp();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const port = getDashboardPort();
|
|
124
|
+
if (!port) {
|
|
125
|
+
console.error(chalk.red(' ✗ Dashboard is not running (no port file).'));
|
|
126
|
+
console.error(chalk.dim(' Start it first: `bizar dash start --bg`'));
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
const baseUrl = `http://127.0.0.1:${port}`;
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
if (sub === 'list') {
|
|
133
|
+
const vaultPath = positional[0] || '';
|
|
134
|
+
const q = vaultPath ? `?vaultPath=${encodeURIComponent(vaultPath)}` : '';
|
|
135
|
+
const r = await getJson(baseUrl, `/api/voice/list${q}`);
|
|
136
|
+
const notes = r.notes || [];
|
|
137
|
+
if (notes.length === 0) {
|
|
138
|
+
console.log(chalk.dim(' (no voice notes)'));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
console.log(chalk.bold(` Voice Notes (${notes.length}):`));
|
|
142
|
+
for (const n of notes) {
|
|
143
|
+
const date = new Date(n.createdAtMs).toLocaleString();
|
|
144
|
+
const transcript = n.transcript
|
|
145
|
+
? n.transcript.slice(0, 60) + (n.transcript.length > 60 ? '…' : '')
|
|
146
|
+
: '(no transcript)';
|
|
147
|
+
console.log(` ${n.id} ${date}`);
|
|
148
|
+
console.log(` ${chalk.dim(transcript)}`);
|
|
149
|
+
}
|
|
150
|
+
} else if (sub === 'delete') {
|
|
151
|
+
const id = positional[0];
|
|
152
|
+
if (!id) {
|
|
153
|
+
console.error(chalk.red(' ✗ Missing note id. Usage: bizar voice delete <id>'));
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
await delJson(baseUrl, `/api/voice/${id}`);
|
|
157
|
+
console.log(chalk.green(` ✓ Deleted note ${id}`));
|
|
158
|
+
} else if (sub === 'configure') {
|
|
159
|
+
const apiKeyIdx = flags.indexOf('--api-key');
|
|
160
|
+
const apiKey = apiKeyIdx !== -1 ? flags[apiKeyIdx + 1] : positional[0];
|
|
161
|
+
if (!apiKey) {
|
|
162
|
+
console.error(chalk.red(' ✗ Missing API key. Usage: bizar voice configure --api-key <key>'));
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
const cfg = loadConfig();
|
|
166
|
+
cfg.openaiApiKey = apiKey;
|
|
167
|
+
saveConfig(cfg);
|
|
168
|
+
console.log(chalk.green(' ✓ Whisper API key saved to voice config.'));
|
|
169
|
+
console.log(chalk.dim(' Set BIZAR_WHISPER_ENDPOINT env var to use a custom endpoint.'));
|
|
170
|
+
} else if (sub === 'transcribe') {
|
|
171
|
+
// CLI-side transcription: reads a local audio file and POSTs to the upload endpoint
|
|
172
|
+
const audioPath = positional[0];
|
|
173
|
+
if (!audioPath) {
|
|
174
|
+
console.error(chalk.red(' ✗ Missing audio path. Usage: bizar voice transcribe <audio-path>'));
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
if (!existsSync(audioPath)) {
|
|
178
|
+
console.error(chalk.red(` ✗ File not found: ${audioPath}`));
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
console.log(chalk.dim(` Transcribing ${audioPath}…`));
|
|
182
|
+
const { readFileSync: rf } = await import('node:fs');
|
|
183
|
+
const audioBuffer = rf(audioPath);
|
|
184
|
+
const form = new FormData();
|
|
185
|
+
form.append('audio', new Blob([audioBuffer], { type: 'audio/webm' }), 'audio.webm');
|
|
186
|
+
form.append('vaultPath', '');
|
|
187
|
+
const r = await fetch(`${baseUrl}/api/voice/upload`, { method: 'POST', body: form });
|
|
188
|
+
if (!r.ok) {
|
|
189
|
+
const text = await r.text();
|
|
190
|
+
throw new Error(`upload failed: ${text}`);
|
|
191
|
+
}
|
|
192
|
+
const data = await r.json();
|
|
193
|
+
console.log(chalk.green(` ✓ Transcribed and saved (id: ${data.id})`));
|
|
194
|
+
if (data.transcription) {
|
|
195
|
+
console.log(chalk.dim(' Transcript:'));
|
|
196
|
+
console.log(` ${data.transcription}`);
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
console.error(chalk.red(` ✗ Unknown voice subcommand: ${sub}`));
|
|
200
|
+
showVoiceHelp();
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
} catch (err) {
|
|
204
|
+
console.error(chalk.red(` ✗ ${err.message}`));
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export async function run(name, args, isHelpRequest) {
|
|
210
|
+
await runVoiceCommand(args);
|
|
211
|
+
}
|