@meshxdata/fops 0.1.45 → 0.1.47
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/CHANGELOG.md +202 -17
- package/package.json +1 -1
- package/src/commands/lifecycle.js +81 -5
- package/src/commands/setup.js +45 -4
- package/src/plugins/bundled/fops-plugin-azure/index.js +29 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-core.js +1185 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-flux.js +1180 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-ingress.js +393 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-naming.js +104 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-network.js +296 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-postgres.js +768 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-reconcilers.js +538 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-secrets.js +849 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-stacks.js +643 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-state.js +145 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-storage.js +496 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks-terraform.js +1032 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-aks.js +155 -4245
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-keyvault.js +186 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-results.js +5 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/commands/infra-cmds.js +758 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/commands/registry-cmds.js +250 -0
- package/src/plugins/bundled/fops-plugin-azure/lib/commands/test-cmds.js +2 -1
- package/src/plugins/bundled/fops-plugin-foundation/lib/apply.js +3 -2
- package/src/plugins/bundled/fops-plugin-foundation/lib/helpers.js +21 -0
- package/src/plugins/bundled/fops-plugin-foundation/lib/tools-read.js +3 -5
- package/src/ui/tui/App.js +13 -13
- package/src/web/dist/assets/index-NXC8Hvnp.css +1 -0
- package/src/web/dist/assets/index-QH1N4ejK.js +112 -0
- package/src/web/dist/index.html +2 -2
- package/src/web/server.js +4 -4
- package/src/web/dist/assets/index-BphVaAUd.css +0 -1
- package/src/web/dist/assets/index-CSckLzuG.js +0 -129
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry commands: login, push, pull for AKS-hosted OCI registry via cloudflared.
|
|
3
|
+
*/
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { spawn, execSync } from "child_process";
|
|
6
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
|
|
10
|
+
const CLOUDFLARED_TOKEN_DIR = join(homedir(), ".fops", "registry");
|
|
11
|
+
const DOCKER_CONFIG_PATH = join(homedir(), ".docker", "config.json");
|
|
12
|
+
|
|
13
|
+
export function registerRegistryCommands(azure) {
|
|
14
|
+
const registry = azure
|
|
15
|
+
.command("registry")
|
|
16
|
+
.description("Push images to AKS-hosted OCI registry via Cloudflare Access");
|
|
17
|
+
|
|
18
|
+
registry
|
|
19
|
+
.command("login")
|
|
20
|
+
.description("Authenticate to the registry via Cloudflare Access")
|
|
21
|
+
.option("--cluster <name>", "Cluster name (default: current context)")
|
|
22
|
+
.option("--domain <url>", "Registry domain", "demo.meshx.app")
|
|
23
|
+
.action(async (opts) => {
|
|
24
|
+
await registryLogin(opts);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
registry
|
|
28
|
+
.command("push <image>")
|
|
29
|
+
.description("Push an image to the AKS registry")
|
|
30
|
+
.option("--cluster <name>", "Cluster name (default: current context)")
|
|
31
|
+
.option("--domain <url>", "Registry domain", "demo.meshx.app")
|
|
32
|
+
.option("--tag <tag>", "Additional tag to apply")
|
|
33
|
+
.action(async (image, opts) => {
|
|
34
|
+
await registryPush(image, opts);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
registry
|
|
38
|
+
.command("pull <image>")
|
|
39
|
+
.description("Pull an image from the AKS registry")
|
|
40
|
+
.option("--cluster <name>", "Cluster name (default: current context)")
|
|
41
|
+
.option("--domain <url>", "Registry domain", "demo.meshx.app")
|
|
42
|
+
.action(async (image, opts) => {
|
|
43
|
+
await registryPull(image, opts);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
registry
|
|
47
|
+
.command("logout")
|
|
48
|
+
.description("Remove registry credentials")
|
|
49
|
+
.option("--domain <url>", "Registry domain", "demo.meshx.app")
|
|
50
|
+
.action(async (opts) => {
|
|
51
|
+
await registryLogout(opts);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function registryLogin(opts) {
|
|
56
|
+
const domain = opts.domain;
|
|
57
|
+
const registryUrl = `https://${domain}/registry/`;
|
|
58
|
+
|
|
59
|
+
console.log(chalk.blue("→ Authenticating to registry via Cloudflare Access..."));
|
|
60
|
+
|
|
61
|
+
// Check if cloudflared is installed
|
|
62
|
+
try {
|
|
63
|
+
execSync("which cloudflared", { stdio: "ignore" });
|
|
64
|
+
} catch {
|
|
65
|
+
console.error(chalk.red("✗ cloudflared not found. Install it:"));
|
|
66
|
+
console.error(chalk.gray(" brew install cloudflared"));
|
|
67
|
+
console.error(chalk.gray(" # or: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/"));
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Authenticate with Cloudflare Access
|
|
72
|
+
console.log(chalk.gray(` Opening browser for ${domain}...`));
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
execSync(`cloudflared access login ${registryUrl}`, {
|
|
76
|
+
stdio: "inherit",
|
|
77
|
+
timeout: 120000
|
|
78
|
+
});
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error(chalk.red("✗ Cloudflare Access login failed"));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Get the token from cloudflared
|
|
85
|
+
let token;
|
|
86
|
+
try {
|
|
87
|
+
token = execSync(`cloudflared access token -app=${registryUrl}`, {
|
|
88
|
+
encoding: "utf-8"
|
|
89
|
+
}).trim();
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.error(chalk.red("✗ Failed to get access token"));
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Store token for later use
|
|
96
|
+
if (!existsSync(CLOUDFLARED_TOKEN_DIR)) {
|
|
97
|
+
mkdirSync(CLOUDFLARED_TOKEN_DIR, { recursive: true });
|
|
98
|
+
}
|
|
99
|
+
writeFileSync(join(CLOUDFLARED_TOKEN_DIR, `${domain}.token`), token, { mode: 0o600 });
|
|
100
|
+
|
|
101
|
+
// Configure docker to use the token
|
|
102
|
+
await configureDockerAuth(domain, token);
|
|
103
|
+
|
|
104
|
+
console.log(chalk.green(`✓ Logged in to ${domain}/registry`));
|
|
105
|
+
console.log(chalk.gray(` Token stored in ~/.fops/registry/`));
|
|
106
|
+
console.log(chalk.gray(` Docker configured for ${domain}`));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function configureDockerAuth(domain, token) {
|
|
110
|
+
const registryHost = `${domain}/registry`;
|
|
111
|
+
|
|
112
|
+
let dockerConfig = { auths: {} };
|
|
113
|
+
if (existsSync(DOCKER_CONFIG_PATH)) {
|
|
114
|
+
try {
|
|
115
|
+
dockerConfig = JSON.parse(readFileSync(DOCKER_CONFIG_PATH, "utf-8"));
|
|
116
|
+
if (!dockerConfig.auths) dockerConfig.auths = {};
|
|
117
|
+
} catch {
|
|
118
|
+
dockerConfig = { auths: {} };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// CF Access token goes in Authorization header, but docker uses base64 auth
|
|
123
|
+
// We'll use a credential helper approach or encode it
|
|
124
|
+
const auth = Buffer.from(`cf-access-token:${token}`).toString("base64");
|
|
125
|
+
|
|
126
|
+
dockerConfig.auths[registryHost] = { auth };
|
|
127
|
+
|
|
128
|
+
const configDir = join(homedir(), ".docker");
|
|
129
|
+
if (!existsSync(configDir)) {
|
|
130
|
+
mkdirSync(configDir, { recursive: true });
|
|
131
|
+
}
|
|
132
|
+
writeFileSync(DOCKER_CONFIG_PATH, JSON.stringify(dockerConfig, null, 2), { mode: 0o600 });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function registryPush(image, opts) {
|
|
136
|
+
const domain = opts.domain;
|
|
137
|
+
const registryHost = `${domain}/registry`;
|
|
138
|
+
|
|
139
|
+
// Check if logged in
|
|
140
|
+
const tokenPath = join(CLOUDFLARED_TOKEN_DIR, `${domain}.token`);
|
|
141
|
+
if (!existsSync(tokenPath)) {
|
|
142
|
+
console.error(chalk.red("✗ Not logged in. Run: fops azure registry login"));
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Refresh token if needed
|
|
147
|
+
const token = readFileSync(tokenPath, "utf-8").trim();
|
|
148
|
+
await configureDockerAuth(domain, token);
|
|
149
|
+
|
|
150
|
+
// Determine source and target image
|
|
151
|
+
const srcImage = image;
|
|
152
|
+
let targetImage;
|
|
153
|
+
|
|
154
|
+
if (image.includes("/")) {
|
|
155
|
+
// Image already has a path, just prepend registry
|
|
156
|
+
const imageParts = image.split("/");
|
|
157
|
+
const nameTag = imageParts.pop();
|
|
158
|
+
targetImage = `${registryHost}/${nameTag}`;
|
|
159
|
+
} else {
|
|
160
|
+
targetImage = `${registryHost}/${image}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Add branch tag if specified
|
|
164
|
+
if (opts.tag) {
|
|
165
|
+
const [name] = targetImage.split(":");
|
|
166
|
+
targetImage = `${name}:${opts.tag}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log(chalk.blue(`→ Tagging ${srcImage} as ${targetImage}...`));
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
execSync(`docker tag ${srcImage} ${targetImage}`, { stdio: "inherit" });
|
|
173
|
+
} catch {
|
|
174
|
+
console.error(chalk.red("✗ Failed to tag image"));
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
console.log(chalk.blue(`→ Pushing ${targetImage}...`));
|
|
179
|
+
|
|
180
|
+
// Start cloudflared access tcp tunnel for the push
|
|
181
|
+
const tunnel = spawn("cloudflared", [
|
|
182
|
+
"access", "tcp",
|
|
183
|
+
"--hostname", `${domain}`,
|
|
184
|
+
"--url", "localhost:5000"
|
|
185
|
+
], { stdio: "pipe" });
|
|
186
|
+
|
|
187
|
+
// Wait for tunnel to be ready
|
|
188
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
execSync(`docker push ${targetImage}`, { stdio: "inherit" });
|
|
192
|
+
console.log(chalk.green(`✓ Pushed ${targetImage}`));
|
|
193
|
+
} catch (err) {
|
|
194
|
+
console.error(chalk.red("✗ Push failed"));
|
|
195
|
+
tunnel.kill();
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
tunnel.kill();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function registryPull(image, opts) {
|
|
203
|
+
const domain = opts.domain;
|
|
204
|
+
const registryHost = `${domain}/registry`;
|
|
205
|
+
|
|
206
|
+
// Check if logged in
|
|
207
|
+
const tokenPath = join(CLOUDFLARED_TOKEN_DIR, `${domain}.token`);
|
|
208
|
+
if (!existsSync(tokenPath)) {
|
|
209
|
+
console.error(chalk.red("✗ Not logged in. Run: fops azure registry login"));
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const token = readFileSync(tokenPath, "utf-8").trim();
|
|
214
|
+
await configureDockerAuth(domain, token);
|
|
215
|
+
|
|
216
|
+
const fullImage = image.startsWith(registryHost) ? image : `${registryHost}/${image}`;
|
|
217
|
+
|
|
218
|
+
console.log(chalk.blue(`→ Pulling ${fullImage}...`));
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
execSync(`docker pull ${fullImage}`, { stdio: "inherit" });
|
|
222
|
+
console.log(chalk.green(`✓ Pulled ${fullImage}`));
|
|
223
|
+
} catch {
|
|
224
|
+
console.error(chalk.red("✗ Pull failed"));
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function registryLogout(opts) {
|
|
230
|
+
const domain = opts.domain;
|
|
231
|
+
const tokenPath = join(CLOUDFLARED_TOKEN_DIR, `${domain}.token`);
|
|
232
|
+
|
|
233
|
+
if (existsSync(tokenPath)) {
|
|
234
|
+
const { unlinkSync } = await import("fs");
|
|
235
|
+
unlinkSync(tokenPath);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Remove from docker config
|
|
239
|
+
if (existsSync(DOCKER_CONFIG_PATH)) {
|
|
240
|
+
try {
|
|
241
|
+
const dockerConfig = JSON.parse(readFileSync(DOCKER_CONFIG_PATH, "utf-8"));
|
|
242
|
+
delete dockerConfig.auths?.[`${domain}/registry`];
|
|
243
|
+
writeFileSync(DOCKER_CONFIG_PATH, JSON.stringify(dockerConfig, null, 2));
|
|
244
|
+
} catch {
|
|
245
|
+
// Ignore errors
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.log(chalk.green(`✓ Logged out from ${domain}/registry`));
|
|
250
|
+
}
|
|
@@ -272,9 +272,10 @@ export function registerTestCommands(azure) {
|
|
|
272
272
|
.command("show <target>")
|
|
273
273
|
.description("Show details of the latest test result for a VM/cluster")
|
|
274
274
|
.option("--run <id>", "Show a specific run by timestamp")
|
|
275
|
+
.option("--json", "Output raw JSON result")
|
|
275
276
|
.action(async (target, opts) => {
|
|
276
277
|
const { resultsShow } = await import("../azure-results.js");
|
|
277
|
-
await resultsShow({ target, run: opts.run });
|
|
278
|
+
await resultsShow({ target, run: opts.run, json: opts.json });
|
|
278
279
|
});
|
|
279
280
|
|
|
280
281
|
test
|
|
@@ -7,6 +7,7 @@ import { getTemplate, resolveBuilder, resolveSchema, buildPipeline, normalizeSch
|
|
|
7
7
|
import { fetchEntityColumns, validateTransformColumnFlow, runComposeCmd, findComposeRoot } from "./tools-write.js";
|
|
8
8
|
import { StorageClient } from "./storage.js";
|
|
9
9
|
import { parseListResponse } from "./api-spec.js";
|
|
10
|
+
import { mapSettled } from "./helpers.js";
|
|
10
11
|
|
|
11
12
|
async function ensureExpectations(client, uuid) {
|
|
12
13
|
try {
|
|
@@ -1278,8 +1279,8 @@ export async function applyLandscape(client, operations, opts = {}) {
|
|
|
1278
1279
|
const start = Date.now();
|
|
1279
1280
|
while (Date.now() - start < maxWait) {
|
|
1280
1281
|
try {
|
|
1281
|
-
const statuses = await
|
|
1282
|
-
sadpUuids
|
|
1282
|
+
const statuses = await mapSettled(
|
|
1283
|
+
sadpUuids, (uuid) => client.get(`/data/compute?identifier=${uuid}`), 5,
|
|
1283
1284
|
);
|
|
1284
1285
|
const stillRunning = statuses.some((r) => {
|
|
1285
1286
|
if (r.status !== "fulfilled") return false;
|
|
@@ -72,6 +72,27 @@ export function projectList(raw, entityType) {
|
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Like Promise.allSettled(items.map(fn)) but limits concurrency.
|
|
77
|
+
* At most `limit` calls to `fn` run at the same time.
|
|
78
|
+
*/
|
|
79
|
+
export async function mapSettled(items, fn, limit = 5) {
|
|
80
|
+
const results = new Array(items.length);
|
|
81
|
+
let next = 0;
|
|
82
|
+
async function worker() {
|
|
83
|
+
while (next < items.length) {
|
|
84
|
+
const i = next++;
|
|
85
|
+
try {
|
|
86
|
+
results[i] = { status: "fulfilled", value: await fn(items[i], i) };
|
|
87
|
+
} catch (reason) {
|
|
88
|
+
results[i] = { status: "rejected", reason };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()));
|
|
93
|
+
return results;
|
|
94
|
+
}
|
|
95
|
+
|
|
75
96
|
// Lazy resolver for the embeddings search service provided by fops-plugin-embeddings
|
|
76
97
|
export function makeEmbedSearch(api) {
|
|
77
98
|
let cached = undefined; // undefined → not resolved yet; null → resolved but missing
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fmt, qs, makeEmbedSearch, projectEntity, projectList } from "./helpers.js";
|
|
1
|
+
import { fmt, qs, makeEmbedSearch, projectEntity, projectList, mapSettled } from "./helpers.js";
|
|
2
2
|
import { runComposeCmd, findComposeRoot } from "./tools-write.js";
|
|
3
3
|
|
|
4
4
|
export function registerReadTools(api, client) {
|
|
@@ -397,15 +397,13 @@ export function registerReadTools(api, client) {
|
|
|
397
397
|
const products = parseListResponse(dpList);
|
|
398
398
|
if (!products.length) return "No active compute jobs.";
|
|
399
399
|
|
|
400
|
-
const results = await
|
|
401
|
-
products.map(async (p) => {
|
|
400
|
+
const results = await mapSettled(products, async (p) => {
|
|
402
401
|
const id = p.identifier || p.id;
|
|
403
402
|
if (!id) return null;
|
|
404
403
|
const cs = await client.get(`/data/compute${qs({ identifier: id })}`);
|
|
405
404
|
const s = cs?.status?.status || cs?.status;
|
|
406
405
|
return { id, name: p.name, suffix: cs?.status?.suffix || "latest", status: typeof s === "object" ? s : { status: s } };
|
|
407
|
-
|
|
408
|
-
);
|
|
406
|
+
}, 5);
|
|
409
407
|
|
|
410
408
|
const jobs = results
|
|
411
409
|
.filter((r) => r.status === "fulfilled" && r.value)
|
package/src/ui/tui/App.js
CHANGED
|
@@ -639,9 +639,9 @@ function TuiApp({ core, version, root }) {
|
|
|
639
639
|
return svcs;
|
|
640
640
|
});
|
|
641
641
|
};
|
|
642
|
-
poll();
|
|
643
|
-
|
|
644
|
-
return () => { cancelled = true;
|
|
642
|
+
const loop = async () => { while (!cancelled) { await poll(); if (cancelled) break; await new Promise(r => setTimeout(r, 30000)); } };
|
|
643
|
+
loop();
|
|
644
|
+
return () => { cancelled = true; };
|
|
645
645
|
}, [root]);
|
|
646
646
|
// While starting stack, poll services every 5s so "Waiting for X" updates quickly
|
|
647
647
|
useEffect(() => {
|
|
@@ -654,9 +654,9 @@ function TuiApp({ core, version, root }) {
|
|
|
654
654
|
return svcs;
|
|
655
655
|
});
|
|
656
656
|
};
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
return () => { cancelled = true;
|
|
657
|
+
const loop = async () => { await new Promise(r => setTimeout(r, 2000)); while (!cancelled) { await poll(); if (cancelled) break; await new Promise(r => setTimeout(r, 5000)); } };
|
|
658
|
+
loop();
|
|
659
|
+
return () => { cancelled = true; };
|
|
660
660
|
}, [root, isRunningCmd, statusText]);
|
|
661
661
|
|
|
662
662
|
// Poll Foundation entities every 60s (meshes, data products, data systems, data sources)
|
|
@@ -690,12 +690,12 @@ function TuiApp({ core, version, root }) {
|
|
|
690
690
|
});
|
|
691
691
|
} catch {}
|
|
692
692
|
};
|
|
693
|
-
poll();
|
|
694
|
-
|
|
695
|
-
return () => { cancelled = true;
|
|
693
|
+
const loop = async () => { while (!cancelled) { await poll(); if (cancelled) break; await new Promise(r => setTimeout(r, 60000)); } };
|
|
694
|
+
loop();
|
|
695
|
+
return () => { cancelled = true; };
|
|
696
696
|
}, [core]);
|
|
697
697
|
|
|
698
|
-
// Poll compute jobs (active + recent) every
|
|
698
|
+
// Poll compute jobs (active + recent) every 15s for responsive sidebar
|
|
699
699
|
useEffect(() => {
|
|
700
700
|
let cancelled = false;
|
|
701
701
|
const poll = async () => {
|
|
@@ -745,9 +745,9 @@ function TuiApp({ core, version, root }) {
|
|
|
745
745
|
});
|
|
746
746
|
} catch {}
|
|
747
747
|
};
|
|
748
|
-
poll();
|
|
749
|
-
|
|
750
|
-
return () => { cancelled = true;
|
|
748
|
+
const loop = async () => { while (!cancelled) { await poll(); if (cancelled) break; await new Promise(r => setTimeout(r, 15000)); } };
|
|
749
|
+
loop();
|
|
750
|
+
return () => { cancelled = true; };
|
|
751
751
|
}, [core]);
|
|
752
752
|
|
|
753
753
|
// Sync sessions list from core. Accept fewer tabs only when exactly one was removed (user closed tab).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */@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-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-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-yellow-300:oklch(90.5% .182 98.111);--color-yellow-400:oklch(85.2% .199 91.936);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-400:oklch(76.5% .177 163.223);--color-cyan-300:oklch(86.5% .127 207.078);--color-cyan-400:oklch(78.9% .154 211.53);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-fuchsia-300:oklch(83.3% .145 321.434);--color-fuchsia-400:oklch(74% .238 322.16);--spacing:.25rem;--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-6xl:3.75rem;--text-6xl--line-height:1;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-normal:0em;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-lg:.5rem;--radius-xl:.75rem;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--blur-2xl:40px;--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%;-moz-tab-size:4;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]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance: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{.absolute{position:absolute}.relative{position:relative}.inset-0{inset:calc(var(--spacing)*0)}.top-full{top:100%}.right-2{right:calc(var(--spacing)*2)}.right-4{right:calc(var(--spacing)*4)}.bottom-0{bottom:calc(var(--spacing)*0)}.bottom-full{bottom:100%}.left-0{left:calc(var(--spacing)*0)}.left-2{left:calc(var(--spacing)*2)}.left-4{left:calc(var(--spacing)*4)}.z-10{z-index:10}.z-50{z-index:50}.mx-1{margin-inline:calc(var(--spacing)*1)}.mx-4{margin-inline:calc(var(--spacing)*4)}.my-0\.5{margin-block:calc(var(--spacing)*.5)}.my-3{margin-block:calc(var(--spacing)*3)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-2\.5{margin-top:calc(var(--spacing)*2.5)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-auto{margin-top:auto}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-2{margin-right:calc(var(--spacing)*2)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-1\.5{margin-bottom:calc(var(--spacing)*1.5)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.ml-0\.5{margin-left:calc(var(--spacing)*.5)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-8{margin-left:calc(var(--spacing)*8)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.h-1\.5{height:calc(var(--spacing)*1.5)}.h-2{height:calc(var(--spacing)*2)}.h-3{height:calc(var(--spacing)*3)}.h-8{height:calc(var(--spacing)*8)}.h-\[2px\]{height:2px}.h-\[5px\]{height:5px}.h-\[6px\]{height:6px}.h-\[14px\]{height:14px}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.max-h-20{max-height:calc(var(--spacing)*20)}.max-h-52{max-height:calc(var(--spacing)*52)}.w-1\.5{width:calc(var(--spacing)*1.5)}.w-1\/2{width:50%}.w-8{width:calc(var(--spacing)*8)}.w-12{width:calc(var(--spacing)*12)}.w-60{width:calc(var(--spacing)*60)}.w-\[2px\]{width:2px}.w-\[5px\]{width:5px}.w-\[6px\]{width:6px}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-\[220px\]{min-width:220px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.rotate-90{rotate:90deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-pulse{animation:var(--animate-pulse)}.cursor-grab{cursor:grab}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize-none{resize:none}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.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-2\.5{gap:calc(var(--spacing)*2.5)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}: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-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)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-\[\#1a1a28\]{border-color:#1a1a28}.border-\[\#1a1a28\]\/50{border-color:#1a1a2880}.border-\[\#2e2e40\]{border-color:#2e2e40}.border-\[\#818cf8\]\/20{border-color:#818cf833}.border-\[\#818cf8\]\/30{border-color:#818cf84d}.border-\[\#26263a\]{border-color:#26263a}.border-\[\#f97316\]{border-color:#f97316}.border-\[\#f97316\]\/20{border-color:#f9731633}.border-\[\#fbbf24\]\/20{border-color:#fbbf2433}.border-\[\#fbbf24\]\/25{border-color:#fbbf2440}.border-cyan-400\/30{border-color:#00d2ef4d}@supports (color:color-mix(in lab,red,red)){.border-cyan-400\/30{border-color:color-mix(in oklab,var(--color-cyan-400)30%,transparent)}}.border-emerald-400\/20{border-color:#00d29433}@supports (color:color-mix(in lab,red,red)){.border-emerald-400\/20{border-color:color-mix(in oklab,var(--color-emerald-400)20%,transparent)}}.border-emerald-400\/30{border-color:#00d2944d}@supports (color:color-mix(in lab,red,red)){.border-emerald-400\/30{border-color:color-mix(in oklab,var(--color-emerald-400)30%,transparent)}}.border-purple-500\/20{border-color:#ac4bff33}@supports (color:color-mix(in lab,red,red)){.border-purple-500\/20{border-color:color-mix(in oklab,var(--color-purple-500)20%,transparent)}}.border-red-400\/20{border-color:#ff656833}@supports (color:color-mix(in lab,red,red)){.border-red-400\/20{border-color:color-mix(in oklab,var(--color-red-400)20%,transparent)}}.border-red-400\/30{border-color:#ff65684d}@supports (color:color-mix(in lab,red,red)){.border-red-400\/30{border-color:color-mix(in oklab,var(--color-red-400)30%,transparent)}}.border-red-500\/20{border-color:#fb2c3633}@supports (color:color-mix(in lab,red,red)){.border-red-500\/20{border-color:color-mix(in oklab,var(--color-red-500)20%,transparent)}}.border-transparent{border-color:#0000}.border-yellow-400\/30{border-color:#fac8004d}@supports (color:color-mix(in lab,red,red)){.border-yellow-400\/30{border-color:color-mix(in oklab,var(--color-yellow-400)30%,transparent)}}.bg-\[\#0a0a10\]{background-color:#0a0a10}.bg-\[\#0c0c12\]{background-color:#0c0c12}.bg-\[\#0c0c14\]{background-color:#0c0c14}.bg-\[\#0e0e16\]{background-color:#0e0e16}.bg-\[\#1a1a28\]{background-color:#1a1a28}.bg-\[\#1e1e2e\]{background-color:#1e1e2e}.bg-\[\#3a3a50\]{background-color:#3a3a50}.bg-\[\#818cf8\]{background-color:#818cf8}.bg-\[\#06060a\]{background-color:#06060a}.bg-\[\#08080c\]{background-color:#08080c}.bg-\[\#12121a\]{background-color:#12121a}.bg-\[\#14141c\]{background-color:#14141c}.bg-\[\#18181f\]{background-color:#18181f}.bg-\[\#f97316\]{background-color:#f97316}.bg-\[\#f97316\]\/8{background-color:#f9731614}.bg-\[\#f97316\]\/20{background-color:#f9731633}.bg-\[\#fbbf24\]\/8{background-color:#fbbf2414}.bg-emerald-400{background-color:var(--color-emerald-400)}.bg-emerald-400\/8{background-color:#00d29414}@supports (color:color-mix(in lab,red,red)){.bg-emerald-400\/8{background-color:color-mix(in oklab,var(--color-emerald-400)8%,transparent)}}.bg-red-400{background-color:var(--color-red-400)}.bg-red-400\/8{background-color:#ff656814}@supports (color:color-mix(in lab,red,red)){.bg-red-400\/8{background-color:color-mix(in oklab,var(--color-red-400)8%,transparent)}}.bg-red-400\/50{background-color:#ff656880}@supports (color:color-mix(in lab,red,red)){.bg-red-400\/50{background-color:color-mix(in oklab,var(--color-red-400)50%,transparent)}}.bg-red-400\/60{background-color:#ff656899}@supports (color:color-mix(in lab,red,red)){.bg-red-400\/60{background-color:color-mix(in oklab,var(--color-red-400)60%,transparent)}}.bg-red-500\/5{background-color:#fb2c360d}@supports (color:color-mix(in lab,red,red)){.bg-red-500\/5{background-color:color-mix(in oklab,var(--color-red-500)5%,transparent)}}.bg-transparent{background-color:#0000}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.px-1{padding-inline:calc(var(--spacing)*1)}.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-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-0{padding-block:calc(var(--spacing)*0)}.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-2{padding-block:calc(var(--spacing)*2)}.py-2\.5{padding-block:calc(var(--spacing)*2.5)}.py-3{padding-block:calc(var(--spacing)*3)}.py-5{padding-block:calc(var(--spacing)*5)}.py-\[1px\]{padding-block:1px}.py-\[2px\]{padding-block:2px}.py-\[3px\]{padding-block:3px}.pr-3{padding-right:calc(var(--spacing)*3)}.pb-0\.5{padding-bottom:calc(var(--spacing)*.5)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-3{padding-left:calc(var(--spacing)*3)}.pl-4{padding-left:calc(var(--spacing)*4)}.pl-5{padding-left:calc(var(--spacing)*5)}.pl-\[6px\]{padding-left:6px}.text-center{text-align:center}.text-left{text-align:left}.align-middle{vertical-align:middle}.font-mono{font-family:var(--font-mono)}.text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[8px\]{font-size:8px}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.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-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-\[0\.2em\]{--tw-tracking:.2em;letter-spacing:.2em}.tracking-normal{--tw-tracking:var(--tracking-normal);letter-spacing:var(--tracking-normal)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.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-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[\#1e1e2a\]{color:#1e1e2a}.text-\[\#2e2e40\]{color:#2e2e40}.text-\[\#3a3a50\]{color:#3a3a50}.text-\[\#4e4e63\]{color:#4e4e63}.text-\[\#5a5a70\]{color:#5a5a70}.text-\[\#6b6b80\]{color:#6b6b80}.text-\[\#6e7a8a\]{color:#6e7a8a}.text-\[\#8b8b9e\]{color:#8b8b9e}.text-\[\#818cf8\]{color:#818cf8}.text-\[\#08080c\]{color:#08080c}.text-\[\#18181f\]{color:#18181f}.text-\[\#26263a\]{color:#26263a}.text-\[\#a0a0b0\]{color:#a0a0b0}.text-\[\#c0bfc6\]{color:#c0bfc6}.text-\[\#e0dfe4\]{color:#e0dfe4}.text-\[\#f97316\]{color:#f97316}.text-\[\#fb923c\]{color:#fb923c}.text-\[\#fbbf24\]{color:#fbbf24}.text-cyan-300\/70{color:#53eafdb3}@supports (color:color-mix(in lab,red,red)){.text-cyan-300\/70{color:color-mix(in oklab,var(--color-cyan-300)70%,transparent)}}.text-cyan-400{color:var(--color-cyan-400)}.text-emerald-300\/70{color:#5ee9b5b3}@supports (color:color-mix(in lab,red,red)){.text-emerald-300\/70{color:color-mix(in oklab,var(--color-emerald-300)70%,transparent)}}.text-emerald-400{color:var(--color-emerald-400)}.text-fuchsia-300\/80{color:#f2a9ffcc}@supports (color:color-mix(in lab,red,red)){.text-fuchsia-300\/80{color:color-mix(in oklab,var(--color-fuchsia-300)80%,transparent)}}.text-fuchsia-400{color:var(--color-fuchsia-400)}.text-purple-400{color:var(--color-purple-400)}.text-red-400{color:var(--color-red-400)}.text-yellow-300\/70{color:#ffe02ab3}@supports (color:color-mix(in lab,red,red)){.text-yellow-300\/70{color:color-mix(in oklab,var(--color-yellow-300)70%,transparent)}}.text-yellow-400{color:var(--color-yellow-400)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.italic{font-style:italic}.underline{text-decoration-line:underline}.decoration-\[\#f97316\]\/30{text-decoration-color:#f973164d}.underline-offset-2{text-underline-offset:2px}.opacity-0{opacity:0}.opacity-60{opacity:.6}.opacity-\[0\.07\]{opacity:.07}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_0_12px_rgba\(249\,115\,22\,0\.3\)\]{--tw-shadow:0 0 12px var(--tw-shadow-color,#f973164d);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)}.blur-2xl{--tw-blur:blur(var(--blur-2xl));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))}.transition-transform{transition-property:transform,translate,scale,rotate;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\:opacity-100:is(:where(.group):hover *){opacity:1}}.placeholder\:text-\[\#2e2e40\]::placeholder{color:#2e2e40}.focus-within\:border-\[\#f97316\]\/50:focus-within{border-color:#f9731680}.focus-within\:shadow-\[0_0_20px_rgba\(249\,115\,22\,0\.08\)\]:focus-within{--tw-shadow:0 0 20px var(--tw-shadow-color,#f9731614);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(hover:hover){.hover\:bg-\[\#1a1a28\]:hover{background-color:#1a1a28}.hover\:bg-\[\#1c1c28\]:hover{background-color:#1c1c28}.hover\:bg-\[\#12121a\]:hover{background-color:#12121a}.hover\:bg-\[\#14141c\]:hover{background-color:#14141c}.hover\:bg-\[\#18181f\]:hover{background-color:#18181f}.hover\:bg-\[\#f97316\]\/20:hover{background-color:#f9731633}.hover\:bg-\[\#fb923c\]:hover{background-color:#fb923c}.hover\:bg-emerald-400\/10:hover{background-color:#00d2941a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-emerald-400\/10:hover{background-color:color-mix(in oklab,var(--color-emerald-400)10%,transparent)}}.hover\:bg-red-400\/10:hover{background-color:#ff65681a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-red-400\/10:hover{background-color:color-mix(in oklab,var(--color-red-400)10%,transparent)}}.hover\:text-\[\#8b8b9e\]:hover{color:#8b8b9e}.hover\:text-\[\#e0dfe4\]:hover{color:#e0dfe4}.hover\:text-\[\#f87171\]:hover{color:#f87171}.hover\:text-\[\#f97316\]:hover{color:#f97316}.hover\:text-\[\#fb923c\]:hover{color:#fb923c}.hover\:decoration-\[\#fb923c\]\/50:hover{text-decoration-color:#fb923c80}}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.active\:cursor-grabbing:active{cursor:grabbing}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}}body{color:#e0dfe4;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background:#08080c;height:100dvh;margin:0;padding:0;font-family:Sora,system-ui,-apple-system,sans-serif;overflow:hidden}#root{flex-direction:column;height:100dvh;display:flex}code,pre,.font-mono{font-family:IBM Plex Mono,JetBrains Mono,Fira Code,monospace}::selection{color:#e0dfe4;background:#f973164d}::-webkit-scrollbar{width:5px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:#26263a;border-radius:10px}::-webkit-scrollbar-thumb:hover{background:#3f3f56}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:2s cubic-bezier(.4,0,.6,1) infinite pulse}@keyframes fadeIn{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}.animate-fade-in{animation:.25s ease-out fadeIn}@keyframes slideIn{0%{opacity:0;transform:translate(-6px)}to{opacity:1;transform:translate(0)}}.animate-slide-in{animation:.2s ease-out slideIn}@keyframes cursorBlink{0%,to{opacity:1}50%{opacity:0}}.animate-cursor{animation:1s step-end infinite cursorBlink}@keyframes glowPulse{0%,to{box-shadow:0 0 8px #f9731626}50%{box-shadow:0 0 16px #f973164d}}.noise:after{content:"";pointer-events:none;opacity:.02;z-index:9999;background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");position:fixed;top:0;right:0;bottom:0;left: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}
|