brainctl 0.1.6 → 0.1.9
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/README.md +215 -136
- package/dist/cli.js +40 -0
- package/dist/commands/mcp.js +35 -0
- package/dist/commands/profile.js +35 -2
- package/dist/executor/resolver.js +1 -38
- package/dist/mcp/server.js +82 -2
- package/dist/services/agent-config-service.d.ts +20 -3
- package/dist/services/agent-config-service.js +84 -16
- package/dist/services/agent-converter-service.d.ts +21 -0
- package/dist/services/agent-converter-service.js +182 -0
- package/dist/services/credential-redaction-service.d.ts +13 -0
- package/dist/services/credential-redaction-service.js +89 -0
- package/dist/services/credential-resolution-service.d.ts +11 -0
- package/dist/services/credential-resolution-service.js +69 -0
- package/dist/services/mcp-preflight-service.d.ts +26 -0
- package/dist/services/mcp-preflight-service.js +238 -0
- package/dist/services/plugin-install-service.d.ts +135 -0
- package/dist/services/plugin-install-service.js +601 -0
- package/dist/services/portable-mcp-classifier.d.ts +12 -0
- package/dist/services/portable-mcp-classifier.js +116 -0
- package/dist/services/portable-profile-pack-service.d.ts +26 -0
- package/dist/services/portable-profile-pack-service.js +264 -0
- package/dist/services/profile-export-service.d.ts +15 -3
- package/dist/services/profile-export-service.js +10 -57
- package/dist/services/profile-import-service.d.ts +9 -1
- package/dist/services/profile-import-service.js +266 -11
- package/dist/services/profile-service.d.ts +1 -0
- package/dist/services/profile-service.js +128 -32
- package/dist/services/runtime-detector.d.ts +9 -0
- package/dist/services/runtime-detector.js +130 -0
- package/dist/services/skill-paths.d.ts +4 -0
- package/dist/services/skill-paths.js +26 -0
- package/dist/services/skill-preflight-service.d.ts +23 -0
- package/dist/services/skill-preflight-service.js +40 -0
- package/dist/services/sync/agent-reader.d.ts +14 -0
- package/dist/services/sync/agent-reader.js +198 -45
- package/dist/services/sync/claude-writer.js +4 -7
- package/dist/services/sync/codex-writer.d.ts +1 -0
- package/dist/services/sync/codex-writer.js +25 -8
- package/dist/services/sync/gemini-writer.js +9 -8
- package/dist/services/sync/managed-plugin-registry.d.ts +17 -0
- package/dist/services/sync/managed-plugin-registry.js +75 -0
- package/dist/services/sync/plugin-skill-reader.d.ts +7 -0
- package/dist/services/sync/plugin-skill-reader.js +174 -0
- package/dist/services/sync-service.js +6 -1
- package/dist/services/update-check-service.d.ts +33 -0
- package/dist/services/update-check-service.js +128 -0
- package/dist/system/executables.d.ts +1 -0
- package/dist/system/executables.js +38 -0
- package/dist/types.d.ts +62 -5
- package/dist/ui/routes.js +293 -8
- package/dist/web/assets/index-Cdb5hbxM.css +1 -0
- package/dist/web/assets/index-gN83hZYA.js +65 -0
- package/dist/web/favicon-light.svg +13 -0
- package/dist/web/favicon.svg +13 -0
- package/dist/web/index.html +7 -2
- package/package.json +9 -1
- package/dist/web/assets/index-364NYWPA.css +0 -1
- package/dist/web/assets/index-BmfE7rus.js +0 -16
package/dist/ui/routes.js
CHANGED
|
@@ -2,12 +2,17 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { loadConfig } from '../config.js';
|
|
4
4
|
import { parseConfigPayload } from '../config.js';
|
|
5
|
-
import { ProfileError, ProfileNotFoundError } from '../errors.js';
|
|
5
|
+
import { BrainctlError, ProfileError, ProfileNotFoundError, ValidationError } from '../errors.js';
|
|
6
6
|
import { loadMemory } from '../context/memory.js';
|
|
7
7
|
import { createAgentConfigService } from '../services/agent-config-service.js';
|
|
8
8
|
import { createConfigWriteService } from '../services/config-write-service.js';
|
|
9
|
+
import { createMcpPreflightService } from '../services/mcp-preflight-service.js';
|
|
10
|
+
import { createPluginInstallService } from '../services/plugin-install-service.js';
|
|
11
|
+
import { createProfileExportService } from '../services/profile-export-service.js';
|
|
12
|
+
import { createProfileImportService } from '../services/profile-import-service.js';
|
|
9
13
|
import { createProfileService } from '../services/profile-service.js';
|
|
10
14
|
import { createRunService } from '../services/run-service.js';
|
|
15
|
+
import { createSkillPreflightService } from '../services/skill-preflight-service.js';
|
|
11
16
|
import { createStatusService } from '../services/status-service.js';
|
|
12
17
|
import { createSyncService } from '../services/sync-service.js';
|
|
13
18
|
import { startSseStream, writeSseEvent } from './streaming.js';
|
|
@@ -19,8 +24,13 @@ export function createUiRouteHandler(dependencies) {
|
|
|
19
24
|
const runService = dependencies.runService ?? createRunService();
|
|
20
25
|
const configWriteService = createConfigWriteService();
|
|
21
26
|
const profileService = createProfileService();
|
|
27
|
+
const profileExportService = createProfileExportService({ profileService });
|
|
28
|
+
const profileImportService = createProfileImportService();
|
|
22
29
|
const syncService = createSyncService({ profileService });
|
|
23
30
|
const agentConfigService = createAgentConfigService();
|
|
31
|
+
const mcpPreflightService = createMcpPreflightService();
|
|
32
|
+
const pluginInstallService = createPluginInstallService();
|
|
33
|
+
const skillPreflightService = createSkillPreflightService();
|
|
24
34
|
return async (request, response) => {
|
|
25
35
|
const url = new URL(request.url ?? '/', 'http://localhost');
|
|
26
36
|
switch (url.pathname) {
|
|
@@ -135,6 +145,68 @@ export function createUiRouteHandler(dependencies) {
|
|
|
135
145
|
}
|
|
136
146
|
return sendJson(response, 405, { error: 'Method not allowed' });
|
|
137
147
|
}
|
|
148
|
+
case '/api/profiles/export': {
|
|
149
|
+
if (request.method !== 'POST') {
|
|
150
|
+
return sendJson(response, 405, { error: 'Method not allowed' });
|
|
151
|
+
}
|
|
152
|
+
const body = await readJsonBody(request);
|
|
153
|
+
if (!body.ok) {
|
|
154
|
+
return sendJson(response, 400, { error: 'Invalid JSON body' });
|
|
155
|
+
}
|
|
156
|
+
const data = body.value;
|
|
157
|
+
const source = data?.source;
|
|
158
|
+
const agent = source?.source === 'agent'
|
|
159
|
+
? source.agent
|
|
160
|
+
: data?.agent;
|
|
161
|
+
const normalizedAgent = agent === 'claude' || agent === 'codex' || agent === 'gemini' ? agent : undefined;
|
|
162
|
+
const name = source?.source === 'profile'
|
|
163
|
+
? source.name
|
|
164
|
+
: data?.name;
|
|
165
|
+
const normalizedName = typeof name === 'string' && name.trim().length > 0 ? name.trim() : undefined;
|
|
166
|
+
if (!normalizedAgent && !normalizedName) {
|
|
167
|
+
return sendJson(response, 400, { error: 'Missing profile name or agent' });
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
const result = await profileExportService.execute({
|
|
171
|
+
cwd: dependencies.cwd,
|
|
172
|
+
source: normalizedAgent
|
|
173
|
+
? { source: 'agent', agent: normalizedAgent, cwd: dependencies.cwd }
|
|
174
|
+
: { source: 'profile', name: normalizedName },
|
|
175
|
+
outputPath: typeof data?.outputPath === 'string' && data.outputPath.trim().length > 0
|
|
176
|
+
? data.outputPath.trim()
|
|
177
|
+
: undefined,
|
|
178
|
+
});
|
|
179
|
+
return sendJson(response, 200, result);
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
return sendProfileError(response, error);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
case '/api/profiles/import': {
|
|
186
|
+
if (request.method !== 'POST') {
|
|
187
|
+
return sendJson(response, 405, { error: 'Method not allowed' });
|
|
188
|
+
}
|
|
189
|
+
const body = await readJsonBody(request);
|
|
190
|
+
if (!body.ok) {
|
|
191
|
+
return sendJson(response, 400, { error: 'Invalid JSON body' });
|
|
192
|
+
}
|
|
193
|
+
const data = body.value;
|
|
194
|
+
if (!data.archivePath || data.archivePath.trim().length === 0) {
|
|
195
|
+
return sendJson(response, 400, { error: 'Missing archivePath' });
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
const result = await profileImportService.execute({
|
|
199
|
+
cwd: dependencies.cwd,
|
|
200
|
+
archivePath: data.archivePath.trim(),
|
|
201
|
+
force: data.force === true,
|
|
202
|
+
credentials: parseCredentialMap(data.credentials),
|
|
203
|
+
});
|
|
204
|
+
return sendJson(response, 200, result);
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
return sendProfileError(response, error);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
138
210
|
case '/api/sync': {
|
|
139
211
|
if (request.method !== 'POST') {
|
|
140
212
|
return sendJson(response, 405, { error: 'Method not allowed' });
|
|
@@ -151,6 +223,29 @@ export function createUiRouteHandler(dependencies) {
|
|
|
151
223
|
}
|
|
152
224
|
default: {
|
|
153
225
|
// Agent MCP routes: /api/agents/:name/mcps(/:key)
|
|
226
|
+
const agentMcpCheckMatch = url.pathname.match(/^\/api\/agents\/(claude|codex|gemini)\/mcps\/check$/);
|
|
227
|
+
if (agentMcpCheckMatch) {
|
|
228
|
+
const agentName = agentMcpCheckMatch[1];
|
|
229
|
+
if (request.method !== 'POST') {
|
|
230
|
+
return sendJson(response, 405, { error: 'Method not allowed' });
|
|
231
|
+
}
|
|
232
|
+
const body = await readJsonBody(request);
|
|
233
|
+
if (!body.ok) {
|
|
234
|
+
return sendJson(response, 400, { error: 'Invalid JSON body' });
|
|
235
|
+
}
|
|
236
|
+
const data = body.value;
|
|
237
|
+
if (!data.key || (!data.entry?.command && !data.remoteEntry?.url)) {
|
|
238
|
+
return sendJson(response, 400, { error: 'Missing key and MCP payload' });
|
|
239
|
+
}
|
|
240
|
+
const result = await mcpPreflightService.execute({
|
|
241
|
+
cwd: dependencies.cwd,
|
|
242
|
+
agent: agentName,
|
|
243
|
+
key: data.key,
|
|
244
|
+
entry: data.entry,
|
|
245
|
+
remoteEntry: data.remoteEntry,
|
|
246
|
+
});
|
|
247
|
+
return sendJson(response, 200, result);
|
|
248
|
+
}
|
|
154
249
|
const agentMcpMatch = url.pathname.match(/^\/api\/agents\/(claude|codex|gemini)\/mcps(?:\/(.+))?$/);
|
|
155
250
|
if (agentMcpMatch) {
|
|
156
251
|
const agentName = agentMcpMatch[1];
|
|
@@ -161,8 +256,8 @@ export function createUiRouteHandler(dependencies) {
|
|
|
161
256
|
return sendJson(response, 400, { error: 'Invalid JSON body' });
|
|
162
257
|
}
|
|
163
258
|
const data = body.value;
|
|
164
|
-
if (!data.key || !data.entry?.command) {
|
|
165
|
-
return sendJson(response, 400, { error: 'Missing key
|
|
259
|
+
if (!data.key || (!data.entry?.command && !data.remoteEntry?.url)) {
|
|
260
|
+
return sendJson(response, 400, { error: 'Missing key and MCP payload' });
|
|
166
261
|
}
|
|
167
262
|
try {
|
|
168
263
|
await agentConfigService.addMcp({
|
|
@@ -170,13 +265,12 @@ export function createUiRouteHandler(dependencies) {
|
|
|
170
265
|
agent: agentName,
|
|
171
266
|
key: data.key,
|
|
172
267
|
entry: data.entry,
|
|
268
|
+
remoteEntry: data.remoteEntry,
|
|
173
269
|
});
|
|
174
270
|
return sendJson(response, 200, { ok: true });
|
|
175
271
|
}
|
|
176
272
|
catch (error) {
|
|
177
|
-
return
|
|
178
|
-
error: error instanceof Error ? error.message : 'Failed to add MCP',
|
|
179
|
-
});
|
|
273
|
+
return sendHandledError(response, error, 'Failed to add MCP');
|
|
180
274
|
}
|
|
181
275
|
}
|
|
182
276
|
if (request.method === 'DELETE' && mcpKey) {
|
|
@@ -189,9 +283,169 @@ export function createUiRouteHandler(dependencies) {
|
|
|
189
283
|
return sendJson(response, 200, { ok: true });
|
|
190
284
|
}
|
|
191
285
|
catch (error) {
|
|
192
|
-
return
|
|
193
|
-
|
|
286
|
+
return sendHandledError(response, error, 'Failed to remove MCP');
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return sendJson(response, 405, { error: 'Method not allowed' });
|
|
290
|
+
}
|
|
291
|
+
// Agent skill routes: /api/agents/:name/skills(/:key)
|
|
292
|
+
const agentSkillCheckMatch = url.pathname.match(/^\/api\/agents\/(claude|codex|gemini)\/skills\/check$/);
|
|
293
|
+
if (agentSkillCheckMatch) {
|
|
294
|
+
const agentName = agentSkillCheckMatch[1];
|
|
295
|
+
if (request.method !== 'POST') {
|
|
296
|
+
return sendJson(response, 405, { error: 'Method not allowed' });
|
|
297
|
+
}
|
|
298
|
+
const body = await readJsonBody(request);
|
|
299
|
+
if (!body.ok) {
|
|
300
|
+
return sendJson(response, 400, { error: 'Invalid JSON body' });
|
|
301
|
+
}
|
|
302
|
+
const data = body.value;
|
|
303
|
+
if (!data.name || !data.sourceAgent) {
|
|
304
|
+
return sendJson(response, 400, { error: 'Missing name or sourceAgent' });
|
|
305
|
+
}
|
|
306
|
+
const result = await skillPreflightService.execute({
|
|
307
|
+
sourceAgent: data.sourceAgent,
|
|
308
|
+
targetAgent: agentName,
|
|
309
|
+
skillName: data.name,
|
|
310
|
+
source: typeof data.source === 'string' ? data.source : undefined,
|
|
311
|
+
});
|
|
312
|
+
return sendJson(response, 200, result);
|
|
313
|
+
}
|
|
314
|
+
const agentSkillMatch = url.pathname.match(/^\/api\/agents\/(claude|codex|gemini)\/skills(?:\/(.+))?$/);
|
|
315
|
+
if (agentSkillMatch) {
|
|
316
|
+
const agentName = agentSkillMatch[1];
|
|
317
|
+
const skillKey = agentSkillMatch[2] ? decodeURIComponent(agentSkillMatch[2]) : null;
|
|
318
|
+
if (request.method === 'POST' && !skillKey) {
|
|
319
|
+
const body = await readJsonBody(request);
|
|
320
|
+
if (!body.ok) {
|
|
321
|
+
return sendJson(response, 400, { error: 'Invalid JSON body' });
|
|
322
|
+
}
|
|
323
|
+
const data = body.value;
|
|
324
|
+
if (!data.name || !data.sourceAgent) {
|
|
325
|
+
return sendJson(response, 400, { error: 'Missing name or sourceAgent' });
|
|
326
|
+
}
|
|
327
|
+
try {
|
|
328
|
+
const preflight = await skillPreflightService.execute({
|
|
329
|
+
sourceAgent: data.sourceAgent,
|
|
330
|
+
targetAgent: agentName,
|
|
331
|
+
skillName: data.name,
|
|
332
|
+
source: typeof data.source === 'string' ? data.source : undefined,
|
|
194
333
|
});
|
|
334
|
+
const firstError = preflight.checks.find((check) => check.status === 'error');
|
|
335
|
+
if (firstError) {
|
|
336
|
+
throw new ValidationError(`Skill "${data.name}" cannot be copied from ${data.sourceAgent} to ${agentName}: ${firstError.message}`);
|
|
337
|
+
}
|
|
338
|
+
await agentConfigService.copySkill({
|
|
339
|
+
sourceAgent: data.sourceAgent,
|
|
340
|
+
targetAgent: agentName,
|
|
341
|
+
skillName: data.name,
|
|
342
|
+
});
|
|
343
|
+
return sendJson(response, 200, { ok: true });
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
return sendHandledError(response, error, 'Failed to copy skill');
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (request.method === 'DELETE' && skillKey) {
|
|
350
|
+
try {
|
|
351
|
+
await agentConfigService.removeSkill({
|
|
352
|
+
agent: agentName,
|
|
353
|
+
skillName: skillKey,
|
|
354
|
+
});
|
|
355
|
+
return sendJson(response, 200, { ok: true });
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
return sendHandledError(response, error, 'Failed to remove skill');
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return sendJson(response, 405, { error: 'Method not allowed' });
|
|
362
|
+
}
|
|
363
|
+
const agentPluginCheckMatch = url.pathname.match(/^\/api\/agents\/(claude|codex|gemini)\/plugins\/check$/);
|
|
364
|
+
if (agentPluginCheckMatch) {
|
|
365
|
+
const agentName = agentPluginCheckMatch[1];
|
|
366
|
+
if (request.method !== 'POST') {
|
|
367
|
+
return sendJson(response, 405, { error: 'Method not allowed' });
|
|
368
|
+
}
|
|
369
|
+
const body = await readJsonBody(request);
|
|
370
|
+
if (!body.ok) {
|
|
371
|
+
return sendJson(response, 400, { error: 'Invalid JSON body' });
|
|
372
|
+
}
|
|
373
|
+
const data = body.value;
|
|
374
|
+
if (!data.name || !data.sourceAgent) {
|
|
375
|
+
return sendJson(response, 400, { error: 'Missing name or sourceAgent' });
|
|
376
|
+
}
|
|
377
|
+
const sourcePlugin = await resolveSourcePlugin(agentConfigService, dependencies.cwd, {
|
|
378
|
+
sourceAgent: data.sourceAgent,
|
|
379
|
+
name: data.name,
|
|
380
|
+
});
|
|
381
|
+
if (!sourcePlugin) {
|
|
382
|
+
return sendJson(response, 400, {
|
|
383
|
+
error: `Plugin "${data.name}" was not found in ${data.sourceAgent}.`,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
const result = await pluginInstallService.plan({
|
|
387
|
+
cwd: dependencies.cwd,
|
|
388
|
+
targetAgent: agentName,
|
|
389
|
+
sourceAgent: data.sourceAgent,
|
|
390
|
+
plugin: sourcePlugin,
|
|
391
|
+
});
|
|
392
|
+
return sendJson(response, 200, result);
|
|
393
|
+
}
|
|
394
|
+
const agentPluginMatch = url.pathname.match(/^\/api\/agents\/(claude|codex|gemini)\/plugins(?:\/(.+))?$/);
|
|
395
|
+
if (agentPluginMatch) {
|
|
396
|
+
const agentName = agentPluginMatch[1];
|
|
397
|
+
const pluginName = agentPluginMatch[2] ? decodeURIComponent(agentPluginMatch[2]) : null;
|
|
398
|
+
if (request.method === 'POST' && !pluginName) {
|
|
399
|
+
const body = await readJsonBody(request);
|
|
400
|
+
if (!body.ok) {
|
|
401
|
+
return sendJson(response, 400, { error: 'Invalid JSON body' });
|
|
402
|
+
}
|
|
403
|
+
const data = body.value;
|
|
404
|
+
if (!data.name || !data.sourceAgent) {
|
|
405
|
+
return sendJson(response, 400, { error: 'Missing name or sourceAgent' });
|
|
406
|
+
}
|
|
407
|
+
const sourcePlugin = await resolveSourcePlugin(agentConfigService, dependencies.cwd, {
|
|
408
|
+
sourceAgent: data.sourceAgent,
|
|
409
|
+
name: data.name,
|
|
410
|
+
});
|
|
411
|
+
if (!sourcePlugin) {
|
|
412
|
+
return sendJson(response, 400, {
|
|
413
|
+
error: `Plugin "${data.name}" was not found in ${data.sourceAgent}.`,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
try {
|
|
417
|
+
const result = await pluginInstallService.execute({
|
|
418
|
+
cwd: dependencies.cwd,
|
|
419
|
+
targetAgent: agentName,
|
|
420
|
+
sourceAgent: data.sourceAgent,
|
|
421
|
+
plugin: sourcePlugin,
|
|
422
|
+
});
|
|
423
|
+
return sendJson(response, 200, result);
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
return sendHandledError(response, error, 'Failed to install plugin');
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (request.method === 'DELETE' && pluginName) {
|
|
430
|
+
const targetPlugin = await resolveTargetPlugin(agentConfigService, dependencies.cwd, {
|
|
431
|
+
targetAgent: agentName,
|
|
432
|
+
name: pluginName,
|
|
433
|
+
});
|
|
434
|
+
if (!targetPlugin) {
|
|
435
|
+
return sendJson(response, 404, {
|
|
436
|
+
error: `Plugin "${pluginName}" was not found in ${agentName}.`,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
try {
|
|
440
|
+
const result = await pluginInstallService.remove({
|
|
441
|
+
cwd: dependencies.cwd,
|
|
442
|
+
targetAgent: agentName,
|
|
443
|
+
plugin: targetPlugin,
|
|
444
|
+
});
|
|
445
|
+
return sendJson(response, 200, result);
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
return sendHandledError(response, error, 'Failed to remove plugin');
|
|
195
449
|
}
|
|
196
450
|
}
|
|
197
451
|
return sendJson(response, 405, { error: 'Method not allowed' });
|
|
@@ -260,6 +514,24 @@ export function createUiRouteHandler(dependencies) {
|
|
|
260
514
|
}
|
|
261
515
|
};
|
|
262
516
|
}
|
|
517
|
+
async function resolveSourcePlugin(agentConfigService, cwd, options) {
|
|
518
|
+
const configs = await agentConfigService.readAll({ cwd });
|
|
519
|
+
const sourceConfig = configs.find((config) => config.agent === options.sourceAgent);
|
|
520
|
+
const plugin = sourceConfig?.skills.find((entry) => entry.kind === 'plugin' && entry.name === options.name);
|
|
521
|
+
return plugin ?? null;
|
|
522
|
+
}
|
|
523
|
+
async function resolveTargetPlugin(agentConfigService, cwd, options) {
|
|
524
|
+
const configs = await agentConfigService.readAll({ cwd });
|
|
525
|
+
const targetConfig = configs.find((config) => config.agent === options.targetAgent);
|
|
526
|
+
const plugin = targetConfig?.skills.find((entry) => entry.kind === 'plugin' && entry.name === options.name);
|
|
527
|
+
return plugin ?? null;
|
|
528
|
+
}
|
|
529
|
+
function sendHandledError(response, error, fallbackMessage) {
|
|
530
|
+
const isUserError = error instanceof BrainctlError && error.category === 'user';
|
|
531
|
+
sendJson(response, isUserError ? 400 : 500, {
|
|
532
|
+
error: error instanceof Error ? error.message : fallbackMessage,
|
|
533
|
+
});
|
|
534
|
+
}
|
|
263
535
|
async function readJsonBody(request) {
|
|
264
536
|
const chunks = [];
|
|
265
537
|
for await (const chunk of request) {
|
|
@@ -314,6 +586,19 @@ function sendProfileError(response, error) {
|
|
|
314
586
|
error: error instanceof Error ? error.message : 'Internal server error',
|
|
315
587
|
});
|
|
316
588
|
}
|
|
589
|
+
function parseCredentialMap(value) {
|
|
590
|
+
if (!value) {
|
|
591
|
+
return undefined;
|
|
592
|
+
}
|
|
593
|
+
const credentials = {};
|
|
594
|
+
for (const [key, credentialValue] of Object.entries(value)) {
|
|
595
|
+
if (typeof credentialValue !== 'string' || credentialValue.trim().length === 0) {
|
|
596
|
+
throw new ProfileError(`Credential "${key}" must be a non-empty string.`);
|
|
597
|
+
}
|
|
598
|
+
credentials[key] = credentialValue;
|
|
599
|
+
}
|
|
600
|
+
return Object.keys(credentials).length > 0 ? credentials : undefined;
|
|
601
|
+
}
|
|
317
602
|
function sendJson(response, statusCode, body) {
|
|
318
603
|
response.statusCode = statusCode;
|
|
319
604
|
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-700:oklch(55.5% .163 48.998);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-800:oklch(43.2% .095 166.913);--color-sky-200:oklch(90.1% .058 230.902);--color-sky-700:oklch(50% .134 242.749);--color-indigo-200:oklch(87% .065 274.039);--color-indigo-700:oklch(45.7% .24 277.023);--color-violet-200:oklch(89.4% .057 293.283);--color-violet-700:oklch(49.1% .27 292.581);--color-zinc-50:oklch(98.5% 0 0);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-600:oklch(44.2% .017 285.786);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-zinc-950:oklch(14.1% .005 285.823);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-md:28rem;--container-xl:36rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-tight:1.25;--leading-relaxed:1.625;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing) * 0)}.inset-y-0{inset-block:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.top-1\/2{top:50%}.top-6{top:calc(var(--spacing) * 6)}.top-full{top:100%}.right-3{right:calc(var(--spacing) * 3)}.left-1\/2{left:50%}.z-20{z-index:20}.z-50{z-index:50}.-m-4{margin:calc(var(--spacing) * -4)}.m-0{margin:calc(var(--spacing) * 0)}.m-3{margin:calc(var(--spacing) * 3)}.mx-auto{margin-inline:auto}.my-6{margin-block:calc(var(--spacing) * 6)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mt-8{margin-top:calc(var(--spacing) * 8)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.size-1\.5{width:calc(var(--spacing) * 1.5);height:calc(var(--spacing) * 1.5)}.size-2{width:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2)}.size-3\.5{width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-6{width:calc(var(--spacing) * 6);height:calc(var(--spacing) * 6)}.size-7{width:calc(var(--spacing) * 7);height:calc(var(--spacing) * 7)}.size-8{width:calc(var(--spacing) * 8);height:calc(var(--spacing) * 8)}.size-10{width:calc(var(--spacing) * 10);height:calc(var(--spacing) * 10)}.size-12{width:calc(var(--spacing) * 12);height:calc(var(--spacing) * 12)}.size-full{width:100%;height:100%}.h-0{height:calc(var(--spacing) * 0)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.max-h-64{max-height:calc(var(--spacing) * 64)}.min-h-0{min-height:calc(var(--spacing) * 0)}.min-h-\[36px\]{min-height:36px}.min-h-\[44px\]{min-height:44px}.min-h-\[120px\]{min-height:120px}.min-h-\[180px\]{min-height:180px}.min-h-\[360px\]{min-height:360px}.min-h-screen{min-height:100vh}.w-full{width:100%}.max-w-md{max-width:var(--container-md)}.max-w-xl{max-width:var(--container-xl)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[28px\]{min-width:28px}.flex-1{flex:1}.flex-none{flex:none}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.rotate-2{rotate:2deg}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-\[ping_400ms_ease-out_1\]{animation:.4s ease-out ping}.animate-spin{animation:var(--animate-spin)}.cursor-grab{cursor:grab}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize-y{resize:vertical}.appearance-none{appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-center{align-items:center}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-10{gap:calc(var(--spacing) * 10)}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-amber-200{border-color:var(--color-amber-200)}.border-emerald-200{border-color:var(--color-emerald-200)}.border-emerald-200\/80{border-color:#a4f4cfcc}@supports (color:color-mix(in lab,red,red)){.border-emerald-200\/80{border-color:color-mix(in oklab,var(--color-emerald-200) 80%,transparent)}}.border-emerald-300{border-color:var(--color-emerald-300)}.border-indigo-200{border-color:var(--color-indigo-200)}.border-red-200{border-color:var(--color-red-200)}.border-red-200\/80{border-color:#ffcacacc}@supports (color:color-mix(in lab,red,red)){.border-red-200\/80{border-color:color-mix(in oklab,var(--color-red-200) 80%,transparent)}}.border-sky-200{border-color:var(--color-sky-200)}.border-transparent{border-color:#0000}.border-violet-200{border-color:var(--color-violet-200)}.border-zinc-100{border-color:var(--color-zinc-100)}.border-zinc-200{border-color:var(--color-zinc-200)}.border-zinc-200\/60{border-color:#e4e4e799}@supports (color:color-mix(in lab,red,red)){.border-zinc-200\/60{border-color:color-mix(in oklab,var(--color-zinc-200) 60%,transparent)}}.border-zinc-200\/80{border-color:#e4e4e7cc}@supports (color:color-mix(in lab,red,red)){.border-zinc-200\/80{border-color:color-mix(in oklab,var(--color-zinc-200) 80%,transparent)}}.border-zinc-300{border-color:var(--color-zinc-300)}.bg-\[\#fcfcfc\]{background-color:#fcfcfc}.bg-black\/40{background-color:#0006}@supports (color:color-mix(in lab,red,red)){.bg-black\/40{background-color:color-mix(in oklab,var(--color-black) 40%,transparent)}}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-emerald-50\/50{background-color:#ecfdf580}@supports (color:color-mix(in lab,red,red)){.bg-emerald-50\/50{background-color:color-mix(in oklab,var(--color-emerald-50) 50%,transparent)}}.bg-red-50{background-color:var(--color-red-50)}.bg-red-50\/50{background-color:#fef2f280}@supports (color:color-mix(in lab,red,red)){.bg-red-50\/50{background-color:color-mix(in oklab,var(--color-red-50) 50%,transparent)}}.bg-red-100{background-color:var(--color-red-100)}.bg-red-500{background-color:var(--color-red-500)}.bg-red-600{background-color:var(--color-red-600)}.bg-white{background-color:var(--color-white)}.bg-white\/60{background-color:#fff9}@supports (color:color-mix(in lab,red,red)){.bg-white\/60{background-color:color-mix(in oklab,var(--color-white) 60%,transparent)}}.bg-zinc-50{background-color:var(--color-zinc-50)}.bg-zinc-50\/50{background-color:#fafafa80}@supports (color:color-mix(in lab,red,red)){.bg-zinc-50\/50{background-color:color-mix(in oklab,var(--color-zinc-50) 50%,transparent)}}.bg-zinc-100{background-color:var(--color-zinc-100)}.bg-zinc-200{background-color:var(--color-zinc-200)}.bg-zinc-400{background-color:var(--color-zinc-400)}.bg-zinc-500{background-color:var(--color-zinc-500)}.bg-zinc-900{background-color:var(--color-zinc-900)}.bg-zinc-950{background-color:var(--color-zinc-950)}.object-contain{object-fit:contain}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-10{padding-block:calc(var(--spacing) * 10)}.pt-0{padding-top:calc(var(--spacing) * 0)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pr-10{padding-right:calc(var(--spacing) * 10)}.pb-0{padding-bottom:calc(var(--spacing) * 0)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.text-center{text-align:center}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.text-amber-700{color:var(--color-amber-700)}.text-emerald-700{color:var(--color-emerald-700)}.text-emerald-800{color:var(--color-emerald-800)}.text-indigo-700{color:var(--color-indigo-700)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-700\/90{color:#bf000fe6}@supports (color:color-mix(in lab,red,red)){.text-red-700\/90{color:color-mix(in oklab,var(--color-red-700) 90%,transparent)}}.text-red-800{color:var(--color-red-800)}.text-red-900{color:var(--color-red-900)}.text-sky-700{color:var(--color-sky-700)}.text-violet-700{color:var(--color-violet-700)}.text-white{color:var(--color-white)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-500{color:var(--color-zinc-500)}.text-zinc-600{color:var(--color-zinc-600)}.text-zinc-700{color:var(--color-zinc-700)}.text-zinc-800{color:var(--color-zinc-800)}.text-zinc-900{color:var(--color-zinc-900)}.uppercase{text-transform:uppercase}.line-through{text-decoration-line:line-through}.opacity-0{opacity:0}.opacity-50{opacity:.5}.opacity-70{opacity:.7}.opacity-80{opacity:.8}.shadow-\[0_0_12px_rgba\(16\,185\,129\,0\.15\)\]{--tw-shadow:0 0 12px var(--tw-shadow-color,#10b98126);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-inner{--tw-shadow:inset 0 2px 4px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-4{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-emerald-400\/20{--tw-ring-color:#00d29433}@supports (color:color-mix(in lab,red,red)){.ring-emerald-400\/20{--tw-ring-color:color-mix(in oklab, var(--color-emerald-400) 20%, transparent)}}.ring-zinc-200\/50{--tw-ring-color:#e4e4e780}@supports (color:color-mix(in lab,red,red)){.ring-zinc-200\/50{--tw-ring-color:color-mix(in oklab, var(--color-zinc-200) 50%, transparent)}}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}@media(hover:hover){.group-hover\:text-zinc-900:is(:where(.group):hover *){color:var(--color-zinc-900)}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}}.placeholder\:font-normal::placeholder{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.placeholder\:text-zinc-400::placeholder{color:var(--color-zinc-400)}@media(hover:hover){.hover\:border-zinc-200:hover{border-color:var(--color-zinc-200)}.hover\:border-zinc-300:hover{border-color:var(--color-zinc-300)}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:bg-white:hover{background-color:var(--color-white)}.hover\:bg-zinc-50:hover{background-color:var(--color-zinc-50)}.hover\:bg-zinc-100:hover{background-color:var(--color-zinc-100)}.hover\:bg-zinc-800:hover{background-color:var(--color-zinc-800)}.hover\:text-red-600:hover{color:var(--color-red-600)}.hover\:text-red-700:hover{color:var(--color-red-700)}.hover\:text-zinc-700:hover{color:var(--color-zinc-700)}.hover\:text-zinc-900:hover{color:var(--color-zinc-900)}.hover\:opacity-80:hover{opacity:.8}.hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-sm:hover{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.focus\:border-zinc-400:focus{border-color:var(--color-zinc-400)}.focus\:ring-4:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-zinc-100:focus{--tw-ring-color:var(--color-zinc-100)}.focus\:ring-zinc-900:focus{--tw-ring-color:var(--color-zinc-900)}.active\:scale-\[0\.98\]:active{scale:.98}.active\:cursor-grabbing:active{cursor:grabbing}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:opacity-50:disabled{opacity:.5}@media(min-width:40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}}@media(min-width:48rem){.md\:w-auto{width:auto}}@media(min-width:64rem){.lg\:flex{display:flex}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-\[1fr_1\.2fr\]{grid-template-columns:1fr 1.2fr}.lg\:grid-cols-\[minmax\(0\,1\.3fr\)_minmax\(300px\,0\.9fr\)\]{grid-template-columns:minmax(0,1.3fr) minmax(300px,.9fr)}.lg\:flex-row{flex-direction:row}.lg\:items-center{align-items:center}.lg\:items-end{align-items:flex-end}.lg\:justify-between{justify-content:space-between}.lg\:border-t-0{border-top-style:var(--tw-border-style);border-top-width:0}.lg\:border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.lg\:p-6{padding:calc(var(--spacing) * 6)}.lg\:p-8{padding:calc(var(--spacing) * 8)}.lg\:px-10{padding-inline:calc(var(--spacing) * 10)}.lg\:py-0{padding-block:calc(var(--spacing) * 0)}.lg\:pr-0{padding-right:calc(var(--spacing) * 0)}.lg\:pr-10{padding-right:calc(var(--spacing) * 10)}.lg\:pl-0{padding-left:calc(var(--spacing) * 0)}.lg\:pl-10{padding-left:calc(var(--spacing) * 10)}}}:root{color-scheme:light;--background:#fff;--foreground:#09090b}html,body,#root{background-color:var(--background);min-height:100vh;color:var(--foreground);margin:0;padding:0}body,button,input,select,textarea{letter-spacing:-.01em;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:Inter,Avenir Next,Helvetica Neue,Segoe UI,sans-serif}code{font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Monaco,Consolas,Liberation Mono,monospace}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}
|