@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,161 @@
1
+ /**
2
+ * cli/commands/deploy/fly.mjs
3
+ *
4
+ * Fly.io deployment via flyctl CLI.
5
+ *
6
+ * Generates fly.toml, optionally uses existing Dockerfile (v4.9),
7
+ * then runs `flyctl deploy`.
8
+ */
9
+ import chalk from 'chalk';
10
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { execSync } from 'node:child_process';
13
+
14
+ // ── Scaffold generation ──────────────────────────────────────────────────────
15
+
16
+ function generateScaffold(outDir, opts) {
17
+ const scaffoldRoot = join(outDir, 'fly');
18
+
19
+ if (!existsSync(scaffoldRoot)) {
20
+ mkdirSync(scaffoldRoot, { recursive: true });
21
+ }
22
+
23
+ // fly.toml
24
+ const flyToml = [
25
+ '# fly.toml — Bizar dashboard',
26
+ '# Generated by `bizar deploy --to fly`',
27
+ '',
28
+ 'app = "' + (opts.appName || 'bizar-dash') + '"',
29
+ '',
30
+ 'primary_region = "' + (opts.region || 'iad') + '"',
31
+ '',
32
+ '[build]',
33
+ ' dockerfile = "../Dockerfile"',
34
+ '',
35
+ '[http_service]',
36
+ ' internal_port = 4097',
37
+ ' force_https = true',
38
+ ' auto_stop_machines = true',
39
+ ' auto_start_machines = true',
40
+ ' min_machines_running = 0',
41
+ '',
42
+ '[[vm]]',
43
+ ' memory = "512mb"',
44
+ ' cpu_kind = "shared"',
45
+ ' cpus = 1',
46
+ '',
47
+ '[env]',
48
+ ' BIZAR_DASHBOARD_PORT = "4097"',
49
+ ' BIZAR_DASHBOARD_HOST = "0.0.0.0"',
50
+ ' NODE_ENV = "production"',
51
+ '',
52
+ '[deploy]',
53
+ ' strategy = "rolling"',
54
+ '',
55
+ ].join('\n');
56
+ writeFileSync(join(scaffoldRoot, 'fly.toml'), flyToml);
57
+
58
+ console.log(chalk.dim(' \u2713 Scaffold generated at', scaffoldRoot));
59
+ return scaffoldRoot;
60
+ }
61
+
62
+ // ── Deployment ───────────────────────────────────────────────────────────────
63
+
64
+ export async function deployToFly({ token, appName, region, projectRoot, outDir }) {
65
+ // Check that flyctl is installed
66
+ const flyctlPath = checkFlyctl();
67
+ if (!flyctlPath) {
68
+ throw new Error(
69
+ 'flyctl not found. Install it first:\n' +
70
+ ' curl -L https://fly.io/install.sh | sh\n' +
71
+ ' Or download from https://fly.io/docs/getting-started/installing-flyctl/',
72
+ );
73
+ }
74
+
75
+ // Check Dockerfile exists (v4.9 work)
76
+ const dockerfilePath = join(projectRoot, 'Dockerfile');
77
+ if (!existsSync(dockerfilePath)) {
78
+ console.log(chalk.yellow(' \u26a0 No Dockerfile found at project root. Fly.io requires one.'));
79
+ }
80
+
81
+ // 1. Generate scaffold
82
+ const scaffoldRoot = generateScaffold(outDir, { appName, region });
83
+ const name = appName || 'bizar-dash';
84
+
85
+ // 2. Check if app exists; if not, launch it
86
+ try {
87
+ execSync(flyctlPath + ' apps list', {
88
+ encoding: 'utf8',
89
+ stdio: 'pipe',
90
+ timeout: 15000,
91
+ });
92
+ } catch {
93
+ console.log(chalk.dim(' App not found. Launching new app\u2026'));
94
+ try {
95
+ execSync(flyctlPath + ' launch --name "' + name + '" --region "' + (region || 'iad') + '" --no-deploy --now', {
96
+ cwd: projectRoot,
97
+ encoding: 'utf8',
98
+ stdio: 'pipe',
99
+ timeout: 60000,
100
+ });
101
+ } catch (err) {
102
+ throw new Error('flyctl launch failed: ' + err.message);
103
+ }
104
+ }
105
+
106
+ // 3. Check auth
107
+ if (!token) {
108
+ // flyctl uses its own auth — the --token flag may not be needed for flyctl
109
+ console.log(chalk.dim(' No FLY_API_TOKEN set. flyctl will use its own auth.'));
110
+ }
111
+
112
+ // 4. Deploy
113
+ console.log(chalk.dim(' Deploying to Fly.io\u2026'));
114
+ const deployCmd = flyctlPath + ' deploy' +
115
+ ' --app "' + name + '"' +
116
+ ' --strategy rolling' +
117
+ (token ? ' --access-token "' + token + '"' : '') +
118
+ ' --remote-only';
119
+
120
+ try {
121
+ execSync(deployCmd, {
122
+ cwd: projectRoot,
123
+ encoding: 'utf8',
124
+ stdio: 'pipe',
125
+ timeout: 300000, // 5 minutes for build + deploy
126
+ });
127
+ } catch (err) {
128
+ throw new Error('flyctl deploy failed: ' + err.message);
129
+ }
130
+
131
+ // 5. Get the app URL
132
+ const appUrl = 'https://' + name + '.fly.dev';
133
+ return {
134
+ url: appUrl,
135
+ deploymentId: name,
136
+ notes: [
137
+ 'Region: ' + (region || 'iad'),
138
+ 'Run `flyctl status --app ' + name + '` for details.',
139
+ ],
140
+ };
141
+ }
142
+
143
+ // ── Helpers ──────────────────────────────────────────────────────────────────
144
+
145
+ function checkFlyctl() {
146
+ // Check common locations
147
+ const candidates = ['flyctl', 'fly'];
148
+ for (const cmd of candidates) {
149
+ try {
150
+ const result = execSync('which ' + cmd, {
151
+ encoding: 'utf8',
152
+ stdio: 'pipe',
153
+ timeout: 3000,
154
+ });
155
+ if (result.trim()) return cmd;
156
+ } catch {
157
+ // try next
158
+ }
159
+ }
160
+ return null;
161
+ }
@@ -0,0 +1,225 @@
1
+ /**
2
+ * cli/commands/deploy/vercel.mjs
3
+ *
4
+ * Vercel deployment via REST API.
5
+ *
6
+ * Generates vercel.json + api/index.js (serverless function entry) +
7
+ * public/ (static dashboard bundle), then POSTs to Vercel Deployments API.
8
+ */
9
+ import chalk from 'chalk';
10
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, statSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { execSync } from 'node:child_process';
13
+
14
+ const API_BASE = 'https://api.vercel.com';
15
+
16
+ // ── Scaffold generation ──────────────────────────────────────────────────────
17
+
18
+ function generateScaffold(outDir, opts) {
19
+ const scaffoldRoot = join(outDir, 'vercel');
20
+
21
+ if (!existsSync(scaffoldRoot)) {
22
+ mkdirSync(scaffoldRoot, { recursive: true });
23
+ }
24
+
25
+ // vercel.json
26
+ const vercelJson = {
27
+ name: opts.projectName || 'bizar-dash',
28
+ version: 2,
29
+ buildCommand: 'npm run build',
30
+ outputDirectory: 'public',
31
+ routes: [
32
+ { src: '/api/(.*)', dest: '/api/index.js' },
33
+ { src: '/(.*)', dest: '/public/$1' },
34
+ ],
35
+ functions: {
36
+ 'api/index.js': {
37
+ memory: 256,
38
+ maxDuration: 30,
39
+ },
40
+ },
41
+ };
42
+ writeFileSync(join(scaffoldRoot, 'vercel.json'), JSON.stringify(vercelJson, null, 2));
43
+
44
+ // api/index.js — serverless entry that serves the dashboard
45
+ const apiDir = join(scaffoldRoot, 'api');
46
+ if (!existsSync(apiDir)) mkdirSync(apiDir, { recursive: true });
47
+
48
+ const apiIndexContent = [
49
+ '// Vercel serverless entry — Bizar dashboard',
50
+ '// Generated by `bizar deploy --to vercel`',
51
+ "import express from 'express';",
52
+ "import { join, dirname } from 'node:path';",
53
+ "import { fileURLToPath } from 'node:url';",
54
+ '',
55
+ 'const __dirname = dirname(fileURLToPath(import.meta.url));',
56
+ 'const app = express();',
57
+ '',
58
+ '// Serve static files from the dashboard build',
59
+ "app.use(express.static(join(__dirname, '..', 'public')));",
60
+ '',
61
+ '// Fallback to index.html for SPA routing',
62
+ "app.get('*', (_req, res) => {",
63
+ " res.sendFile(join(__dirname, '..', 'public', 'index.html'));",
64
+ '});',
65
+ '',
66
+ 'export default app;',
67
+ '',
68
+ ].join('\n');
69
+ writeFileSync(join(apiDir, 'index.js'), apiIndexContent);
70
+
71
+ // Build the dashboard bundle into public/
72
+ const publicDir = join(scaffoldRoot, 'public');
73
+ if (!existsSync(publicDir)) mkdirSync(publicDir, { recursive: true });
74
+
75
+ console.log(chalk.dim(' Building dashboard bundle\u2026'));
76
+ try {
77
+ execSync('npm run build', {
78
+ cwd: opts.projectRoot,
79
+ encoding: 'utf8',
80
+ stdio: 'pipe',
81
+ timeout: 120000,
82
+ });
83
+ } catch (err) {
84
+ throw new Error('Dashboard build failed: ' + err.message);
85
+ }
86
+
87
+ // Copy built assets into public/
88
+ const distDir = join(opts.projectRoot, 'dist');
89
+ if (existsSync(distDir)) {
90
+ execSync('cp -r "' + distDir + '"/* "' + publicDir + '/"', { stdio: 'pipe' });
91
+ } else {
92
+ console.log(chalk.yellow(' \u26a0 No dist/ found \u2014 deploying scaffold only'));
93
+ }
94
+
95
+ console.log(chalk.dim(' \u2713 Scaffold generated at', scaffoldRoot));
96
+ return scaffoldRoot;
97
+ }
98
+
99
+ // ── API helpers ──────────────────────────────────────────────────────────────
100
+
101
+ async function vercelPost(path, token, body) {
102
+ const url = API_BASE + path;
103
+ const res = await fetch(url, {
104
+ method: 'POST',
105
+ headers: {
106
+ Authorization: 'Bearer ' + token,
107
+ 'Content-Type': 'application/json',
108
+ },
109
+ body: JSON.stringify(body),
110
+ });
111
+ const text = await res.text();
112
+ let json = null;
113
+ try { json = text ? JSON.parse(text) : null; } catch { /* keep raw */ }
114
+ if (!res.ok) {
115
+ const msg = json?.error?.message || json?.error?.code || text || 'HTTP ' + res.status;
116
+ throw new Error('Vercel API error: ' + msg);
117
+ }
118
+ return json;
119
+ }
120
+
121
+ async function vercelGet(path, token) {
122
+ const url = API_BASE + path;
123
+ const res = await fetch(url, {
124
+ headers: { Authorization: 'Bearer ' + token },
125
+ });
126
+ const text = await res.text();
127
+ let json = null;
128
+ try { json = text ? JSON.parse(text) : null; } catch { /* keep raw */ }
129
+ if (!res.ok) {
130
+ const msg = json?.error?.message || json?.error?.code || text || 'HTTP ' + res.status;
131
+ throw new Error('Vercel API error: ' + msg);
132
+ }
133
+ return json;
134
+ }
135
+
136
+ // ── Deployment ───────────────────────────────────────────────────────────────
137
+
138
+ export async function deployToVercel({ token, projectName, projectRoot, outDir }) {
139
+ if (!token) {
140
+ throw new Error('Vercel token required. Pass --token or set VERCEL_TOKEN env var.');
141
+ }
142
+
143
+ // 1. Generate scaffold
144
+ const scaffoldRoot = generateScaffold(outDir, { projectName, projectRoot });
145
+ const name = projectName || 'bizar-dash';
146
+
147
+ // 2. Upload to Vercel
148
+ console.log(chalk.dim(' Uploading to Vercel\u2026'));
149
+
150
+ const body = {
151
+ name,
152
+ deploymentType: 'instant',
153
+ files: collectFiles(scaffoldRoot),
154
+ projectSettings: {
155
+ framework: 'other',
156
+ installCommand: 'npm install',
157
+ buildCommand: 'npm run build',
158
+ outputDirectory: 'public',
159
+ },
160
+ };
161
+
162
+ const result = await vercelPost('/v13/deployments', token, body);
163
+
164
+ // 3. Poll for ready state
165
+ const deploymentId = result.id || result.uid;
166
+ if (!deploymentId) {
167
+ throw new Error('No deployment ID returned from Vercel');
168
+ }
169
+
170
+ console.log(chalk.dim(' Deployment ID:', deploymentId));
171
+
172
+ // Poll up to 60 seconds
173
+ for (let i = 0; i < 30; i++) {
174
+ const status = await vercelGet('/v13/deployments/' + deploymentId, token);
175
+ const state = status.readyState || status.state;
176
+ if (state === 'READY') {
177
+ const url = status.url || 'https://' + deploymentId + '.vercel.app';
178
+ return {
179
+ url: 'https://' + url,
180
+ deploymentId,
181
+ notes: ['Open the URL in your browser to view your dashboard'],
182
+ };
183
+ }
184
+ if (state === 'ERROR' || state === 'CANCELED') {
185
+ throw new Error('Deployment failed with state: ' + state);
186
+ }
187
+ // Wait 2 seconds before polling again
188
+ await new Promise((r) => setTimeout(r, 2000));
189
+ }
190
+
191
+ // Timeout — return whatever we have
192
+ return {
193
+ url: 'https://' + deploymentId + '.vercel.app',
194
+ deploymentId,
195
+ notes: ['Deployment is still building. Check the Vercel dashboard for status.'],
196
+ };
197
+ }
198
+
199
+ // ── Helpers ──────────────────────────────────────────────────────────────────
200
+
201
+ /**
202
+ * Collect files from a directory for the Vercel API.
203
+ * Returns an array of { file, data } objects (base64-encoded).
204
+ */
205
+ function collectFiles(rootDir) {
206
+ const files = [];
207
+
208
+ function walk(dir, prefix) {
209
+ const entries = readdirSync(dir);
210
+ for (const entry of entries) {
211
+ const full = join(dir, entry);
212
+ const rel = prefix ? prefix + '/' + entry : entry;
213
+ const stat = statSync(full);
214
+ if (stat.isDirectory()) {
215
+ walk(full, rel);
216
+ } else {
217
+ const content = readFileSync(full, 'base64');
218
+ files.push({ file: rel, data: content, encoding: 'base64' });
219
+ }
220
+ }
221
+ }
222
+
223
+ walk(rootDir, '');
224
+ return files;
225
+ }
@@ -0,0 +1,240 @@
1
+ /**
2
+ * cli/commands/deploy.mjs
3
+ *
4
+ * v5.0 — One-click deploy to Vercel, Cloudflare, Fly.io, or Docker registry.
5
+ *
6
+ * Usage:
7
+ * bizar deploy --to vercel [--token <token>] [--project-name <name>]
8
+ * bizar deploy --to cloudflare [--token <token>] [--project-name <name>]
9
+ * bizar deploy --to fly [--token <token>] [--app-name <name>] [--region <region>]
10
+ * bizar deploy --to docker [--registry <ghcr.io|docker.io|...>] [--image-name <name>]
11
+ * bizar deploy --to docker --compose
12
+ */
13
+ import chalk from 'chalk';
14
+ import { existsSync } from 'node:fs';
15
+ import { join } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+
18
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
19
+ const PROJECT_ROOT = join(__dirname, '..', '..');
20
+
21
+ // ── Help text ──────────────────────────────────────────────────────────────────
22
+
23
+ export function showDeployHelp() {
24
+ console.log(`
25
+ bizar deploy — One-click deploy to your hosting platform of choice
26
+
27
+ Usage:
28
+ bizar deploy --to <platform> [options]
29
+
30
+ Platforms:
31
+ vercel Deploy to Vercel (serverless)
32
+ cloudflare Deploy to Cloudflare Pages + Workers
33
+ fly Deploy to Fly.io
34
+ docker Build & push to a Docker registry (ghcr.io, docker.io, …)
35
+
36
+ Options:
37
+ --to <platform> Required. Target platform.
38
+ --token <token> API token for the platform.
39
+ --project-name <name> Project / site name (Vercel, Cloudflare).
40
+ --app-name <name> Fly.io app name.
41
+ --region <region> Fly.io region (default: iad).
42
+ --registry <url> Docker registry URL (default: ghcr.io).
43
+ --image-name <name> Docker image name.
44
+ --compose Generate docker-compose.yml + .env template.
45
+ --out-dir <path> Output directory for scaffold files
46
+ (default: ./bizar-deploy/).
47
+ --help Show this help.
48
+
49
+ Environment variables (fallback when --token is omitted):
50
+ VERCEL_TOKEN Vercel API token
51
+ CLOUDFLARE_API_TOKEN Cloudflare API token
52
+ FLY_API_TOKEN Fly.io API token
53
+
54
+ Examples:
55
+ bizar deploy --to vercel --token \$(cat ~/.vercel/token)
56
+ bizar deploy --to cloudflare --project-name my-dash
57
+ bizar deploy --to fly --app-name bizar-dash --region lhr
58
+ bizar deploy --to docker --registry ghcr.io/my-org --image-name bizar-dash
59
+ bizar deploy --to docker --compose
60
+ `);
61
+ }
62
+
63
+ // ── Option parsing ────────────────────────────────────────────────────────────
64
+
65
+ function parseDeployOpts(args) {
66
+ const opts = {
67
+ to: null,
68
+ token: null,
69
+ projectName: null,
70
+ appName: null,
71
+ region: null,
72
+ registry: null,
73
+ imageName: null,
74
+ compose: false,
75
+ outDir: null,
76
+ help: false,
77
+ };
78
+
79
+ for (let i = 0; i < args.length; i++) {
80
+ const a = args[i];
81
+ switch (a) {
82
+ case '--to':
83
+ opts.to = args[++i];
84
+ break;
85
+ case '--token':
86
+ opts.token = args[++i];
87
+ break;
88
+ case '--project-name':
89
+ opts.projectName = args[++i];
90
+ break;
91
+ case '--app-name':
92
+ opts.appName = args[++i];
93
+ break;
94
+ case '--region':
95
+ opts.region = args[++i];
96
+ break;
97
+ case '--registry':
98
+ opts.registry = args[++i];
99
+ break;
100
+ case '--image-name':
101
+ opts.imageName = args[++i];
102
+ break;
103
+ case '--out-dir':
104
+ opts.outDir = args[++i];
105
+ break;
106
+ case '--compose':
107
+ opts.compose = true;
108
+ break;
109
+ case '--help':
110
+ case '-h':
111
+ opts.help = true;
112
+ break;
113
+ }
114
+ }
115
+
116
+ return opts;
117
+ }
118
+
119
+ // ── Prerequisite checks ──────────────────────────────────────────────────────
120
+
121
+ /**
122
+ * Check that required CLI tools are on PATH.
123
+ * Returns array of missing tool names.
124
+ */
125
+ function checkPrerequisites(required) {
126
+ const { execFileSync } = require_node('child_process');
127
+ const missing = [];
128
+ for (const tool of required) {
129
+ try {
130
+ execFileSync('which', [tool], { encoding: 'utf8', timeout: 3000, stdio: 'pipe' });
131
+ } catch {
132
+ missing.push(tool);
133
+ }
134
+ }
135
+ return missing;
136
+ }
137
+
138
+ function require_node(mod) {
139
+ // eslint-disable-next-line no-new-func
140
+ return Function('return require("' + mod + '")')();
141
+ }
142
+
143
+ // ── Main dispatch ─────────────────────────────────────────────────────────────
144
+
145
+ export async function runDeploy(deployArgs) {
146
+ const opts = parseDeployOpts(deployArgs);
147
+
148
+ if (opts.help || !opts.to) {
149
+ showDeployHelp();
150
+ if (!opts.to) {
151
+ console.error(chalk.red(' ✗ --to <platform> is required.'));
152
+ process.exit(1);
153
+ }
154
+ return;
155
+ }
156
+
157
+ const platform = opts.to.toLowerCase();
158
+ const outDir = opts.outDir || join(process.cwd(), 'bizar-deploy');
159
+
160
+ // Resolve tokens from env if not provided
161
+ if (!opts.token) {
162
+ const envMap = {
163
+ vercel: 'VERCEL_TOKEN',
164
+ cloudflare: 'CLOUDFLARE_API_TOKEN',
165
+ fly: 'FLY_API_TOKEN',
166
+ };
167
+ const envVar = envMap[platform];
168
+ if (envVar && process.env[envVar]) {
169
+ opts.token = process.env[envVar];
170
+ }
171
+ }
172
+
173
+ const context = {
174
+ ...opts,
175
+ projectRoot: PROJECT_ROOT,
176
+ outDir,
177
+ };
178
+
179
+ try {
180
+ switch (platform) {
181
+ case 'vercel': {
182
+ const { deployToVercel } = await import('./deploy/vercel.mjs');
183
+ const result = await deployToVercel(context);
184
+ printResult('Vercel', result);
185
+ break;
186
+ }
187
+ case 'cloudflare': {
188
+ const { deployToCloudflare } = await import('./deploy/cloudflare.mjs');
189
+ const result = await deployToCloudflare(context);
190
+ printResult('Cloudflare', result);
191
+ break;
192
+ }
193
+ case 'fly': {
194
+ const { deployToFly } = await import('./deploy/fly.mjs');
195
+ const result = await deployToFly(context);
196
+ printResult('Fly.io', result);
197
+ break;
198
+ }
199
+ case 'docker': {
200
+ const { deployToDocker } = await import('./deploy/docker.mjs');
201
+ const result = await deployToDocker(context);
202
+ printResult('Docker', result);
203
+ break;
204
+ }
205
+ default:
206
+ console.error(chalk.red(` ✗ Unknown platform: ${platform}`));
207
+ showDeployHelp();
208
+ process.exit(1);
209
+ }
210
+ } catch (err) {
211
+ console.error(chalk.red(` ✗ Deploy failed: ${err.message}`));
212
+ if (err.stderr) {
213
+ console.error(chalk.dim(err.stderr));
214
+ }
215
+ process.exit(1);
216
+ }
217
+ }
218
+
219
+ function printResult(platformName, result) {
220
+ console.log(chalk.green(` ✓ Deployed to ${platformName}`));
221
+ if (result.url) {
222
+ console.log(` URL: ${chalk.cyan(result.url)}`);
223
+ }
224
+ if (result.deploymentId) {
225
+ console.log(` ID: ${chalk.dim(result.deploymentId)}`);
226
+ }
227
+ if (result.notes && result.notes.length > 0) {
228
+ for (const note of result.notes) {
229
+ console.log(chalk.dim(` ${note}`));
230
+ }
231
+ }
232
+ }
233
+
234
+ export async function run(name, args, isHelpRequest) {
235
+ if (args.length === 0 || isHelpRequest) {
236
+ showDeployHelp();
237
+ return;
238
+ }
239
+ await runDeploy(args);
240
+ }