open-edison 0.1.44__py3-none-any.whl → 0.1.72rc1__py3-none-any.whl
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.
- {open_edison-0.1.44.dist-info → open_edison-0.1.72rc1.dist-info}/METADATA +2 -21
- open_edison-0.1.72rc1.dist-info/RECORD +41 -0
- src/cli.py +30 -113
- src/config.py +30 -9
- src/events.py +5 -2
- src/frontend_dist/assets/index-D05VN_1l.css +1 -0
- src/frontend_dist/assets/index-D6ziuTsl.js +51 -0
- src/frontend_dist/index.html +2 -2
- src/frontend_dist/sw.js +22 -2
- src/mcp_importer/__main__.py +0 -2
- src/mcp_importer/api.py +254 -44
- src/mcp_importer/export_cli.py +2 -2
- src/mcp_importer/exporters.py +1 -1
- src/mcp_importer/import_api.py +0 -2
- src/mcp_importer/parsers.py +47 -9
- src/mcp_importer/quick_cli.py +0 -2
- src/mcp_importer/types.py +0 -2
- src/mcp_stdio_capture.py +144 -0
- src/middleware/data_access_tracker.py +49 -4
- src/middleware/session_tracking.py +123 -34
- src/oauth_manager.py +5 -3
- src/oauth_override.py +10 -0
- src/permissions.py +110 -10
- src/server.py +57 -16
- src/setup_tui/main.py +160 -21
- src/single_user_mcp.py +246 -105
- src/tools/io.py +35 -0
- src/vulture_whitelist.py +3 -0
- open_edison-0.1.44.dist-info/RECORD +0 -37
- src/frontend_dist/assets/index-BUUcUfTt.js +0 -51
- src/frontend_dist/assets/index-o6_8mdM8.css +0 -1
- {open_edison-0.1.44.dist-info → open_edison-0.1.72rc1.dist-info}/WHEEL +0 -0
- {open_edison-0.1.44.dist-info → open_edison-0.1.72rc1.dist-info}/entry_points.txt +0 -0
- {open_edison-0.1.44.dist-info → open_edison-0.1.72rc1.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: open-edison
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.72rc1
|
4
4
|
Summary: Open-source MCP security, aggregation, and monitoring. Single-user, self-hosted MCP proxy.
|
5
5
|
Author-email: Hugo Berg <hugo@edison.watch>
|
6
6
|
License-File: LICENSE
|
@@ -72,13 +72,7 @@ curl -fsSL https://raw.githubusercontent.com/Edison-Watch/open-edison/main/curl_
|
|
72
72
|
```
|
73
73
|
|
74
74
|
Run locally with uvx: `uvx open-edison`
|
75
|
-
|
76
|
-
Optionally, run the setup wizard to import/configure MCP:
|
77
|
-
|
78
|
-
```bash
|
79
|
-
uv run python -m src.setup_tui.main
|
80
|
-
# add --dry-run to preview without writing
|
81
|
-
```
|
75
|
+
That will run the setup wizard if necessary.
|
82
76
|
|
83
77
|
<details>
|
84
78
|
<summary>⬇️ Install Node.js/npm (optional for MCP tools)</summary>
|
@@ -129,19 +123,6 @@ OPEN_EDISON_CONFIG_DIR=~/edison-config open-edison run
|
|
129
123
|
|
130
124
|
</details>
|
131
125
|
|
132
|
-
<details>
|
133
|
-
<summary>🔄 Import from Cursor/VS Code/Claude Code</summary>
|
134
|
-
|
135
|
-
Run the interactive setup wizard to detect clients, import servers, and configure your editor:
|
136
|
-
|
137
|
-
```bash
|
138
|
-
uv run python -m src.setup_tui.main
|
139
|
-
```
|
140
|
-
|
141
|
-
Use `--dry-run` to preview without writing.
|
142
|
-
|
143
|
-
</details>
|
144
|
-
|
145
126
|
<details>
|
146
127
|
<summary><img src="https://img.shields.io/badge/Docker-2CA5E0?style=for-the-badge&logo=docker&logoColor=white" alt="Docker"> Run with Docker</summary>
|
147
128
|
|
@@ -0,0 +1,41 @@
|
|
1
|
+
src/__init__.py,sha256=bEYMwBiuW9jzF07iWhas4Vb30EcpnqfpNfz_Q6yO1jU,209
|
2
|
+
src/__main__.py,sha256=kQsaVyzRa_ESC57JpKDSQJAHExuXme0rM5beJsYxFeA,161
|
3
|
+
src/cli.py,sha256=kr93TljS2y8E0N-L0Cga0udJpa1POo4bjmJbtg1WJ-s,4737
|
4
|
+
src/config.py,sha256=cFbdY55w2JimV6kLv3XDnxDORz533R-GkW0EmKfNqCc,11325
|
5
|
+
src/config.pyi,sha256=FgehEGli8ZXSjGlANBgMGv5497q4XskQciOc1fUcxqM,2033
|
6
|
+
src/events.py,sha256=KkhrQ9CE5-WBBCeDkUgdCZURKsXakNP3Kj3gP91NYQM,5046
|
7
|
+
src/mcp_stdio_capture.py,sha256=SpMnUqQYm207WpiEwBahUxq4JFG9bnmNkUQVotZX7cc,5458
|
8
|
+
src/oauth_manager.py,sha256=qw87VxfRLfvd3YI1EhMmEJJ51N_WJsNpo17GUCvi13c,9971
|
9
|
+
src/oauth_override.py,sha256=C7QS8sPA6JqJDiNZA0FGeXcB7jU-yYu-k8V56QVpsqU,393
|
10
|
+
src/permissions.py,sha256=G_rCkVRC6yvNEoTe7g6gHXmmnwcmpS6vFScm7bI8oeY,14632
|
11
|
+
src/server.py,sha256=aKXEZsw6DXL4mm8LPASPYXehkmjojXqILtb2apaAu8Q,47419
|
12
|
+
src/single_user_mcp.py,sha256=ML-rBl1Nr2okzmLl2fLJYEvkphr-71n6eVtXpilmqio,24909
|
13
|
+
src/telemetry.py,sha256=-RZPIjpI53zbsKmp-63REeZ1JirWHV5WvpSRa2nqZEk,11321
|
14
|
+
src/vulture_whitelist.py,sha256=CjBOSsarbzbQt_9ATWc8MbruBsYX3hJVa_ysbRD9ZYM,135
|
15
|
+
src/frontend_dist/index.html,sha256=eCG8DLoWNQc5GImBg0Z9dT8HZUMPhAw-4Wc6dEqgKCU,673
|
16
|
+
src/frontend_dist/sw.js,sha256=YXwdeSQVg0BaSyhS2REI2DorNEO1kGUn_YCFw14D8VI,3321
|
17
|
+
src/frontend_dist/assets/index-D05VN_1l.css,sha256=TVt1HETNMBbfMcrUfYuEE0a-tfpxCsATihxmA8Jp890,17953
|
18
|
+
src/frontend_dist/assets/index-D6ziuTsl.js,sha256=lwKVKobmch7-DzowsVXL3VazVV0nr7-D8gmAsoJ-Hj8,264213
|
19
|
+
src/mcp_importer/__init__.py,sha256=Mk59pVr7OMGfYGWeSYk8-URfhIcrs3SPLYS7fmJbMII,275
|
20
|
+
src/mcp_importer/__main__.py,sha256=mFcxXFqJMC0SFEqIP-9WVEqLJSYqShC0x1Ht7PQZPm8,479
|
21
|
+
src/mcp_importer/api.py,sha256=N5oVaTj3OMIROLx__UOSr60VMqXXX20JsOHmeHIGP48,17431
|
22
|
+
src/mcp_importer/cli.py,sha256=Pe0GLWm1nMd1VuNXOSkxIrFZuGNFc9dNvfBsvf-bdBI,3487
|
23
|
+
src/mcp_importer/export_cli.py,sha256=Fw0jDQCI8gGW4BDrJLzWjLUtV4q6v0h2QZ7HF1V2Jcg,6279
|
24
|
+
src/mcp_importer/exporters.py,sha256=NsXBa1FvwPmIak0AJUrfbCwhnBIdGc5oXjTtMblFRwk,11345
|
25
|
+
src/mcp_importer/import_api.py,sha256=wD5yqxWwFfn1MQNKE79rEeyZODdmPgUDhsRYdCJYh4Q,59
|
26
|
+
src/mcp_importer/importers.py,sha256=zGN8lT7qQJ95jDTd-ck09j_w5PSvH-uj33TILoHfHbs,2191
|
27
|
+
src/mcp_importer/merge.py,sha256=KIGT7UgbAm07-LdyoUXEJ7ABSIiPTFlj_qjz669yFxg,1569
|
28
|
+
src/mcp_importer/parsers.py,sha256=MDhzODsvX5t1U_CI8byBxCpx6rA4WkpqX4bJMiNE74s,6298
|
29
|
+
src/mcp_importer/paths.py,sha256=4L-cPr7KCM9X9gAUP7Da6ictLNrPWuQ_IM419zqY-2I,2700
|
30
|
+
src/mcp_importer/quick_cli.py,sha256=Vv2vjNzpSOaic0YHFbPAuX0nZByawS2kDw6KiCtEX3A,1798
|
31
|
+
src/mcp_importer/types.py,sha256=nSaOLGqpCmA3R14QCO6wrpgX75VaLz9HfslUWzw_GPQ,102
|
32
|
+
src/middleware/data_access_tracker.py,sha256=ZW-E44U_iOE4jRArRmnQ1HK_zRKs_WZvz4Aa49wXaZk,17105
|
33
|
+
src/middleware/session_tracking.py,sha256=_igPVEH_l2hQ5onLb5cdn7MOXNtSxr9USEdJklhM_OA,26984
|
34
|
+
src/setup_tui/__init__.py,sha256=mDFrQoiOtQOHc0sFfGKrNXVLEDeB1S0O5aISBVzfxYo,184
|
35
|
+
src/setup_tui/main.py,sha256=9-m5p3HUnENGbfzd7Mk6LI5NjWtgMeXCWtFi65EBsVc,11257
|
36
|
+
src/tools/io.py,sha256=hhc4pv3eUzYWSZ7BbThclxSMwWBQaGMoGsItIPf_pco,1047
|
37
|
+
open_edison-0.1.72rc1.dist-info/METADATA,sha256=y9zSbh-9YyBsUonrlHIi3Vf0OFU-sTId55qENBR4Qt0,11996
|
38
|
+
open_edison-0.1.72rc1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
39
|
+
open_edison-0.1.72rc1.dist-info/entry_points.txt,sha256=YiGNm9x2I00hgT10HDyB4gxC1LcaV_mu8bXFjolu0Yw,171
|
40
|
+
open_edison-0.1.72rc1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
41
|
+
open_edison-0.1.72rc1.dist-info/RECORD,,
|
src/cli.py
CHANGED
@@ -7,8 +7,6 @@ Provides `open-edison` executable when installed via pip/uvx/pipx.
|
|
7
7
|
import argparse
|
8
8
|
import asyncio
|
9
9
|
import os
|
10
|
-
import subprocess as _subprocess
|
11
|
-
from contextlib import suppress
|
12
10
|
from pathlib import Path
|
13
11
|
from typing import Any, NoReturn
|
14
12
|
|
@@ -17,6 +15,7 @@ from loguru import logger as _log # type: ignore[reportMissingImports]
|
|
17
15
|
from src.config import Config, get_config_dir, get_config_json_path
|
18
16
|
from src.mcp_importer.cli import run_cli
|
19
17
|
from src.server import OpenEdisonProxy
|
18
|
+
from src.setup_tui.main import run_import_tui
|
20
19
|
|
21
20
|
log: Any = _log
|
22
21
|
|
@@ -37,6 +36,22 @@ def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
37
36
|
parser.add_argument(
|
38
37
|
"--port", type=int, help="Server port override (FastMCP on port, FastAPI on port+1)"
|
39
38
|
)
|
39
|
+
# For the setup wizard
|
40
|
+
parser.add_argument(
|
41
|
+
"--wizard-dry-run",
|
42
|
+
action="store_true",
|
43
|
+
help="(For the setup wizard) Show changes without writing to config.json",
|
44
|
+
)
|
45
|
+
parser.add_argument(
|
46
|
+
"--wizard-skip-oauth",
|
47
|
+
action="store_true",
|
48
|
+
help="(For the setup wizard) Skip OAuth for remote servers (they will be omitted from import)",
|
49
|
+
)
|
50
|
+
parser.add_argument(
|
51
|
+
"--wizard-force",
|
52
|
+
action="store_true",
|
53
|
+
help="(For the setup wizard) Force running the setup wizard even if it has already been run",
|
54
|
+
)
|
40
55
|
# Website runs from packaged assets by default; no extra website flags
|
41
56
|
|
42
57
|
# Subcommands (extensible)
|
@@ -88,93 +103,7 @@ def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
88
103
|
return parser.parse_args(argv)
|
89
104
|
|
90
105
|
|
91
|
-
def _spawn_frontend_dev( # noqa: C901 - pragmatic complexity for env probing
|
92
|
-
port: int,
|
93
|
-
override_dir: Path | None = None,
|
94
|
-
config_dir: Path | None = None,
|
95
|
-
) -> tuple[int, _subprocess.Popen[bytes] | None]:
|
96
|
-
"""Try to start the frontend dev server by running `npm run dev`.
|
97
|
-
|
98
|
-
Search order for working directory:
|
99
|
-
1) Packaged project path: <pkg_root>/frontend
|
100
|
-
2) Current working directory (if it contains a package.json)
|
101
|
-
"""
|
102
|
-
candidates: list[Path] = []
|
103
|
-
# Prefer packaged static assets; if present, the backend serves /dashboard
|
104
|
-
static_candidates = [
|
105
|
-
Path(__file__).parent / "frontend_dist", # inside package dir
|
106
|
-
Path(__file__).parent.parent / "frontend_dist", # site-packages root
|
107
|
-
]
|
108
|
-
static_dir = next((p for p in static_candidates if p.exists() and p.is_dir()), None)
|
109
|
-
if static_dir is not None:
|
110
|
-
log.info(
|
111
|
-
f"Packaged dashboard detected at {static_dir}. It will be served at /dashboard by the API server."
|
112
|
-
)
|
113
|
-
# No separate website process needed. Return sentinel port (-1) so caller knows not to warn.
|
114
|
-
return (-1, None)
|
115
|
-
|
116
|
-
if static_dir is None:
|
117
|
-
raise RuntimeError(
|
118
|
-
"No packaged dashboard detected. The website will be served from the frontend directory."
|
119
|
-
)
|
120
|
-
|
121
|
-
pkg_frontend_candidates = [
|
122
|
-
Path(__file__).parent / "frontend", # inside package dir
|
123
|
-
Path(__file__).parent.parent / "frontend", # site-packages root
|
124
|
-
]
|
125
|
-
if override_dir is not None:
|
126
|
-
candidates.append(override_dir)
|
127
|
-
for pf in pkg_frontend_candidates:
|
128
|
-
if pf.exists():
|
129
|
-
candidates.append(pf)
|
130
|
-
if config_dir is not None and (config_dir / "package.json").exists():
|
131
|
-
candidates.append(config_dir)
|
132
|
-
cwd_pkg = Path.cwd()
|
133
|
-
if (cwd_pkg / "package.json").exists():
|
134
|
-
candidates.append(cwd_pkg)
|
135
|
-
|
136
|
-
if not candidates:
|
137
|
-
log.warning(
|
138
|
-
"No frontend directory found (no packaged frontend and no package.json in CWD). Skipping website."
|
139
|
-
)
|
140
|
-
return (port, None)
|
141
|
-
|
142
|
-
for candidate in candidates:
|
143
|
-
try:
|
144
|
-
# If no package.json but directory exists, try a basic npm i per user request
|
145
|
-
if not (candidate / "package.json").exists():
|
146
|
-
log.info(f"No package.json in {candidate}. Running 'npm i' as best effort...")
|
147
|
-
_ = _subprocess.call(["npm", "i"], cwd=str(candidate))
|
148
|
-
|
149
|
-
# Install deps if needed
|
150
|
-
if (
|
151
|
-
not (candidate / "node_modules").exists()
|
152
|
-
and (candidate / "package-lock.json").exists()
|
153
|
-
):
|
154
|
-
log.info(f"Installing frontend dependencies with npm ci in {candidate}...")
|
155
|
-
r_install = _subprocess.call(["npm", "ci"], cwd=str(candidate))
|
156
|
-
if r_install != 0:
|
157
|
-
log.error("Failed to install frontend dependencies")
|
158
|
-
continue
|
159
|
-
|
160
|
-
log.info(f"Starting frontend dev server in {candidate} on port {port}...")
|
161
|
-
cmd_default = ["npm", "run", "dev", "--", "--port", str(port)]
|
162
|
-
proc = _subprocess.Popen(cmd_default, cwd=str(candidate))
|
163
|
-
return (port, proc)
|
164
|
-
except FileNotFoundError:
|
165
|
-
log.error("npm not found. Please install Node.js to run the website dev server.")
|
166
|
-
return (port, None)
|
167
|
-
|
168
|
-
# If all candidates failed
|
169
|
-
return (port, None)
|
170
|
-
|
171
|
-
|
172
106
|
async def _run_server(args: Any) -> None:
|
173
|
-
# TODO check this works as we want it to
|
174
|
-
# Resolve config dir and expose via env for the rest of the app
|
175
|
-
config_dir_arg = getattr(args, "config_dir", None)
|
176
|
-
if config_dir_arg is not None:
|
177
|
-
os.environ["OPEN_EDISON_CONFIG_DIR"] = str(Path(config_dir_arg).expanduser().resolve())
|
178
107
|
config_dir = get_config_dir()
|
179
108
|
|
180
109
|
# Load config after setting env override
|
@@ -186,44 +115,32 @@ async def _run_server(args: Any) -> None:
|
|
186
115
|
log.info(f"Using config directory: {config_dir}")
|
187
116
|
proxy = OpenEdisonProxy(host=host, port=port)
|
188
117
|
|
189
|
-
# Website served from packaged assets by default; still detect and log
|
190
|
-
frontend_proc = None
|
191
|
-
used_port, frontend_proc = _spawn_frontend_dev(5173, None, config_dir)
|
192
|
-
if frontend_proc is None and used_port == -1:
|
193
|
-
log.info("Frontend is being served from packaged assets at /dashboard")
|
194
|
-
|
195
118
|
try:
|
196
119
|
await proxy.start()
|
197
|
-
_ = await asyncio.Event().wait()
|
198
120
|
except KeyboardInterrupt:
|
199
121
|
log.info("Received shutdown signal")
|
200
|
-
finally:
|
201
|
-
if frontend_proc is not None:
|
202
|
-
with suppress(Exception):
|
203
|
-
frontend_proc.terminate()
|
204
|
-
_ = frontend_proc.wait(timeout=5)
|
205
|
-
with suppress(Exception):
|
206
|
-
frontend_proc.kill()
|
207
|
-
|
208
|
-
|
209
|
-
def _run_website(port: int, website_dir: Path | None = None) -> int:
|
210
|
-
# Use the same spawning logic, then return 0 if started or 1 if failed
|
211
|
-
_, proc = _spawn_frontend_dev(port, website_dir)
|
212
|
-
return 0 if proc is not None else 1
|
213
122
|
|
214
123
|
|
215
124
|
def main(argv: list[str] | None = None) -> NoReturn: # noqa: C901
|
216
125
|
args = _parse_args(argv)
|
217
126
|
|
218
|
-
|
219
|
-
|
220
|
-
|
127
|
+
# Resolve config dir and expose via env for the rest of the app
|
128
|
+
config_dir_arg = getattr(args, "config_dir", None)
|
129
|
+
if config_dir_arg is not None:
|
130
|
+
os.environ["OPEN_EDISON_CONFIG_DIR"] = str(Path(config_dir_arg).expanduser().resolve())
|
131
|
+
|
132
|
+
if args.command is None:
|
133
|
+
args.command = "run"
|
221
134
|
|
222
|
-
if
|
135
|
+
if args.command == "import-mcp":
|
223
136
|
result_code = run_cli(argv)
|
224
137
|
raise SystemExit(result_code)
|
225
138
|
|
226
|
-
#
|
139
|
+
# Run import tui if necessary
|
140
|
+
tui_success = run_import_tui(args, force=args.wizard_force)
|
141
|
+
if not tui_success:
|
142
|
+
raise SystemExit(1)
|
143
|
+
|
227
144
|
try:
|
228
145
|
asyncio.run(_run_server(args))
|
229
146
|
raise SystemExit(0)
|
src/config.py
CHANGED
@@ -108,12 +108,23 @@ class MCPServerConfig:
|
|
108
108
|
Remote servers use mcp-remote with HTTPS URLs and may require OAuth.
|
109
109
|
Local servers run as child processes and don't need OAuth.
|
110
110
|
"""
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
111
|
+
# TODO find out if having the remote_server functionality is necessary, and if so how we can make it interact well with servers who don't like the fastmcp networking stack.
|
112
|
+
return False
|
113
|
+
# if self.command != "npx":
|
114
|
+
# return False
|
115
|
+
|
116
|
+
# # Be tolerant of npx flags by scanning for 'mcp-remote' and the subsequent HTTPS URL
|
117
|
+
# try:
|
118
|
+
# if "mcp-remote" not in self.args:
|
119
|
+
# return False
|
120
|
+
# idx: int = self.args.index("mcp-remote")
|
121
|
+
# # Look for first https?:// argument after 'mcp-remote'
|
122
|
+
# for candidate in self.args[idx + 1 :]:
|
123
|
+
# if candidate.startswith(("https://", "http://")):
|
124
|
+
# return candidate.startswith("https://")
|
125
|
+
# return False
|
126
|
+
# except Exception:
|
127
|
+
# return False
|
117
128
|
|
118
129
|
def get_remote_url(self) -> str | None:
|
119
130
|
"""
|
@@ -122,9 +133,19 @@ class MCPServerConfig:
|
|
122
133
|
Returns:
|
123
134
|
The HTTPS URL if this is a remote server, None otherwise
|
124
135
|
"""
|
125
|
-
|
126
|
-
return self.args[2]
|
136
|
+
# TODO see above
|
127
137
|
return None
|
138
|
+
# # Reuse the same tolerant parsing as is_remote_server
|
139
|
+
# if self.command != "npx" or "mcp-remote" not in self.args:
|
140
|
+
# return None
|
141
|
+
# try:
|
142
|
+
# # idx: int = self.args.index("mcp-remote")
|
143
|
+
# for candidate in self.args[:]:
|
144
|
+
# if candidate.startswith(("https://", "http://")):
|
145
|
+
# return candidate
|
146
|
+
# return None
|
147
|
+
# except Exception:
|
148
|
+
# return None
|
128
149
|
|
129
150
|
|
130
151
|
@dataclass
|
@@ -148,7 +169,7 @@ def load_json_file(path: Path) -> dict[str, Any]:
|
|
148
169
|
|
149
170
|
|
150
171
|
def clear_json_file_cache() -> None:
|
151
|
-
"""Clear the cache for the
|
172
|
+
"""Clear the cache for the JSON file loading"""
|
152
173
|
load_json_file.cache_clear()
|
153
174
|
|
154
175
|
|
src/events.py
CHANGED
@@ -26,12 +26,15 @@ def _approval_key(session_id: str, kind: str, name: str) -> str:
|
|
26
26
|
|
27
27
|
|
28
28
|
def requires_loop(func: Callable[..., Any]) -> Callable[..., None | Any]: # noqa: ANN401
|
29
|
-
"""Decorator to ensure the function is called when there is
|
29
|
+
"""Decorator to ensure the function is called when there is a running asyncio loop.
|
30
30
|
This is for sync(!) functions that return None / can do so on error"""
|
31
31
|
|
32
32
|
@wraps(func)
|
33
33
|
def wrapper(*args: Any, **kwargs: Any) -> None | Any:
|
34
|
-
|
34
|
+
try:
|
35
|
+
# get_running_loop() raises RuntimeError if no loop is running in this thread
|
36
|
+
_ = asyncio.get_running_loop()
|
37
|
+
except RuntimeError:
|
35
38
|
log.warning("fire_and_forget called in non-async context")
|
36
39
|
return None
|
37
40
|
return func(*args, **kwargs)
|
@@ -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:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";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:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,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}.\!container{width:100%!important}.container{width:100%}@media (min-width: 640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width: 768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media (min-width: 1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media (min-width: 1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media (min-width: 1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.-inset-0\.5{top:-.125rem;right:-.125rem;bottom:-.125rem;left:-.125rem}.bottom-4{bottom:1rem}.left-4{left:1rem}.right-4{right:1rem}.right-auto{right:auto}.z-50{z-index:50}.m-0{margin:0}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-auto{margin-left:auto}.mr-2{margin-right:.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-2{height:.5rem}.h-3{height:.75rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-\[580px\]{height:580px}.w-10{width:2.5rem}.w-2{width:.5rem}.w-3{width:.75rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-\[260px\]{width:260px}.w-\[min\(92vw\,28rem\)\]{width:min(92vw,28rem)}.w-full{width:100%}.min-w-\[240px\]{min-width:240px}.max-w-\[1400px\]{max-width:1400px}.max-w-\[260px\]{max-width:260px}.max-w-sm{max-width:24rem}.border-collapse{border-collapse:collapse}.translate-x-1{--tw-translate-x: .25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-5{--tw-translate-x: 1.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-\[-4px\]{--tw-translate-y: -4px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.animate-\[pop_300ms_ease-out\]{animation:pop .3s ease-out}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.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-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-r{border-right-width:1px}.border-amber-400\/30{border-color:#fbbf244d}.border-app-accent{border-color:var(--accent)}.border-app-border{border-color:var(--border)}.border-blue-400\/30{border-color:#60a5fa4d}.border-blue-400\/60{border-color:#60a5fa99}.border-rose-400\/30{border-color:#fb71854d}.\!bg-blue-100{--tw-bg-opacity: 1 !important;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))!important}.\!bg-blue-600{--tw-bg-opacity: 1 !important;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))!important}.bg-amber-400{--tw-bg-opacity: 1;background-color:rgb(251 191 36 / var(--tw-bg-opacity, 1))}.bg-app-accent{background-color:var(--accent)}.bg-app-bg{background-color:var(--bg)}.bg-app-border{background-color:var(--border)}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-400\/20{background-color:#60a5fa33}.bg-blue-50{--tw-bg-opacity: 1;background-color:rgb(239 246 255 / var(--tw-bg-opacity, 1))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-rose-400{--tw-bg-opacity: 1;background-color:rgb(251 113 133 / var(--tw-bg-opacity, 1))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity, 1))}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.\!px-2{padding-left:.5rem!important;padding-right:.5rem!important}.\!px-3{padding-left:.75rem!important;padding-right:.75rem!important}.\!py-1\.5{padding-top:.375rem!important;padding-bottom:.375rem!important}.\!py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.pb-2{padding-bottom:.5rem}.text-left{text-align:left}.text-center{text-align:center}.align-bottom{vertical-align:bottom}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.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-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.\!text-white{--tw-text-opacity: 1 !important;color:rgb(255 255 255 / var(--tw-text-opacity, 1))!important}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-app-accent{color:var(--accent)}.text-app-muted{color:var(--muted)}.text-app-text{color:var(--text)}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.text-blue-900{--tw-text-opacity: 1;color:rgb(30 58 138 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-rose-400{--tw-text-opacity: 1;color:rgb(251 113 133 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity, 1))}.accent-blue-500{accent-color:#3b82f6}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px 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-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px 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)}.blur-md{--tw-blur: blur(12px);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)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}:root{--bg: #0b0c10;--card: #111318;--border: #1f2430;--text: #e6e6e6;--muted: #a0a7b4;--accent: #7c3aed;--success: #10b981;--warning: #f59e0b;--danger: #ef4444}[data-theme=dark]{--bg: #0b0c10;--card: #111318;--border: #1f2430;--text: #e6e6e6;--muted: #a0a7b4}[data-theme=light]{--bg: #f8fafc;--card: #ffffff;--border: #e5e7eb;--text: #0f172a;--muted: #475569}@media (prefers-color-scheme: light){:root{--bg: #f8fafc;--card: #ffffff;--border: #e5e7eb;--text: #0f172a;--muted: #475569}}html,body,#root{height:100%}body{margin:0;background:var(--bg);color:var(--text)}.container{margin:0 auto;padding:24px;max-width:1100px}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px}.card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:16px;box-shadow:0 1px 2px #0000000a,0 2px 12px #00000014}.stat{display:flex;align-items:center;gap:12px}.badge{display:inline-block;font-size:12px;padding:2px 8px;border-radius:999px;border:1px solid var(--border);background:#7c3aed14;color:var(--text)}.table{width:100%;border-collapse:collapse}.table th,.table td{border-bottom:1px solid var(--border);padding:8px 4px;text-align:left}.muted{color:var(--muted)}.accent{color:var(--accent)}.success{color:var(--success)}.warning{color:var(--warning)}.danger{color:var(--danger)}.toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px}.button{border:1px solid var(--border);background:var(--card);color:var(--text);padding:6px 10px;border-radius:8px;cursor:pointer}.button:hover{filter:brightness(1.05)}.hover\:\!bg-blue-700:hover{--tw-bg-opacity: 1 !important;background-color:rgb(29 78 216 / var(--tw-bg-opacity, 1))!important}.hover\:text-gray-200:hover{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-2:focus-visible{--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(2px + 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)}.focus-visible\:ring-blue-400:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(96 165 250 / var(--tw-ring-opacity, 1))}@media (min-width: 640px){.sm\:flex{display:flex}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-end{align-items:flex-end}}@media (min-width: 1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-\[220px_1fr\]{grid-template-columns:220px 1fr}}@media (prefers-color-scheme: dark){.dark\:\!bg-blue-800\/40{background-color:#1e40af66!important}.dark\:bg-blue-900\/20{background-color:#1e3a8a33}.dark\:text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}}
|