kiwime 0.1.0__tar.gz
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.
- kiwime-0.1.0/.gitignore +78 -0
- kiwime-0.1.0/PKG-INFO +117 -0
- kiwime-0.1.0/README.md +84 -0
- kiwime-0.1.0/pyproject.toml +83 -0
- kiwime-0.1.0/src/kiwime_host/__init__.py +13 -0
- kiwime-0.1.0/src/kiwime_host/__main__.py +102 -0
- kiwime-0.1.0/src/kiwime_host/_ui/assets/core-DhEqZVGG.js +2 -0
- kiwime-0.1.0/src/kiwime_host/_ui/assets/core-DhEqZVGG.js.map +1 -0
- kiwime-0.1.0/src/kiwime_host/_ui/assets/event-BK_86lmQ.js +2 -0
- kiwime-0.1.0/src/kiwime_host/_ui/assets/event-BK_86lmQ.js.map +1 -0
- kiwime-0.1.0/src/kiwime_host/_ui/assets/index-C4lDuuOm.js +2 -0
- kiwime-0.1.0/src/kiwime_host/_ui/assets/index-C4lDuuOm.js.map +1 -0
- kiwime-0.1.0/src/kiwime_host/_ui/assets/index-Cb6s5rq3.css +1 -0
- kiwime-0.1.0/src/kiwime_host/_ui/assets/index-mBvznwA4.js +50 -0
- kiwime-0.1.0/src/kiwime_host/_ui/assets/index-mBvznwA4.js.map +1 -0
- kiwime-0.1.0/src/kiwime_host/_ui/index.html +22 -0
- kiwime-0.1.0/src/kiwime_host/events.py +49 -0
- kiwime-0.1.0/src/kiwime_host/host.py +295 -0
- kiwime-0.1.0/src/kiwime_host/ollama.py +112 -0
- kiwime-0.1.0/src/kiwime_host/sync.py +173 -0
- kiwime-0.1.0/src/kiwime_host/web.py +572 -0
- kiwime-0.1.0/tests/conftest.py +46 -0
- kiwime-0.1.0/tests/test_e2e_smoke.py +154 -0
- kiwime-0.1.0/tests/test_host.py +64 -0
- kiwime-0.1.0/tests/test_sync.py +105 -0
- kiwime-0.1.0/tests/test_web.py +264 -0
kiwime-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# OS
|
|
2
|
+
.DS_Store
|
|
3
|
+
Thumbs.db
|
|
4
|
+
|
|
5
|
+
# Editors
|
|
6
|
+
.vscode/*
|
|
7
|
+
!.vscode/settings.json
|
|
8
|
+
.idea/
|
|
9
|
+
*.swp
|
|
10
|
+
*.swo
|
|
11
|
+
|
|
12
|
+
# Rust
|
|
13
|
+
target/
|
|
14
|
+
Cargo.lock.bak
|
|
15
|
+
|
|
16
|
+
# Node
|
|
17
|
+
node_modules/
|
|
18
|
+
dist/
|
|
19
|
+
.pnpm-store/
|
|
20
|
+
*.log
|
|
21
|
+
|
|
22
|
+
# Python
|
|
23
|
+
__pycache__/
|
|
24
|
+
*.py[cod]
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.venv/
|
|
27
|
+
.pytest_cache/
|
|
28
|
+
.mypy_cache/
|
|
29
|
+
.ruff_cache/
|
|
30
|
+
.coverage
|
|
31
|
+
htmlcov/
|
|
32
|
+
|
|
33
|
+
# Tauri
|
|
34
|
+
apps/desktop/src-tauri/target/
|
|
35
|
+
|
|
36
|
+
# Frozen Python sidecars (build artifacts, see ADR 0006)
|
|
37
|
+
apps/desktop/src-tauri/binaries/
|
|
38
|
+
target/pyinstaller/
|
|
39
|
+
|
|
40
|
+
# uv
|
|
41
|
+
.python-version
|
|
42
|
+
|
|
43
|
+
# Frontend build copied into the host package by the publish workflow
|
|
44
|
+
apps/host/src/kiwime_host/_ui/
|
|
45
|
+
|
|
46
|
+
# Local data
|
|
47
|
+
*.sqlite
|
|
48
|
+
*.sqlite-journal
|
|
49
|
+
*.db
|
|
50
|
+
data/
|
|
51
|
+
blobs/
|
|
52
|
+
|
|
53
|
+
# Secrets
|
|
54
|
+
.env
|
|
55
|
+
.env.local
|
|
56
|
+
*.pem
|
|
57
|
+
|
|
58
|
+
# Per-user Claude Code settings (personal permission allowlists, local paths)
|
|
59
|
+
.claude/settings.local.json
|
|
60
|
+
|
|
61
|
+
# TypeScript incremental build state
|
|
62
|
+
*.tsbuildinfo
|
|
63
|
+
apps/desktop/dist-types-node/
|
|
64
|
+
|
|
65
|
+
# Internal planning notes (kept out of the public tree; see docs/ for the
|
|
66
|
+
# published roadmap, ADRs, and release docs)
|
|
67
|
+
.internal-notes/
|
|
68
|
+
docs/release/C_NOTES.md
|
|
69
|
+
docs/release/D_NOTES.md
|
|
70
|
+
docs/release/oss-readiness-review.md
|
|
71
|
+
docs/release/implementation-plan-*.md
|
|
72
|
+
docs/release/webification-plan.md
|
|
73
|
+
|
|
74
|
+
# Tool caches / scratch
|
|
75
|
+
.mypy_cache/
|
|
76
|
+
.pytest_cache/
|
|
77
|
+
.ruff_cache/
|
|
78
|
+
.a1/
|
kiwime-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kiwime
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Compile a person into AI-readable expert context. Local-first web runtime + MCP server.
|
|
5
|
+
Project-URL: Homepage, https://github.com/kiwiberry-ai/kiwime
|
|
6
|
+
Project-URL: Documentation, https://github.com/kiwiberry-ai/kiwime/tree/main/docs
|
|
7
|
+
Project-URL: Changelog, https://github.com/kiwiberry-ai/kiwime/blob/main/CHANGELOG.md
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
Keywords: ai,context,knowledge-graph,local-first,mcp,memory
|
|
10
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
11
|
+
Classifier: Environment :: Web Environment
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Requires-Dist: fastapi>=0.115
|
|
19
|
+
Requires-Dist: kiwime-compiler==0.1.0
|
|
20
|
+
Requires-Dist: kiwime-connector-gdrive==0.1.0
|
|
21
|
+
Requires-Dist: kiwime-connector-github==0.1.0
|
|
22
|
+
Requires-Dist: kiwime-connector-gmail==0.1.0
|
|
23
|
+
Requires-Dist: kiwime-connector-local-files==0.1.0
|
|
24
|
+
Requires-Dist: kiwime-connector-mcp-client==0.1.0
|
|
25
|
+
Requires-Dist: kiwime-connector-notion==0.1.0
|
|
26
|
+
Requires-Dist: kiwime-connector-slack==0.1.0
|
|
27
|
+
Requires-Dist: kiwime-export==0.1.0
|
|
28
|
+
Requires-Dist: kiwime-sdk==0.1.0
|
|
29
|
+
Requires-Dist: kiwime-secret==0.1.0
|
|
30
|
+
Requires-Dist: kiwime-store==0.1.0
|
|
31
|
+
Requires-Dist: uvicorn>=0.34
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# kiwiMe web host
|
|
35
|
+
|
|
36
|
+
Run kiwiMe as a local web app — a background service on `127.0.0.1` plus a
|
|
37
|
+
browser UI — instead of the Tauri desktop shell. This is the primary
|
|
38
|
+
distribution for developers and technical users (no code signing, no native
|
|
39
|
+
installer): you start a service and open a browser.
|
|
40
|
+
|
|
41
|
+
It is **local-first**: the service binds to loopback only, and all data
|
|
42
|
+
(credentials, content, indexes) stays on your machine, exactly as in the
|
|
43
|
+
desktop app. The host runs every sidecar *in-process* — there are no child
|
|
44
|
+
processes and no stdio plumbing; see `docs/release/webification-plan.md`.
|
|
45
|
+
|
|
46
|
+
## Run from a checkout (current)
|
|
47
|
+
|
|
48
|
+
Prerequisites:
|
|
49
|
+
|
|
50
|
+
- [uv](https://docs.astral.sh/uv/) (Python package manager)
|
|
51
|
+
- Node ≥ 20 **and [pnpm](https://pnpm.io/installation)** — the easiest way to
|
|
52
|
+
get pnpm is `corepack enable` (ships with Node), or
|
|
53
|
+
`npm install -g pnpm`.
|
|
54
|
+
- Python ≥ 3.11 with SQLite loadable-extension support (standard CPython
|
|
55
|
+
builds have it).
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# 1. install the Python workspace (all sidecars + the host)
|
|
59
|
+
uv sync --all-packages -p 3.12
|
|
60
|
+
|
|
61
|
+
# 2. build the frontend once (served as static files by the host)
|
|
62
|
+
cd apps/desktop && pnpm install && pnpm build && cd -
|
|
63
|
+
|
|
64
|
+
# 3. start the host — opens http://127.0.0.1:8765 in your browser
|
|
65
|
+
uv run kiwime
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
If you skip step 2, the host still starts and serves the API, and shows a
|
|
69
|
+
"UI not built" page with these instructions instead of the app.
|
|
70
|
+
|
|
71
|
+
Options:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
kiwime [--host 127.0.0.1] [--port 8765] [--db PATH] [--no-browser]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- `--db PATH` — use a specific store SQLite file (default: the platform app
|
|
78
|
+
data dir, the same one the desktop app uses).
|
|
79
|
+
- `--no-browser` — don't auto-open a browser. The startup log prints an
|
|
80
|
+
authenticated `…/auth?token=…` link — open that (or use the token as an
|
|
81
|
+
`Authorization: Bearer` header for scripting).
|
|
82
|
+
- `KIWIME_DIST=/path/to/dist` — override where the built UI is served from.
|
|
83
|
+
|
|
84
|
+
### Security
|
|
85
|
+
|
|
86
|
+
The API requires a per-session token even on loopback: the browser gets it
|
|
87
|
+
via the `/auth?token=…` link (exchanged for an HttpOnly cookie), other
|
|
88
|
+
clients pass `Authorization: Bearer <token>`. Requests with a non-localhost
|
|
89
|
+
`Host` header or a non-JSON body are rejected. A fresh token is minted on
|
|
90
|
+
every start.
|
|
91
|
+
|
|
92
|
+
### Linux note
|
|
93
|
+
|
|
94
|
+
Connector credentials go to the OS keyring. On Linux this needs a Secret
|
|
95
|
+
Service provider (GNOME Keyring / KWallet via `libsecret`); on a headless
|
|
96
|
+
box or minimal WSL distro, install `gnome-keyring` (or configure another
|
|
97
|
+
[keyring backend](https://pypi.org/project/keyring/)) before adding sources.
|
|
98
|
+
|
|
99
|
+
## Local models
|
|
100
|
+
|
|
101
|
+
The host exposes the same one-click Ollama flow as the desktop app
|
|
102
|
+
(detect / download with progress). Install [Ollama](https://ollama.com/download)
|
|
103
|
+
and use the in-app model manager to pull Qwen3 / Gemma 3. The curated model
|
|
104
|
+
list lives in `shared/model-catalog.json`, shared with the desktop runtime.
|
|
105
|
+
|
|
106
|
+
## Installing as a tool (future)
|
|
107
|
+
|
|
108
|
+
`pipx install kiwime` / `uv tool install kiwime` will be the one-line
|
|
109
|
+
install once the packages are published to PyPI and the built UI is bundled
|
|
110
|
+
into the wheel. Until then, use the checkout flow above.
|
|
111
|
+
|
|
112
|
+
## How it relates to the desktop app
|
|
113
|
+
|
|
114
|
+
The React frontend and all Python sidecars are shared. Only the *host* differs:
|
|
115
|
+
the desktop app uses a Rust (Tauri) shell that spawns sidecars as processes;
|
|
116
|
+
this web host holds them in one Python process and serves the same UI over
|
|
117
|
+
HTTP + SSE. `apps/desktop/src/api/tauri.ts` picks the transport at runtime.
|
kiwime-0.1.0/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# kiwiMe web host
|
|
2
|
+
|
|
3
|
+
Run kiwiMe as a local web app — a background service on `127.0.0.1` plus a
|
|
4
|
+
browser UI — instead of the Tauri desktop shell. This is the primary
|
|
5
|
+
distribution for developers and technical users (no code signing, no native
|
|
6
|
+
installer): you start a service and open a browser.
|
|
7
|
+
|
|
8
|
+
It is **local-first**: the service binds to loopback only, and all data
|
|
9
|
+
(credentials, content, indexes) stays on your machine, exactly as in the
|
|
10
|
+
desktop app. The host runs every sidecar *in-process* — there are no child
|
|
11
|
+
processes and no stdio plumbing; see `docs/release/webification-plan.md`.
|
|
12
|
+
|
|
13
|
+
## Run from a checkout (current)
|
|
14
|
+
|
|
15
|
+
Prerequisites:
|
|
16
|
+
|
|
17
|
+
- [uv](https://docs.astral.sh/uv/) (Python package manager)
|
|
18
|
+
- Node ≥ 20 **and [pnpm](https://pnpm.io/installation)** — the easiest way to
|
|
19
|
+
get pnpm is `corepack enable` (ships with Node), or
|
|
20
|
+
`npm install -g pnpm`.
|
|
21
|
+
- Python ≥ 3.11 with SQLite loadable-extension support (standard CPython
|
|
22
|
+
builds have it).
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# 1. install the Python workspace (all sidecars + the host)
|
|
26
|
+
uv sync --all-packages -p 3.12
|
|
27
|
+
|
|
28
|
+
# 2. build the frontend once (served as static files by the host)
|
|
29
|
+
cd apps/desktop && pnpm install && pnpm build && cd -
|
|
30
|
+
|
|
31
|
+
# 3. start the host — opens http://127.0.0.1:8765 in your browser
|
|
32
|
+
uv run kiwime
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
If you skip step 2, the host still starts and serves the API, and shows a
|
|
36
|
+
"UI not built" page with these instructions instead of the app.
|
|
37
|
+
|
|
38
|
+
Options:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
kiwime [--host 127.0.0.1] [--port 8765] [--db PATH] [--no-browser]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- `--db PATH` — use a specific store SQLite file (default: the platform app
|
|
45
|
+
data dir, the same one the desktop app uses).
|
|
46
|
+
- `--no-browser` — don't auto-open a browser. The startup log prints an
|
|
47
|
+
authenticated `…/auth?token=…` link — open that (or use the token as an
|
|
48
|
+
`Authorization: Bearer` header for scripting).
|
|
49
|
+
- `KIWIME_DIST=/path/to/dist` — override where the built UI is served from.
|
|
50
|
+
|
|
51
|
+
### Security
|
|
52
|
+
|
|
53
|
+
The API requires a per-session token even on loopback: the browser gets it
|
|
54
|
+
via the `/auth?token=…` link (exchanged for an HttpOnly cookie), other
|
|
55
|
+
clients pass `Authorization: Bearer <token>`. Requests with a non-localhost
|
|
56
|
+
`Host` header or a non-JSON body are rejected. A fresh token is minted on
|
|
57
|
+
every start.
|
|
58
|
+
|
|
59
|
+
### Linux note
|
|
60
|
+
|
|
61
|
+
Connector credentials go to the OS keyring. On Linux this needs a Secret
|
|
62
|
+
Service provider (GNOME Keyring / KWallet via `libsecret`); on a headless
|
|
63
|
+
box or minimal WSL distro, install `gnome-keyring` (or configure another
|
|
64
|
+
[keyring backend](https://pypi.org/project/keyring/)) before adding sources.
|
|
65
|
+
|
|
66
|
+
## Local models
|
|
67
|
+
|
|
68
|
+
The host exposes the same one-click Ollama flow as the desktop app
|
|
69
|
+
(detect / download with progress). Install [Ollama](https://ollama.com/download)
|
|
70
|
+
and use the in-app model manager to pull Qwen3 / Gemma 3. The curated model
|
|
71
|
+
list lives in `shared/model-catalog.json`, shared with the desktop runtime.
|
|
72
|
+
|
|
73
|
+
## Installing as a tool (future)
|
|
74
|
+
|
|
75
|
+
`pipx install kiwime` / `uv tool install kiwime` will be the one-line
|
|
76
|
+
install once the packages are published to PyPI and the built UI is bundled
|
|
77
|
+
into the wheel. Until then, use the checkout flow above.
|
|
78
|
+
|
|
79
|
+
## How it relates to the desktop app
|
|
80
|
+
|
|
81
|
+
The React frontend and all Python sidecars are shared. Only the *host* differs:
|
|
82
|
+
the desktop app uses a Rust (Tauri) shell that spawns sidecars as processes;
|
|
83
|
+
this web host holds them in one Python process and serves the same UI over
|
|
84
|
+
HTTP + SSE. `apps/desktop/src/api/tauri.ts` picks the transport at runtime.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
# Published as "kiwime" so `uvx kiwime` / `pipx install kiwime` just work —
|
|
3
|
+
# this is the package users install; everything else is a dependency of it.
|
|
4
|
+
name = "kiwime"
|
|
5
|
+
version = "0.1.0"
|
|
6
|
+
description = "Compile a person into AI-readable expert context. Local-first web runtime + MCP server."
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
license = "Apache-2.0"
|
|
10
|
+
keywords = ["mcp", "ai", "context", "local-first", "knowledge-graph", "memory"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
13
|
+
"Environment :: Web Environment",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"kiwime-sdk==0.1.0",
|
|
22
|
+
"kiwime-store==0.1.0",
|
|
23
|
+
"kiwime-compiler==0.1.0",
|
|
24
|
+
"kiwime-export==0.1.0",
|
|
25
|
+
"kiwime-secret==0.1.0",
|
|
26
|
+
# P0 connectors, imported in-process.
|
|
27
|
+
"kiwime-connector-local-files==0.1.0",
|
|
28
|
+
"kiwime-connector-github==0.1.0",
|
|
29
|
+
"kiwime-connector-notion==0.1.0",
|
|
30
|
+
"kiwime-connector-gdrive==0.1.0",
|
|
31
|
+
"kiwime-connector-gmail==0.1.0",
|
|
32
|
+
"kiwime-connector-slack==0.1.0",
|
|
33
|
+
"kiwime-connector-mcp-client==0.1.0",
|
|
34
|
+
"fastapi>=0.115",
|
|
35
|
+
"uvicorn>=0.34",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://github.com/kiwiberry-ai/kiwime"
|
|
40
|
+
Documentation = "https://github.com/kiwiberry-ai/kiwime/tree/main/docs"
|
|
41
|
+
Changelog = "https://github.com/kiwiberry-ai/kiwime/blob/main/CHANGELOG.md"
|
|
42
|
+
|
|
43
|
+
[project.scripts]
|
|
44
|
+
kiwime = "kiwime_host.__main__:main"
|
|
45
|
+
|
|
46
|
+
[build-system]
|
|
47
|
+
requires = ["hatchling"]
|
|
48
|
+
build-backend = "hatchling.build"
|
|
49
|
+
|
|
50
|
+
# The publish workflow copies the built frontend (apps/desktop/dist) into
|
|
51
|
+
# src/kiwime_host/_ui before `uv build`, so `uvx kiwime` serves the UI with
|
|
52
|
+
# no Node/pnpm on the user's machine. _ui is gitignored; `artifacts` tells
|
|
53
|
+
# hatchling to package it anyway. When absent (dev checkouts) the build
|
|
54
|
+
# still succeeds and _find_dist() falls back to apps/desktop/dist.
|
|
55
|
+
[tool.hatch.build]
|
|
56
|
+
artifacts = ["src/kiwime_host/_ui"]
|
|
57
|
+
|
|
58
|
+
[tool.hatch.build.targets.wheel]
|
|
59
|
+
packages = ["src/kiwime_host"]
|
|
60
|
+
|
|
61
|
+
[tool.uv.sources]
|
|
62
|
+
kiwime-sdk = { workspace = true }
|
|
63
|
+
kiwime-store = { workspace = true }
|
|
64
|
+
kiwime-compiler = { workspace = true }
|
|
65
|
+
kiwime-export = { workspace = true }
|
|
66
|
+
kiwime-secret = { workspace = true }
|
|
67
|
+
kiwime-connector-local-files = { workspace = true }
|
|
68
|
+
kiwime-connector-github = { workspace = true }
|
|
69
|
+
kiwime-connector-notion = { workspace = true }
|
|
70
|
+
kiwime-connector-gdrive = { workspace = true }
|
|
71
|
+
kiwime-connector-gmail = { workspace = true }
|
|
72
|
+
kiwime-connector-slack = { workspace = true }
|
|
73
|
+
kiwime-connector-mcp-client = { workspace = true }
|
|
74
|
+
|
|
75
|
+
[dependency-groups]
|
|
76
|
+
dev = [
|
|
77
|
+
"pytest>=8.0",
|
|
78
|
+
"pytest-asyncio>=0.24",
|
|
79
|
+
"httpx>=0.27",
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
[tool.pytest.ini_options]
|
|
83
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""kiwiMe web host (distribution route B).
|
|
2
|
+
|
|
3
|
+
In-process FastAPI runtime: holds every sidecar's Server instance in one
|
|
4
|
+
process and routes UI requests to ``Server.dispatch(method, params)`` directly
|
|
5
|
+
— no child processes, no stdio JSON-RPC. Replaces the Rust supervisor's
|
|
6
|
+
process orchestration with an in-process method table.
|
|
7
|
+
|
|
8
|
+
See docs/release/webification-plan.md.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from kiwime_host.host import InProcessHost
|
|
12
|
+
|
|
13
|
+
__all__ = ["InProcessHost"]
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""``kiwime`` entry point — start the local web host.
|
|
2
|
+
|
|
3
|
+
kiwime [--host 127.0.0.1] [--port 8765] [--db PATH] [--no-browser]
|
|
4
|
+
|
|
5
|
+
Binds to loopback by default (local-first; no remote access in v1), serves the
|
|
6
|
+
built UI + the /api surface, and opens the browser. This is the pipx-installed
|
|
7
|
+
command users run (route B).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import sys
|
|
14
|
+
import threading
|
|
15
|
+
import webbrowser
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _find_dist() -> Path | None:
|
|
20
|
+
"""Locate the built frontend.
|
|
21
|
+
|
|
22
|
+
Resolution order:
|
|
23
|
+
1. $KIWIME_DIST — explicit override (packaging / unusual layouts).
|
|
24
|
+
2. kiwime_host/_ui — bundled into the wheel by the publish workflow;
|
|
25
|
+
this is what `uvx kiwime` / `pipx install kiwime` hits.
|
|
26
|
+
3. apps/desktop/dist relative to the repo root (source checkout).
|
|
27
|
+
4. cwd/apps/desktop/dist — running from a clone without installing.
|
|
28
|
+
|
|
29
|
+
Returns None if no build is found; the host still serves /api so the UI
|
|
30
|
+
can be run separately (e.g. `vite dev`) against it during development.
|
|
31
|
+
"""
|
|
32
|
+
import os
|
|
33
|
+
|
|
34
|
+
env = os.environ.get("KIWIME_DIST")
|
|
35
|
+
if env:
|
|
36
|
+
p = Path(env)
|
|
37
|
+
return p if p.is_dir() else None
|
|
38
|
+
|
|
39
|
+
here = Path(__file__).resolve()
|
|
40
|
+
candidates = [
|
|
41
|
+
here.parent / "_ui", # bundled in the wheel
|
|
42
|
+
here.parents[4] / "apps" / "desktop" / "dist", # source checkout
|
|
43
|
+
Path.cwd() / "apps" / "desktop" / "dist", # run from repo root
|
|
44
|
+
]
|
|
45
|
+
for c in candidates:
|
|
46
|
+
# index.html (not just the dir) so a stray empty _ui doesn't win.
|
|
47
|
+
if (c / "index.html").is_file():
|
|
48
|
+
return c
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def main(argv: list[str] | None = None) -> int:
|
|
53
|
+
parser = argparse.ArgumentParser(prog="kiwime")
|
|
54
|
+
parser.add_argument("--host", default="127.0.0.1", help="Bind address (loopback only by default).")
|
|
55
|
+
parser.add_argument("--port", type=int, default=8765)
|
|
56
|
+
parser.add_argument("--db", default=None, help="Store SQLite path (default: platform app dir).")
|
|
57
|
+
parser.add_argument("--no-browser", action="store_true", help="Do not open a browser on start.")
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"--sync-interval",
|
|
60
|
+
type=int,
|
|
61
|
+
default=15,
|
|
62
|
+
metavar="MINUTES",
|
|
63
|
+
help="Incremental-sync + auto-compile cadence in minutes (0 disables).",
|
|
64
|
+
)
|
|
65
|
+
args = parser.parse_args(argv)
|
|
66
|
+
|
|
67
|
+
import uvicorn
|
|
68
|
+
|
|
69
|
+
from kiwime_host.host import InProcessHost
|
|
70
|
+
from kiwime_host.web import create_app, mint_token
|
|
71
|
+
|
|
72
|
+
host = InProcessHost(db_path=args.db)
|
|
73
|
+
# Ensure the schema exists before serving (idempotent).
|
|
74
|
+
import asyncio
|
|
75
|
+
|
|
76
|
+
asyncio.run(host.call("store", "store.migrate", {}))
|
|
77
|
+
|
|
78
|
+
token = mint_token()
|
|
79
|
+
app = create_app(
|
|
80
|
+
host,
|
|
81
|
+
dist_dir=_find_dist(),
|
|
82
|
+
token=token,
|
|
83
|
+
sync_interval=args.sync_interval * 60 if args.sync_interval > 0 else None,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
url = f"http://{args.host}:{args.port}"
|
|
87
|
+
auth_url = f"{url}/auth?token={token}"
|
|
88
|
+
print(f"kiwiMe web host running at {url}", file=sys.stderr)
|
|
89
|
+
if args.no_browser:
|
|
90
|
+
# No browser will pick up the session cookie, so hand the user the
|
|
91
|
+
# authenticated entry link (and the token for curl/scripts).
|
|
92
|
+
print(f"open {auth_url} to start a session", file=sys.stderr)
|
|
93
|
+
else:
|
|
94
|
+
threading.Timer(1.0, lambda: webbrowser.open(auth_url)).start()
|
|
95
|
+
|
|
96
|
+
uvicorn.run(app, host=args.host, port=args.port, log_level="warning")
|
|
97
|
+
host.close()
|
|
98
|
+
return 0
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
if __name__ == "__main__":
|
|
102
|
+
sys.exit(main())
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function t(n,e,i,s){if(typeof e=="function"?n!==e||!s:!e.has(n))throw new TypeError("Cannot read private member from an object whose class did not declare it");return i==="m"?s:i==="a"?s.call(n):s?s.value:e.get(n)}function c(n,e,i,s,_){if(typeof e=="function"?n!==e||!0:!e.has(n))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(n,i),i}var a,r,o,l,u;const d="__TAURI_TO_IPC_KEY__";function w(n,e=!1){return window.__TAURI_INTERNALS__.transformCallback(n,e)}class g{constructor(e){a.set(this,void 0),r.set(this,0),o.set(this,[]),l.set(this,void 0),c(this,a,e||(()=>{})),this.id=w(i=>{const s=i.index;if("end"in i){s==t(this,r,"f")?this.cleanupCallback():c(this,l,s);return}const _=i.message;if(s==t(this,r,"f")){for(t(this,a,"f").call(this,_),c(this,r,t(this,r,"f")+1);t(this,r,"f")in t(this,o,"f");){const p=t(this,o,"f")[t(this,r,"f")];t(this,a,"f").call(this,p),delete t(this,o,"f")[t(this,r,"f")],c(this,r,t(this,r,"f")+1)}t(this,r,"f")===t(this,l,"f")&&this.cleanupCallback()}else t(this,o,"f")[s]=_})}cleanupCallback(){window.__TAURI_INTERNALS__.unregisterCallback(this.id)}set onmessage(e){c(this,a,e)}get onmessage(){return t(this,a,"f")}[(a=new WeakMap,r=new WeakMap,o=new WeakMap,l=new WeakMap,d)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[d]()}}class f{constructor(e,i,s){this.plugin=e,this.event=i,this.channelId=s}async unregister(){return h(`plugin:${this.plugin}|remove_listener`,{event:this.event,channelId:this.channelId})}}async function m(n,e,i){const s=new g(i);try{return await h(`plugin:${n}|register_listener`,{event:e,handler:s}),new f(n,e,s.id)}catch{return await h(`plugin:${n}|registerListener`,{event:e,handler:s}),new f(n,e,s.id)}}async function I(n){return h(`plugin:${n}|check_permissions`)}async function C(n){return h(`plugin:${n}|request_permissions`)}async function h(n,e={},i){return window.__TAURI_INTERNALS__.invoke(n,e,i)}function T(n,e="asset"){return window.__TAURI_INTERNALS__.convertFileSrc(n,e)}class k{get rid(){return t(this,u,"f")}constructor(e){u.set(this,void 0),c(this,u,e)}async close(){return h("plugin:resources|close",{rid:this.rid})}}u=new WeakMap;function E(){return!!(globalThis||window).isTauri}export{g as Channel,f as PluginListener,k as Resource,d as SERIALIZE_TO_IPC_FN,m as addPluginListener,I as checkPermissions,T as convertFileSrc,h as invoke,E as isTauri,C as requestPermissions,w as transformCallback};
|
|
2
|
+
//# sourceMappingURL=core-DhEqZVGG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core-DhEqZVGG.js","sources":["../../../../node_modules/@tauri-apps/api/external/tslib/tslib.es6.js","../../../../node_modules/@tauri-apps/api/core.js"],"sourcesContent":["/******************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise, SuppressedError, Symbol, Iterator */\r\n\r\n\r\nfunction __classPrivateFieldGet(receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n}\r\n\r\nfunction __classPrivateFieldSet(receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n}\r\n\r\ntypeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\r\n var e = new Error(message);\r\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\r\n};\n\nexport { __classPrivateFieldGet, __classPrivateFieldSet };\n","import { __classPrivateFieldSet, __classPrivateFieldGet } from './external/tslib/tslib.es6.js';\n\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\nvar _Channel_onmessage, _Channel_nextMessageIndex, _Channel_pendingMessages, _Channel_messageEndIndex, _Resource_rid;\n/**\n * Invoke your custom commands.\n *\n * This package is also accessible with `window.__TAURI__.core` when [`app.withGlobalTauri`](https://v2.tauri.app/reference/config/#withglobaltauri) in `tauri.conf.json` is set to `true`.\n * @module\n */\n/**\n * A key to be used to implement a special function\n * on your types that define how your type should be serialized\n * when passing across the IPC.\n * @example\n * Given a type in Rust that looks like this\n * ```rs\n * #[derive(serde::Serialize, serde::Deserialize)\n * enum UserId {\n * String(String),\n * Number(u32),\n * }\n * ```\n * `UserId::String(\"id\")` would be serialized into `{ String: \"id\" }`\n * and so we need to pass the same structure back to Rust\n * ```ts\n * import { SERIALIZE_TO_IPC_FN } from \"@tauri-apps/api/core\"\n *\n * class UserIdString {\n * id\n * constructor(id) {\n * this.id = id\n * }\n *\n * [SERIALIZE_TO_IPC_FN]() {\n * return { String: this.id }\n * }\n * }\n *\n * class UserIdNumber {\n * id\n * constructor(id) {\n * this.id = id\n * }\n *\n * [SERIALIZE_TO_IPC_FN]() {\n * return { Number: this.id }\n * }\n * }\n *\n * type UserId = UserIdString | UserIdNumber\n * ```\n *\n */\n// if this value changes, make sure to update it in:\n// 1. ipc.js\n// 2. process-ipc-message-fn.js\nconst SERIALIZE_TO_IPC_FN = '__TAURI_TO_IPC_KEY__';\n/**\n * Stores the callback in a known location, and returns an identifier that can be passed to the backend.\n * The backend uses the identifier to `eval()` the callback.\n *\n * @return An unique identifier associated with the callback function.\n *\n * @since 1.0.0\n */\nfunction transformCallback(\n// TODO: Make this not optional in v3\ncallback, once = false) {\n return window.__TAURI_INTERNALS__.transformCallback(callback, once);\n}\nclass Channel {\n constructor(onmessage) {\n _Channel_onmessage.set(this, void 0);\n // the index is used as a mechanism to preserve message order\n _Channel_nextMessageIndex.set(this, 0);\n _Channel_pendingMessages.set(this, []);\n _Channel_messageEndIndex.set(this, void 0);\n __classPrivateFieldSet(this, _Channel_onmessage, onmessage || (() => { }), \"f\");\n this.id = transformCallback((rawMessage) => {\n const index = rawMessage.index;\n if ('end' in rawMessage) {\n if (index == __classPrivateFieldGet(this, _Channel_nextMessageIndex, \"f\")) {\n this.cleanupCallback();\n }\n else {\n __classPrivateFieldSet(this, _Channel_messageEndIndex, index, \"f\");\n }\n return;\n }\n const message = rawMessage.message;\n // Process the message if we're at the right order\n if (index == __classPrivateFieldGet(this, _Channel_nextMessageIndex, \"f\")) {\n __classPrivateFieldGet(this, _Channel_onmessage, \"f\").call(this, message);\n __classPrivateFieldSet(this, _Channel_nextMessageIndex, __classPrivateFieldGet(this, _Channel_nextMessageIndex, \"f\") + 1, \"f\");\n // process pending messages\n while (__classPrivateFieldGet(this, _Channel_nextMessageIndex, \"f\") in __classPrivateFieldGet(this, _Channel_pendingMessages, \"f\")) {\n const message = __classPrivateFieldGet(this, _Channel_pendingMessages, \"f\")[__classPrivateFieldGet(this, _Channel_nextMessageIndex, \"f\")];\n __classPrivateFieldGet(this, _Channel_onmessage, \"f\").call(this, message);\n // eslint-disable-next-line @typescript-eslint/no-array-delete\n delete __classPrivateFieldGet(this, _Channel_pendingMessages, \"f\")[__classPrivateFieldGet(this, _Channel_nextMessageIndex, \"f\")];\n __classPrivateFieldSet(this, _Channel_nextMessageIndex, __classPrivateFieldGet(this, _Channel_nextMessageIndex, \"f\") + 1, \"f\");\n }\n if (__classPrivateFieldGet(this, _Channel_nextMessageIndex, \"f\") === __classPrivateFieldGet(this, _Channel_messageEndIndex, \"f\")) {\n this.cleanupCallback();\n }\n }\n // Queue the message if we're not\n else {\n // eslint-disable-next-line security/detect-object-injection\n __classPrivateFieldGet(this, _Channel_pendingMessages, \"f\")[index] = message;\n }\n });\n }\n cleanupCallback() {\n window.__TAURI_INTERNALS__.unregisterCallback(this.id);\n }\n set onmessage(handler) {\n __classPrivateFieldSet(this, _Channel_onmessage, handler, \"f\");\n }\n get onmessage() {\n return __classPrivateFieldGet(this, _Channel_onmessage, \"f\");\n }\n [(_Channel_onmessage = new WeakMap(), _Channel_nextMessageIndex = new WeakMap(), _Channel_pendingMessages = new WeakMap(), _Channel_messageEndIndex = new WeakMap(), SERIALIZE_TO_IPC_FN)]() {\n return `__CHANNEL__:${this.id}`;\n }\n toJSON() {\n // eslint-disable-next-line security/detect-object-injection\n return this[SERIALIZE_TO_IPC_FN]();\n }\n}\nclass PluginListener {\n constructor(plugin, event, channelId) {\n this.plugin = plugin;\n this.event = event;\n this.channelId = channelId;\n }\n async unregister() {\n return invoke(`plugin:${this.plugin}|remove_listener`, {\n event: this.event,\n channelId: this.channelId\n });\n }\n}\n/**\n * Adds a listener to a plugin event.\n *\n * @returns The listener object to stop listening to the events.\n *\n * @since 2.0.0\n */\nasync function addPluginListener(plugin, event, cb) {\n const handler = new Channel(cb);\n try {\n await invoke(`plugin:${plugin}|register_listener`, {\n event,\n handler\n });\n return new PluginListener(plugin, event, handler.id);\n }\n catch {\n // TODO(v3): remove this fallback\n // note: we must try with camelCase here for backwards compatibility\n await invoke(`plugin:${plugin}|registerListener`, { event, handler });\n return new PluginListener(plugin, event, handler.id);\n }\n}\n/**\n * Get permission state for a plugin.\n *\n * This should be used by plugin authors to wrap their actual implementation.\n */\nasync function checkPermissions(plugin) {\n return invoke(`plugin:${plugin}|check_permissions`);\n}\n/**\n * Request permissions.\n *\n * This should be used by plugin authors to wrap their actual implementation.\n */\nasync function requestPermissions(plugin) {\n return invoke(`plugin:${plugin}|request_permissions`);\n}\n/**\n * Sends a message to the backend.\n * @example\n * ```typescript\n * import { invoke } from '@tauri-apps/api/core';\n * await invoke('login', { user: 'tauri', password: 'poiwe3h4r5ip3yrhtew9ty' });\n * ```\n *\n * @param cmd The command name.\n * @param args The optional arguments to pass to the command.\n * @param options The request options.\n * @return A promise resolving or rejecting to the backend response.\n *\n * @since 1.0.0\n */\nasync function invoke(cmd, args = {}, options) {\n return window.__TAURI_INTERNALS__.invoke(cmd, args, options);\n}\n/**\n * Convert a device file path to an URL that can be loaded by the webview.\n * Note that `asset:` and `http://asset.localhost` must be added to [`app.security.csp`](https://v2.tauri.app/reference/config/#csp-1) in `tauri.conf.json`.\n * Example CSP value: `\"csp\": \"default-src 'self' ipc: http://ipc.localhost; img-src 'self' asset: http://asset.localhost\"` to use the asset protocol on image sources.\n *\n * Additionally, `\"enable\" : \"true\"` must be added to [`app.security.assetProtocol`](https://v2.tauri.app/reference/config/#assetprotocolconfig)\n * in `tauri.conf.json` and its access scope must be defined on the `scope` array on the same `assetProtocol` object.\n *\n * @param filePath The file path.\n * @param protocol The protocol to use. Defaults to `asset`. You only need to set this when using a custom protocol.\n * @example\n * ```typescript\n * import { appDataDir, join } from '@tauri-apps/api/path';\n * import { convertFileSrc } from '@tauri-apps/api/core';\n * const appDataDirPath = await appDataDir();\n * const filePath = await join(appDataDirPath, 'assets/video.mp4');\n * const assetUrl = convertFileSrc(filePath);\n *\n * const video = document.getElementById('my-video');\n * const source = document.createElement('source');\n * source.type = 'video/mp4';\n * source.src = assetUrl;\n * video.appendChild(source);\n * video.load();\n * ```\n *\n * @return the URL that can be used as source on the webview.\n *\n * @since 1.0.0\n */\nfunction convertFileSrc(filePath, protocol = 'asset') {\n return window.__TAURI_INTERNALS__.convertFileSrc(filePath, protocol);\n}\n/**\n * A rust-backed resource stored through `tauri::Manager::resources_table` API.\n *\n * The resource lives in the main process and does not exist\n * in the Javascript world, and thus will not be cleaned up automatically\n * except on application exit. If you want to clean it up early, call {@linkcode Resource.close}\n *\n * @example\n * ```typescript\n * import { Resource, invoke } from '@tauri-apps/api/core';\n * export class DatabaseHandle extends Resource {\n * static async open(path: string): Promise<DatabaseHandle> {\n * const rid: number = await invoke('open_db', { path });\n * return new DatabaseHandle(rid);\n * }\n *\n * async execute(sql: string): Promise<void> {\n * await invoke('execute_sql', { rid: this.rid, sql });\n * }\n * }\n * ```\n */\nclass Resource {\n get rid() {\n return __classPrivateFieldGet(this, _Resource_rid, \"f\");\n }\n constructor(rid) {\n _Resource_rid.set(this, void 0);\n __classPrivateFieldSet(this, _Resource_rid, rid, \"f\");\n }\n /**\n * Destroys and cleans up this resource from memory.\n * **You should not call any method on this object anymore and should drop any reference to it.**\n */\n async close() {\n return invoke('plugin:resources|close', {\n rid: this.rid\n });\n }\n}\n_Resource_rid = new WeakMap();\nfunction isTauri() {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access\n return !!(globalThis || window).isTauri;\n}\n\nexport { Channel, PluginListener, Resource, SERIALIZE_TO_IPC_FN, addPluginListener, checkPermissions, convertFileSrc, invoke, isTauri, requestPermissions, transformCallback };\n"],"names":["__classPrivateFieldGet","receiver","state","kind","f","__classPrivateFieldSet","value","_Channel_onmessage","_Channel_nextMessageIndex","_Channel_pendingMessages","_Channel_messageEndIndex","_Resource_rid","SERIALIZE_TO_IPC_FN","transformCallback","callback","once","Channel","onmessage","rawMessage","index","message","handler","PluginListener","plugin","event","channelId","invoke","addPluginListener","cb","checkPermissions","requestPermissions","cmd","args","options","convertFileSrc","filePath","protocol","Resource","rid","isTauri"],"mappings":"AAiBA,SAASA,EAAuBC,EAAUC,EAAOC,EAAMC,EAAG,CAEtD,GAAI,OAAOF,GAAU,WAAaD,IAAaC,GAAS,CAACE,EAAI,CAACF,EAAM,IAAID,CAAQ,EAAG,MAAM,IAAI,UAAU,0EAA0E,EACjL,OAAOE,IAAS,IAAMC,EAAID,IAAS,IAAMC,EAAE,KAAKH,CAAQ,EAAIG,EAAIA,EAAE,MAAQF,EAAM,IAAID,CAAQ,CAChG,CAEA,SAASI,EAAuBJ,EAAUC,EAAOI,EAAOH,EAAMC,EAAG,CAG7D,GAAI,OAAOF,GAAU,WAAaD,IAAaC,GAAS,GAAK,CAACA,EAAM,IAAID,CAAQ,EAAG,MAAM,IAAI,UAAU,yEAAyE,EAChL,OAAuEC,EAAM,IAAID,EAAUK,CAAK,EAAIA,CACxG,CCvBA,IAAIC,EAAoBC,EAA2BC,EAA0BC,EAA0BC,EAsDlG,MAACC,EAAsB,uBAS5B,SAASC,EAETC,EAAUC,EAAO,GAAO,CACpB,OAAO,OAAO,oBAAoB,kBAAkBD,EAAUC,CAAI,CACtE,CACA,MAAMC,CAAQ,CACV,YAAYC,EAAW,CACnBV,EAAmB,IAAI,KAAM,MAAM,EAEnCC,EAA0B,IAAI,KAAM,CAAC,EACrCC,EAAyB,IAAI,KAAM,EAAE,EACrCC,EAAyB,IAAI,KAAM,MAAM,EACzCL,EAAuB,KAAME,EAAoBU,IAAc,IAAM,CAAE,EAAO,EAC9E,KAAK,GAAKJ,EAAmBK,GAAe,CACxC,MAAMC,EAAQD,EAAW,MACzB,GAAI,QAASA,EAAY,CACjBC,GAASnB,EAAuB,KAAMQ,EAA2B,GAAG,EACpE,KAAK,gBAAe,EAGpBH,EAAuB,KAAMK,EAA0BS,CAAU,EAErE,MACJ,CACA,MAAMC,EAAUF,EAAW,QAE3B,GAAIC,GAASnB,EAAuB,KAAMQ,EAA2B,GAAG,EAAG,CAIvE,IAHAR,EAAuB,KAAMO,EAAoB,GAAG,EAAE,KAAK,KAAMa,CAAO,EACxEf,EAAuB,KAAMG,EAA2BR,EAAuB,KAAMQ,EAA2B,GAAG,EAAI,CAAM,EAEtHR,EAAuB,KAAMQ,EAA2B,GAAG,IAAKR,EAAuB,KAAMS,EAA0B,GAAG,GAAG,CAChI,MAAMW,EAAUpB,EAAuB,KAAMS,EAA0B,GAAG,EAAET,EAAuB,KAAMQ,EAA2B,GAAG,CAAC,EACxIR,EAAuB,KAAMO,EAAoB,GAAG,EAAE,KAAK,KAAMa,CAAO,EAExE,OAAOpB,EAAuB,KAAMS,EAA0B,GAAG,EAAET,EAAuB,KAAMQ,EAA2B,GAAG,CAAC,EAC/HH,EAAuB,KAAMG,EAA2BR,EAAuB,KAAMQ,EAA2B,GAAG,EAAI,CAAM,CACjI,CACIR,EAAuB,KAAMQ,EAA2B,GAAG,IAAMR,EAAuB,KAAMU,EAA0B,GAAG,GAC3H,KAAK,gBAAe,CAE5B,MAIIV,EAAuB,KAAMS,EAA0B,GAAG,EAAEU,CAAK,EAAIC,CAE7E,CAAC,CACL,CACA,iBAAkB,CACd,OAAO,oBAAoB,mBAAmB,KAAK,EAAE,CACzD,CACA,IAAI,UAAUC,EAAS,CACnBhB,EAAuB,KAAME,EAAoBc,CAAY,CACjE,CACA,IAAI,WAAY,CACZ,OAAOrB,EAAuB,KAAMO,EAAoB,GAAG,CAC/D,CACA,EAAEA,EAAqB,IAAI,QAAWC,EAA4B,IAAI,QAAWC,EAA2B,IAAI,QAAWC,EAA2B,IAAI,QAAWE,EAAmB,GAAK,CACzL,MAAO,eAAe,KAAK,EAAE,EACjC,CACA,QAAS,CAEL,OAAO,KAAKA,CAAmB,EAAC,CACpC,CACJ,CACA,MAAMU,CAAe,CACjB,YAAYC,EAAQC,EAAOC,EAAW,CAClC,KAAK,OAASF,EACd,KAAK,MAAQC,EACb,KAAK,UAAYC,CACrB,CACA,MAAM,YAAa,CACf,OAAOC,EAAO,UAAU,KAAK,MAAM,mBAAoB,CACnD,MAAO,KAAK,MACZ,UAAW,KAAK,SAC5B,CAAS,CACL,CACJ,CAQA,eAAeC,EAAkBJ,EAAQC,EAAOI,EAAI,CAChD,MAAMP,EAAU,IAAIL,EAAQY,CAAE,EAC9B,GAAI,CACA,aAAMF,EAAO,UAAUH,CAAM,qBAAsB,CAC/C,MAAAC,EACA,QAAAH,CACZ,CAAS,EACM,IAAIC,EAAeC,EAAQC,EAAOH,EAAQ,EAAE,CACvD,MACM,CAGF,aAAMK,EAAO,UAAUH,CAAM,oBAAqB,CAAE,MAAAC,EAAO,QAAAH,EAAS,EAC7D,IAAIC,EAAeC,EAAQC,EAAOH,EAAQ,EAAE,CACvD,CACJ,CAMA,eAAeQ,EAAiBN,EAAQ,CACpC,OAAOG,EAAO,UAAUH,CAAM,oBAAoB,CACtD,CAMA,eAAeO,EAAmBP,EAAQ,CACtC,OAAOG,EAAO,UAAUH,CAAM,sBAAsB,CACxD,CAgBA,eAAeG,EAAOK,EAAKC,EAAO,CAAA,EAAIC,EAAS,CAC3C,OAAO,OAAO,oBAAoB,OAAOF,EAAKC,EAAMC,CAAO,CAC/D,CA+BA,SAASC,EAAeC,EAAUC,EAAW,QAAS,CAClD,OAAO,OAAO,oBAAoB,eAAeD,EAAUC,CAAQ,CACvE,CAuBA,MAAMC,CAAS,CACX,IAAI,KAAM,CACN,OAAOrC,EAAuB,KAAMW,EAAe,GAAG,CAC1D,CACA,YAAY2B,EAAK,CACb3B,EAAc,IAAI,KAAM,MAAM,EAC9BN,EAAuB,KAAMM,EAAe2B,CAAQ,CACxD,CAKA,MAAM,OAAQ,CACV,OAAOZ,EAAO,yBAA0B,CACpC,IAAK,KAAK,GACtB,CAAS,CACL,CACJ,CACAf,EAAgB,IAAI,QACpB,SAAS4B,GAAU,CAEf,MAAO,CAAC,EAAE,YAAc,QAAQ,OACpC","x_google_ignoreList":[0,1]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{invoke as n,transformCallback as d}from"./core-DhEqZVGG.js";var i;(function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_CREATED="tauri://window-created",e.WINDOW_SUSPENDED="tauri://suspended",e.WINDOW_RESUMED="tauri://resumed",e.WEBVIEW_CREATED="tauri://webview-created",e.DRAG_ENTER="tauri://drag-enter",e.DRAG_OVER="tauri://drag-over",e.DRAG_DROP="tauri://drag-drop",e.DRAG_LEAVE="tauri://drag-leave"})(i||(i={}));async function _(e,r){window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(e,r),await n("plugin:event|unlisten",{event:e,eventId:r})}async function u(e,r,t){var a;const l=typeof t?.target=="string"?{kind:"AnyLabel",label:t.target}:(a=t?.target)!==null&&a!==void 0?a:{kind:"Any"};return n("plugin:event|listen",{event:e,target:l,handler:d(r)}).then(D=>async()=>_(e,D))}async function c(e,r,t){return u(e,a=>{_(e,a.id),r(a)},t)}async function o(e,r){await n("plugin:event|emit",{event:e,payload:r})}async function s(e,r,t){await n("plugin:event|emit_to",{target:typeof e=="string"?{kind:"AnyLabel",label:e}:e,event:r,payload:t})}export{i as TauriEvent,o as emit,s as emitTo,u as listen,c as once};
|
|
2
|
+
//# sourceMappingURL=event-BK_86lmQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-BK_86lmQ.js","sources":["../../../../node_modules/@tauri-apps/api/event.js"],"sourcesContent":["import { invoke, transformCallback } from './core.js';\n\n// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n/**\n * The event system allows you to emit events to the backend and listen to events from it.\n *\n * This package is also accessible with `window.__TAURI__.event` when [`app.withGlobalTauri`](https://v2.tauri.app/reference/config/#withglobaltauri) in `tauri.conf.json` is set to `true`.\n * @module\n */\n/**\n * @since 1.1.0\n */\nvar TauriEvent;\n(function (TauriEvent) {\n TauriEvent[\"WINDOW_RESIZED\"] = \"tauri://resize\";\n TauriEvent[\"WINDOW_MOVED\"] = \"tauri://move\";\n TauriEvent[\"WINDOW_CLOSE_REQUESTED\"] = \"tauri://close-requested\";\n TauriEvent[\"WINDOW_DESTROYED\"] = \"tauri://destroyed\";\n TauriEvent[\"WINDOW_FOCUS\"] = \"tauri://focus\";\n TauriEvent[\"WINDOW_BLUR\"] = \"tauri://blur\";\n TauriEvent[\"WINDOW_SCALE_FACTOR_CHANGED\"] = \"tauri://scale-change\";\n TauriEvent[\"WINDOW_THEME_CHANGED\"] = \"tauri://theme-changed\";\n TauriEvent[\"WINDOW_CREATED\"] = \"tauri://window-created\";\n TauriEvent[\"WINDOW_SUSPENDED\"] = \"tauri://suspended\";\n TauriEvent[\"WINDOW_RESUMED\"] = \"tauri://resumed\";\n TauriEvent[\"WEBVIEW_CREATED\"] = \"tauri://webview-created\";\n TauriEvent[\"DRAG_ENTER\"] = \"tauri://drag-enter\";\n TauriEvent[\"DRAG_OVER\"] = \"tauri://drag-over\";\n TauriEvent[\"DRAG_DROP\"] = \"tauri://drag-drop\";\n TauriEvent[\"DRAG_LEAVE\"] = \"tauri://drag-leave\";\n})(TauriEvent || (TauriEvent = {}));\n/**\n * Unregister the event listener associated with the given name and id.\n *\n * @ignore\n * @param event The event name\n * @param eventId Event identifier\n * @returns\n */\nasync function _unlisten(event, eventId) {\n window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(event, eventId);\n await invoke('plugin:event|unlisten', {\n event,\n eventId\n });\n}\n/**\n * Listen to an emitted event to any {@link EventTarget|target}.\n *\n * @example\n * ```typescript\n * import { listen } from '@tauri-apps/api/event';\n * const unlisten = await listen<string>('error', (event) => {\n * console.log(`Got error, payload: ${event.payload}`);\n * });\n *\n * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted\n * unlisten();\n * ```\n *\n * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.\n * @param handler Event handler callback.\n * @param options Event listening options.\n * @returns A promise resolving to a function to unlisten to the event.\n * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.\n *\n * @since 1.0.0\n */\nasync function listen(event, handler, options) {\n var _a;\n const target = typeof (options === null || options === void 0 ? void 0 : options.target) === 'string'\n ? { kind: 'AnyLabel', label: options.target }\n : ((_a = options === null || options === void 0 ? void 0 : options.target) !== null && _a !== void 0 ? _a : { kind: 'Any' });\n return invoke('plugin:event|listen', {\n event,\n target,\n handler: transformCallback(handler)\n }).then((eventId) => {\n return async () => _unlisten(event, eventId);\n });\n}\n/**\n * Listens once to an emitted event to any {@link EventTarget|target}.\n *\n * @example\n * ```typescript\n * import { once } from '@tauri-apps/api/event';\n * interface LoadedPayload {\n * loggedIn: boolean,\n * token: string\n * }\n * const unlisten = await once<LoadedPayload>('loaded', (event) => {\n * console.log(`App is loaded, loggedIn: ${event.payload.loggedIn}, token: ${event.payload.token}`);\n * });\n *\n * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted\n * unlisten();\n * ```\n *\n * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.\n * @param handler Event handler callback.\n * @param options Event listening options.\n * @returns A promise resolving to a function to unlisten to the event.\n * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.\n *\n * @since 1.0.0\n */\nasync function once(event, handler, options) {\n return listen(event, (eventData) => {\n void _unlisten(event, eventData.id);\n handler(eventData);\n }, options);\n}\n/**\n * Emits an event to all {@link EventTarget|targets}.\n *\n * @example\n * ```typescript\n * import { emit } from '@tauri-apps/api/event';\n * await emit('frontend-loaded', { loggedIn: true, token: 'authToken' });\n * ```\n *\n * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.\n * @param payload Event payload.\n *\n * @since 1.0.0\n */\nasync function emit(event, payload) {\n await invoke('plugin:event|emit', {\n event,\n payload\n });\n}\n/**\n * Emits an event to all {@link EventTarget|targets} matching the given target.\n *\n * @example\n * ```typescript\n * import { emitTo } from '@tauri-apps/api/event';\n * await emitTo('main', 'frontend-loaded', { loggedIn: true, token: 'authToken' });\n * ```\n *\n * @param target Label of the target Window/Webview/WebviewWindow or raw {@link EventTarget} object.\n * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.\n * @param payload Event payload.\n *\n * @since 2.0.0\n */\nasync function emitTo(target, event, payload) {\n const eventTarget = typeof target === 'string' ? { kind: 'AnyLabel', label: target } : target;\n await invoke('plugin:event|emit_to', {\n target: eventTarget,\n event,\n payload\n });\n}\n\nexport { TauriEvent, emit, emitTo, listen, once };\n"],"names":["TauriEvent","_unlisten","event","eventId","invoke","listen","handler","options","_a","target","transformCallback","once","eventData","emit","payload","emitTo"],"mappings":"mEAcG,IAACA,GACH,SAAUA,EAAY,CACnBA,EAAW,eAAoB,iBAC/BA,EAAW,aAAkB,eAC7BA,EAAW,uBAA4B,0BACvCA,EAAW,iBAAsB,oBACjCA,EAAW,aAAkB,gBAC7BA,EAAW,YAAiB,eAC5BA,EAAW,4BAAiC,uBAC5CA,EAAW,qBAA0B,wBACrCA,EAAW,eAAoB,yBAC/BA,EAAW,iBAAsB,oBACjCA,EAAW,eAAoB,kBAC/BA,EAAW,gBAAqB,0BAChCA,EAAW,WAAgB,qBAC3BA,EAAW,UAAe,oBAC1BA,EAAW,UAAe,oBAC1BA,EAAW,WAAgB,oBAC/B,GAAGA,IAAeA,EAAa,CAAA,EAAG,EASlC,eAAeC,EAAUC,EAAOC,EAAS,CACrC,OAAO,iCAAiC,mBAAmBD,EAAOC,CAAO,EACzE,MAAMC,EAAO,wBAAyB,CAClC,MAAAF,EACA,QAAAC,CACR,CAAK,CACL,CAuBA,eAAeE,EAAOH,EAAOI,EAASC,EAAS,CAC3C,IAAIC,EACJ,MAAMC,EAAS,OAA0DF,GAAQ,QAAY,SACvF,CAAE,KAAM,WAAY,MAAOA,EAAQ,MAAM,GACvCC,EAAuDD,GAAQ,UAAY,MAAQC,IAAO,OAASA,EAAK,CAAE,KAAM,OACxH,OAAOJ,EAAO,sBAAuB,CACjC,MAAAF,EACA,OAAAO,EACA,QAASC,EAAkBJ,CAAO,CAC1C,CAAK,EAAE,KAAMH,GACE,SAAYF,EAAUC,EAAOC,CAAO,CAC9C,CACL,CA2BA,eAAeQ,EAAKT,EAAOI,EAASC,EAAS,CACzC,OAAOF,EAAOH,EAAQU,GAAc,CAC3BX,EAAUC,EAAOU,EAAU,EAAE,EAClCN,EAAQM,CAAS,CACrB,EAAGL,CAAO,CACd,CAeA,eAAeM,EAAKX,EAAOY,EAAS,CAChC,MAAMV,EAAO,oBAAqB,CAC9B,MAAAF,EACA,QAAAY,CACR,CAAK,CACL,CAgBA,eAAeC,EAAON,EAAQP,EAAOY,EAAS,CAE1C,MAAMV,EAAO,uBAAwB,CACjC,OAFgB,OAAOK,GAAW,SAAW,CAAE,KAAM,WAAY,MAAOA,CAAM,EAAKA,EAGnF,MAAAP,EACA,QAAAY,CACR,CAAK,CACL","x_google_ignoreList":[0]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-C4lDuuOm.js","sources":["../../../../node_modules/@tauri-apps/plugin-dialog/dist-js/index.js"],"sourcesContent":["import { invoke } from '@tauri-apps/api/core';\n\n// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n/**\n * Internal function to convert the buttons to the Rust type.\n */\nfunction buttonsToRust(buttons) {\n if (buttons === undefined) {\n return undefined;\n }\n if (typeof buttons === 'string') {\n return buttons;\n }\n else if ('ok' in buttons && 'cancel' in buttons) {\n return { OkCancelCustom: [buttons.ok, buttons.cancel] };\n }\n else if ('yes' in buttons && 'no' in buttons && 'cancel' in buttons) {\n return {\n YesNoCancelCustom: [buttons.yes, buttons.no, buttons.cancel]\n };\n }\n else if ('ok' in buttons) {\n return { OkCustom: buttons.ok };\n }\n return undefined;\n}\n/**\n * Open a file/directory selection dialog.\n *\n * The selected paths are added to the filesystem and asset protocol scopes.\n * When security is more important than the easy of use of this API,\n * prefer writing a dedicated command instead.\n *\n * Note that the scope change is not persisted, so the values are cleared when the application is restarted.\n * You can save it to the filesystem using [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope).\n * @example\n * ```typescript\n * import { open } from '@tauri-apps/plugin-dialog';\n * // Open a selection dialog for image files\n * const selected = await open({\n * multiple: true,\n * filters: [{\n * name: 'Image',\n * extensions: ['png', 'jpeg']\n * }]\n * });\n * if (Array.isArray(selected)) {\n * // user selected multiple files\n * } else if (selected === null) {\n * // user cancelled the selection\n * } else {\n * // user selected a single file\n * }\n * ```\n *\n * @example\n * ```typescript\n * import { open } from '@tauri-apps/plugin-dialog';\n * import { appDir } from '@tauri-apps/api/path';\n * // Open a selection dialog for directories\n * const selected = await open({\n * directory: true,\n * multiple: true,\n * defaultPath: await appDir(),\n * });\n * if (Array.isArray(selected)) {\n * // user selected multiple directories\n * } else if (selected === null) {\n * // user cancelled the selection\n * } else {\n * // user selected a single directory\n * }\n * ```\n *\n * @returns A promise resolving to the selected path(s)\n *\n * @since 2.0.0\n */\nasync function open(options = {}) {\n if (typeof options === 'object') {\n Object.freeze(options);\n }\n return await invoke('plugin:dialog|open', { options });\n}\n/**\n * Open a file/directory save dialog.\n *\n * The selected path is added to the filesystem and asset protocol scopes.\n * When security is more important than the easy of use of this API,\n * prefer writing a dedicated command instead.\n *\n * Note that the scope change is not persisted, so the values are cleared when the application is restarted.\n * You can save it to the filesystem using [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope).\n * @example\n * ```typescript\n * import { save } from '@tauri-apps/plugin-dialog';\n * const filePath = await save({\n * filters: [{\n * name: 'Image',\n * extensions: ['png', 'jpeg']\n * }]\n * });\n * ```\n *\n * @returns A promise resolving to the selected path.\n *\n * @since 2.0.0\n */\nasync function save(options = {}) {\n if (typeof options === 'object') {\n Object.freeze(options);\n }\n return await invoke('plugin:dialog|save', { options });\n}\nasync function messageCommand(message, options) {\n return await invoke('plugin:dialog|message', {\n message,\n title: options?.title,\n kind: options?.kind,\n buttons: buttonsToRust(options?.buttons)\n });\n}\n/**\n * Shows a message dialog with an `Ok` button.\n * @example\n * ```typescript\n * import { message } from '@tauri-apps/plugin-dialog';\n * await message('Tauri is awesome', 'Tauri');\n * await message('File not found', { title: 'Tauri', kind: 'error' });\n * ```\n *\n * @param message The message to show.\n * @param options The dialog's options. If a string, it represents the dialog title.\n *\n * @returns A promise indicating the success or failure of the operation.\n *\n * @since 2.0.0\n *\n */\nasync function message(message, options) {\n const opts = typeof options === 'string' ? { title: options } : options;\n if (opts && !opts.buttons && opts.okLabel) {\n opts.buttons = { ok: opts.okLabel };\n }\n return messageCommand(message, opts);\n}\n/**\n * Shows a question dialog with `Yes` and `No` buttons.\n *\n * Convenient wrapper for `await message('msg', { buttons: 'YesNo' }) === 'Yes'`\n *\n * @example\n * ```typescript\n * import { ask } from '@tauri-apps/plugin-dialog';\n * const yes = await ask('Are you sure?', 'Tauri');\n * const yes2 = await ask('This action cannot be reverted. Are you sure?', { title: 'Tauri', kind: 'warning' });\n * ```\n *\n * @param message The message to show.\n * @param options The dialog's options. If a string, it represents the dialog title.\n *\n * @returns A promise resolving to a boolean indicating whether `Yes` was clicked or not.\n *\n * @since 2.0.0\n */\nasync function ask(message, options) {\n const opts = typeof options === 'string' ? { title: options } : options;\n const customButtons = opts?.okLabel || opts?.cancelLabel;\n const okLabel = opts?.okLabel ?? 'Yes';\n return ((await messageCommand(message, {\n title: opts?.title,\n kind: opts?.kind,\n buttons: customButtons\n ? { ok: okLabel, cancel: opts.cancelLabel ?? 'No' }\n : 'YesNo'\n })) === okLabel);\n}\n/**\n * Shows a question dialog with `Ok` and `Cancel` buttons.\n *\n * Convenient wrapper for `await message('msg', { buttons: 'OkCancel' }) === 'Ok'`\n *\n * @example\n * ```typescript\n * import { confirm } from '@tauri-apps/plugin-dialog';\n * const confirmed = await confirm('Are you sure?', 'Tauri');\n * const confirmed2 = await confirm('This action cannot be reverted. Are you sure?', { title: 'Tauri', kind: 'warning' });\n * ```\n *\n * @param message The message to show.\n * @param options The dialog's options. If a string, it represents the dialog title.\n *\n * @returns A promise resolving to a boolean indicating whether `Ok` was clicked or not.\n *\n * @since 2.0.0\n */\nasync function confirm(message, options) {\n const opts = typeof options === 'string' ? { title: options } : options;\n const customButtons = opts?.okLabel || opts?.cancelLabel;\n const okLabel = opts?.okLabel ?? 'Ok';\n return ((await messageCommand(message, {\n title: opts?.title,\n kind: opts?.kind,\n buttons: customButtons\n ? { ok: okLabel, cancel: opts.cancelLabel ?? 'Cancel' }\n : 'OkCancel'\n })) === okLabel);\n}\n\nexport { ask, confirm, message, open, save };\n"],"names":["open","options","invoke"],"mappings":"4CAgFA,eAAeA,EAAKC,EAAU,GAAI,CAC9B,OAAI,OAAOA,GAAY,UACnB,OAAO,OAAOA,CAAO,EAElB,MAAMC,EAAO,qBAAsB,CAAE,QAAAD,CAAO,CAAE,CACzD","x_google_ignoreList":[0]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}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;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:JetBrains Mono,ui-monospace,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{font-family:Inter,system-ui,sans-serif;color-scheme:dark}html,body,#root{height:100%}body{background:radial-gradient(1200px 800px at 0% 0%,rgba(34,211,238,.06),transparent 60%),radial-gradient(1000px 700px at 100% 100%,rgba(167,139,250,.05),transparent 60%),#0b0e14}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.invisible{visibility:hidden}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.bottom-4{bottom:1rem}.left-6{left:1.5rem}.right-0{right:0}.top-0{top:0}.top-4{top:1rem}.isolate{isolation:isolate}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.col-span-2{grid-column:span 2 / span 2}.row-start-1{grid-row-start:1}.row-start-2{grid-row-start:2}.m-3{margin:.75rem}.m-5{margin:1.25rem}.m-6{margin:1.5rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.mb-6{margin-bottom:1.5rem}.ml-2{margin-left:.5rem}.ml-auto{margin-left:auto}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-auto{margin-top:auto}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.h-1{height:.25rem}.h-1\.5{height:.375rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-4{height:1rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-\[70vh\]{height:70vh}.h-\[calc\(100vh-44px-3rem\)\]{height:calc(100vh - 44px - 3rem)}.h-full{height:100%}.max-h-\[85vh\]{max-height:85vh}.max-h-\[90vh\]{max-height:90vh}.min-h-screen{min-height:100vh}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-4{width:1rem}.w-6{width:1.5rem}.w-7{width:1.75rem}.w-72{width:18rem}.w-\[360px\]{width:360px}.w-\[420px\]{width:420px}.w-\[min\(720px\,92vw\)\]{width:min(720px,92vw)}.w-\[min\(900px\,90vw\)\]{width:min(900px,90vw)}.w-full{width:100%}.min-w-0{min-width:0px}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-5xl{max-width:64rem}.max-w-\[200px\]{max-width:200px}.max-w-\[260px\]{max-width:260px}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}@keyframes pulseSoft{0%,to{opacity:.85}50%{opacity:1}}.animate-pulse-soft{animation:pulseSoft 2.4s ease-in-out infinite}@keyframes riverIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.animate-river-in{animation:riverIn .32s ease-out both}@keyframes viewIn{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.animate-view-in{animation:viewIn .24s ease-out both}.cursor-default{cursor:default}.resize-none{resize:none}.resize-y{resize:vertical}.resize{resize:both}.list-decimal{list-style-type:decimal}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[100px_1fr\]{grid-template-columns:100px 1fr}.grid-cols-\[110px_1fr\]{grid-template-columns:110px 1fr}.grid-cols-\[140px_1fr\]{grid-template-columns:140px 1fr}.grid-cols-\[160px_1fr\]{grid-template-columns:160px 1fr}.grid-cols-\[200px_1fr\]{grid-template-columns:200px 1fr}.grid-cols-\[260px_1fr\]{grid-template-columns:260px 1fr}.grid-cols-\[320px_1fr\]{grid-template-columns:320px 1fr}.grid-cols-\[360px_1fr\]{grid-template-columns:360px 1fr}.grid-rows-\[44px_1fr\]{grid-template-rows:44px 1fr}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-0\.5{row-gap:.125rem}.gap-y-1{row-gap:.25rem}.gap-y-1\.5{row-gap:.375rem}.gap-y-2{row-gap:.5rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-line>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(31 38 51 / var(--tw-divide-opacity, 1))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-amber-400\/30{border-color:#fbbf244d}.border-amber-500\/30{border-color:#f59e0b4d}.border-cyan-400\/20{border-color:#22d3ee33}.border-cyan-400\/30{border-color:#22d3ee4d}.border-cyan-400\/40{border-color:#22d3ee66}.border-cyan-400\/50{border-color:#22d3ee80}.border-emerald-400\/30{border-color:#34d3994d}.border-emerald-500\/30{border-color:#10b9814d}.border-line{--tw-border-opacity: 1;border-color:rgb(31 38 51 / var(--tw-border-opacity, 1))}.border-line\/50{border-color:#1f263380}.border-line\/60{border-color:#1f263399}.border-red-400\/30{border-color:#f871714d}.border-red-500\/30{border-color:#ef44444d}.border-rose-400\/30{border-color:#fb71854d}.border-rose-500\/30{border-color:#f43f5e4d}.border-violet-400\/30{border-color:#a78bfa4d}.bg-amber-400\/10{background-color:#fbbf241a}.bg-amber-500\/10{background-color:#f59e0b1a}.bg-black\/60{background-color:#0009}.bg-canvas{--tw-bg-opacity: 1;background-color:rgb(11 14 20 / var(--tw-bg-opacity, 1))}.bg-canvas\/40{background-color:#0b0e1466}.bg-canvas\/60{background-color:#0b0e1499}.bg-cyan-400{--tw-bg-opacity: 1;background-color:rgb(34 211 238 / var(--tw-bg-opacity, 1))}.bg-cyan-400\/10{background-color:#22d3ee1a}.bg-cyan-400\/15{background-color:#22d3ee26}.bg-cyan-400\/20{background-color:#22d3ee33}.bg-cyan-400\/5{background-color:#22d3ee0d}.bg-cyan-500\/15{background-color:#06b6d426}.bg-cyan-500\/20{background-color:#06b6d433}.bg-emerald-400{--tw-bg-opacity: 1;background-color:rgb(52 211 153 / var(--tw-bg-opacity, 1))}.bg-emerald-500\/10{background-color:#10b9811a}.bg-line{--tw-bg-opacity: 1;background-color:rgb(31 38 51 / var(--tw-bg-opacity, 1))}.bg-panel{--tw-bg-opacity: 1;background-color:rgb(16 20 28 / var(--tw-bg-opacity, 1))}.bg-panel\/30{background-color:#10141c4d}.bg-panel\/40{background-color:#10141c66}.bg-panel\/60{background-color:#10141c99}.bg-panel\/80{background-color:#10141ccc}.bg-panel\/95{background-color:#10141cf2}.bg-panelHi{--tw-bg-opacity: 1;background-color:rgb(22 27 38 / var(--tw-bg-opacity, 1))}.bg-panelHi\/40{background-color:#161b2666}.bg-red-400\/10{background-color:#f871711a}.bg-red-500\/10{background-color:#ef44441a}.bg-rose-400\/10{background-color:#fb71851a}.bg-rose-500\/10{background-color:#f43f5e1a}.bg-sky-400\/10{background-color:#38bdf81a}.bg-slate-400\/10{background-color:#94a3b81a}.bg-slate-700\/40{background-color:#33415566}.bg-slate-800{--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity, 1))}.bg-slate-900\/60{background-color:#0f172a99}.bg-slate-950{--tw-bg-opacity: 1;background-color:rgb(2 6 23 / var(--tw-bg-opacity, 1))}.bg-violet-400{--tw-bg-opacity: 1;background-color:rgb(167 139 250 / var(--tw-bg-opacity, 1))}.bg-violet-400\/10{background-color:#a78bfa1a}.bg-violet-500\/20{background-color:#8b5cf633}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-cyan-400{--tw-gradient-from: #22d3ee var(--tw-gradient-from-position);--tw-gradient-to: rgb(34 211 238 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.to-violet-400{--tw-gradient-to: #a78bfa var(--tw-gradient-to-position)}.p-1{padding:.25rem}.p-2\.5{padding:.625rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.pl-5{padding-left:1.25rem}.pl-9{padding-left:2.25rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:JetBrains Mono,ui-monospace,monospace}.font-sans{font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[9px\]{font-size:9px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.normal-case{text-transform:none}.not-italic{font-style:normal}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.tracking-normal{letter-spacing:0em}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.tracking-widest{letter-spacing:.1em}.text-amber-200{--tw-text-opacity: 1;color:rgb(253 230 138 / var(--tw-text-opacity, 1))}.text-amber-300{--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity, 1))}.text-amber-400\/90{color:#fbbf24e6}.text-cyan-100{--tw-text-opacity: 1;color:rgb(207 250 254 / var(--tw-text-opacity, 1))}.text-cyan-200{--tw-text-opacity: 1;color:rgb(165 243 252 / var(--tw-text-opacity, 1))}.text-cyan-200\/70{color:#a5f3fcb3}.text-cyan-300{--tw-text-opacity: 1;color:rgb(103 232 249 / var(--tw-text-opacity, 1))}.text-cyan-300\/80{color:#67e8f9cc}.text-emerald-200{--tw-text-opacity: 1;color:rgb(167 243 208 / var(--tw-text-opacity, 1))}.text-emerald-300{--tw-text-opacity: 1;color:rgb(110 231 183 / var(--tw-text-opacity, 1))}.text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}.text-red-300{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.text-rose-200{--tw-text-opacity: 1;color:rgb(254 205 211 / var(--tw-text-opacity, 1))}.text-rose-300{--tw-text-opacity: 1;color:rgb(253 164 175 / var(--tw-text-opacity, 1))}.text-sky-300{--tw-text-opacity: 1;color:rgb(125 211 252 / var(--tw-text-opacity, 1))}.text-slate-100{--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity, 1))}.text-slate-200{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.text-slate-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity, 1))}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.text-slate-600{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity, 1))}.text-violet-200{--tw-text-opacity: 1;color:rgb(221 214 254 / var(--tw-text-opacity, 1))}.text-violet-300{--tw-text-opacity: 1;color:rgb(196 181 253 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.line-through{text-decoration-line:line-through}.decoration-slate-600\/50{text-decoration-color:#47556980}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-50{opacity:.5}.opacity-70{opacity:.7}.opacity-90{opacity:.9}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-amber-400\/30{--tw-ring-color: rgb(251 191 36 / .3)}.ring-amber-400\/40{--tw-ring-color: rgb(251 191 36 / .4)}.ring-cyan-400\/20{--tw-ring-color: rgb(34 211 238 / .2)}.ring-cyan-400\/30{--tw-ring-color: rgb(34 211 238 / .3)}.ring-emerald-400\/30{--tw-ring-color: rgb(52 211 153 / .3)}.ring-emerald-400\/40{--tw-ring-color: rgb(52 211 153 / .4)}.ring-line{--tw-ring-opacity: 1;--tw-ring-color: rgb(31 38 51 / var(--tw-ring-opacity, 1))}.ring-rose-400\/30{--tw-ring-color: rgb(251 113 133 / .3)}.ring-rose-400\/40{--tw-ring-color: rgb(251 113 133 / .4)}.ring-sky-400\/30{--tw-ring-color: rgb(56 189 248 / .3)}.ring-slate-400\/30{--tw-ring-color: rgb(148 163 184 / .3)}.ring-slate-500\/30{--tw-ring-color: rgb(100 116 139 / .3)}.ring-violet-400\/20{--tw-ring-color: rgb(167 139 250 / .2)}.ring-violet-400\/30{--tw-ring-color: rgb(167 139 250 / .3)}.ring-violet-400\/40{--tw-ring-color: rgb(167 139 250 / .4)}.\!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)!important}.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)}.backdrop-blur{--tw-backdrop-blur: blur(8px);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}.glass{background:linear-gradient(180deg,#161b26b3,#10141cb3);backdrop-filter:blur(8px);border:1px solid rgba(31,38,51,.8)}.scrollbar-thin{scrollbar-width:thin}.scrollbar-thin::-webkit-scrollbar{width:8px}.scrollbar-thin::-webkit-scrollbar-thumb{background:#94a3b82e;border-radius:4px}.placeholder\:text-slate-600::-moz-placeholder{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity, 1))}.placeholder\:text-slate-600::placeholder{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity, 1))}.last\:border-b-0:last-child{border-bottom-width:0px}.hover\:bg-amber-400\/10:hover{background-color:#fbbf241a}.hover\:bg-cyan-400\/20:hover{background-color:#22d3ee33}.hover\:bg-cyan-400\/25:hover{background-color:#22d3ee40}.hover\:bg-cyan-500\/25:hover{background-color:#06b6d440}.hover\:bg-cyan-500\/30:hover{background-color:#06b6d44d}.hover\:bg-emerald-400\/10:hover{background-color:#34d3991a}.hover\:bg-panel:hover{--tw-bg-opacity: 1;background-color:rgb(16 20 28 / var(--tw-bg-opacity, 1))}.hover\:bg-panelHi:hover{--tw-bg-opacity: 1;background-color:rgb(22 27 38 / var(--tw-bg-opacity, 1))}.hover\:bg-panelHi\/40:hover{background-color:#161b2666}.hover\:bg-panelHi\/50:hover{background-color:#161b2680}.hover\:bg-panelHi\/60:hover{background-color:#161b2699}.hover\:bg-red-400\/20:hover{background-color:#f8717133}.hover\:bg-rose-400\/10:hover{background-color:#fb71851a}.hover\:bg-rose-400\/20:hover{background-color:#fb718533}.hover\:bg-violet-400\/20:hover{background-color:#a78bfa33}.hover\:bg-violet-500\/30:hover{background-color:#8b5cf64d}.hover\:text-amber-100:hover{--tw-text-opacity: 1;color:rgb(254 243 199 / var(--tw-text-opacity, 1))}.hover\:text-cyan-200:hover{--tw-text-opacity: 1;color:rgb(165 243 252 / var(--tw-text-opacity, 1))}.hover\:text-rose-300:hover{--tw-text-opacity: 1;color:rgb(253 164 175 / var(--tw-text-opacity, 1))}.hover\:text-slate-100:hover{--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity, 1))}.hover\:text-slate-200:hover{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:ring-rose-400\/30:hover{--tw-ring-color: rgb(251 113 133 / .3)}.hover\:ring-rose-400\/40:hover{--tw-ring-color: rgb(251 113 133 / .4)}.hover\:brightness-125:hover{--tw-brightness: brightness(1.25);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)}.focus\:border-cyan-400\/40:focus{border-color:#22d3ee66}.focus\:border-cyan-400\/50:focus{border-color:#22d3ee80}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.enabled\:hover\:bg-cyan-400\/20:hover:enabled{background-color:#22d3ee33}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width: 640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}
|