@polderlabs/bizar 4.4.12 → 4.4.13

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.
@@ -9,11 +9,15 @@
9
9
  * POST /api/minimax/remains/refresh — force re-fetch from the API
10
10
  * POST /api/minimax/test — smoke-test the key with a one-shot chat call
11
11
  * GET /api/minimax/cache — read the on-disk cache file (for debugging)
12
+ * DELETE /api/minimax/cache — clear cache
13
+ * POST /api/minimax/onboarding — onboarding wizard state (dismissed, hiddenModels)
14
+ * POST /api/minimax/onboarding/save-key — write the user's key to opencode's
15
+ * auth.json (the canonical place)
12
16
  *
13
- * All endpoints read the API key from settings.json (no key is ever
14
- * accepted as a URL param or body field — the user puts it in the
15
- * settings UI). Responses never echo the key back; they include a
16
- * masked `apiKeyHint` (last 4 chars only).
17
+ * The MiniMax key is read from opencode's auth store, NOT from
18
+ * settings.json. The dashboard's onboarding wizard writes the key
19
+ * directly to ~/.local/share/opencode/auth.json so opencode itself
20
+ * can pick it up that way the user only enters the key once.
17
21
  */
18
22
  import { Router } from 'express';
19
23
  import {
@@ -21,7 +25,10 @@ import {
21
25
  chatCompletion,
22
26
  readCachedRemains,
23
27
  clearRemainsCache,
24
- readMinimaxSettings,
28
+ getStatus,
29
+ writeAuthFile,
30
+ writeOnboarding,
31
+ readOnboarding,
25
32
  maskKey,
26
33
  KNOWN_MODELS,
27
34
  } from '../minimax.mjs';
@@ -38,39 +45,46 @@ export function createMinimaxRouter({ state, broadcast }) {
38
45
 
39
46
  // GET / — health + config summary
40
47
  router.get('/minimax/status', wrap(async (_req, res) => {
41
- const settings = readMinimaxSettings();
42
- const cached = readCachedRemains();
43
- res.json({
44
- enabled: settings.enabled,
45
- configured: !!settings.apiKey,
46
- apiKeyHint: maskKey(settings.apiKey),
47
- groupId: settings.groupId,
48
- baseUrl: settings.baseUrl,
49
- chatBaseUrl: settings.chatBaseUrl,
50
- knownModels: KNOWN_MODELS,
51
- cache: cached
52
- ? {
53
- fetchedAt: cached.fetchedAt,
54
- apiKeyHint: cached.apiKeyHint,
55
- groupId: cached.groupId,
56
- modelCount: (cached.models || []).length,
57
- }
58
- : null,
59
- });
48
+ res.json(getStatus());
60
49
  }));
61
50
 
62
- // GET /remainscurrent snapshot (cache-or-fetch)
63
- router.get('/minimax/remains', wrap(async (_req, res) => {
64
- const settings = readMinimaxSettings();
65
- if (!settings.enabled) {
66
- res.json({ ok: false, error: 'disabled', message: 'MiniMax integration is disabled in settings' });
51
+ // GET /onboardingwizard state (dismissed?, hidden models)
52
+ router.get('/minimax/onboarding', wrap(async (_req, res) => {
53
+ res.json(readOnboarding());
54
+ }));
55
+
56
+ // POST /onboarding — update wizard state
57
+ router.post('/minimax/onboarding', wrap(async (req, res) => {
58
+ const patch = req.body || {};
59
+ const next = writeOnboarding(patch);
60
+ res.json(next);
61
+ }));
62
+
63
+ // POST /onboarding/save-key — write the key to opencode's auth.json
64
+ router.post('/minimax/onboarding/save-key', wrap(async (req, res) => {
65
+ const { key, groupId } = req.body || {};
66
+ if (typeof key !== 'string' || !key.trim()) {
67
+ res.status(400).json({ ok: false, error: 'no_key', message: 'Key is required' });
67
68
  return;
68
69
  }
69
- const result = await fetchRemains({
70
- apiKey: settings.apiKey,
71
- groupId: settings.groupId,
72
- baseUrl: settings.baseUrl,
73
- });
70
+ try {
71
+ const path = writeAuthFile(key.trim(), groupId || 'default');
72
+ // Clear the remains cache so the next load picks up the new key.
73
+ clearRemainsCache();
74
+ // Mark the onboarding as dismissed (user has the key now).
75
+ writeOnboarding({ dismissedAt: Date.now() });
76
+ if (broadcast) {
77
+ broadcast({ type: 'minimax:configured', source: 'auth.json' });
78
+ }
79
+ res.json({ ok: true, path, apiKeyHint: maskKey(key) });
80
+ } catch (err) {
81
+ res.status(500).json({ ok: false, error: 'write_failed', message: err.message });
82
+ }
83
+ }));
84
+
85
+ // GET /remains — current snapshot (cache-or-fetch)
86
+ router.get('/minimax/remains', wrap(async (_req, res) => {
87
+ const result = await fetchRemains();
74
88
  if (broadcast && result.ok) {
75
89
  broadcast({ type: 'minimax:remains', remains: result });
76
90
  }
@@ -79,17 +93,7 @@ export function createMinimaxRouter({ state, broadcast }) {
79
93
 
80
94
  // POST /remains/refresh — force re-fetch (bypass cache TTL)
81
95
  router.post('/minimax/remains/refresh', wrap(async (_req, res) => {
82
- const settings = readMinimaxSettings();
83
- if (!settings.enabled) {
84
- res.json({ ok: false, error: 'disabled', message: 'MiniMax integration is disabled in settings' });
85
- return;
86
- }
87
- const result = await fetchRemains({
88
- apiKey: settings.apiKey,
89
- groupId: settings.groupId,
90
- baseUrl: settings.baseUrl,
91
- force: true,
92
- });
96
+ const result = await fetchRemains({ force: true });
93
97
  if (broadcast && result.ok) {
94
98
  broadcast({ type: 'minimax:remains:refreshed', remains: result });
95
99
  }
@@ -98,15 +102,6 @@ export function createMinimaxRouter({ state, broadcast }) {
98
102
 
99
103
  // POST /test — smoke-test the key with a chat completion
100
104
  router.post('/minimax/test', wrap(async (req, res) => {
101
- const settings = readMinimaxSettings();
102
- if (!settings.enabled) {
103
- res.json({ ok: false, error: 'disabled', message: 'MiniMax integration is disabled in settings' });
104
- return;
105
- }
106
- if (!settings.apiKey) {
107
- res.json({ ok: false, error: 'no_api_key', message: 'MiniMax API key is not configured' });
108
- return;
109
- }
110
105
  const body = req.body || {};
111
106
  const prompt = typeof body.prompt === 'string' && body.prompt.trim()
112
107
  ? body.prompt
@@ -115,10 +110,8 @@ export function createMinimaxRouter({ state, broadcast }) {
115
110
  ? body.model.trim()
116
111
  : 'MiniMax-M3';
117
112
  const result = await chatCompletion({
118
- apiKey: settings.apiKey,
119
113
  prompt,
120
114
  model,
121
- baseUrl: settings.chatBaseUrl,
122
115
  maxTokens: typeof body.maxTokens === 'number' ? body.maxTokens : 32,
123
116
  });
124
117
  res.json(result);
@@ -129,7 +122,7 @@ export function createMinimaxRouter({ state, broadcast }) {
129
122
  res.json(readCachedRemains() || null);
130
123
  }));
131
124
 
132
- // DELETE /cache — clear cache (admin endpoint)
125
+ // DELETE /cache — clear cache
133
126
  router.delete('/minimax/cache', wrap(async (_req, res) => {
134
127
  clearRemainsCache();
135
128
  if (broadcast) broadcast({ type: 'minimax:cache:cleared' });
@@ -213,6 +213,142 @@
213
213
  font-family: var(--font-mono, ui-monospace, monospace);
214
214
  }
215
215
 
216
+ /* ── Onboarding wizard ─────────────────────────────────────────── */
217
+ .view-minimax-onboarding {
218
+ display: flex;
219
+ flex-direction: column;
220
+ gap: 16px;
221
+ padding: 16px 20px 32px;
222
+ max-width: 800px;
223
+ margin: 0 auto;
224
+ width: 100%;
225
+ }
226
+ .view-minimax-onboarding .minimax-wizard-stepper {
227
+ display: flex;
228
+ align-items: center;
229
+ justify-content: space-between;
230
+ gap: 6px;
231
+ padding: 12px 16px;
232
+ background: var(--bg-1);
233
+ border: 1px solid var(--border);
234
+ border-radius: 8px;
235
+ }
236
+ .view-minimax-onboarding .minimax-wizard-step {
237
+ display: flex;
238
+ align-items: center;
239
+ gap: 8px;
240
+ flex: 1;
241
+ font-size: 12px;
242
+ color: var(--text-muted);
243
+ }
244
+ .view-minimax-onboarding .minimax-wizard-step-bullet {
245
+ width: 24px;
246
+ height: 24px;
247
+ display: inline-flex;
248
+ align-items: center;
249
+ justify-content: center;
250
+ border-radius: 50%;
251
+ background: var(--bg-2);
252
+ border: 1px solid var(--border);
253
+ font-family: var(--font-mono, ui-monospace, monospace);
254
+ font-size: 11px;
255
+ color: var(--text);
256
+ flex-shrink: 0;
257
+ }
258
+ .view-minimax-onboarding .minimax-wizard-step.is-current .minimax-wizard-step-bullet {
259
+ background: var(--accent);
260
+ border-color: var(--accent);
261
+ color: #fff;
262
+ }
263
+ .view-minimax-onboarding .minimax-wizard-step.is-done .minimax-wizard-step-bullet {
264
+ background: var(--success, #3fb950);
265
+ border-color: var(--success, #3fb950);
266
+ color: #fff;
267
+ }
268
+ .view-minimax-onboarding .minimax-wizard-step.is-done { color: var(--success, #3fb950); }
269
+ .view-minimax-onboarding .minimax-wizard-step.is-current { color: var(--text); font-weight: 500; }
270
+ .view-minimax-onboarding .minimax-wizard-step-label { font-size: 12px; }
271
+ .view-minimax-onboarding .minimax-wizard-step.is-current .minimax-wizard-step-label {
272
+ font-weight: 500;
273
+ }
274
+ .view-minimax-onboarding .minimax-wizard-body {
275
+ padding: 4px 4px 0;
276
+ display: flex;
277
+ flex-direction: column;
278
+ gap: 12px;
279
+ }
280
+ .view-minimax-onboarding .minimax-wizard-title {
281
+ font-size: 16px;
282
+ font-weight: 600;
283
+ margin: 0;
284
+ color: var(--text);
285
+ display: flex;
286
+ align-items: center;
287
+ }
288
+ .view-minimax-onboarding .minimax-wizard-prose {
289
+ margin: 0;
290
+ color: var(--text-muted);
291
+ font-size: 13px;
292
+ line-height: 1.55;
293
+ }
294
+ .view-minimax-onboarding .minimax-wizard-prose code {
295
+ font-family: var(--font-mono, ui-monospace, monospace);
296
+ font-size: 12px;
297
+ padding: 1px 5px;
298
+ border-radius: 3px;
299
+ background: var(--bg-2);
300
+ color: var(--text);
301
+ }
302
+ .view-minimax-onboarding .minimax-wizard-prose--muted { font-size: 12px; opacity: 0.85; }
303
+ .view-minimax-onboarding .minimax-wizard-list {
304
+ margin: 0;
305
+ padding-left: 22px;
306
+ font-size: 13px;
307
+ color: var(--text-muted);
308
+ line-height: 1.6;
309
+ }
310
+ .view-minimax-onboarding .minimax-wizard-list li { margin-bottom: 4px; }
311
+ .view-minimax-onboarding .minimax-wizard-link {
312
+ color: var(--accent);
313
+ text-decoration: underline;
314
+ display: inline-flex;
315
+ align-items: center;
316
+ gap: 3px;
317
+ }
318
+ .view-minimax-onboarding .minimax-wizard-actions {
319
+ display: flex;
320
+ align-items: center;
321
+ gap: 8px;
322
+ margin-top: 8px;
323
+ flex-wrap: wrap;
324
+ }
325
+ .view-minimax-onboarding .minimax-wizard-error {
326
+ display: flex;
327
+ gap: 8px;
328
+ padding: 8px 10px;
329
+ border-radius: 6px;
330
+ background: rgba(248, 81, 73, 0.08);
331
+ border: 1px solid rgba(248, 81, 73, 0.30);
332
+ color: var(--text);
333
+ font-size: 12px;
334
+ }
335
+ .view-minimax-onboarding .minimax-wizard-footer {
336
+ display: flex;
337
+ align-items: center;
338
+ gap: 6px;
339
+ font-size: 11px;
340
+ color: var(--text-muted);
341
+ padding: 0 4px;
342
+ margin-top: 8px;
343
+ }
344
+ .view-minimax-onboarding .minimax-wizard-footer code {
345
+ font-family: var(--font-mono, ui-monospace, monospace);
346
+ font-size: 10px;
347
+ padding: 0 3px;
348
+ background: rgba(0,0,0,0.25);
349
+ border-radius: 2px;
350
+ }
351
+
216
352
  /* ── Subscription Key card ──────────────────────────────────────── */
217
353
  .view-minimax-usage .minimax-link {
218
354
  color: var(--accent);