mirror of
https://github.com/usestrix/strix.git
synced 2026-11-09 05:19:39 -10:00
Compare commits
31
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10376b412b | ||
|
|
95046a6cea | ||
|
|
aac59de1e5 | ||
|
|
ce358aa879 | ||
|
|
dab93bcc12 | ||
|
|
3b8980c47b | ||
|
|
8157ccba27 | ||
|
|
95d2e5fba9 | ||
|
|
21243486e2 | ||
|
|
97ed7e79a1 | ||
|
|
31c18f8f75 | ||
|
|
f23fadfbff | ||
|
|
08126eb518 | ||
|
|
d4f4697533 | ||
|
|
57304b0084 | ||
|
|
cd8270c98b | ||
|
|
93af2b94a2 | ||
|
|
960caf86aa | ||
|
|
7e02b8d8da | ||
|
|
137a42c3e3 | ||
|
|
473b3c4af1 | ||
|
|
d1a73a24f8 | ||
|
|
a2f5e3acb6 | ||
|
|
e8c2564595 | ||
|
|
78594e1645 | ||
|
|
8ec54d9e2b | ||
|
|
1f7373714b | ||
|
|
ef07bad945 | ||
|
|
89a707ff51 | ||
|
|
59f49a1fa2 | ||
|
|
2bb730c366 |
No files matched your search
@@ -6,6 +6,9 @@ on:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
@@ -24,13 +27,15 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
- uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
@@ -50,7 +55,7 @@ jobs:
|
||||
tar -C dist/release -czvf "dist/release/strix-${VERSION}-${{ matrix.target }}.tar.gz" "strix-${VERSION}-${{ matrix.target }}"
|
||||
fi
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: strix-${{ matrix.target }}
|
||||
path: |
|
||||
@@ -65,13 +70,13 @@ jobs:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
path: release
|
||||
merge-multiple: true
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
|
||||
with:
|
||||
prerelease: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
generate_release_notes: true
|
||||
|
||||
@@ -267,6 +267,20 @@ export STRIX_REASONING_EFFORT="high" # control thinking effort (default: high,
|
||||
> [!NOTE]
|
||||
> Strix automatically saves your configuration to `~/.strix/cli-config.json`, so you don't have to re-enter it on every run.
|
||||
|
||||
#### Sign in with a ChatGPT subscription
|
||||
|
||||
Instead of a metered API key, you can run Strix on your ChatGPT Plus/Pro subscription:
|
||||
|
||||
```bash
|
||||
strix auth login chatgpt # sign in with your ChatGPT account
|
||||
|
||||
export STRIX_LLM="chatgpt/gpt-5.4" # chatgpt/<model> runs on the subscription
|
||||
strix --target ./app-directory
|
||||
|
||||
strix auth status # show the active sign-in
|
||||
strix auth logout # forget the sign-in
|
||||
```
|
||||
|
||||
**Recommended models for best results:**
|
||||
|
||||
- [OpenAI GPT-5.4](https://openai.com/api/) - `openai/gpt-5.4`
|
||||
|
||||
+54
-17
@@ -1,3 +1,26 @@
|
||||
# ---------------------------------------------------------------------------
|
||||
# Builder stage: compile the Go tools here so the Go toolchain (~225MB) and the
|
||||
# module/build caches never reach the runtime image. The resulting binaries are
|
||||
# statically linked and copied into the final stage.
|
||||
# ---------------------------------------------------------------------------
|
||||
FROM kalilinux/kali-rolling:latest AS gobuilder
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y kali-archive-keyring && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends golang-go git ca-certificates
|
||||
|
||||
ENV GOBIN=/out/bin
|
||||
RUN mkdir -p /out/bin && \
|
||||
go install -v github.com/projectdiscovery/httpx/cmd/httpx@latest && \
|
||||
go install -v github.com/projectdiscovery/katana/cmd/katana@latest && \
|
||||
go install -v github.com/projectdiscovery/cvemap/cmd/vulnx@latest && \
|
||||
go install -v github.com/jaeles-project/gospider@latest && \
|
||||
go install -v github.com/projectdiscovery/interactsh/cmd/interactsh-client@latest
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Runtime stage
|
||||
# ---------------------------------------------------------------------------
|
||||
FROM kalilinux/kali-rolling:latest
|
||||
|
||||
LABEL description="AI Agent Penetration Testing Environment with Comprehensive Automated Tools"
|
||||
@@ -19,14 +42,13 @@ RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
wget curl git vim nano unzip tar \
|
||||
apt-transport-https ca-certificates gnupg lsb-release \
|
||||
build-essential software-properties-common \
|
||||
gcc libc6-dev pkg-config libpcap-dev libssl-dev \
|
||||
python3 python3-pip python3-dev python3-venv python3-setuptools \
|
||||
golang-go \
|
||||
software-properties-common \
|
||||
gcc libc6-dev \
|
||||
python3 python3-pip python3-venv python3-setuptools \
|
||||
net-tools dnsutils whois \
|
||||
file xxd \
|
||||
jq parallel ripgrep grep \
|
||||
less man-db procps htop \
|
||||
less procps htop \
|
||||
iproute2 iputils-ping netcat-traditional \
|
||||
nmap ncat ndiff \
|
||||
sqlmap nuclei subfinder naabu ffuf \
|
||||
@@ -66,11 +88,8 @@ RUN curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR=/usr/local/b
|
||||
USER pentester
|
||||
WORKDIR /tmp
|
||||
|
||||
RUN go install -v github.com/projectdiscovery/httpx/cmd/httpx@latest && \
|
||||
go install -v github.com/projectdiscovery/katana/cmd/katana@latest && \
|
||||
go install -v github.com/projectdiscovery/cvemap/cmd/vulnx@latest && \
|
||||
go install -v github.com/jaeles-project/gospider@latest && \
|
||||
go install -v github.com/projectdiscovery/interactsh/cmd/interactsh-client@latest
|
||||
# Go tools are built in the gobuilder stage; copy the static binaries only.
|
||||
COPY --from=gobuilder --chown=pentester:pentester /out/bin/ /home/pentester/go/bin/
|
||||
|
||||
RUN nuclei -update-templates
|
||||
|
||||
@@ -87,7 +106,10 @@ RUN npm install -g retire@latest && \
|
||||
npm install -g js-beautify@latest && \
|
||||
npm install -g @ast-grep/cli@latest && \
|
||||
npm install -g tree-sitter-cli@latest && \
|
||||
npm install -g [email protected]
|
||||
npm install -g [email protected] && \
|
||||
npm cache clean --force && \
|
||||
# ast-grep ships two identical binaries (`ast-grep` and `sg`); dedupe (~52MB)
|
||||
ln -sf ast-grep /home/pentester/.npm-global/lib/node_modules/@ast-grep/cli/sg
|
||||
|
||||
ENV AGENT_BROWSER_EXECUTABLE_PATH=/usr/bin/chromium
|
||||
ENV AGENT_BROWSER_USER_AGENT="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
@@ -132,7 +154,14 @@ RUN git clone https://github.com/aravind0x7/JS-Snooper.git && \
|
||||
|
||||
USER root
|
||||
|
||||
RUN curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin
|
||||
# Install trufflehog into a pentester-owned dir on PATH so its runtime self-update
|
||||
# (which replaces the binary in place) succeeds: as non-root `pentester` it cannot
|
||||
# overwrite a root-owned binary under /usr/local/bin, which otherwise fails with
|
||||
# "cannot move binary" and aborts the scan. Pin the initial version for
|
||||
# reproducible builds; self-update then pulls fresh detectors at runtime.
|
||||
ARG TRUFFLEHOG_VERSION=3.95.9
|
||||
RUN curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /home/pentester/.local/bin "v${TRUFFLEHOG_VERSION}" && \
|
||||
chown -R pentester:pentester /home/pentester/.local
|
||||
RUN set -eux; \
|
||||
ARCH="$(uname -m)"; \
|
||||
case "$ARCH" in \
|
||||
@@ -146,8 +175,6 @@ RUN set -eux; \
|
||||
install -m 0755 /tmp/gitleaks /usr/local/bin/gitleaks; \
|
||||
rm -f /tmp/gitleaks /tmp/gitleaks.tgz
|
||||
|
||||
RUN apt-get update && apt-get install -y zaproxy
|
||||
|
||||
RUN curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
|
||||
|
||||
RUN apt-get install -y wapiti
|
||||
@@ -163,7 +190,12 @@ USER root
|
||||
|
||||
RUN apt-get autoremove -y && \
|
||||
apt-get autoclean && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
|
||||
# Purge non-English locales (~160MB)
|
||||
find /usr/share/locale -mindepth 1 -maxdepth 1 -type d \
|
||||
! -name 'en' ! -name 'en_US' ! -name 'C' -exec rm -rf {} + && \
|
||||
# Remove package documentation and man pages not needed at runtime (~95MB)
|
||||
rm -rf /usr/share/doc/* /usr/share/doc-base/* /usr/share/man/*
|
||||
|
||||
ENV PATH="/home/pentester/go/bin:/home/pentester/.local/bin:/home/pentester/.npm-global/bin:/app/.venv/bin:$PATH"
|
||||
ENV VIRTUAL_ENV="/app/.venv"
|
||||
@@ -205,8 +237,13 @@ RUN python3 -m venv /app/.venv && \
|
||||
COPY --chown=pentester:pentester strix/tools/proxy/caido_api.py /opt/strix-python/caido_api.py
|
||||
ENV PYTHONPATH=/opt/strix-python
|
||||
|
||||
RUN echo 'export PATH="/home/pentester/go/bin:/home/pentester/.local/bin:/home/pentester/.npm-global/bin:$PATH"' >> /home/pentester/.bashrc && \
|
||||
echo 'export PATH="/home/pentester/go/bin:/home/pentester/.local/bin:/home/pentester/.npm-global/bin:$PATH"' >> /home/pentester/.profile
|
||||
# Login shells (e.g. `bash -lc`) source /etc/profile, which on Debian/Kali
|
||||
# hard-resets PATH and drops the image's ENV PATH entries. Re-add the same
|
||||
# directories here — including /app/.venv/bin — so `python3`/`pip` resolve to
|
||||
# the venv (which ships requests, httpx, bs4, lxml, pyjwt, cryptography, and the
|
||||
# Caido SDK) instead of the externally-managed system interpreter.
|
||||
RUN echo 'export PATH="/home/pentester/go/bin:/home/pentester/.local/bin:/home/pentester/.npm-global/bin:/app/.venv/bin:$PATH"' >> /home/pentester/.bashrc && \
|
||||
echo 'export PATH="/home/pentester/go/bin:/home/pentester/.local/bin:/home/pentester/.npm-global/bin:/app/.venv/bin:$PATH"' >> /home/pentester/.profile
|
||||
|
||||
USER root
|
||||
COPY containers/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
@@ -35,6 +35,31 @@ Configure Strix using environment variables or a config file.
|
||||
Timeout in seconds for memory compression operations (context summarization).
|
||||
</ParamField>
|
||||
|
||||
### Dedicated deduplication model
|
||||
|
||||
Finding deduplication is a cheap, structured classification task. By default it
|
||||
runs on the main model, but you can route it to a smaller/cheaper model without
|
||||
affecting the agents that do the actual testing.
|
||||
|
||||
<ParamField path="STRIX_DEDUPE_MODEL" type="string">
|
||||
Model used to judge whether a candidate finding duplicates an existing report.
|
||||
Falls back to `STRIX_LLM` when unset.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="DEDUPE_LLM_API_KEY" type="string">
|
||||
Optional provider key for the deduplication model.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="DEDUPE_LLM_API_BASE" type="string">
|
||||
Optional custom API base URL for the deduplication model. Use when the dedupe
|
||||
model runs on a different endpoint than the main model.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="STRIX_DEDUPE_REASONING_EFFORT" type="string">
|
||||
Reasoning effort for the deduplication model. Defaults to the model's own
|
||||
baseline when unset.
|
||||
</ParamField>
|
||||
|
||||
## Optional Features
|
||||
|
||||
<ParamField path="PERPLEXITY_API_KEY" type="string">
|
||||
|
||||
+2
-1
@@ -116,5 +116,6 @@ strix --mount ./huge-monorepo
|
||||
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| 0 | Scan completed, no vulnerabilities found |
|
||||
| 0 | Scan completed successfully (interactive mode always exits `0`; in headless mode, `0` means no vulnerabilities were found) |
|
||||
| 1 | A fatal error occurred before or during the scan (e.g. missing environment variables, Docker unavailable, invalid config file, diff-scope resolution failure, or an unhandled error) |
|
||||
| 2 | Vulnerabilities found (headless mode only) |
|
||||
+17
-3
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "strix-agent"
|
||||
version = "1.2.0"
|
||||
version = "1.3.1"
|
||||
description = "Open-source AI Hackers for your apps"
|
||||
readme = "README.md"
|
||||
license = "Apache-2.0"
|
||||
@@ -46,7 +46,9 @@ dependencies = [
|
||||
"caido-sdk-client>=0.2.0",
|
||||
"reportlab>=4.0",
|
||||
"pypdf>=5.0",
|
||||
"cryptography>=42",
|
||||
# Cap <49: 49.x drops the universal2 macOS wheel (arm64-only), which breaks
|
||||
# the Intel macOS (macos-x86_64) release build's `uv sync --frozen`.
|
||||
"cryptography>=48.0.1,<49",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
@@ -120,6 +122,7 @@ module = [
|
||||
"pydantic_settings.*",
|
||||
"reportlab.*",
|
||||
"pypdf.*",
|
||||
"pygments.*",
|
||||
]
|
||||
ignore_missing_imports = true
|
||||
disable_error_code = ["import-untyped"]
|
||||
@@ -213,6 +216,10 @@ ignore = [
|
||||
# Test doubles use fixture tokens/passwords and match a callee signature whose
|
||||
# args they intentionally ignore.
|
||||
"tests/test_viewer_auth.py" = ["S105", "S106", "ARG001"]
|
||||
"tests/test_codex_auth.py" = ["S105", "S106", "SLF001"]
|
||||
# Stdlib HTTP handler overrides (do_GET/do_POST).
|
||||
"strix/interface/auth_cli.py" = ["N802"]
|
||||
"tests/test_codex_streaming.py" = ["N802"]
|
||||
"tests/test_report_pdf.py" = ["S105", "S106"]
|
||||
# Stdlib HTTP handler overrides (do_GET/do_POST) and lazy imports that avoid a
|
||||
# circular dependency with strix.telemetry / strix.viewer.report_pdf.
|
||||
@@ -251,9 +258,16 @@ ignore = [
|
||||
"strix/core/runner.py" = ["TC003", "PLR0912", "PLR0915", "PLC0415"]
|
||||
# ReportState carries scan artifact/report fields and
|
||||
# a runtime ``Callable`` annotation on ``vulnerability_found_callback``.
|
||||
"strix/report/state.py" = ["TC003", "PLR0912", "PLR0915", "E501", "PERF401"]
|
||||
"strix/report/state.py" = ["TC003", "PLR0912", "PLR0915", "E501", "PERF401", "PLC0415"]
|
||||
"strix/report/usage.py" = ["PLC0415"]
|
||||
# Lazy import of strix.config.models avoids a circular dependency between the
|
||||
# report pipeline and the config layer.
|
||||
"strix/report/dedupe.py" = ["PLC0415"]
|
||||
"strix/telemetry/logging.py" = ["PLC0415"]
|
||||
"strix/config/models.py" = ["PLC0415"]
|
||||
# Heavy inference deps (httpx, openai) imported lazily so auth-status checks
|
||||
# don't pull them in.
|
||||
"strix/config/codex.py" = ["PLC0415"]
|
||||
# Interface utility branches per scope-mode / target-type combination;
|
||||
# splitting would obscure the decision tree without simplifying it.
|
||||
"strix/interface/utils.py" = ["PLR0912", "BLE001", "PLC0415"]
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ set -euo pipefail
|
||||
|
||||
APP=strix
|
||||
REPO="usestrix/strix"
|
||||
STRIX_IMAGE="ghcr.io/usestrix/strix-sandbox:1.0.0"
|
||||
STRIX_IMAGE="ghcr.io/usestrix/strix-sandbox:1.1.0"
|
||||
|
||||
MUTED='\033[0;2m'
|
||||
RED='\033[0;31m'
|
||||
|
||||
+83
-13
@@ -16,6 +16,7 @@ from agents.tool import CustomTool, FunctionTool, Tool
|
||||
from pydantic import ValidationError
|
||||
|
||||
from strix.agents.prompt import render_system_prompt
|
||||
from strix.config import load_settings
|
||||
from strix.tools.agents_graph.tools import (
|
||||
agent_finish,
|
||||
create_agent,
|
||||
@@ -33,6 +34,7 @@ from strix.tools.notes.tools import (
|
||||
list_notes,
|
||||
update_note,
|
||||
)
|
||||
from strix.tools.output_store import bound_text
|
||||
from strix.tools.proxy.tools import (
|
||||
list_requests,
|
||||
list_sitemap,
|
||||
@@ -103,8 +105,36 @@ def _extract_custom_input(tool: CustomTool, raw_input: str | dict[str, Any]) ->
|
||||
return value if isinstance(value, str) else ""
|
||||
|
||||
|
||||
def _tool_output_limits() -> tuple[int, int]:
|
||||
context = load_settings().context
|
||||
return context.tool_output_max_lines, context.tool_output_max_bytes
|
||||
|
||||
|
||||
def _bound_result(result: Any) -> Any:
|
||||
if not isinstance(result, str):
|
||||
return result
|
||||
max_lines, max_bytes = _tool_output_limits()
|
||||
return bound_text(result, max_lines=max_lines, max_bytes=max_bytes)
|
||||
|
||||
|
||||
def _format_tool_error(exc: Exception) -> str:
|
||||
return str(exc) or exc.__class__.__name__
|
||||
message = str(exc) or exc.__class__.__name__
|
||||
max_lines, max_bytes = _tool_output_limits()
|
||||
return bound_text(message, max_lines=max_lines, max_bytes=max_bytes)
|
||||
|
||||
|
||||
def _with_bounded_result(tool: FunctionTool) -> FunctionTool:
|
||||
"""Cap a tool's result size before it enters history (idempotent)."""
|
||||
if getattr(tool, "_strix_bounded", False):
|
||||
return tool
|
||||
invoke_tool = tool.on_invoke_tool
|
||||
|
||||
async def invoke(ctx: Any, raw_input: str) -> Any:
|
||||
return _bound_result(await invoke_tool(ctx, raw_input))
|
||||
|
||||
tool.on_invoke_tool = invoke
|
||||
tool._strix_bounded = True # type: ignore[attr-defined]
|
||||
return tool
|
||||
|
||||
|
||||
def _function_tool_with_error_result(tool: FunctionTool) -> FunctionTool:
|
||||
@@ -112,7 +142,7 @@ def _function_tool_with_error_result(tool: FunctionTool) -> FunctionTool:
|
||||
|
||||
async def invoke(ctx: Any, raw_input: str) -> Any:
|
||||
try:
|
||||
return await invoke_tool(ctx, raw_input)
|
||||
return _bound_result(await invoke_tool(ctx, raw_input))
|
||||
except Exception as exc: # noqa: BLE001 - tool errors should be model-visible results.
|
||||
logger.debug("Tool %s failed; returning error as result", tool.name, exc_info=True)
|
||||
return _format_tool_error(exc)
|
||||
@@ -127,7 +157,7 @@ def _custom_tool_as_function_tool(tool: CustomTool) -> FunctionTool:
|
||||
if not custom_input:
|
||||
return f"`{_custom_tool_input_field(tool)}` must be a non-empty string."
|
||||
try:
|
||||
return await tool.on_invoke_tool(ctx, custom_input)
|
||||
return _bound_result(await tool.on_invoke_tool(ctx, custom_input))
|
||||
except Exception as exc: # noqa: BLE001 - matches SDK CustomTool error-as-result behavior.
|
||||
logger.debug("Tool %s failed; returning error as result", tool.name, exc_info=True)
|
||||
return _format_tool_error(exc)
|
||||
@@ -159,12 +189,35 @@ def _custom_tool_as_function_tool(tool: CustomTool) -> FunctionTool:
|
||||
)
|
||||
|
||||
|
||||
def _configure_chat_completions_filesystem_tools(toolset: Any) -> None:
|
||||
def _bound_custom_tool(tool: CustomTool) -> CustomTool:
|
||||
"""Bound a native ``CustomTool`` result in place (Responses path)."""
|
||||
invoke_tool = tool.on_invoke_tool
|
||||
|
||||
async def invoke(ctx: Any, raw_input: str) -> Any:
|
||||
return _bound_result(await invoke_tool(ctx, raw_input))
|
||||
|
||||
tool.on_invoke_tool = invoke
|
||||
return tool
|
||||
|
||||
|
||||
def _configure_filesystem_tools(toolset: Any, *, chat_completions: bool) -> None:
|
||||
for name, tool in vars(toolset).items():
|
||||
if isinstance(tool, CustomTool):
|
||||
setattr(toolset, name, _custom_tool_as_function_tool(tool))
|
||||
if chat_completions:
|
||||
if isinstance(tool, CustomTool):
|
||||
setattr(toolset, name, _custom_tool_as_function_tool(tool))
|
||||
elif isinstance(tool, FunctionTool):
|
||||
setattr(toolset, name, _function_tool_with_error_result(tool))
|
||||
elif isinstance(tool, CustomTool):
|
||||
setattr(toolset, name, _bound_custom_tool(tool))
|
||||
elif isinstance(tool, FunctionTool):
|
||||
setattr(toolset, name, _function_tool_with_error_result(tool))
|
||||
setattr(toolset, name, _with_bounded_result(tool))
|
||||
|
||||
|
||||
def _make_filesystem_configurator(*, chat_completions: bool) -> Any:
|
||||
def configure(toolset: Any) -> None:
|
||||
_configure_filesystem_tools(toolset, chat_completions=chat_completions)
|
||||
|
||||
return configure
|
||||
|
||||
|
||||
_CHARS_ESCAPE_RE = re.compile(r"\\(?:u[0-9a-fA-F]{4}|x[0-9a-fA-F]{2}|[0abtnvfr\\])")
|
||||
@@ -205,6 +258,16 @@ def _format_validation_error(tool_name: str, exc: ValidationError) -> str:
|
||||
return f"{tool_name}: invalid arguments — " + "; ".join(parts)
|
||||
|
||||
|
||||
def _apply_shell_output_cap(parsed: dict[str, Any]) -> None:
|
||||
"""Clamp the SDK shell tools' ``max_output_tokens`` to the configured
|
||||
ceiling; a smaller explicit value is respected."""
|
||||
ceiling = load_settings().context.tool_output_max_tokens
|
||||
requested = parsed.get("max_output_tokens")
|
||||
parsed["max_output_tokens"] = (
|
||||
ceiling if not isinstance(requested, int) or requested > ceiling else requested
|
||||
)
|
||||
|
||||
|
||||
def _wrap_exec_command(tool: FunctionTool) -> FunctionTool:
|
||||
invoke_tool = tool.on_invoke_tool
|
||||
|
||||
@@ -213,8 +276,10 @@ def _wrap_exec_command(tool: FunctionTool) -> FunctionTool:
|
||||
parsed = json.loads(raw_input)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
parsed = None
|
||||
if isinstance(parsed, dict) and "shell" not in parsed:
|
||||
parsed["shell"] = "bash"
|
||||
if isinstance(parsed, dict):
|
||||
if "shell" not in parsed:
|
||||
parsed["shell"] = "bash"
|
||||
_apply_shell_output_cap(parsed)
|
||||
raw_input = json.dumps(parsed)
|
||||
try:
|
||||
return await invoke_tool(ctx, raw_input)
|
||||
@@ -240,8 +305,10 @@ def _wrap_write_stdin(tool: FunctionTool) -> FunctionTool:
|
||||
parsed = json.loads(raw_input)
|
||||
except json.JSONDecodeError:
|
||||
parsed = None
|
||||
if isinstance(parsed, dict) and isinstance(parsed.get("chars"), str):
|
||||
parsed["chars"] = _decode_chars_escape(parsed["chars"])
|
||||
if isinstance(parsed, dict):
|
||||
if isinstance(parsed.get("chars"), str):
|
||||
parsed["chars"] = _decode_chars_escape(parsed["chars"])
|
||||
_apply_shell_output_cap(parsed)
|
||||
raw_input = json.dumps(parsed)
|
||||
try:
|
||||
return await invoke_tool(ctx, raw_input)
|
||||
@@ -440,6 +507,9 @@ def build_strix_agent(
|
||||
else:
|
||||
tools = [*_BASE_TOOLS, *agent_tools, agent_finish]
|
||||
_ensure_unique_tool_names(tools)
|
||||
tools = [
|
||||
_with_bounded_result(tool) if isinstance(tool, FunctionTool) else tool for tool in tools
|
||||
]
|
||||
|
||||
logger.info(
|
||||
"Built %s agent '%s' (skills=%d, tools=%d, scan_mode=%s, whitebox=%s)",
|
||||
@@ -459,8 +529,8 @@ def build_strix_agent(
|
||||
model=None,
|
||||
capabilities=[
|
||||
Filesystem(
|
||||
configure_tools=(
|
||||
_configure_chat_completions_filesystem_tools if chat_completions_tools else None
|
||||
configure_tools=_make_filesystem_configurator(
|
||||
chat_completions=chat_completions_tools,
|
||||
),
|
||||
),
|
||||
Shell(
|
||||
|
||||
@@ -91,6 +91,7 @@ def render_system_prompt(
|
||||
loaded_skill_names=list(skill_content.keys()),
|
||||
available_skills=get_available_skills(),
|
||||
interactive=interactive,
|
||||
is_root=is_root,
|
||||
system_prompt_context=system_prompt_context or {},
|
||||
**skill_content,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
You are Strix, an advanced AI application security validation agent developed by OmniSecure Labs. Your purpose is to perform authorized security verification, reproduce and validate weaknesses on in-scope assets, and help remediate real security issues.
|
||||
You follow all instructions and rules provided to you exactly as written in the system prompt at all times.
|
||||
{% if is_root %}
|
||||
<root_agent_directive>
|
||||
YOU ARE THE ROOT AGENT. Your job is ORCHESTRATION, not hands-on testing.
|
||||
- You accomplish security work by DELEGATING to specialized subagents via create_agent — you do NOT run scanners, crawlers, fuzzers, or send exploit/injection payloads yourself.
|
||||
- IMPORTANT — how to read this prompt as root: the rest of this system prompt is written in the second person ("you") and describes the hands-on testing methodology (recon, mapping, scanning, payload spraying, PoC building, fixing). When you are the root agent, treat every such hands-on instruction as something you ensure gets done BY A SUBAGENT, not as a task you perform in your own turns. The "map the target", "recon first", "mandatory initial phases", and "spray payloads" directives are DELEGATION REQUIREMENTS for you — spawn recon/mapping/testing subagents to satisfy them.
|
||||
- Do NOT probe endpoints, run "basic" or "quick" injection/XSS/etc. tests, or do exploratory scanning before delegating. Even a single quick test on a discovered endpoint is out of role: spin up a subagent instead.
|
||||
- Your own turns should be spent on: reading scope/config, decomposing the target, spawning and monitoring subagents, tracking todos/notes/coverage, deciding next steps, and aggregating results into the final report.
|
||||
</root_agent_directive>
|
||||
{% endif %}
|
||||
|
||||
<core_capabilities>
|
||||
- Security assessment and vulnerability scanning
|
||||
@@ -125,10 +134,8 @@ WHITE-BOX TESTING (code provided):
|
||||
- Local execution, unit/integration testing, patch verification, and HTTP requests against locally started in-scope services are normal authorized white-box validation
|
||||
- If dynamically running the code proves impossible after exhaustive attempts, pivot to comprehensive static analysis.
|
||||
- Try to infer how to run the code based on its structure and content.
|
||||
- FIX discovered vulnerabilities in code in same file.
|
||||
- Test patches to confirm vulnerability removal.
|
||||
- Do not stop until all reported vulnerabilities are fixed.
|
||||
- Include code diff in final report.
|
||||
- Derive the code fix as PART OF reporting, not as a separate later pass: create_vulnerability_report already requires the concrete patch inline (`code_locations` with verbatim `fix_before`/`fix_after` and `fix_pr_body`), so the reporting agent that analyzes the root cause is the one that produces the fix. Do NOT spawn a downstream agent afterwards to re-derive/re-apply the same patch.
|
||||
- If you also apply and verify the patch in the repo (edit the file, re-test that the vulnerability is gone), do it in the same agent/turn while the analysis is fresh — right before or as part of filing the report — never as a second re-analysis pass.
|
||||
|
||||
COMBINED MODE (code + deployed target present):
|
||||
- Treat this as static analysis plus dynamic testing simultaneously
|
||||
@@ -189,7 +196,7 @@ EFFICIENCY TACTICS:
|
||||
- For Caido proxy automation inside Python, explicitly import from
|
||||
`caido_api`:
|
||||
`from caido_api import list_requests, view_request, repeat_request, list_sitemap, view_sitemap_entry, scope_rules`
|
||||
- Prefer established fuzzers/scanners where applicable: ffuf, sqlmap, zaproxy, nuclei, wapiti, arjun, httpx, katana, semgrep, bandit, trufflehog, nmap. Use scripts mainly to coordinate or validate around them, not to replace them without reason
|
||||
- Prefer established fuzzers/scanners where applicable: ffuf, sqlmap, nuclei, wapiti, arjun, httpx, katana, semgrep, bandit, trufflehog, nmap. Use scripts mainly to coordinate or validate around them, not to replace them without reason
|
||||
- For trial-heavy vectors (SQLi, XSS, XXE, SSRF, RCE, auth/JWT, deserialization), DO NOT iterate payloads manually in the browser. Always spray payloads via Python scripts through `exec_command` or terminal tools.
|
||||
- When using established fuzzers/scanners, use the proxy for inspection where helpful
|
||||
- Generate/adapt large payload corpora: combine encodings (URL, unicode, base64), comment styles, wrappers, time-based/differential probes. Expand with wordlists/templates
|
||||
@@ -206,7 +213,7 @@ VALIDATION REQUIREMENTS:
|
||||
- Document complete attack chain
|
||||
- Keep going until you find something that matters
|
||||
- A vulnerability is ONLY considered reported when a reporting agent uses create_vulnerability_report (or create_dependency_report for known-CVE dependency/supply-chain findings) with full details. Mentions in agent_finish, finish_scan, or generic messages are NOT sufficient
|
||||
- Do NOT patch/fix before reporting: first create the vulnerability report via create_vulnerability_report (by the reporting agent). Only after reporting is completed should fixing/patching proceed
|
||||
- Reporting and fixing are ONE step, not two: when source is available, the reporting agent derives the concrete fix and files it INLINE via create_vulnerability_report (`code_locations` with `fix_before`/`fix_after` + `fix_pr_body`) — the report is not complete without it. Do NOT report first and then spawn a separate downstream agent to re-derive and re-apply the same patch; that just re-does the analysis and wastes tokens. (Do not silently patch a finding WITHOUT filing a report — the report, with its embedded fix, is the deliverable.)
|
||||
- DEDUPLICATION: The create_vulnerability_report tool uses LLM-based deduplication. If it rejects your report as a duplicate, DO NOT attempt to re-submit the same vulnerability. Accept the rejection and move on to testing other areas. The vulnerability has already been reported by another agent
|
||||
</execution_guidelines>
|
||||
|
||||
@@ -262,7 +269,9 @@ DISK & SCRATCH HYGIENE:
|
||||
- If disk gets tight or a write fails for space, check what's large under /workspace and clean up files from your own task; leave another agent's files unless you've confirmed they're no longer in use
|
||||
|
||||
MANDATORY INITIAL PHASES:
|
||||
|
||||
{% if is_root %}
|
||||
- ROOT AGENT: these phases are mandatory for the assessment, but you MUST accomplish them by delegating to reconnaissance/mapping subagents — do NOT run recon, crawling, enumeration, or mapping tools in your own turns. Spawn the appropriate subagent(s) and track their coverage.
|
||||
{% endif %}
|
||||
BLACK-BOX TESTING - PHASE 1 (RECON & MAPPING):
|
||||
- COMPLETE full reconnaissance: subdomain enumeration, port scanning, service detection
|
||||
- MAP entire attack surface: all endpoints, parameters, APIs, forms, inputs
|
||||
@@ -291,13 +300,14 @@ ROOT AGENT ROLE:
|
||||
- The root agent should coordinate strategy, delegate meaningful work, track progress, maintain todo lists, maintain notes, monitor subagent results, and decide next steps
|
||||
- The root agent should keep a clear view of overall coverage, uncovered attack surfaces, validation status, and reporting/fixing progress
|
||||
- The root agent should avoid spending its own iterations on detailed testing, payload execution, or deep target-specific investigation when that work can be delegated to specialized subagents
|
||||
- The root agent may do lightweight triage, quick verification, or setup work when necessary to unblock delegation, but its default mode should be coordinator/controller
|
||||
- The root agent may do orchestration-support work needed to delegate well — reading scope/config, inspecting workspace layout, reading subagent output/reports, and light bookkeeping. It must NOT do the actual security testing itself: no running scanners/fuzzers/crawlers, no sending injection/XSS/SSRF/etc. payloads, and no "basic" or "quick" probing of discovered endpoints. If a check requires touching the target, delegate it to a subagent rather than doing it yourself
|
||||
- Its default and near-exclusive mode is coordinator/controller
|
||||
- Subagents should do the substantive testing, validation, reporting, and fixing work
|
||||
- The root agent is responsible for ensuring that work is broken down clearly, tracked, and completed across the agent tree
|
||||
|
||||
1. **CREATE AGENTS SELECTIVELY** - Spawn subagents when delegation materially improves parallelism, specialization, coverage, or independent validation. Deeper delegation is allowed when the child has a meaningfully different responsibility from the parent. Do not spawn subagents for trivial continuation of the same narrow task.
|
||||
2. **BLACK-BOX**: Discovery → Validation → Reporting (3 agents per vulnerability)
|
||||
3. **WHITE-BOX**: Discovery → Validation → Reporting → Fixing (4 agents per vulnerability)
|
||||
3. **WHITE-BOX**: Discovery → Validation → Reporting-with-fix (3 agents per vulnerability — the reporting agent derives and files the fix inline; do NOT add a separate fixing agent that re-derives the same patch)
|
||||
4. **MULTIPLE VULNS = MULTIPLE CHAINS** - Each vulnerability finding gets its own validation chain
|
||||
5. **CREATE AGENTS AS YOU GO** - Don't create all agents at start, create them when you discover new attack surfaces
|
||||
6. **ONE JOB PER AGENT** - Each agent has ONE specific task only
|
||||
@@ -316,8 +326,7 @@ BLACK-BOX (domain/URL only):
|
||||
WHITE-BOX (source code provided):
|
||||
- Found authentication code issues? → Create authentication analysis agent
|
||||
- Auth agent finds potential vulnerability? → Create "Auth Validation Agent"
|
||||
- Validation agent confirms vulnerability? → Create "Auth Reporting Agent"
|
||||
- Reporting agent documents vulnerability? → Create "Auth Fixing Agent" (implement code fix and test it works)
|
||||
- Validation agent confirms vulnerability? → Create "Auth Reporting Agent" that files the report AND its inline fix (`code_locations` + `fix_pr_body`) in one shot — no separate fixing agent
|
||||
|
||||
VULNERABILITY WORKFLOW (MANDATORY FOR EVERY FINDING):
|
||||
|
||||
@@ -338,9 +347,11 @@ Authentication Code Agent finds weak password validation
|
||||
↓
|
||||
Spawns "Auth Validation Agent" (proves it's exploitable)
|
||||
↓
|
||||
If valid → Spawns "Auth Reporting Agent" (creates vulnerability report)
|
||||
If valid → Spawns "Auth Reporting Agent" (creates the vulnerability report
|
||||
WITH the fix inline: code_locations fix_before/fix_after + fix_pr_body,
|
||||
applying/verifying the patch in the same turn if desired)
|
||||
↓
|
||||
Spawns "Auth Fixing Agent" (implements secure code fix)
|
||||
STOP - no separate fixing agent; the fix was derived once, at report time
|
||||
```
|
||||
|
||||
CRITICAL RULES:
|
||||
@@ -376,7 +387,7 @@ FOCUS PRINCIPLES:
|
||||
REALISTIC TESTING OUTCOMES:
|
||||
- **No Findings**: Agent completes testing but finds no vulnerabilities
|
||||
- **Validation Failed**: Initial finding was false positive, validation agent confirms it's not exploitable
|
||||
- **Valid Vulnerability**: Validation succeeds, spawns reporting agent and then fixing agent (white-box)
|
||||
- **Valid Vulnerability**: Validation succeeds, spawns a reporting agent that files the report with the fix inline (white-box) — no separate fixing agent
|
||||
|
||||
PERSISTENCE IS MANDATORY:
|
||||
- Real vulnerabilities take TIME - expect to need 2000+ steps minimum
|
||||
@@ -401,7 +412,6 @@ VULNERABILITY ASSESSMENT:
|
||||
- nuclei - Vulnerability scanner with templates
|
||||
- sqlmap - SQL injection detection/exploitation
|
||||
- trivy - Container/dependency vulnerability scanner
|
||||
- zaproxy - OWASP ZAP web app scanner
|
||||
- wapiti - Web vulnerability scanner
|
||||
|
||||
WEB FUZZING & DISCOVERY:
|
||||
@@ -439,10 +449,10 @@ PROXY & INTERCEPTION:
|
||||
- Ignore Caido proxy-generated 50x HTML error pages; these are proxy issues (might happen when requesting a wrong host or SSL/TLS issues, etc).
|
||||
|
||||
PROGRAMMING:
|
||||
- Python 3, uv, Go, Node.js/npm
|
||||
- Python 3, uv, Node.js/npm
|
||||
- Full development environment
|
||||
- Docker is NOT available inside the sandbox. Do not run docker; rely on provided tools to run locally.
|
||||
- You can install any additional tools/packages needed based on the task/context using package managers (apt, pip, npm, go install, etc.)
|
||||
- You can install any additional tools/packages needed based on the task/context using package managers (apt, pip, npm, etc.)
|
||||
|
||||
Directories:
|
||||
- /workspace - where you should work.
|
||||
|
||||
@@ -17,6 +17,8 @@ from strix.config.loader import (
|
||||
persist_current,
|
||||
)
|
||||
from strix.config.settings import (
|
||||
ContextSettings,
|
||||
DedupeSettings,
|
||||
IntegrationSettings,
|
||||
LlmSettings,
|
||||
RuntimeSettings,
|
||||
@@ -26,6 +28,8 @@ from strix.config.settings import (
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ContextSettings",
|
||||
"DedupeSettings",
|
||||
"IntegrationSettings",
|
||||
"LlmSettings",
|
||||
"RuntimeSettings",
|
||||
|
||||
@@ -0,0 +1,411 @@
|
||||
"""ChatGPT (Codex) subscription auth: OAuth login, token refresh, and the OpenAI
|
||||
client that routes inference through the ChatGPT backend.
|
||||
|
||||
Mirrors OpenAI's Codex CLI: OAuth 2.0 + PKCE against ``auth.openai.com``, with the
|
||||
access token sent as a ``Bearer`` token to ``chatgpt.com/backend-api/codex``. Using
|
||||
a ChatGPT subscription outside OpenAI's own products is not officially supported by
|
||||
OpenAI; the user chooses this path knowingly. The OAuth constants are OpenAI's own
|
||||
Codex CLI values (the backend only accepts that client).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import contextlib
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import secrets
|
||||
import threading
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
from openai import AsyncOpenAI
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
PROVIDER = "codex"
|
||||
|
||||
CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann"
|
||||
AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize"
|
||||
TOKEN_URL = "https://auth.openai.com/oauth/token" # noqa: S105 # nosec B105 - URL, not a secret
|
||||
CALLBACK_HOST = "localhost"
|
||||
CALLBACK_PORT = 1455
|
||||
CALLBACK_PATH = "/auth/callback"
|
||||
REDIRECT_URI = f"http://{CALLBACK_HOST}:{CALLBACK_PORT}{CALLBACK_PATH}"
|
||||
SCOPE = "openid profile email offline_access"
|
||||
|
||||
CODEX_BASE_URL = "https://chatgpt.com/backend-api/codex"
|
||||
ORIGINATOR = "codex_cli_rs"
|
||||
_ACCOUNT_CLAIM = "https://api.openai.com/auth"
|
||||
|
||||
_TOKEN_TIMEOUT = 30
|
||||
_EXPIRY_SKEW_S = 300
|
||||
|
||||
_refresh_lock = threading.Lock()
|
||||
|
||||
# Kept separate from cli-config.json so OAuth tokens never land in the env-var config.
|
||||
AUTH_PATH = Path.home() / ".strix" / "subscription-auth.json"
|
||||
|
||||
|
||||
def _read_store() -> dict[str, Any]:
|
||||
try:
|
||||
data = json.loads(AUTH_PATH.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return {}
|
||||
return data if isinstance(data, dict) else {}
|
||||
|
||||
|
||||
def _write_store(data: dict[str, Any]) -> None:
|
||||
AUTH_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
tmp = AUTH_PATH.with_suffix(".json.tmp")
|
||||
tmp.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
||||
with contextlib.suppress(OSError):
|
||||
tmp.chmod(0o600)
|
||||
tmp.replace(AUTH_PATH)
|
||||
with contextlib.suppress(OSError):
|
||||
AUTH_PATH.chmod(0o600)
|
||||
|
||||
|
||||
def read_record() -> dict[str, Any] | None:
|
||||
record = _read_store().get(PROVIDER)
|
||||
if not isinstance(record, dict) or record.get("type") != "oauth":
|
||||
return None
|
||||
if not (record.get("access") and record.get("refresh") and record.get("account_id")):
|
||||
return None
|
||||
return record
|
||||
|
||||
|
||||
def is_authenticated() -> bool:
|
||||
return read_record() is not None
|
||||
|
||||
|
||||
def save_record(record: dict[str, Any]) -> None:
|
||||
data = _read_store()
|
||||
data[PROVIDER] = record
|
||||
_write_store(data)
|
||||
|
||||
|
||||
def logout() -> None:
|
||||
data = _read_store()
|
||||
if PROVIDER not in data:
|
||||
return
|
||||
del data[PROVIDER]
|
||||
if data:
|
||||
_write_store(data)
|
||||
return
|
||||
with contextlib.suppress(OSError):
|
||||
AUTH_PATH.unlink()
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _refresh_guard() -> Iterator[None]:
|
||||
"""Serialize token refresh within (lock) and across (flock) Strix processes,
|
||||
so concurrent runs can't both spend the single-use refresh token."""
|
||||
with _refresh_lock:
|
||||
try:
|
||||
import fcntl
|
||||
|
||||
lock_path = AUTH_PATH.with_suffix(".lock")
|
||||
lock_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
handle = lock_path.open("w")
|
||||
except (ImportError, OSError):
|
||||
yield
|
||||
return
|
||||
try:
|
||||
with contextlib.suppress(OSError):
|
||||
fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
|
||||
yield
|
||||
finally:
|
||||
with contextlib.suppress(OSError):
|
||||
fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
|
||||
handle.close()
|
||||
|
||||
|
||||
class CodexAuthError(Exception):
|
||||
def __init__(self, code: str, message: str | None = None) -> None:
|
||||
self.code = code
|
||||
super().__init__(message or code)
|
||||
|
||||
|
||||
class CodexContentGuardrailError(Exception):
|
||||
"""The ChatGPT backend refused a request via its content guardrail.
|
||||
Terminal — retrying identical content never clears the block."""
|
||||
|
||||
def __init__(self, model: str, original: BaseException | None = None) -> None:
|
||||
self.model = model
|
||||
self.original = original
|
||||
super().__init__(
|
||||
f"'{model}' was blocked by ChatGPT's content guardrails "
|
||||
f"(flagged as a possible cybersecurity risk). "
|
||||
f"Set STRIX_LLM to a model that isn't blocked and re-run."
|
||||
)
|
||||
|
||||
|
||||
_GUARDRAIL_MARKERS = (
|
||||
"flagged for possible cybersecurity risk",
|
||||
"trusted access for cyber",
|
||||
)
|
||||
|
||||
|
||||
def is_content_guardrail_error(exc: BaseException) -> bool:
|
||||
if isinstance(exc, CodexContentGuardrailError):
|
||||
return True
|
||||
text = str(exc).lower()
|
||||
return any(marker in text for marker in _GUARDRAIL_MARKERS)
|
||||
|
||||
|
||||
def _b64url(raw: bytes) -> str:
|
||||
return base64.urlsafe_b64encode(raw).rstrip(b"=").decode("ascii")
|
||||
|
||||
|
||||
def generate_pkce() -> tuple[str, str]:
|
||||
verifier = _b64url(secrets.token_bytes(64))
|
||||
challenge = _b64url(hashlib.sha256(verifier.encode("ascii")).digest())
|
||||
return verifier, challenge
|
||||
|
||||
|
||||
def create_state() -> str:
|
||||
return secrets.token_hex(16)
|
||||
|
||||
|
||||
def build_authorize_url(challenge: str, state: str) -> str:
|
||||
params = {
|
||||
"response_type": "code",
|
||||
"client_id": CLIENT_ID,
|
||||
"redirect_uri": REDIRECT_URI,
|
||||
"scope": SCOPE,
|
||||
"code_challenge": challenge,
|
||||
"code_challenge_method": "S256",
|
||||
"state": state,
|
||||
"id_token_add_organizations": "true",
|
||||
"codex_cli_simplified_flow": "true",
|
||||
"originator": ORIGINATOR,
|
||||
}
|
||||
return f"{AUTHORIZE_URL}?{urllib.parse.urlencode(params)}"
|
||||
|
||||
|
||||
def parse_redirect_input(value: str) -> tuple[str | None, str | None]:
|
||||
"""Extract ``(code, state)`` from a pasted redirect URL, ``code#state``,
|
||||
query string, or bare code."""
|
||||
value = (value or "").strip()
|
||||
if not value:
|
||||
return None, None
|
||||
with contextlib.suppress(ValueError):
|
||||
parsed = urllib.parse.urlparse(value)
|
||||
if parsed.scheme and parsed.query:
|
||||
query = urllib.parse.parse_qs(parsed.query)
|
||||
return _first(query, "code"), _first(query, "state")
|
||||
if "#" in value:
|
||||
code, _, state = value.partition("#")
|
||||
return code or None, state or None
|
||||
if "code=" in value:
|
||||
query = urllib.parse.parse_qs(value)
|
||||
return _first(query, "code"), _first(query, "state")
|
||||
return value, None
|
||||
|
||||
|
||||
def _first(query: dict[str, list[str]], key: str) -> str | None:
|
||||
values = query.get(key)
|
||||
return values[0] if values else None
|
||||
|
||||
|
||||
def _post_form(payload: dict[str, str]) -> dict[str, Any]:
|
||||
body = urllib.parse.urlencode(payload).encode("ascii")
|
||||
request = urllib.request.Request( # noqa: S310 - fixed https OAuth endpoint
|
||||
TOKEN_URL,
|
||||
data=body,
|
||||
headers={
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen( # noqa: S310 # nosec B310 - fixed https endpoint
|
||||
request, timeout=_TOKEN_TIMEOUT
|
||||
) as response:
|
||||
data = json.loads(response.read() or b"{}")
|
||||
except urllib.error.HTTPError as exc:
|
||||
detail = exc.read().decode("utf-8", "replace")[:300]
|
||||
raise CodexAuthError("token_http_error", f"HTTP {exc.code}: {detail}") from exc
|
||||
except (urllib.error.URLError, TimeoutError, OSError) as exc:
|
||||
raise CodexAuthError("unavailable", str(exc)) from exc
|
||||
if not isinstance(data, dict):
|
||||
raise CodexAuthError("bad_response", "token endpoint returned non-object")
|
||||
return data
|
||||
|
||||
|
||||
def _record_from_token_response(
|
||||
data: dict[str, Any], refresh_fallback: str | None = None
|
||||
) -> dict[str, Any]:
|
||||
access = data.get("access_token")
|
||||
# A refresh response may omit refresh_token when it isn't rotated; keep the old one.
|
||||
refresh = data.get("refresh_token") or refresh_fallback
|
||||
expires_in = data.get("expires_in")
|
||||
if not isinstance(access, str) or not access:
|
||||
raise CodexAuthError("bad_response", "token response missing access_token")
|
||||
if not isinstance(refresh, str) or not refresh:
|
||||
raise CodexAuthError("bad_response", "token response missing refresh_token")
|
||||
account_id = _account_id_from_jwt(access) or _account_id_from_jwt(
|
||||
data.get("id_token") if isinstance(data.get("id_token"), str) else ""
|
||||
)
|
||||
if not account_id:
|
||||
raise CodexAuthError("no_account_id", "could not read chatgpt_account_id from token")
|
||||
ttl = expires_in if isinstance(expires_in, int | float) else 3600
|
||||
return {
|
||||
"type": "oauth",
|
||||
"provider": PROVIDER,
|
||||
"access": access,
|
||||
"refresh": refresh,
|
||||
"account_id": account_id,
|
||||
"expires_at": time.time() + ttl,
|
||||
}
|
||||
|
||||
|
||||
def exchange_code(code: str, verifier: str) -> dict[str, Any]:
|
||||
data = _post_form(
|
||||
{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": CLIENT_ID,
|
||||
"code": code,
|
||||
"code_verifier": verifier,
|
||||
"redirect_uri": REDIRECT_URI,
|
||||
}
|
||||
)
|
||||
return _record_from_token_response(data)
|
||||
|
||||
|
||||
def refresh_tokens(refresh_token: str) -> dict[str, Any]:
|
||||
data = _post_form(
|
||||
{
|
||||
"grant_type": "refresh_token",
|
||||
"client_id": CLIENT_ID,
|
||||
"refresh_token": refresh_token,
|
||||
}
|
||||
)
|
||||
return _record_from_token_response(data, refresh_fallback=refresh_token)
|
||||
|
||||
|
||||
def _account_id_from_jwt(token: str | None) -> str | None:
|
||||
"""Read the account id claim without verifying the JWT (the server enforces
|
||||
authenticity on use); it feeds the ``chatgpt-account-id`` header."""
|
||||
if not token or token.count(".") != 2:
|
||||
return None
|
||||
payload_b64 = token.split(".")[1]
|
||||
padding = "=" * (-len(payload_b64) % 4)
|
||||
try:
|
||||
payload = json.loads(base64.urlsafe_b64decode(payload_b64 + padding))
|
||||
except (ValueError, json.JSONDecodeError):
|
||||
return None
|
||||
if not isinstance(payload, dict):
|
||||
return None
|
||||
auth = payload.get(_ACCOUNT_CLAIM)
|
||||
if isinstance(auth, dict):
|
||||
account_id = auth.get("chatgpt_account_id")
|
||||
if isinstance(account_id, str) and account_id:
|
||||
return account_id
|
||||
organizations = payload.get("organizations")
|
||||
if isinstance(organizations, list) and organizations and isinstance(organizations[0], dict):
|
||||
org_id = organizations[0].get("id")
|
||||
if isinstance(org_id, str) and org_id:
|
||||
return org_id
|
||||
return None
|
||||
|
||||
|
||||
def _near_expiry(record: dict[str, Any]) -> bool:
|
||||
expires_at = record.get("expires_at")
|
||||
if not isinstance(expires_at, int | float):
|
||||
return True
|
||||
return expires_at - _EXPIRY_SKEW_S <= time.time()
|
||||
|
||||
|
||||
def get_valid_token() -> tuple[str, str]:
|
||||
"""Return ``(access_token, account_id)``, refreshing under the cross-process
|
||||
guard if near expiry."""
|
||||
record = read_record()
|
||||
if record is None:
|
||||
raise CodexAuthError("not_authenticated", "not signed in; run: strix auth login")
|
||||
if not _near_expiry(record):
|
||||
return record["access"], record["account_id"]
|
||||
with _refresh_guard():
|
||||
record = read_record()
|
||||
if record is None:
|
||||
raise CodexAuthError("not_authenticated", "not signed in; run: strix auth login")
|
||||
if not _near_expiry(record):
|
||||
return record["access"], record["account_id"]
|
||||
try:
|
||||
refreshed = refresh_tokens(record["refresh"])
|
||||
except CodexAuthError:
|
||||
# A peer process may have already spent this single-use refresh token.
|
||||
latest = read_record()
|
||||
if latest and latest["refresh"] != record["refresh"] and not _near_expiry(latest):
|
||||
return latest["access"], latest["account_id"]
|
||||
raise
|
||||
save_record(refreshed)
|
||||
return refreshed["access"], refreshed["account_id"]
|
||||
|
||||
|
||||
def build_openai_client() -> AsyncOpenAI:
|
||||
"""An ``AsyncOpenAI`` for the ChatGPT backend. A per-request hook re-stamps a
|
||||
fresh bearer token so long scans survive token expiry."""
|
||||
import asyncio
|
||||
|
||||
import httpx
|
||||
from openai import AsyncOpenAI
|
||||
|
||||
get_valid_token() # fail fast at configure time if the sign-in is dead
|
||||
|
||||
async def _auth_hook(request: httpx.Request) -> None:
|
||||
access, account_id = await asyncio.to_thread(get_valid_token)
|
||||
request.headers["Authorization"] = f"Bearer {access}"
|
||||
request.headers["chatgpt-account-id"] = account_id
|
||||
|
||||
http_client = httpx.AsyncClient(
|
||||
timeout=httpx.Timeout(600.0, connect=30.0),
|
||||
event_hooks={"request": [_auth_hook]},
|
||||
)
|
||||
return AsyncOpenAI(
|
||||
api_key="strix-codex-oauth", # placeholder; the hook overwrites Authorization
|
||||
base_url=CODEX_BASE_URL,
|
||||
http_client=http_client,
|
||||
default_headers={
|
||||
"OpenAI-Beta": "responses=experimental",
|
||||
"originator": ORIGINATOR,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
_subscription_client: AsyncOpenAI | None = None
|
||||
|
||||
|
||||
def get_subscription_client() -> AsyncOpenAI:
|
||||
global _subscription_client # noqa: PLW0603
|
||||
if _subscription_client is None:
|
||||
_subscription_client = build_openai_client()
|
||||
return _subscription_client
|
||||
|
||||
|
||||
SUBSCRIPTION_PREFIX = "chatgpt/"
|
||||
|
||||
|
||||
def subscription_model(model_name: str | None) -> str | None:
|
||||
"""The model slug behind a ``chatgpt/<model>`` STRIX_LLM, or None."""
|
||||
name = (model_name or "").strip()
|
||||
if not name.lower().startswith(SUBSCRIPTION_PREFIX):
|
||||
return None
|
||||
return name[len(SUBSCRIPTION_PREFIX) :] or None
|
||||
|
||||
|
||||
def auth_mode(model_name: str | None) -> str:
|
||||
return "subscription" if subscription_model(model_name) else "api_key"
|
||||
+128
-12
@@ -2,23 +2,38 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import inspect
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from agents import set_default_openai_api, set_default_openai_key, set_tracing_disabled
|
||||
from agents import (
|
||||
set_default_openai_api,
|
||||
set_default_openai_key,
|
||||
set_tracing_disabled,
|
||||
)
|
||||
from agents.model_settings import ModelSettings
|
||||
from agents.models.multi_provider import MultiProvider
|
||||
from agents.models.openai_responses import OpenAIResponsesModel
|
||||
from agents.retry import (
|
||||
ModelRetryBackoffSettings,
|
||||
ModelRetrySettings,
|
||||
RetryPolicyContext,
|
||||
retry_policies,
|
||||
)
|
||||
from openai.types.shared import Reasoning
|
||||
|
||||
from strix.config import codex
|
||||
from strix.config.loader import load_settings
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from agents.models.interface import ModelProvider
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
from strix.config.settings import Settings
|
||||
from agents.models.interface import Model, ModelProvider
|
||||
from openai import AsyncOpenAI
|
||||
|
||||
from strix.config.settings import ReasoningEffort, Settings
|
||||
|
||||
|
||||
def request_timeout_extra_args(timeout_s: float | None) -> dict[str, float] | None:
|
||||
@@ -33,9 +48,93 @@ def _retry_statusless_provider_errors(context: RetryPolicyContext) -> bool:
|
||||
normalized = context.normalized
|
||||
if normalized.is_abort:
|
||||
return False
|
||||
if codex.is_content_guardrail_error(context.error):
|
||||
return False
|
||||
return normalized.status_code is None
|
||||
|
||||
|
||||
class _CodexResponsesModel(OpenAIResponsesModel):
|
||||
"""Responses model for the ChatGPT subscription backend (always streamed, stateless)."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model: str,
|
||||
openai_client: AsyncOpenAI,
|
||||
*,
|
||||
reasoning_effort: ReasoningEffort | None = None,
|
||||
) -> None:
|
||||
super().__init__(model, openai_client)
|
||||
self._reasoning_effort = reasoning_effort
|
||||
|
||||
def _codex_settings(self, model_settings: ModelSettings) -> ModelSettings:
|
||||
overrides = ModelSettings(store=False, response_include=["reasoning.encrypted_content"])
|
||||
effort = self._reasoning_effort
|
||||
if effort and effort != "none":
|
||||
# Clamp to efforts the backend accepts.
|
||||
if effort == "minimal":
|
||||
effort = "low"
|
||||
elif effort == "xhigh":
|
||||
effort = "high"
|
||||
overrides = overrides.resolve(ModelSettings(reasoning=Reasoning(effort=effort)))
|
||||
return model_settings.resolve(overrides)
|
||||
|
||||
async def _fetch_response(self, *args: Any, stream: bool = False, **kwargs: Any) -> Any:
|
||||
if len(args) >= 3: # model_settings is positional arg 2
|
||||
args = (*args[:2], self._codex_settings(args[2]), *args[3:])
|
||||
try:
|
||||
events = await super()._fetch_response(*args, stream=True, **kwargs) # type: ignore[call-overload]
|
||||
except Exception as exc:
|
||||
guardrail = self._as_guardrail(exc)
|
||||
if guardrail is not None:
|
||||
raise guardrail from exc
|
||||
raise
|
||||
guarded = self._guarded(events)
|
||||
if stream:
|
||||
return guarded
|
||||
final_response = None
|
||||
async for event in guarded:
|
||||
if getattr(event, "type", None) == "response.completed":
|
||||
final_response = event.response
|
||||
if final_response is None:
|
||||
msg = "ChatGPT backend stream ended without a completed response"
|
||||
raise RuntimeError(msg)
|
||||
return final_response
|
||||
|
||||
def _as_guardrail(self, exc: BaseException) -> codex.CodexContentGuardrailError | None:
|
||||
if isinstance(exc, codex.CodexContentGuardrailError):
|
||||
return exc
|
||||
if codex.is_content_guardrail_error(exc):
|
||||
return codex.CodexContentGuardrailError(self.model, exc)
|
||||
return None
|
||||
|
||||
async def _guarded(self, events: Any) -> AsyncIterator[Any]:
|
||||
"""Convert mid-stream guardrail rejections and close the stream on exit."""
|
||||
try:
|
||||
async for event in events:
|
||||
yield event
|
||||
except Exception as exc:
|
||||
guardrail = self._as_guardrail(exc)
|
||||
if guardrail is not None:
|
||||
raise guardrail from exc
|
||||
raise
|
||||
finally:
|
||||
await self._aclose(events)
|
||||
|
||||
@staticmethod
|
||||
async def _aclose(events: Any) -> None:
|
||||
aclose = getattr(events, "aclose", None)
|
||||
if callable(aclose):
|
||||
with contextlib.suppress(Exception):
|
||||
await aclose()
|
||||
return
|
||||
close = getattr(events, "close", None)
|
||||
if callable(close):
|
||||
with contextlib.suppress(Exception):
|
||||
result = close()
|
||||
if inspect.isawaitable(result):
|
||||
await result
|
||||
|
||||
|
||||
class StrixProvider(MultiProvider):
|
||||
"""Route any non-OpenAI prefix through LiteLLM with the prefix preserved,
|
||||
so users type ``deepseek/deepseek-chat`` rather than
|
||||
@@ -59,6 +158,16 @@ class StrixProvider(MultiProvider):
|
||||
return self._get_fallback_provider("litellm"), f"ollama_chat/{stripped_model_name}"
|
||||
return self._get_fallback_provider("litellm"), original_model_name
|
||||
|
||||
def get_model(self, model_name: str | None) -> Model:
|
||||
slug = codex.subscription_model(model_name)
|
||||
if slug:
|
||||
return _CodexResponsesModel(
|
||||
slug,
|
||||
codex.get_subscription_client(),
|
||||
reasoning_effort=load_settings().llm.reasoning_effort,
|
||||
)
|
||||
return super().get_model(model_name)
|
||||
|
||||
|
||||
DEFAULT_MODEL_RETRY = ModelRetrySettings(
|
||||
max_retries=5,
|
||||
@@ -77,39 +186,42 @@ DEFAULT_MODEL_RETRY = ModelRetrySettings(
|
||||
)
|
||||
|
||||
RECOMMENDED_MODEL_NAMES = (
|
||||
"openai/gpt-5.6",
|
||||
"openai/gpt-5.6-sol",
|
||||
"openai/gpt-5.6-terra",
|
||||
"openai/gpt-5.5",
|
||||
"openai/gpt-5.6-luna",
|
||||
"openai/gpt-5.6",
|
||||
"openai/gpt-5.5-pro",
|
||||
"openai/gpt-5.5",
|
||||
"openai/gpt-5.4",
|
||||
"openai/gpt-5.3-codex",
|
||||
"anthropic/claude-fable-5",
|
||||
"anthropic/claude-opus-5",
|
||||
"anthropic/claude-opus-4-8",
|
||||
"anthropic/claude-opus-4-7",
|
||||
"anthropic/claude-sonnet-5",
|
||||
"anthropic/claude-sonnet-4-6",
|
||||
"vertex_ai/gemini-3.1-pro-preview",
|
||||
"gemini/gemini-3.1-pro-preview",
|
||||
"gemini/gemini-3.6-flash",
|
||||
"deepseek/deepseek-v4-pro",
|
||||
"deepseek/deepseek-v4-flash",
|
||||
"dashscope/qwen3.8-max",
|
||||
"dashscope/qwen3.7-max-2026-06-08",
|
||||
"moonshot/kimi-k3",
|
||||
"moonshot/kimi-k2.7-code",
|
||||
"moonshot/kimi-k2.6",
|
||||
)
|
||||
|
||||
_RECOMMENDED_MODEL_NAME_SET = frozenset(name.lower() for name in RECOMMENDED_MODEL_NAMES)
|
||||
|
||||
FRONTIER_MODEL_FAMILIES = (
|
||||
(("azure", "azure_ai", "bedrock_mantle", "openai"), ("gpt-5",)),
|
||||
(("azure", "azure_ai", "bedrock_mantle", "chatgpt", "openai"), ("gpt-5",)),
|
||||
(
|
||||
("anthropic", "azure_ai", "bedrock", "claude", "databricks", "snowflake", "vertex_ai"),
|
||||
("claude-fable-5", "claude-opus-4", "claude-sonnet-5", "claude-sonnet-4"),
|
||||
("claude-fable-5", "claude-opus-5", "claude-opus-4", "claude-sonnet-5", "claude-sonnet-4"),
|
||||
),
|
||||
(("google", "gemini", "vertex_ai"), ("gemini-3",)),
|
||||
(("deepseek",), ("deepseek-v4", "deepseek-r1", "deepseek-reasoner")),
|
||||
(("alibaba", "dashscope", "qwen"), ("qwen3.7", "qwen3.5", "qwen3-max")),
|
||||
(("moonshot", "moonshotai", "kimi"), ("kimi-k2.7", "kimi-k2.6", "kimi-k2.5")),
|
||||
(("alibaba", "dashscope", "qwen"), ("qwen3.8", "qwen3.7", "qwen3-max")),
|
||||
(("moonshot", "moonshotai", "kimi"), ("kimi-k3", "kimi-k2.7", "kimi-k2.6")),
|
||||
)
|
||||
|
||||
|
||||
@@ -117,6 +229,8 @@ def configure_sdk_model_defaults(settings: Settings) -> None:
|
||||
"""Apply Strix config to SDK-native defaults."""
|
||||
llm = settings.llm
|
||||
set_tracing_disabled(True)
|
||||
if codex.subscription_model(llm.model):
|
||||
return
|
||||
_configure_litellm_compatibility()
|
||||
_configure_openrouter_attribution(llm.model)
|
||||
if llm.api_key:
|
||||
@@ -211,6 +325,8 @@ def _configure_litellm_default(name: str, value: str) -> None:
|
||||
|
||||
def uses_chat_completions_tool_schema(model_name: str, settings: Settings) -> bool:
|
||||
"""Return whether the resolved SDK route can only receive JSON function tools."""
|
||||
if codex.subscription_model(model_name):
|
||||
return False
|
||||
model = model_name.strip().lower()
|
||||
if "/" in model and not model.startswith("openai/"):
|
||||
return True
|
||||
|
||||
@@ -43,11 +43,43 @@ class LlmSettings(BaseSettings):
|
||||
timeout: int = Field(default=300, alias="LLM_TIMEOUT")
|
||||
|
||||
|
||||
class DedupeSettings(BaseSettings):
|
||||
model_config = _BASE_CONFIG
|
||||
|
||||
model: str | None = Field(default=None, alias="STRIX_DEDUPE_MODEL")
|
||||
reasoning_effort: ReasoningEffort | None = Field(
|
||||
default=None,
|
||||
alias="STRIX_DEDUPE_REASONING_EFFORT",
|
||||
)
|
||||
api_key: str | None = Field(default=None, alias="DEDUPE_LLM_API_KEY")
|
||||
api_base: str | None = Field(default=None, alias="DEDUPE_LLM_API_BASE")
|
||||
|
||||
|
||||
class ContextSettings(BaseSettings):
|
||||
"""Context-window management: per-tool-output caps and history compaction."""
|
||||
|
||||
model_config = _BASE_CONFIG
|
||||
|
||||
auto_compact: bool = Field(default=True, alias="STRIX_CONTEXT_AUTO_COMPACT")
|
||||
compact_buffer_tokens: int = Field(default=20_000, gt=0, alias="STRIX_CONTEXT_BUFFER_TOKENS")
|
||||
keep_tokens: int = Field(default=8_000, gt=0, alias="STRIX_CONTEXT_KEEP_TOKENS")
|
||||
fallback_context_tokens: int = Field(
|
||||
default=200_000, gt=0, alias="STRIX_CONTEXT_FALLBACK_TOKENS"
|
||||
)
|
||||
summary_max_tokens: int = Field(default=4_096, gt=0, alias="STRIX_CONTEXT_SUMMARY_TOKENS")
|
||||
tool_output_max_tokens: int = Field(default=8_000, gt=0, alias="STRIX_TOOL_OUTPUT_MAX_TOKENS")
|
||||
tool_output_max_lines: int = Field(default=2_000, gt=0, alias="STRIX_TOOL_OUTPUT_MAX_LINES")
|
||||
# Floor above the truncation-notice size so a preview always fits.
|
||||
tool_output_max_bytes: int = Field(
|
||||
default=50 * 1024, ge=1024, alias="STRIX_TOOL_OUTPUT_MAX_BYTES"
|
||||
)
|
||||
|
||||
|
||||
class RuntimeSettings(BaseSettings):
|
||||
model_config = _BASE_CONFIG
|
||||
|
||||
image: str = Field(
|
||||
default="ghcr.io/usestrix/strix-sandbox:1.0.0",
|
||||
default="ghcr.io/usestrix/strix-sandbox:1.1.0",
|
||||
alias="STRIX_IMAGE",
|
||||
)
|
||||
backend: str = Field(default="docker", alias="STRIX_RUNTIME_BACKEND")
|
||||
@@ -85,7 +117,9 @@ class Settings(BaseSettings):
|
||||
model_config = _BASE_CONFIG
|
||||
|
||||
llm: LlmSettings = Field(default_factory=LlmSettings)
|
||||
dedupe: DedupeSettings = Field(default_factory=DedupeSettings)
|
||||
runtime: RuntimeSettings = Field(default_factory=RuntimeSettings)
|
||||
context: ContextSettings = Field(default_factory=ContextSettings)
|
||||
telemetry: TelemetrySettings = Field(default_factory=TelemetrySettings)
|
||||
integrations: IntegrationSettings = Field(default_factory=IntegrationSettings)
|
||||
viewer: ViewerSettings = Field(default_factory=ViewerSettings)
|
||||
+18
-3
@@ -41,6 +41,7 @@ class AgentCoordinator:
|
||||
self.names: dict[str, str] = {}
|
||||
self.metadata: dict[str, dict[str, Any]] = {}
|
||||
self.pending_counts: dict[str, int] = {}
|
||||
self.errors: dict[str, str] = {}
|
||||
self.runtimes: dict[str, AgentRuntime] = {}
|
||||
self._lock = asyncio.Lock()
|
||||
self._snapshot_path: Path | None = None
|
||||
@@ -107,16 +108,23 @@ class AgentCoordinator:
|
||||
async with self._lock:
|
||||
if agent_id in self.statuses:
|
||||
self.statuses[agent_id] = "running"
|
||||
self.errors.pop(agent_id, None)
|
||||
await self._maybe_snapshot()
|
||||
|
||||
async def park_waiting(self, agent_id: str) -> None:
|
||||
await self.set_status(agent_id, "waiting")
|
||||
|
||||
async def set_status(self, agent_id: str, status: Status | str) -> None:
|
||||
async def set_status(
|
||||
self, agent_id: str, status: Status | str, *, error: str | None = None
|
||||
) -> None:
|
||||
async with self._lock:
|
||||
if agent_id not in self.statuses:
|
||||
return
|
||||
self.statuses[agent_id] = status # type: ignore[assignment]
|
||||
if error is not None:
|
||||
self.errors[agent_id] = error
|
||||
elif status == "running":
|
||||
self.errors.pop(agent_id, None)
|
||||
runtime = self.runtimes.setdefault(agent_id, AgentRuntime())
|
||||
runtime.wake.set()
|
||||
logger.info("agent.status %s=%s", agent_id, status)
|
||||
@@ -246,9 +254,14 @@ class AgentCoordinator:
|
||||
|
||||
async def graph_snapshot(
|
||||
self,
|
||||
) -> tuple[dict[str, str | None], dict[str, Status], dict[str, str]]:
|
||||
) -> tuple[dict[str, str | None], dict[str, Status], dict[str, str], dict[str, str]]:
|
||||
async with self._lock:
|
||||
return dict(self.parent_of), dict(self.statuses), dict(self.names)
|
||||
return (
|
||||
dict(self.parent_of),
|
||||
dict(self.statuses),
|
||||
dict(self.names),
|
||||
dict(self.errors),
|
||||
)
|
||||
|
||||
def _message_to_session_item(self, message: dict[str, Any]) -> TResponseInputItem:
|
||||
sender = str(message.get("from", "unknown"))
|
||||
@@ -286,6 +299,7 @@ class AgentCoordinator:
|
||||
"names": dict(self.names),
|
||||
"metadata": {aid: dict(md) for aid, md in self.metadata.items()},
|
||||
"pending_counts": dict(self.pending_counts),
|
||||
"errors": dict(self.errors),
|
||||
}
|
||||
|
||||
async def restore(self, snap: dict[str, Any]) -> None:
|
||||
@@ -295,6 +309,7 @@ class AgentCoordinator:
|
||||
self.names = dict(snap.get("names", {}))
|
||||
self.metadata = {aid: dict(md) for aid, md in snap.get("metadata", {}).items()}
|
||||
self.pending_counts = dict(snap.get("pending_counts", {}))
|
||||
self.errors = dict(snap.get("errors", {}))
|
||||
for aid in self.statuses:
|
||||
self.runtimes.setdefault(aid, AgentRuntime())
|
||||
|
||||
|
||||
@@ -437,10 +437,8 @@ async def _run_cycle( # noqa: PLR0912, PLR0915
|
||||
else:
|
||||
status = "crashed"
|
||||
logger.exception("agent run failed for %s; parking as %s", agent_id, status)
|
||||
await coordinator.set_status(agent_id, status)
|
||||
await coordinator.set_status(agent_id, status, error=str(exc) or type(exc).__name__)
|
||||
await _notify_parent_on_crash(coordinator, agent_id, status)
|
||||
if context.get("parent_id") is None and status in {"failed", "crashed"}:
|
||||
raise
|
||||
return None
|
||||
else:
|
||||
await _settle_run_result(coordinator, agent_id, interactive)
|
||||
|
||||
@@ -238,7 +238,7 @@ async def run_strix_scan(
|
||||
)
|
||||
|
||||
root_agent = build_strix_agent(
|
||||
name="strix",
|
||||
name="Strix",
|
||||
skills=skills,
|
||||
is_root=True,
|
||||
scan_mode=scan_mode,
|
||||
@@ -252,7 +252,7 @@ async def run_strix_scan(
|
||||
if not is_resume:
|
||||
await coordinator.register(
|
||||
root_id,
|
||||
"strix",
|
||||
"Strix",
|
||||
parent_id=None,
|
||||
task=root_task,
|
||||
skills=skills,
|
||||
|
||||
@@ -0,0 +1,419 @@
|
||||
"""`strix auth` — ChatGPT subscription sign-in (login / status / logout).
|
||||
|
||||
Signing in only stores OAuth tokens (``~/.strix/subscription-auth.json``); model
|
||||
selection stays with ``STRIX_LLM``. A ``chatgpt/<model>`` STRIX_LLM runs on the
|
||||
subscription.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import logging
|
||||
import threading
|
||||
import webbrowser
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
|
||||
from strix.config import codex, load_settings
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_CALLBACK_TIMEOUT_S = 300
|
||||
|
||||
# CLI-facing name for the login provider. Internally this is the Codex OAuth
|
||||
# flow (``codex.PROVIDER``), but users know it as ChatGPT, so that's what the
|
||||
# command and messaging say. ``codex`` is accepted as an alias.
|
||||
LOGIN_PROVIDER = "chatgpt"
|
||||
_ACCEPTED_PROVIDERS = frozenset({LOGIN_PROVIDER, codex.PROVIDER})
|
||||
|
||||
_USAGE = "Usage:\n strix auth login chatgpt [--manual]\n strix auth status\n strix auth logout"
|
||||
|
||||
|
||||
def run_auth(argv: list[str]) -> int:
|
||||
"""Entry point for ``strix auth …``. Returns a process exit code."""
|
||||
console = Console()
|
||||
# Bare `strix auth` (no subcommand) defaults to login.
|
||||
subcommand = argv[0] if argv else "login"
|
||||
rest = argv[1:]
|
||||
|
||||
if subcommand in ("-h", "--help", "help"):
|
||||
console.print(_USAGE)
|
||||
return 0
|
||||
|
||||
handlers: dict[str, Callable[[], int]] = {
|
||||
"login": lambda: _login(console, rest),
|
||||
"status": lambda: _status(console),
|
||||
"logout": lambda: _logout(console),
|
||||
}
|
||||
handler = handlers.get(subcommand)
|
||||
if handler is not None:
|
||||
return handler()
|
||||
|
||||
console.print(f"[red]Unknown auth command:[/] {subcommand}\n")
|
||||
console.print(_USAGE)
|
||||
return 2
|
||||
|
||||
|
||||
def _login(console: Console, argv: list[str]) -> int:
|
||||
parser = argparse.ArgumentParser(prog="strix auth login", add_help=True)
|
||||
parser.add_argument(
|
||||
"provider",
|
||||
nargs="?",
|
||||
default=LOGIN_PROVIDER,
|
||||
help="Model provider to sign in with (default: chatgpt).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--manual",
|
||||
action="store_true",
|
||||
help="Skip the local callback server and paste the redirect URL by hand.",
|
||||
)
|
||||
try:
|
||||
args = parser.parse_args(argv)
|
||||
except SystemExit as exc: # argparse already printed the message
|
||||
return int(exc.code or 2)
|
||||
|
||||
if args.provider.lower() not in _ACCEPTED_PROVIDERS:
|
||||
console.print(
|
||||
f"[red]Unsupported provider:[/] {args.provider}. "
|
||||
f"Only '{LOGIN_PROVIDER}' (ChatGPT subscription) is supported."
|
||||
)
|
||||
return 2
|
||||
|
||||
verifier, challenge = codex.generate_pkce()
|
||||
state = codex.create_state()
|
||||
authorize_url = codex.build_authorize_url(challenge, state)
|
||||
|
||||
console.print()
|
||||
console.print("[bold]Signing in with ChatGPT[/] [dim](provider: chatgpt)[/]")
|
||||
console.print(
|
||||
"[dim]This uses your ChatGPT Plus/Pro plan for inference instead of a metered API key.[/]"
|
||||
)
|
||||
console.print()
|
||||
|
||||
try:
|
||||
record = _run_oauth_flow(console, authorize_url, verifier, state, manual=args.manual)
|
||||
except codex.CodexAuthError as exc:
|
||||
return _fail(console, exc)
|
||||
except KeyboardInterrupt:
|
||||
console.print("\n[yellow]Sign-in cancelled.[/]")
|
||||
return 130
|
||||
|
||||
codex.save_record(record)
|
||||
_print_success(console)
|
||||
return 0
|
||||
|
||||
|
||||
def _run_oauth_flow(
|
||||
console: Console,
|
||||
authorize_url: str,
|
||||
verifier: str,
|
||||
state: str,
|
||||
*,
|
||||
manual: bool,
|
||||
) -> dict[str, Any]:
|
||||
"""Drive the browser (or manual) OAuth flow and return a token record."""
|
||||
server = None if manual else _try_start_callback_server()
|
||||
|
||||
console.print("Open this URL in your browser to authorize:")
|
||||
console.print(f"[cyan]{authorize_url}[/]")
|
||||
console.print()
|
||||
if not manual:
|
||||
try:
|
||||
webbrowser.open(authorize_url)
|
||||
except Exception: # noqa: BLE001 - opening a browser is best-effort
|
||||
logger.debug("could not open browser", exc_info=True)
|
||||
|
||||
if server is not None:
|
||||
console.print("[dim]Waiting for you to finish signing in…[/]")
|
||||
result = server.wait(_CALLBACK_TIMEOUT_S)
|
||||
server.shutdown()
|
||||
if result is not None:
|
||||
code, returned_state, error = result
|
||||
if error:
|
||||
raise codex.CodexAuthError("oauth_error", error)
|
||||
return _finish(code, returned_state, verifier, state, require_state=True)
|
||||
console.print("[yellow]Timed out waiting for the browser. Falling back to manual paste.[/]")
|
||||
|
||||
# Manual fallback: the user completes sign-in and pastes the redirect URL
|
||||
# (the browser lands on a localhost page that won't load if no server is up;
|
||||
# the address bar still holds the code+state).
|
||||
console.print()
|
||||
try:
|
||||
pasted = console.input("Paste the full redirect URL (or code#state): ").strip()
|
||||
except EOFError as exc:
|
||||
raise codex.CodexAuthError("no_input", "no redirect URL provided") from exc
|
||||
code, returned_state = codex.parse_redirect_input(pasted)
|
||||
return _finish(code, returned_state, verifier, state, require_state=False)
|
||||
|
||||
|
||||
def _finish(
|
||||
code: str | None,
|
||||
returned_state: str | None,
|
||||
verifier: str,
|
||||
expected_state: str,
|
||||
*,
|
||||
require_state: bool,
|
||||
) -> dict[str, Any]:
|
||||
if not code:
|
||||
raise codex.CodexAuthError("no_code", "no authorization code found in the redirect")
|
||||
# The loopback callback from OpenAI always carries state, so a missing or
|
||||
# mismatched value there is forged (CSRF) and must be rejected. Manual paste
|
||||
# is user-initiated (the user copies their own redirect), so state is only
|
||||
# validated when the pasted value includes it.
|
||||
if require_state and returned_state is None:
|
||||
raise codex.CodexAuthError("state_mismatch", "missing state in callback; possible CSRF")
|
||||
if returned_state is not None and returned_state != expected_state:
|
||||
raise codex.CodexAuthError("state_mismatch", "state did not match; possible CSRF")
|
||||
return codex.exchange_code(code, verifier)
|
||||
|
||||
|
||||
class _CallbackServer:
|
||||
"""A one-shot local HTTP server that catches the OAuth redirect."""
|
||||
|
||||
def __init__(self, httpd: HTTPServer, event: threading.Event, holder: dict[str, Any]) -> None:
|
||||
self._httpd = httpd
|
||||
self._event = event
|
||||
self._holder = holder
|
||||
self._thread = threading.Thread(target=httpd.serve_forever, daemon=True)
|
||||
self._thread.start()
|
||||
|
||||
def wait(self, timeout: float) -> tuple[str | None, str | None, str | None] | None:
|
||||
if not self._event.wait(timeout):
|
||||
return None
|
||||
return (
|
||||
self._holder.get("code"),
|
||||
self._holder.get("state"),
|
||||
self._holder.get("error"),
|
||||
)
|
||||
|
||||
def shutdown(self) -> None:
|
||||
self._httpd.shutdown()
|
||||
self._httpd.server_close()
|
||||
|
||||
|
||||
def _try_start_callback_server() -> _CallbackServer | None:
|
||||
event = threading.Event()
|
||||
holder: dict[str, Any] = {}
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def log_message(self, *args: Any) -> None: # silence default stderr logging
|
||||
pass
|
||||
|
||||
def do_GET(self) -> None:
|
||||
parsed = urlparse(self.path)
|
||||
if parsed.path != codex.CALLBACK_PATH:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
return
|
||||
query = parse_qs(parsed.query)
|
||||
holder["code"] = _first(query, "code")
|
||||
holder["state"] = _first(query, "state")
|
||||
holder["error"] = _first(query, "error_description") or _first(query, "error")
|
||||
body = _render_callback_html().encode("utf-8")
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
event.set()
|
||||
|
||||
try:
|
||||
httpd = HTTPServer(("127.0.0.1", codex.CALLBACK_PORT), Handler)
|
||||
except OSError:
|
||||
logger.debug("could not bind callback port %d", codex.CALLBACK_PORT, exc_info=True)
|
||||
return None
|
||||
return _CallbackServer(httpd, event, holder)
|
||||
|
||||
|
||||
def _first(query: dict[str, list[str]], key: str) -> str | None:
|
||||
values = query.get(key)
|
||||
return values[0] if values else None
|
||||
|
||||
|
||||
def _status(console: Console) -> int:
|
||||
record = codex.read_record()
|
||||
if record is None:
|
||||
console.print("[yellow]Not signed in.[/] Run [cyan]strix auth login chatgpt[/] to sign in.")
|
||||
return 1
|
||||
settings = load_settings()
|
||||
console.print("[green]Signed in[/] with a ChatGPT subscription.")
|
||||
console.print(f" Account: [bold]{record.get('account_id')}[/]")
|
||||
if codex.subscription_model(settings.llm.model):
|
||||
console.print(f" Runs use the subscription (STRIX_LLM=[bold]{settings.llm.model}[/]).")
|
||||
else:
|
||||
console.print(
|
||||
" [yellow]Note:[/] set [cyan]STRIX_LLM[/] to e.g. [cyan]chatgpt/gpt-5.4[/] "
|
||||
"to run on the subscription."
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def _logout(console: Console) -> int:
|
||||
codex.logout()
|
||||
console.print("[green]Signed out.[/] Stored subscription credentials removed.")
|
||||
return 0
|
||||
|
||||
|
||||
def _fail(console: Console, exc: codex.CodexAuthError) -> int:
|
||||
error_text = Text()
|
||||
error_text.append("SIGN-IN FAILED", style="bold red")
|
||||
error_text.append("\n\n", style="white")
|
||||
error_text.append(f"{exc}", style="white")
|
||||
console.print()
|
||||
console.print(
|
||||
Panel(
|
||||
error_text,
|
||||
title="[bold white]STRIX",
|
||||
title_align="left",
|
||||
border_style="red",
|
||||
padding=(1, 2),
|
||||
)
|
||||
)
|
||||
return 1
|
||||
|
||||
|
||||
def _print_success(console: Console) -> None:
|
||||
text = Text()
|
||||
text.append("Signed in with your ChatGPT subscription", style="bold #22c55e")
|
||||
text.append("\n\n", style="white")
|
||||
text.append("Set ", style="white")
|
||||
text.append("STRIX_LLM", style="bold white")
|
||||
text.append(" to a ", style="white")
|
||||
text.append("chatgpt/", style="bold cyan")
|
||||
text.append(" model (e.g. ", style="white")
|
||||
text.append("chatgpt/gpt-5.4", style="bold cyan")
|
||||
text.append(") — runs are billed to your ChatGPT plan.", style="white")
|
||||
text.append("\n\n", style="white")
|
||||
text.append("Run a scan as usual, e.g. ", style="white")
|
||||
text.append("strix --target https://example.com", style="bold cyan")
|
||||
console.print()
|
||||
console.print(
|
||||
Panel(
|
||||
text,
|
||||
title="[bold white]STRIX",
|
||||
title_align="left",
|
||||
border_style="#22c55e",
|
||||
padding=(1, 2),
|
||||
)
|
||||
)
|
||||
console.print()
|
||||
|
||||
|
||||
_LOGO_PATH = Path(__file__).resolve().parent.parent / "viewer" / "static" / "logo.png"
|
||||
|
||||
|
||||
def _logo_img_tag() -> str:
|
||||
"""Return an ``<img>`` for the Strix logo as an inline data URI, or "".
|
||||
|
||||
The callback page is served offline by the local OAuth server, so the logo
|
||||
is embedded rather than linked. Missing/unreadable file degrades to just the
|
||||
"Strix" wordmark.
|
||||
"""
|
||||
try:
|
||||
data = _LOGO_PATH.read_bytes()
|
||||
except OSError:
|
||||
return ""
|
||||
encoded = base64.b64encode(data).decode("ascii")
|
||||
return f'<img class="logo" src="data:image/png;base64,{encoded}" alt="" />'
|
||||
|
||||
|
||||
def _render_callback_html() -> str:
|
||||
return _CALLBACK_HTML.replace("<!--LOGO-->", _logo_img_tag())
|
||||
|
||||
|
||||
_CALLBACK_HTML = """<!doctype html>
|
||||
<html lang="en"><head><meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Strix — signed in</title>
|
||||
<style>
|
||||
:root { color-scheme: dark; }
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0; min-height: 100vh; padding: 24px;
|
||||
font-family: 'Geist', 'Geist Sans', ui-sans-serif, system-ui, -apple-system,
|
||||
"Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
||||
background: #000; color: #ededed;
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
}
|
||||
.topbar {
|
||||
position: absolute; top: 20px; left: 22px;
|
||||
display: flex; align-items: center; gap: 6px; text-decoration: none;
|
||||
}
|
||||
.topbar .logo { width: 40px; height: 40px; display: block; }
|
||||
.topbar span {
|
||||
font-size: 1.1rem; font-weight: 600; letter-spacing: -.01em; color: #fff;
|
||||
transition: color .15s ease;
|
||||
}
|
||||
.topbar:hover span { color: #c9c9c9; }
|
||||
.brand {
|
||||
font-size: 2.1rem; font-weight: 700; letter-spacing: -.02em; color: #fff;
|
||||
text-align: center; margin: 0 0 10px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.35rem; font-weight: 600; letter-spacing: -.01em; color: #f5f5f5;
|
||||
text-align: center; margin: 0 0 28px;
|
||||
}
|
||||
.card {
|
||||
width: 100%; max-width: 430px; text-align: center;
|
||||
background: #171717; border: 1px solid rgba(255, 255, 255, .06);
|
||||
border-radius: 24px; padding: 40px 40px 34px;
|
||||
}
|
||||
.badge {
|
||||
margin: 0 auto 22px; width: 52px; height: 52px; border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center; font-size: 23px; color: #fff;
|
||||
background: rgba(255, 255, 255, .05); border: 1px solid rgba(255, 255, 255, .14);
|
||||
}
|
||||
.msg { margin: 0 auto; max-width: 34ch; color: #b5b5b5; line-height: 1.6; font-size: .98rem; }
|
||||
.rule { height: 1px; background: rgba(255, 255, 255, .07); margin: 26px 0 0; }
|
||||
.tagline { margin: 22px 0 0; color: #7c7c7c; font-size: .9rem; line-height: 1.55; }
|
||||
.tagline b { color: #ededed; font-weight: 500; }
|
||||
.links {
|
||||
margin-top: 18px; display: flex; gap: 8px; justify-content: center;
|
||||
align-items: center; flex-wrap: wrap; font-size: .84rem;
|
||||
}
|
||||
.links a { color: #a3a3a3; text-decoration: none; transition: color .15s ease; }
|
||||
.links a:hover { color: #fff; }
|
||||
.links .dot { color: #3a3a3a; }
|
||||
.close { margin: 24px 0 0; color: #5a5a5a; font-size: .78rem; text-align: center; }
|
||||
</style></head>
|
||||
<body>
|
||||
<a class="topbar" href="https://strix.ai" target="_blank" rel="noopener"
|
||||
aria-label="Strix — strix.ai">
|
||||
<!--LOGO-->
|
||||
<span>Strix</span>
|
||||
</a>
|
||||
<div class="brand">Strix</div>
|
||||
<h1>You're signed in</h1>
|
||||
<main class="card">
|
||||
<div class="badge">✓</div>
|
||||
<p class="msg">Strix is connected to your ChatGPT subscription. Head back to your
|
||||
terminal — your security test runs there.</p>
|
||||
<div class="rule"></div>
|
||||
<p class="tagline">Autonomous AI hackers that <b>find and fix</b> your app's
|
||||
vulnerabilities.</p>
|
||||
<nav class="links">
|
||||
<a href="https://strix.ai" target="_blank" rel="noopener">strix.ai</a>
|
||||
<span class="dot">·</span>
|
||||
<a href="https://docs.strix.ai" target="_blank" rel="noopener">docs</a>
|
||||
<span class="dot">·</span>
|
||||
<a href="https://discord.gg/strix-ai" target="_blank" rel="noopener">community</a>
|
||||
</nav>
|
||||
</main>
|
||||
<p class="close">You can close this tab.</p>
|
||||
</body></html>"""
|
||||
|
||||
|
||||
__all__ = ["run_auth"]
|
||||
+128
-69
@@ -5,9 +5,9 @@ Strix Agent Interface
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
@@ -20,6 +20,7 @@ from rich.text import Text
|
||||
|
||||
from strix.config import (
|
||||
apply_config_override,
|
||||
codex,
|
||||
load_settings,
|
||||
persist_current,
|
||||
)
|
||||
@@ -33,6 +34,13 @@ from strix.config.models import (
|
||||
from strix.core.paths import run_dir_for, runtime_state_dir
|
||||
from strix.interface.cli import run_cli
|
||||
from strix.interface.tui import run_tui
|
||||
from strix.interface.update_check import (
|
||||
is_binary_install,
|
||||
notify_update,
|
||||
prompt_update_if_available,
|
||||
self_update,
|
||||
start_background_check,
|
||||
)
|
||||
from strix.interface.utils import (
|
||||
assign_workspace_subdirs,
|
||||
build_final_stats_text,
|
||||
@@ -85,6 +93,16 @@ def validate_environment() -> None:
|
||||
|
||||
settings = load_settings()
|
||||
|
||||
if codex.subscription_model(settings.llm.model):
|
||||
if not codex.is_authenticated():
|
||||
console.print(
|
||||
f"[red]STRIX_LLM={settings.llm.model} uses your ChatGPT subscription, "
|
||||
"but you're not signed in.[/] Run [cyan]strix auth login chatgpt[/] first."
|
||||
)
|
||||
sys.exit(1)
|
||||
logger.info("Environment OK (ChatGPT subscription)")
|
||||
return
|
||||
|
||||
if not settings.llm.model:
|
||||
missing_required_vars.append("STRIX_LLM")
|
||||
|
||||
@@ -267,6 +285,29 @@ def _provider_import_hint(exc: BaseException, model: str) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def _subscription_error_hint(exc: BaseException) -> str | None:
|
||||
"""Return an actionable hint for a known ChatGPT-subscription error, or None."""
|
||||
if not codex.subscription_model(load_settings().llm.model):
|
||||
return None
|
||||
joined = " ".join(_exception_messages(exc)).lower()
|
||||
if "not supported when using codex with a chatgpt account" in joined:
|
||||
return (
|
||||
"This model isn't available on your ChatGPT subscription. "
|
||||
"Set STRIX_LLM to a model your plan includes (e.g. chatgpt/gpt-5.4)."
|
||||
)
|
||||
if (
|
||||
"error code: 401" in joined
|
||||
or "http 401" in joined
|
||||
or "unauthorized" in joined
|
||||
or "invalid_grant" in joined
|
||||
):
|
||||
return (
|
||||
"Your ChatGPT sign-in has expired or was revoked. Sign in again:\n"
|
||||
" strix auth login chatgpt"
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
async def warm_up_llm(show_model_warning: bool = True) -> None:
|
||||
console = Console()
|
||||
logger.info("Warming up LLM connection")
|
||||
@@ -276,8 +317,8 @@ async def warm_up_llm(show_model_warning: bool = True) -> None:
|
||||
settings = load_settings()
|
||||
configure_sdk_model_defaults(settings)
|
||||
llm = settings.llm
|
||||
|
||||
raw_model = (llm.model or "").strip()
|
||||
|
||||
if (
|
||||
raw_model
|
||||
and "/" not in raw_model
|
||||
@@ -353,23 +394,63 @@ async def warm_up_llm(show_model_warning: bool = True) -> None:
|
||||
)
|
||||
logger.info("LLM warm-up succeeded for model %s", (llm.model or "").strip())
|
||||
|
||||
if settings.dedupe.model:
|
||||
from strix.report.dedupe import _dedupe_extra_args
|
||||
|
||||
dedupe_model = settings.dedupe.model.strip()
|
||||
raw_model = dedupe_model
|
||||
deduper = StrixProvider().get_model(dedupe_model)
|
||||
# Match the runtime path: send the dedupe key/endpoint per call so a
|
||||
# separate-provider dedupe model authenticates during warm-up too.
|
||||
deduper_extra = _dedupe_extra_args(settings.dedupe)
|
||||
deduper_settings = ModelSettings(extra_args=deduper_extra or None)
|
||||
await asyncio.wait_for(
|
||||
deduper.get_response(
|
||||
system_instructions="You are a helpful assistant.",
|
||||
input="Reply with just 'OK'.",
|
||||
model_settings=deduper_settings,
|
||||
tools=[],
|
||||
output_schema=None,
|
||||
handoffs=[],
|
||||
tracing=ModelTracing.DISABLED,
|
||||
previous_response_id=None,
|
||||
conversation_id=None,
|
||||
prompt=None,
|
||||
),
|
||||
timeout=llm.timeout,
|
||||
)
|
||||
logger.info("LLM warm-up succeeded for dedupe model %s", dedupe_model)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("LLM warm-up failed")
|
||||
error_text = Text()
|
||||
error_text.append("LLM CONNECTION FAILED", style="bold red")
|
||||
error_text.append("\n\n", style="white")
|
||||
error_text.append("Could not establish connection to the language model.\n", style="white")
|
||||
error_text.append("Please check your configuration and try again.\n", style="white")
|
||||
hint = _provider_import_hint(e, raw_model)
|
||||
if hint is not None:
|
||||
error_text.append(f"\n{hint}\n", style="bold yellow")
|
||||
error_text.append(f"\nError: {e}", style="dim white")
|
||||
sub_hint = _subscription_error_hint(e)
|
||||
if sub_hint is not None:
|
||||
# The model/backend answered with a clear, actionable rejection —
|
||||
# show that instead of a generic "connection failed".
|
||||
border_style = "yellow"
|
||||
error_text.append("MODEL NOT AVAILABLE ON SUBSCRIPTION", style="bold yellow")
|
||||
error_text.append("\n\n", style="white")
|
||||
error_text.append(f"{sub_hint}\n", style="white")
|
||||
error_text.append(f"\nDetails: {e}", style="dim white")
|
||||
else:
|
||||
border_style = "red"
|
||||
error_text.append("LLM CONNECTION FAILED", style="bold red")
|
||||
error_text.append("\n\n", style="white")
|
||||
error_text.append(
|
||||
"Could not establish connection to the language model.\n", style="white"
|
||||
)
|
||||
error_text.append("Please check your configuration and try again.\n", style="white")
|
||||
hint = _provider_import_hint(e, raw_model)
|
||||
if hint is not None:
|
||||
error_text.append(f"\n{hint}\n", style="bold yellow")
|
||||
error_text.append(f"\nError: {e}", style="dim white")
|
||||
|
||||
panel = Panel(
|
||||
error_text,
|
||||
title="[bold white]STRIX",
|
||||
title_align="left",
|
||||
border_style="red",
|
||||
border_style=border_style,
|
||||
padding=(1, 2),
|
||||
)
|
||||
|
||||
@@ -448,6 +529,14 @@ Examples:
|
||||
version=f"strix {get_version()}",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--update",
|
||||
action="store_true",
|
||||
help="Update strix to the latest version and exit. Self-updates the "
|
||||
"standalone binary install; for pip/pipx/uv installs, prints the "
|
||||
"matching upgrade command instead.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--target",
|
||||
@@ -566,6 +655,9 @@ Examples:
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.update:
|
||||
sys.exit(0 if self_update() else 1)
|
||||
|
||||
if args.instruction and args.instruction_file:
|
||||
parser.error(
|
||||
"Cannot specify both --instruction and --instruction-file. Use one or the other."
|
||||
@@ -664,6 +756,7 @@ def _persist_run_record(args: argparse.Namespace) -> None:
|
||||
"status": "running",
|
||||
"start_time": datetime.now(UTC).isoformat(),
|
||||
"end_time": None,
|
||||
"auth_mode": codex.auth_mode(load_settings().llm.model),
|
||||
"targets_info": args.targets_info,
|
||||
"scan_mode": args.scan_mode,
|
||||
"instruction": args.instruction,
|
||||
@@ -721,9 +814,7 @@ def _load_resume_state(args: argparse.Namespace, parser: argparse.ArgumentParser
|
||||
args.scan_mode = persisted_scan_mode
|
||||
|
||||
|
||||
def display_completion_message(
|
||||
args: argparse.Namespace, results_path: Path, web_url: str | None = None
|
||||
) -> None:
|
||||
def display_completion_message(args: argparse.Namespace, results_path: Path) -> None:
|
||||
console = Console()
|
||||
report_state = get_global_report_state()
|
||||
|
||||
@@ -762,28 +853,12 @@ def display_completion_message(
|
||||
results_text.append(str(results_path), style="#60a5fa")
|
||||
panel_parts.extend(["\n", results_text])
|
||||
|
||||
if web_url:
|
||||
web_text = Text()
|
||||
web_text.append("\n")
|
||||
web_text.append("View in web", style="dim")
|
||||
web_text.append(" ")
|
||||
# OSC-8 hyperlink: clickable in modern terminals, falls back to the URL.
|
||||
web_text.append(web_url, style=f"#60a5fa link {web_url}")
|
||||
panel_parts.extend(["\n", web_text])
|
||||
|
||||
reopen_text = Text()
|
||||
reopen_text.append("\n")
|
||||
reopen_text.append("Reopen", style="dim")
|
||||
reopen_text.append(" ")
|
||||
reopen_text.append(f"strix view {args.run_name}", style="#22c55e")
|
||||
panel_parts.extend(["\n", reopen_text])
|
||||
else:
|
||||
view_text = Text()
|
||||
view_text.append("\n")
|
||||
view_text.append("View", style="dim")
|
||||
view_text.append(" ")
|
||||
view_text.append(f"strix view {args.run_name}", style="#22c55e")
|
||||
panel_parts.extend(["\n", view_text])
|
||||
view_text = Text()
|
||||
view_text.append("\n")
|
||||
view_text.append("View", style="dim")
|
||||
view_text.append(" ")
|
||||
view_text.append(f"strix view {args.run_name}", style="#22c55e")
|
||||
panel_parts.extend(["\n", view_text])
|
||||
|
||||
if not scan_completed:
|
||||
resume_text = Text()
|
||||
@@ -814,6 +889,8 @@ def display_completion_message(
|
||||
"[#60a5fa]discord.gg/strix-ai[/]"
|
||||
)
|
||||
console.print()
|
||||
if not args.non_interactive:
|
||||
notify_update(console)
|
||||
|
||||
|
||||
def pull_docker_image() -> None:
|
||||
@@ -880,11 +957,24 @@ def main() -> None:
|
||||
run_view(sys.argv[2:])
|
||||
return
|
||||
|
||||
# `strix auth …` manages model-subscription sign-in and exits; it needs no
|
||||
# target, Docker, or scan setup.
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "auth":
|
||||
from strix.interface.auth_cli import run_auth
|
||||
|
||||
sys.exit(run_auth(sys.argv[2:]))
|
||||
|
||||
args = parse_arguments()
|
||||
|
||||
if args.config:
|
||||
apply_config_override(validate_config_file(args.config))
|
||||
|
||||
start_background_check()
|
||||
if not args.non_interactive and prompt_update_if_available(Console()):
|
||||
if is_binary_install() and sys.platform != "win32":
|
||||
os.execv(sys.executable, sys.argv) # noqa: S606 # nosec B606
|
||||
sys.exit(0)
|
||||
|
||||
check_docker_installed()
|
||||
pull_docker_image()
|
||||
|
||||
@@ -941,6 +1031,7 @@ def main() -> None:
|
||||
|
||||
_telemetry_start_kwargs = {
|
||||
"model": load_settings().llm.model,
|
||||
"auth_mode": codex.auth_mode(load_settings().llm.model),
|
||||
"scan_mode": args.scan_mode,
|
||||
"is_whitebox": is_whitebox_scan(args.targets_info),
|
||||
"interactive": not args.non_interactive,
|
||||
@@ -975,39 +1066,7 @@ def main() -> None:
|
||||
|
||||
results_path = run_dir_for(args.run_name)
|
||||
|
||||
# For an interactive run, host the local viewer so the completion panel can
|
||||
# show a clickable "View in web" link. Skipped in non-interactive/CI runs
|
||||
# (no TTY to serve and it would block the process).
|
||||
viewer_httpd = None
|
||||
web_url = None
|
||||
if not args.non_interactive and sys.stdout.isatty():
|
||||
from strix.viewer.server import authorized_url, bundle_is_built, serve
|
||||
|
||||
if bundle_is_built():
|
||||
try:
|
||||
viewer_httpd, base_url, token = serve(results_path, open_browser=False)
|
||||
# The completion panel's "View in web" link must authorize the
|
||||
# browser, so hand it the tokened URL rather than the bare host.
|
||||
web_url = authorized_url(base_url, token)
|
||||
posthog.viewer_opened(source="post_scan", live=False)
|
||||
except Exception:
|
||||
logger.debug("could not start local viewer", exc_info=True)
|
||||
viewer_httpd, web_url = None, None
|
||||
|
||||
display_completion_message(args, results_path, web_url=web_url)
|
||||
|
||||
if viewer_httpd is not None:
|
||||
console = Console()
|
||||
console.print("[dim]Hosting the local viewer. Press Ctrl-C to stop.[/]")
|
||||
console.print()
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
console.print("\n[dim]Viewer stopped.[/]")
|
||||
finally:
|
||||
viewer_httpd.shutdown()
|
||||
viewer_httpd.server_close()
|
||||
display_completion_message(args, results_path)
|
||||
|
||||
if args.non_interactive:
|
||||
report_state = get_global_report_state()
|
||||
|
||||
+39
-15
@@ -42,6 +42,12 @@ from strix.interface.tui.renderers.agent_message_renderer import AgentMessageRen
|
||||
from strix.interface.tui.renderers.user_message_renderer import UserMessageRenderer
|
||||
from strix.interface.utils import build_tui_stats_text
|
||||
from strix.report.state import ReportState, set_global_report_state
|
||||
from strix.report.writer import (
|
||||
guess_language_name,
|
||||
parse_fenced_code,
|
||||
resolve_lexer,
|
||||
safe_fence,
|
||||
)
|
||||
from strix.runtime import session_manager
|
||||
|
||||
|
||||
@@ -330,12 +336,11 @@ class VulnerabilityDetailScreen(ModalScreen): # type: ignore[misc]
|
||||
return "#65a30d"
|
||||
return "#6b7280"
|
||||
|
||||
def _highlight_python(self, code: str) -> Text:
|
||||
def _highlight_python(self, code: str, language: str | None = None) -> Text:
|
||||
try:
|
||||
from pygments.lexers import PythonLexer
|
||||
from pygments.styles import get_style_by_name
|
||||
|
||||
lexer = PythonLexer()
|
||||
lexer = resolve_lexer(language, code)
|
||||
style = get_style_by_name("native")
|
||||
colors = {
|
||||
token: f"#{style_def['color']}" for token, style_def in style if style_def["color"]
|
||||
@@ -501,10 +506,11 @@ class VulnerabilityDetailScreen(ModalScreen): # type: ignore[misc]
|
||||
|
||||
poc_script_code = vuln.get("poc_script_code", "")
|
||||
if poc_script_code:
|
||||
poc_language, poc_code = parse_fenced_code(poc_script_code)
|
||||
text.append("\n\n")
|
||||
text.append("PoC Code", style=self.FIELD_STYLE)
|
||||
text.append("\n")
|
||||
text.append_text(self._highlight_python(poc_script_code))
|
||||
text.append_text(self._highlight_python(poc_code, poc_language))
|
||||
|
||||
remediation_steps = vuln.get("remediation_steps", "")
|
||||
if remediation_steps:
|
||||
@@ -601,9 +607,12 @@ class VulnerabilityDetailScreen(ModalScreen): # type: ignore[misc]
|
||||
lines.append(vuln["poc_description"])
|
||||
lines.append("")
|
||||
if vuln.get("poc_script_code"):
|
||||
lines.append("```python")
|
||||
lines.append(vuln["poc_script_code"])
|
||||
lines.append("```")
|
||||
poc_language, poc_code = parse_fenced_code(vuln["poc_script_code"])
|
||||
fence_lang = poc_language or guess_language_name(poc_code)
|
||||
fence = safe_fence(poc_code)
|
||||
lines.append(f"{fence}{fence_lang}")
|
||||
lines.append(poc_code)
|
||||
lines.append(fence)
|
||||
|
||||
if vuln.get("code_locations"):
|
||||
lines.extend(["", "## Code Analysis", ""])
|
||||
@@ -619,7 +628,9 @@ class VulnerabilityDetailScreen(ModalScreen): # type: ignore[misc]
|
||||
if loc.get("label"):
|
||||
lines.append(f" {loc['label']}")
|
||||
if loc.get("snippet"):
|
||||
lines.append(f"```\n{loc['snippet']}\n```")
|
||||
snippet = str(loc["snippet"])
|
||||
snippet_fence = safe_fence(snippet)
|
||||
lines.append(f"{snippet_fence}\n{snippet}\n{snippet_fence}")
|
||||
if loc.get("fix_before") or loc.get("fix_after"):
|
||||
lines.append("**Suggested Fix:**")
|
||||
lines.append("```diff")
|
||||
@@ -802,6 +813,7 @@ class StrixTUIApp(App): # type: ignore[misc]
|
||||
self._scan_stop_event = threading.Event()
|
||||
self._scan_completed = threading.Event()
|
||||
self._scan_error: BaseException | None = None
|
||||
self._error_noted_agents: set[str] = set()
|
||||
|
||||
self._spinner_frame_index: int = 0
|
||||
self._sweep_num_squares: int = 6
|
||||
@@ -1015,22 +1027,32 @@ class StrixTUIApp(App): # type: ignore[misc]
|
||||
else:
|
||||
self._agent_graph_sync_future = None
|
||||
try:
|
||||
parent_of, statuses, names = future.result()
|
||||
parent_of, statuses, names, errors = future.result()
|
||||
except Exception:
|
||||
logger.exception("TUI agent graph sync failed")
|
||||
else:
|
||||
for agent_id, status in statuses.items():
|
||||
error = errors.get(agent_id)
|
||||
self.live_view.upsert_agent(
|
||||
agent_id,
|
||||
name=names.get(agent_id, agent_id),
|
||||
parent_id=parent_of.get(agent_id),
|
||||
status=status,
|
||||
error_message=error,
|
||||
)
|
||||
if status in {"failed", "crashed"} and error:
|
||||
if agent_id not in self._error_noted_agents:
|
||||
self._error_noted_agents.add(agent_id)
|
||||
self.live_view.record_agent_error(agent_id, error)
|
||||
else:
|
||||
self._error_noted_agents.discard(agent_id)
|
||||
|
||||
if self._scan_loop is None or self._scan_loop.is_closed():
|
||||
return
|
||||
|
||||
async def collect() -> tuple[dict[str, str | None], dict[str, Any], dict[str, str]]:
|
||||
async def collect() -> tuple[
|
||||
dict[str, str | None], dict[str, Any], dict[str, str], dict[str, str]
|
||||
]:
|
||||
return await self.coordinator.graph_snapshot()
|
||||
|
||||
self._agent_graph_sync_future = asyncio.run_coroutine_threadsafe(collect(), self._scan_loop)
|
||||
@@ -1049,6 +1071,7 @@ class StrixTUIApp(App): # type: ignore[misc]
|
||||
"waiting": "⏸",
|
||||
"completed": "🟢",
|
||||
"failed": "🔴",
|
||||
"crashed": "🔴",
|
||||
"stopped": "■",
|
||||
}
|
||||
|
||||
@@ -1234,13 +1257,12 @@ class StrixTUIApp(App): # type: ignore[misc]
|
||||
text.append(msg)
|
||||
return (text, Text(), False)
|
||||
|
||||
if status == "failed":
|
||||
if status in {"failed", "crashed"}:
|
||||
error_msg = agent_data.get("error_message", "")
|
||||
text = Text()
|
||||
if error_msg:
|
||||
text.append(error_msg, style="red")
|
||||
else:
|
||||
text.append("Scan failed", style="red")
|
||||
text.append(error_msg or "Agent failed", style="red")
|
||||
text.append(" · ", style="dim")
|
||||
text.append("Send message to resume", style="dim")
|
||||
self._stop_dot_animation()
|
||||
return (text, Text(), False)
|
||||
|
||||
@@ -1539,6 +1561,7 @@ class StrixTUIApp(App): # type: ignore[misc]
|
||||
"waiting": "⏸",
|
||||
"completed": "🟢",
|
||||
"failed": "🔴",
|
||||
"crashed": "🔴",
|
||||
"stopped": "■",
|
||||
}
|
||||
|
||||
@@ -1584,6 +1607,7 @@ class StrixTUIApp(App): # type: ignore[misc]
|
||||
"waiting": "⏸",
|
||||
"completed": "🟢",
|
||||
"failed": "🔴",
|
||||
"crashed": "🔴",
|
||||
"stopped": "■",
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,17 @@ class TuiLiveView:
|
||||
current["error_message"] = error_message
|
||||
current["updated_at"] = now
|
||||
|
||||
def record_agent_error(self, agent_id: str, error: str) -> None:
|
||||
self._append_event(
|
||||
agent_id,
|
||||
"chat",
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": (f"An error occurred: {error}\nI'm now waiting for new instructions."),
|
||||
"metadata": {"source": "agent_error"},
|
||||
},
|
||||
)
|
||||
|
||||
def record_user_message(self, agent_id: str, content: str) -> None:
|
||||
self._append_event(
|
||||
agent_id,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
from functools import cache
|
||||
from typing import Any, ClassVar
|
||||
|
||||
from pygments.lexers import PythonLexer
|
||||
from pygments.styles import get_style_by_name
|
||||
from rich.text import Text
|
||||
from textual.widgets import Static
|
||||
|
||||
from strix.report.writer import parse_fenced_code, resolve_lexer
|
||||
|
||||
from .base_renderer import BaseToolRenderer
|
||||
from .registry import register_tool_renderer
|
||||
|
||||
@@ -61,8 +62,8 @@ class CreateVulnerabilityReportRenderer(BaseToolRenderer):
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _highlight_python(cls, code: str) -> Text:
|
||||
lexer = PythonLexer()
|
||||
def _highlight_code(cls, code: str, language: str | None) -> Text:
|
||||
lexer = resolve_lexer(language, code)
|
||||
text = Text()
|
||||
|
||||
for token_type, token_value in lexer.get_tokens(code):
|
||||
@@ -234,10 +235,11 @@ class CreateVulnerabilityReportRenderer(BaseToolRenderer):
|
||||
text.append(poc_description)
|
||||
|
||||
if poc_script_code:
|
||||
poc_language, poc_code = parse_fenced_code(poc_script_code)
|
||||
text.append("\n\n")
|
||||
text.append("PoC Code", style=FIELD_STYLE)
|
||||
text.append("\n")
|
||||
text.append_text(cls._highlight_python(poc_script_code))
|
||||
text.append_text(cls._highlight_code(poc_code, poc_language))
|
||||
|
||||
if remediation_steps:
|
||||
text.append("\n\n")
|
||||
|
||||
@@ -0,0 +1,389 @@
|
||||
"""Update notifications and self-update for the strix CLI.
|
||||
|
||||
Follows the pattern used by tools like gh, uv, and pip: a background,
|
||||
rate-limited (once per 24h) check against the release source, a cached
|
||||
result in ``~/.strix``, a non-intrusive notice with the upgrade command
|
||||
for the detected install method, and a ``strix --update`` self-update
|
||||
path for the standalone binary install.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
import requests
|
||||
from rich.console import Console
|
||||
from rich.prompt import Prompt
|
||||
|
||||
from strix.telemetry._common import get_version
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
GITHUB_REPO = "usestrix/strix"
|
||||
PYPI_PACKAGE = "strix-agent"
|
||||
CHECK_INTERVAL_SECONDS = 24 * 60 * 60
|
||||
REQUEST_TIMEOUT_SECONDS = 5
|
||||
|
||||
_CACHE_PATH = Path.home() / ".strix" / "update-check.json"
|
||||
|
||||
_background_thread: threading.Thread | None = None
|
||||
|
||||
|
||||
def _is_disabled() -> bool:
|
||||
return bool(os.environ.get("STRIX_NO_UPDATE_CHECK")) or any(
|
||||
os.environ.get(key)
|
||||
for key in ("CI", "GITHUB_ACTIONS", "GITLAB_CI", "JENKINS_URL", "BUILDKITE", "CIRCLECI")
|
||||
)
|
||||
|
||||
|
||||
def is_binary_install() -> bool:
|
||||
return bool(getattr(sys, "frozen", False))
|
||||
|
||||
|
||||
def get_install_method() -> str:
|
||||
if is_binary_install():
|
||||
return "binary"
|
||||
prefix = str(Path(sys.prefix)).replace("\\", "/")
|
||||
if "/pipx/" in prefix or prefix.endswith("/pipx"):
|
||||
return "pipx"
|
||||
if "/uv/tools/" in prefix:
|
||||
return "uv"
|
||||
return "pip"
|
||||
|
||||
|
||||
def get_upgrade_command(method: str | None = None) -> str:
|
||||
method = method or get_install_method()
|
||||
commands = {
|
||||
"binary": "strix --update",
|
||||
"pipx": "pipx upgrade strix-agent",
|
||||
"uv": "uv tool upgrade strix-agent",
|
||||
"pip": "pip install --upgrade strix-agent",
|
||||
}
|
||||
return commands[method]
|
||||
|
||||
|
||||
def _parse_version(value: str) -> tuple[int, ...] | None:
|
||||
parts = value.strip().lstrip("v").split(".")
|
||||
try:
|
||||
return tuple(int(part) for part in parts)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def _is_newer(latest: str, current: str) -> bool:
|
||||
latest_parts = _parse_version(latest)
|
||||
current_parts = _parse_version(current)
|
||||
if latest_parts is None or current_parts is None:
|
||||
return False
|
||||
return latest_parts > current_parts
|
||||
|
||||
|
||||
def _fetch_latest_version() -> str | None:
|
||||
try:
|
||||
if is_binary_install():
|
||||
response = requests.get(
|
||||
f"https://api.github.com/repos/{GITHUB_REPO}/releases/latest",
|
||||
timeout=REQUEST_TIMEOUT_SECONDS,
|
||||
)
|
||||
response.raise_for_status()
|
||||
tag = response.json().get("tag_name", "")
|
||||
return tag.lstrip("v") or None
|
||||
response = requests.get(
|
||||
f"https://pypi.org/pypi/{PYPI_PACKAGE}/json",
|
||||
timeout=REQUEST_TIMEOUT_SECONDS,
|
||||
)
|
||||
response.raise_for_status()
|
||||
version = response.json().get("info", {}).get("version")
|
||||
return str(version) if version else None
|
||||
except Exception: # noqa: BLE001
|
||||
logger.debug("update check failed", exc_info=True)
|
||||
return None
|
||||
|
||||
|
||||
def _fetch_asset_digest(version: str, filename: str) -> str | None:
|
||||
"""Return the expected sha256 (hex) for a release asset, if the API provides one."""
|
||||
try:
|
||||
response = requests.get(
|
||||
f"https://api.github.com/repos/{GITHUB_REPO}/releases/tags/v{version}",
|
||||
timeout=REQUEST_TIMEOUT_SECONDS,
|
||||
)
|
||||
response.raise_for_status()
|
||||
for asset in response.json().get("assets", []):
|
||||
if asset.get("name") == filename:
|
||||
digest = asset.get("digest") or ""
|
||||
if digest.startswith("sha256:"):
|
||||
return digest.removeprefix("sha256:")
|
||||
except Exception: # noqa: BLE001
|
||||
logger.debug("release asset digest lookup failed", exc_info=True)
|
||||
return None
|
||||
|
||||
|
||||
def _sha256_file(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as f:
|
||||
for chunk in iter(lambda: f.read(1 << 20), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _read_cache() -> dict[str, object]:
|
||||
try:
|
||||
with _CACHE_PATH.open(encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
if isinstance(data, dict):
|
||||
return cast("dict[str, object]", data)
|
||||
except Exception: # noqa: BLE001, S110
|
||||
pass # nosec B110
|
||||
return {}
|
||||
|
||||
|
||||
def _write_cache(**fields: object) -> None:
|
||||
try:
|
||||
cache = _read_cache()
|
||||
cache.update(fields)
|
||||
_CACHE_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
_CACHE_PATH.write_text(json.dumps(cache), encoding="utf-8")
|
||||
except Exception: # noqa: BLE001, S110
|
||||
pass # nosec B110
|
||||
|
||||
|
||||
def skip_version(version: str) -> None:
|
||||
"""Remember not to prompt again for this version (newer releases still notify)."""
|
||||
_write_cache(skipped_version=version)
|
||||
|
||||
|
||||
def _refresh_cache() -> None:
|
||||
latest = _fetch_latest_version()
|
||||
if latest:
|
||||
_write_cache(latest_version=latest, checked_at=time.time())
|
||||
|
||||
|
||||
def start_background_check() -> None:
|
||||
"""Refresh the cached latest-version info in a daemon thread (at most once per 24h)."""
|
||||
global _background_thread # noqa: PLW0603
|
||||
if _is_disabled():
|
||||
return
|
||||
cache = _read_cache()
|
||||
checked_at = cache.get("checked_at")
|
||||
if isinstance(checked_at, int | float) and time.time() - checked_at < CHECK_INTERVAL_SECONDS:
|
||||
return
|
||||
_background_thread = threading.Thread(target=_refresh_cache, daemon=True)
|
||||
_background_thread.start()
|
||||
|
||||
|
||||
def get_available_update(*, respect_skip: bool = True) -> str | None:
|
||||
"""Return the newer version from the cache, or None if up to date / unknown."""
|
||||
if _is_disabled():
|
||||
return None
|
||||
if _background_thread is not None:
|
||||
_background_thread.join(timeout=0.2)
|
||||
cache = _read_cache()
|
||||
latest = cache.get("latest_version")
|
||||
current = get_version()
|
||||
if not isinstance(latest, str) or current == "unknown" or not _is_newer(latest, current):
|
||||
return None
|
||||
if respect_skip and cache.get("skipped_version") == latest:
|
||||
return None
|
||||
return latest
|
||||
|
||||
|
||||
def notify_update(console: Console) -> None:
|
||||
latest = get_available_update()
|
||||
if not latest:
|
||||
return
|
||||
console.print(
|
||||
f"[#eab308]A new version of strix is available:[/] "
|
||||
f"[dim]{get_version()}[/] [dim]→[/] [bold #22c55e]{latest}[/]"
|
||||
f" [dim]·[/] [#60a5fa]{get_upgrade_command()}[/]"
|
||||
)
|
||||
console.print()
|
||||
|
||||
|
||||
def run_package_upgrade(console: Console, method: str) -> bool:
|
||||
"""Upgrade a package-manager install by running its upgrade command."""
|
||||
command = get_upgrade_command(method).split()
|
||||
console.print(f"[dim]Running[/] [#60a5fa]{' '.join(command)}[/]")
|
||||
try:
|
||||
result = subprocess.run(command, check=False) # noqa: S603
|
||||
except OSError as e:
|
||||
console.print(f"[bold red]Update failed:[/] {e}")
|
||||
return False
|
||||
if result.returncode != 0:
|
||||
console.print(
|
||||
f"[bold red]Update failed[/] [dim](exit code {result.returncode}).[/] "
|
||||
f"Run it manually: [#60a5fa]{get_upgrade_command(method)}[/]"
|
||||
)
|
||||
return False
|
||||
console.print("[#22c55e]✓ strix updated — restart the scan to use the new version[/]")
|
||||
return True
|
||||
|
||||
|
||||
def prompt_update_if_available(console: Console) -> bool:
|
||||
"""Offer an interactive update before a scan starts.
|
||||
|
||||
Returns True if strix was updated (caller should re-exec / exit).
|
||||
"""
|
||||
latest = get_available_update()
|
||||
if not latest or not sys.stdin.isatty() or not sys.stdout.isatty():
|
||||
return False
|
||||
console.print()
|
||||
console.print(
|
||||
f"[#eab308]A new version of strix is available:[/] "
|
||||
f"[dim]{get_version()}[/] [dim]→[/] [bold #22c55e]{latest}[/]"
|
||||
)
|
||||
console.print(
|
||||
"[dim] y — update now n — not now (ask again next run) s — skip this version[/]"
|
||||
)
|
||||
choice = Prompt.ask("Update strix?", choices=["y", "n", "s"], default="n")
|
||||
console.print()
|
||||
if choice == "s":
|
||||
skip_version(latest)
|
||||
return False
|
||||
if choice != "y":
|
||||
return False
|
||||
method = get_install_method()
|
||||
if method == "binary":
|
||||
return self_update(console, version=latest)
|
||||
return run_package_upgrade(console, method)
|
||||
|
||||
|
||||
def _release_target() -> str | None:
|
||||
raw_os = platform.system().lower()
|
||||
os_name = {"darwin": "macos", "linux": "linux", "windows": "windows"}.get(raw_os)
|
||||
arch = platform.machine().lower()
|
||||
arch = {"aarch64": "arm64", "amd64": "x86_64"}.get(arch, arch)
|
||||
if os_name is None:
|
||||
return None
|
||||
target = f"{os_name}-{arch}"
|
||||
supported = {"linux-x86_64", "macos-x86_64", "macos-arm64", "windows-x86_64"}
|
||||
return target if target in supported else None
|
||||
|
||||
|
||||
def _download_and_replace(version: str, target: str, console: Console) -> bool:
|
||||
is_windows = target.startswith("windows")
|
||||
archive_ext = ".zip" if is_windows else ".tar.gz"
|
||||
filename = f"strix-{version}-{target}{archive_ext}"
|
||||
url = f"https://github.com/{GITHUB_REPO}/releases/download/v{version}/{filename}"
|
||||
binary_name = f"strix-{version}-{target}" + (".exe" if is_windows else "")
|
||||
current_exe = Path(sys.executable).resolve()
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
tmp_dir = Path(tmp)
|
||||
archive_path = tmp_dir / filename
|
||||
console.print(f"[dim]Downloading[/] {url}")
|
||||
with requests.get( # nosec B113
|
||||
url,
|
||||
stream=True,
|
||||
timeout=REQUEST_TIMEOUT_SECONDS * 12,
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
with archive_path.open("wb") as f:
|
||||
for chunk in response.iter_content(chunk_size=1 << 20):
|
||||
f.write(chunk)
|
||||
|
||||
expected_digest = _fetch_asset_digest(version, filename)
|
||||
if expected_digest:
|
||||
actual_digest = _sha256_file(archive_path)
|
||||
if actual_digest != expected_digest:
|
||||
raise RuntimeError(
|
||||
f"checksum mismatch for {filename}: "
|
||||
f"expected sha256 {expected_digest}, got {actual_digest}"
|
||||
)
|
||||
else:
|
||||
console.print("[dim yellow]No published checksum available; skipping verification[/]")
|
||||
|
||||
if is_windows:
|
||||
with zipfile.ZipFile(archive_path) as zf:
|
||||
zf.extract(binary_name, tmp_dir)
|
||||
else:
|
||||
with tarfile.open(archive_path, "r:gz") as tf:
|
||||
tf.extract(binary_name, tmp_dir, filter="data")
|
||||
|
||||
new_binary = tmp_dir / binary_name
|
||||
new_binary.chmod(new_binary.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
||||
|
||||
staged = current_exe.with_name(current_exe.name + ".new")
|
||||
try:
|
||||
shutil.copy2(new_binary, staged)
|
||||
if is_windows:
|
||||
# Windows can't replace a running executable in place; move it aside first.
|
||||
old = current_exe.with_name(current_exe.name + ".old")
|
||||
old.unlink(missing_ok=True)
|
||||
current_exe.rename(old)
|
||||
try:
|
||||
staged.replace(current_exe)
|
||||
except Exception:
|
||||
old.rename(current_exe)
|
||||
raise
|
||||
else:
|
||||
staged.replace(current_exe)
|
||||
except Exception:
|
||||
staged.unlink(missing_ok=True)
|
||||
raise
|
||||
return True
|
||||
|
||||
|
||||
def self_update(console: Console | None = None, version: str | None = None) -> bool:
|
||||
"""Replace the running standalone binary with the latest release.
|
||||
|
||||
Returns True on success. For package-manager installs this only
|
||||
prints the right upgrade command and returns False.
|
||||
"""
|
||||
console = console or Console()
|
||||
|
||||
if not is_binary_install():
|
||||
method = get_install_method()
|
||||
console.print(
|
||||
f"[#eab308]This strix was installed via {method};[/] "
|
||||
f"upgrade it with: [#60a5fa]{get_upgrade_command(method)}[/]"
|
||||
)
|
||||
return False
|
||||
|
||||
latest = version or _fetch_latest_version()
|
||||
if not latest:
|
||||
console.print("[bold red]Could not determine the latest strix version.[/]")
|
||||
return False
|
||||
|
||||
current = get_version()
|
||||
if current != "unknown" and not _is_newer(latest, current):
|
||||
console.print(f"[#22c55e]strix {current} is already the latest version.[/]")
|
||||
return True
|
||||
|
||||
target = _release_target()
|
||||
if not target:
|
||||
console.print(
|
||||
f"[bold red]No prebuilt binary for this platform "
|
||||
f"({platform.system()}/{platform.machine()}).[/]"
|
||||
)
|
||||
return False
|
||||
|
||||
try:
|
||||
_download_and_replace(latest, target, console)
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.debug("self-update failed", exc_info=True)
|
||||
console.print(f"[bold red]Update failed:[/] {e}")
|
||||
console.print(
|
||||
"[dim]You can reinstall manually with:[/] "
|
||||
"[#60a5fa]curl -sSL https://strix.ai/install | bash[/]"
|
||||
)
|
||||
return False
|
||||
|
||||
_write_cache(latest_version=latest, checked_at=time.time())
|
||||
console.print(f"[#22c55e]✓ Updated strix to {latest}[/]")
|
||||
return True
|
||||
@@ -253,6 +253,20 @@ def _llm_usage(report_state: Any) -> dict[str, Any]:
|
||||
return usage if isinstance(usage, dict) else {}
|
||||
|
||||
|
||||
def _is_subscription(report_state: Any) -> bool:
|
||||
"""Whether this run uses a model subscription (no metered cost).
|
||||
|
||||
Prefers the run record so it's correct for hydrated/resumed runs; falls back
|
||||
to current settings.
|
||||
"""
|
||||
record = getattr(report_state, "run_record", None)
|
||||
if isinstance(record, dict) and record.get("auth_mode"):
|
||||
return record.get("auth_mode") == "subscription"
|
||||
from strix.config import codex
|
||||
|
||||
return codex.auth_mode(load_settings().llm.model) == "subscription"
|
||||
|
||||
|
||||
def _int_stat(usage: dict[str, Any], key: str) -> int:
|
||||
try:
|
||||
return max(0, int(usage.get(key) or 0))
|
||||
@@ -283,11 +297,16 @@ def _build_llm_usage_stats(
|
||||
*,
|
||||
live: bool = False,
|
||||
) -> None:
|
||||
subscription = _is_subscription(report_state)
|
||||
usage = _llm_usage(report_state)
|
||||
if not usage or _int_stat(usage, "requests") <= 0:
|
||||
stats_text.append("\n")
|
||||
stats_text.append("Cost ", style="dim")
|
||||
stats_text.append("$0.0000 ", style="#fbbf24")
|
||||
if subscription:
|
||||
stats_text.append("$0.00 ", style="#22c55e")
|
||||
stats_text.append("(subscription) ", style="dim")
|
||||
else:
|
||||
stats_text.append("$0.0000 ", style="#fbbf24")
|
||||
stats_text.append("· ", style="dim white")
|
||||
stats_text.append("Tokens ", style="dim")
|
||||
stats_text.append("0", style="white")
|
||||
@@ -312,7 +331,12 @@ def _build_llm_usage_stats(
|
||||
stats_text.append("Output Tokens ", style="dim")
|
||||
stats_text.append(format_token_count(output_tokens), style="white")
|
||||
|
||||
if live or cost > 0:
|
||||
if subscription:
|
||||
stats_text.append(" · ", style="dim white")
|
||||
stats_text.append("Cost ", style="dim")
|
||||
stats_text.append("$0.00", style="#22c55e")
|
||||
stats_text.append(" (subscription)", style="dim")
|
||||
elif live or cost > 0:
|
||||
stats_text.append(" · ", style="dim white")
|
||||
stats_text.append("Cost ", style="dim")
|
||||
stats_text.append(f"${cost:.4f}", style="#fbbf24")
|
||||
@@ -337,6 +361,9 @@ def build_live_stats_text(report_state: Any) -> Text:
|
||||
model = load_settings().llm.model or "unknown"
|
||||
stats_text.append("Model ", style="dim")
|
||||
stats_text.append(str(model), style="white")
|
||||
if _is_subscription(report_state):
|
||||
stats_text.append(" · ", style="dim white")
|
||||
stats_text.append("ChatGPT subscription", style="#22c55e")
|
||||
stats_text.append("\n")
|
||||
|
||||
vuln_count = len(report_state.vulnerability_reports)
|
||||
@@ -379,6 +406,10 @@ def build_tui_stats_text(report_state: Any) -> Text:
|
||||
|
||||
model = load_settings().llm.model or "unknown"
|
||||
stats_text.append(str(model), style="white")
|
||||
subscription = _is_subscription(report_state)
|
||||
if subscription:
|
||||
stats_text.append("\n")
|
||||
stats_text.append("ChatGPT subscription", style="#22c55e")
|
||||
|
||||
usage = _llm_usage(report_state)
|
||||
if usage and _int_stat(usage, "total_tokens") > 0:
|
||||
@@ -388,7 +419,10 @@ def build_tui_stats_text(report_state: Any) -> Text:
|
||||
style="white",
|
||||
)
|
||||
cost = _float_stat(usage, "cost")
|
||||
if cost > 0:
|
||||
if subscription:
|
||||
stats_text.append(" · ", style="white")
|
||||
stats_text.append("$0.00", style="white")
|
||||
elif cost > 0:
|
||||
stats_text.append(" · ", style="white")
|
||||
stats_text.append(f"${cost:.2f}", style="white")
|
||||
|
||||
@@ -1147,9 +1181,7 @@ def read_target_list_file(path_str: str) -> list[str]:
|
||||
if (target := line.strip()) and not target.startswith("#")
|
||||
]
|
||||
except UnicodeDecodeError as e:
|
||||
raise ValueError(
|
||||
f"Target list file '{path_str}' must be valid UTF-8 text: {e!s}"
|
||||
) from e
|
||||
raise ValueError(f"Target list file '{path_str}' must be valid UTF-8 text: {e!s}") from e
|
||||
except OSError as e:
|
||||
raise ValueError(f"Failed to read target list file '{path_str}': {e!s}") from e
|
||||
|
||||
|
||||
+42
-8
@@ -13,20 +13,55 @@ from openai.types.responses import ResponseOutputMessage
|
||||
|
||||
from strix.config import load_settings
|
||||
from strix.config.models import (
|
||||
DEFAULT_MODEL_RETRY,
|
||||
StrixProvider,
|
||||
configure_sdk_model_defaults,
|
||||
request_timeout_extra_args,
|
||||
)
|
||||
from strix.core.inputs import make_model_settings
|
||||
from strix.report.state import get_global_report_state
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from agents.items import ModelResponse
|
||||
|
||||
from strix.config.settings import DedupeSettings
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _dedupe_extra_args(dedupe: DedupeSettings) -> dict[str, str]:
|
||||
"""Per-call credential + endpoint for the dedupe model.
|
||||
|
||||
Provider env vars and the global base URL are process-wide, so a
|
||||
shared-provider dedupe key or a distinct dedupe endpoint can't be installed
|
||||
globally without clobbering (or being clobbered by) the main model's
|
||||
config. Passing them per call keeps the two apart. Only applies when a
|
||||
dedicated dedupe model is configured.
|
||||
"""
|
||||
if not dedupe.model:
|
||||
return {}
|
||||
extra: dict[str, str] = {}
|
||||
if dedupe.api_key and dedupe.api_key.strip():
|
||||
extra["api_key"] = dedupe.api_key.strip()
|
||||
if dedupe.api_base and dedupe.api_base.strip():
|
||||
extra["api_base"] = dedupe.api_base.strip()
|
||||
return extra
|
||||
|
||||
|
||||
def _dedupe_model_settings(
|
||||
dedupe: DedupeSettings, model_name: str, request_timeout: float | None
|
||||
) -> ModelSettings:
|
||||
settings = make_model_settings(
|
||||
dedupe.reasoning_effort,
|
||||
model_name=model_name,
|
||||
force_required_tool_choice=False,
|
||||
request_timeout=request_timeout,
|
||||
)
|
||||
extra = _dedupe_extra_args(dedupe)
|
||||
if extra:
|
||||
settings = settings.resolve(ModelSettings(extra_args=extra))
|
||||
return settings
|
||||
|
||||
DEDUPE_SYSTEM_PROMPT = """You are an expert vulnerability report deduplication judge.
|
||||
Your task is to determine if a candidate vulnerability report describes the SAME vulnerability
|
||||
as any existing report.
|
||||
@@ -286,13 +321,14 @@ async def check_duplicate(
|
||||
|
||||
try:
|
||||
settings = load_settings()
|
||||
model_name = settings.llm.model
|
||||
dedupe = settings.dedupe
|
||||
model_name = (dedupe.model or "").strip() or settings.llm.model
|
||||
if not model_name:
|
||||
return {
|
||||
"is_duplicate": False,
|
||||
"duplicate_id": "",
|
||||
"confidence": 0.0,
|
||||
"reason": "STRIX_LLM not configured; skipping dedupe check",
|
||||
"reason": "No LLM model configured; skipping dedupe check",
|
||||
}
|
||||
|
||||
candidate_cleaned = _prepare_report_for_comparison(candidate)
|
||||
@@ -311,10 +347,8 @@ async def check_duplicate(
|
||||
response = await model.get_response(
|
||||
system_instructions=DEDUPE_SYSTEM_PROMPT,
|
||||
input=user_msg,
|
||||
model_settings=ModelSettings(
|
||||
retry=DEFAULT_MODEL_RETRY,
|
||||
include_usage=True,
|
||||
extra_args=request_timeout_extra_args(settings.llm.timeout),
|
||||
model_settings=_dedupe_model_settings(
|
||||
dedupe, resolved_model, settings.llm.timeout
|
||||
),
|
||||
tools=[],
|
||||
output_schema=None,
|
||||
|
||||
@@ -10,6 +10,8 @@ from uuid import uuid4
|
||||
|
||||
from agents.usage import Usage
|
||||
|
||||
from strix.config import codex
|
||||
from strix.config.loader import load_settings
|
||||
from strix.core.paths import run_dir_for
|
||||
from strix.report.sarif import write_sarif
|
||||
from strix.report.usage import LLMUsageLedger
|
||||
@@ -117,12 +119,15 @@ class ReportState:
|
||||
self.scan_results: dict[str, Any] | None = None
|
||||
self.scan_config: dict[str, Any] | None = None
|
||||
self._llm_usage = LLMUsageLedger()
|
||||
auth_mode = codex.auth_mode(load_settings().llm.model)
|
||||
self._llm_usage.zero_cost = auth_mode == "subscription"
|
||||
self.run_record: dict[str, Any] = {
|
||||
"run_id": self.run_id,
|
||||
"run_name": self.run_name,
|
||||
"start_time": self.start_time,
|
||||
"end_time": None,
|
||||
"status": "running",
|
||||
"auth_mode": auth_mode,
|
||||
"targets_info": [],
|
||||
"llm_usage": self._build_llm_usage_record(),
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ class LLMUsageLedger:
|
||||
self._agent_usage: dict[str, Usage] = {}
|
||||
self._agent_metadata: dict[str, dict[str, str]] = {}
|
||||
self._total_cost = 0.0
|
||||
# When True, tokens are still tracked but cost stays $0 — the run is on a
|
||||
# model subscription, so there is no metered per-token charge to report.
|
||||
self.zero_cost = False
|
||||
|
||||
def record(
|
||||
self,
|
||||
@@ -41,7 +44,7 @@ class LLMUsageLedger:
|
||||
if model:
|
||||
metadata["model"] = model
|
||||
|
||||
if not _is_litellm_routed(model):
|
||||
if not self.zero_cost and not _is_litellm_routed(model):
|
||||
estimated = _estimate_litellm_cost(usage, model)
|
||||
if estimated:
|
||||
self._total_cost += estimated
|
||||
@@ -49,6 +52,8 @@ class LLMUsageLedger:
|
||||
return True
|
||||
|
||||
def record_observed_cost(self, cost: float) -> None:
|
||||
if self.zero_cost:
|
||||
return
|
||||
if isinstance(cost, int | float) and cost > 0:
|
||||
self._total_cost += float(cost)
|
||||
|
||||
|
||||
+65
-6
@@ -10,19 +10,27 @@ import re
|
||||
import tempfile
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
from pygments.lexers import PythonLexer, get_lexer_by_name, guess_lexer
|
||||
from pygments.lexers.special import TextLexer
|
||||
from pygments.util import ClassNotFound
|
||||
|
||||
from strix.core.paths import run_record_path
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pygments.lexer import Lexer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_SEVERITY_ORDER = {"critical": 0, "high": 1, "medium": 2, "low": 3, "info": 4}
|
||||
|
||||
_FENCE_RE = re.compile(r"^```([^\n`]*)\r?\n(.*?)\r?\n?```$", re.DOTALL)
|
||||
_BACKTICK_RUN = re.compile(r"`+")
|
||||
|
||||
|
||||
def _safe_fence(content: str) -> str:
|
||||
def safe_fence(content: str) -> str:
|
||||
"""Return a backtick fence that ``content`` cannot break out of.
|
||||
|
||||
Per CommonMark a fenced code block is closed only by a run of backticks at
|
||||
@@ -35,6 +43,56 @@ def _safe_fence(content: str) -> str:
|
||||
return "`" * max(3, longest + 1)
|
||||
|
||||
|
||||
def parse_fenced_code(raw: str) -> tuple[str | None, str]:
|
||||
"""Split an optionally fenced code string into ``(language, code)``.
|
||||
|
||||
Agent-generated code fields (e.g. ``poc_script_code``) are stored wrapped in
|
||||
a markdown fence carrying the language, like ``` ```python\n...\n``` ```.
|
||||
Return the fence's language tag and the inner code, or ``(None, raw)`` when
|
||||
the value isn't fenced.
|
||||
"""
|
||||
match = _FENCE_RE.match(raw.strip())
|
||||
if not match:
|
||||
return None, raw
|
||||
info = match.group(1).strip()
|
||||
language = info.split()[0] if info else None
|
||||
return (language or None), match.group(2)
|
||||
|
||||
|
||||
def resolve_lexer(language: str | None, code: str) -> Lexer:
|
||||
"""Pick a pygments lexer for ``code``.
|
||||
|
||||
Prefer the explicit fence ``language`` when it names a known lexer, otherwise
|
||||
auto-detect from the source. Fall back to Python when detection is
|
||||
inconclusive, since legacy (unfenced) PoC scripts are Python.
|
||||
"""
|
||||
if language:
|
||||
try:
|
||||
return get_lexer_by_name(language)
|
||||
except ClassNotFound:
|
||||
pass
|
||||
try:
|
||||
lexer = guess_lexer(code)
|
||||
except ClassNotFound:
|
||||
return cast("Lexer", PythonLexer())
|
||||
# ``guess_lexer`` returns the plain-text lexer when it can't detect anything.
|
||||
if isinstance(lexer, TextLexer):
|
||||
return cast("Lexer", PythonLexer())
|
||||
return lexer
|
||||
|
||||
|
||||
def guess_language_name(code: str) -> str:
|
||||
"""Return a markdown fence tag for ``code``, defaulting to ``python`` when
|
||||
auto-detection is inconclusive."""
|
||||
try:
|
||||
lexer = guess_lexer(code)
|
||||
except ClassNotFound:
|
||||
return "python"
|
||||
if isinstance(lexer, TextLexer) or not lexer.aliases:
|
||||
return "python"
|
||||
return str(lexer.aliases[0])
|
||||
|
||||
|
||||
def read_run_record(run_dir: Path) -> dict[str, Any]:
|
||||
path = run_record_path(run_dir)
|
||||
if not path.exists():
|
||||
@@ -187,9 +245,10 @@ def render_vulnerability_md(report: dict[str, Any]) -> str: # noqa: PLR0912, PL
|
||||
lines.append(str(report["poc_description"]))
|
||||
lines.append("")
|
||||
if report.get("poc_script_code"):
|
||||
code = str(report["poc_script_code"])
|
||||
fence = _safe_fence(code)
|
||||
lines.append(fence)
|
||||
language, code = parse_fenced_code(str(report["poc_script_code"]))
|
||||
fence_lang = language or guess_language_name(code)
|
||||
fence = safe_fence(code)
|
||||
lines.append(f"{fence}{fence_lang}")
|
||||
lines.append(code)
|
||||
lines.append(fence)
|
||||
lines.append("")
|
||||
@@ -209,7 +268,7 @@ def render_vulnerability_md(report: dict[str, Any]) -> str: # noqa: PLR0912, PL
|
||||
lines.append(f" {loc['label']}")
|
||||
if loc.get("snippet"):
|
||||
snippet = str(loc["snippet"])
|
||||
fence = _safe_fence(snippet)
|
||||
fence = safe_fence(snippet)
|
||||
lines.append(f" {fence}")
|
||||
lines.extend(f" {ln}" for ln in snippet.splitlines())
|
||||
lines.append(f" {fence}")
|
||||
|
||||
@@ -110,7 +110,7 @@ def stage_symlink_safe_dir(src_root: Path) -> tuple[Path, Path | None]:
|
||||
if not tree_has_symlink(root):
|
||||
return root, None
|
||||
|
||||
staged = Path(tempfile.mkdtemp(prefix=_STAGING_PREFIX))
|
||||
staged = Path(tempfile.mkdtemp(prefix=_STAGING_PREFIX)).resolve()
|
||||
try:
|
||||
_stage_dir(root, staged, root, frozenset({root}))
|
||||
except OSError:
|
||||
|
||||
@@ -5,7 +5,7 @@ description: Orchestration layer that coordinates specialized subagents for secu
|
||||
|
||||
# Root Agent
|
||||
|
||||
Orchestration layer for security assessments. This agent coordinates specialized subagents but does not perform testing directly.
|
||||
Orchestration layer for security assessments. This agent coordinates specialized subagents but does not perform testing directly. You never run scanners, crawlers, or fuzzers and never send exploit/injection payloads yourself — not even a quick "basic" test on a discovered endpoint. Any work that touches the target is delegated to a subagent.
|
||||
|
||||
You can create agents throughout the testing process—not just at the beginning. Spawn agents dynamically based on findings and evolving scope.
|
||||
|
||||
@@ -18,7 +18,7 @@ You can create agents throughout the testing process—not just at the beginning
|
||||
|
||||
## Scope Decomposition
|
||||
|
||||
Before spawning agents, analyze the target:
|
||||
Before spawning agents, analyze the target from the scan config/scope and any provided context (and, once recon subagents report, from their results) — not by running recon tools yourself:
|
||||
|
||||
1. **Identify attack surfaces** - web apps, APIs, infrastructure, etc.
|
||||
2. **Define boundaries** - in-scope domains, IP ranges, excluded assets
|
||||
@@ -72,8 +72,7 @@ Before creating agents:
|
||||
Complex findings warrant specialized subagents:
|
||||
- Discovery agent finds potential vulnerability
|
||||
- Validation agent confirms exploitability
|
||||
- Reporting agent documents with reproduction steps
|
||||
- Fix agent provides remediation (if needed)
|
||||
- Reporting agent documents with reproduction steps AND supplies the fix inline (the report tool carries the patch via `code_locations`/`fix_pr_body`) — do not add a separate fix agent that re-derives the same patch
|
||||
|
||||
**Resource Efficiency**
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ We collect only very **basic** usage data including:
|
||||
**Session Errors:** Duration and error types (not messages or stack traces)\
|
||||
**System Context:** OS type, architecture, Strix version\
|
||||
**Scan Context:** Scan mode (quick/standard/deep), scan type (whitebox/blackbox)\
|
||||
**Model Usage:** Which LLM model is being used (not prompts or responses)\
|
||||
**Model Usage:** Which LLM model is being used and whether it runs via an API key or a model subscription (not prompts or responses)\
|
||||
**Feature Usage:** Which built-in skills are loaded\
|
||||
**Aggregate Metrics:** Vulnerability counts by severity and weakness category (CWE)
|
||||
|
||||
|
||||
@@ -58,12 +58,14 @@ def start(
|
||||
is_whitebox: bool,
|
||||
interactive: bool,
|
||||
has_instructions: bool,
|
||||
auth_mode: str | None = None,
|
||||
) -> None:
|
||||
_send(
|
||||
"scan_started",
|
||||
{
|
||||
**base_props(),
|
||||
"model": model or "unknown",
|
||||
"auth_mode": auth_mode or "api_key",
|
||||
"scan_mode": scan_mode or "unknown",
|
||||
"scan_type": "whitebox" if is_whitebox else "blackbox",
|
||||
"interactive": interactive,
|
||||
@@ -133,6 +135,7 @@ def end(report_state: "ReportState", exit_reason: str = "completed") -> None:
|
||||
"scan_ended",
|
||||
{
|
||||
**base_props(),
|
||||
"auth_mode": report_state.run_record.get("auth_mode") or "api_key",
|
||||
"exit_reason": report_state.scan_ended_exit_reason,
|
||||
"duration_seconds": round(duration),
|
||||
"vulnerabilities_total": len(report_state.vulnerability_reports),
|
||||
@@ -180,6 +183,14 @@ def viewer_email_event(step: str, purpose: str | None = None) -> None:
|
||||
)
|
||||
|
||||
|
||||
def viewer_feedback_submitted() -> None:
|
||||
_send("viewer_feedback_submitted", {**base_props()})
|
||||
|
||||
|
||||
def viewer_agent_steered() -> None:
|
||||
_send("viewer_agent_steered", {**base_props()})
|
||||
|
||||
|
||||
def error(error_type: str) -> None:
|
||||
props = {**base_props(), "error_type": error_type}
|
||||
_send("error", props)
|
||||
@@ -59,6 +59,7 @@ def start(
|
||||
is_whitebox: bool,
|
||||
interactive: bool,
|
||||
has_instructions: bool,
|
||||
auth_mode: str | None = None,
|
||||
) -> None:
|
||||
_send(
|
||||
"scan_started",
|
||||
@@ -66,6 +67,7 @@ def start(
|
||||
**base_props(),
|
||||
"session": SESSION_ID,
|
||||
"model": model or "unknown",
|
||||
"auth_mode": auth_mode or "api_key",
|
||||
"scan_mode": scan_mode or "unknown",
|
||||
"scan_type": "whitebox" if is_whitebox else "blackbox",
|
||||
"interactive": interactive,
|
||||
@@ -140,6 +142,7 @@ def end(report_state: ReportState, exit_reason: str = "completed") -> None:
|
||||
{
|
||||
**base_props(),
|
||||
"session": SESSION_ID,
|
||||
"auth_mode": report_state.run_record.get("auth_mode") or "api_key",
|
||||
"exit_reason": report_state.scan_ended_exit_reason,
|
||||
"duration_seconds": round(duration),
|
||||
"vulnerabilities_total": len(report_state.vulnerability_reports),
|
||||
|
||||
@@ -87,7 +87,7 @@ async def view_agent_graph(ctx: RunContextWrapper) -> str:
|
||||
default=str,
|
||||
)
|
||||
|
||||
parent_of, statuses, names = await coordinator.graph_snapshot()
|
||||
parent_of, statuses, names, _ = await coordinator.graph_snapshot()
|
||||
|
||||
lines: list[str] = []
|
||||
|
||||
@@ -635,7 +635,7 @@ async def stop_agent(
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
_, statuses, _ = await coordinator.graph_snapshot()
|
||||
_, statuses, _, _ = await coordinator.graph_snapshot()
|
||||
if target_agent_id not in statuses:
|
||||
return json.dumps(
|
||||
{"success": False, "error": f"Unknown agent_id: {target_agent_id}"},
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
"""Bound oversized tool results before they enter agent history.
|
||||
|
||||
Keeps a head + tail slice and drops the middle, replacing it with a notice of
|
||||
how much was removed.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
_TRUNCATION_NOTICE = "[... {lines} lines ({bytes} bytes) truncated ...]"
|
||||
|
||||
|
||||
def _byte_len(text: str) -> int:
|
||||
return len(text.encode("utf-8"))
|
||||
|
||||
|
||||
def _take_prefix(text: str, max_bytes: int) -> str:
|
||||
budget = 0
|
||||
out: list[str] = []
|
||||
for char in text:
|
||||
size = len(char.encode("utf-8"))
|
||||
if budget + size > max_bytes:
|
||||
break
|
||||
out.append(char)
|
||||
budget += size
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def _take_suffix(text: str, max_bytes: int) -> str:
|
||||
budget = 0
|
||||
out: list[str] = []
|
||||
for char in reversed(text):
|
||||
size = len(char.encode("utf-8"))
|
||||
if budget + size > max_bytes:
|
||||
break
|
||||
out.append(char)
|
||||
budget += size
|
||||
out.reverse()
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def bound_text(text: str, *, max_lines: int, max_bytes: int) -> str:
|
||||
"""Return ``text`` unchanged when small, else a head+tail preview.
|
||||
|
||||
Truncates on whichever limit is hit first (line count or UTF-8 byte size).
|
||||
``max_bytes`` bounds the entire joined result, notice and separators
|
||||
included.
|
||||
"""
|
||||
lines = text.split("\n")
|
||||
total_bytes = _byte_len(text)
|
||||
if len(lines) <= max_lines and total_bytes <= max_bytes:
|
||||
return text
|
||||
|
||||
# Reserve notice + separator bytes up front; ``+ 4`` covers the two "\n\n".
|
||||
notice_overhead = _byte_len(_TRUNCATION_NOTICE.format(lines=len(lines), bytes=total_bytes)) + 4
|
||||
byte_budget = max(2, max_bytes - notice_overhead)
|
||||
|
||||
head_lines = max(1, max_lines // 2)
|
||||
tail_lines = max_lines - head_lines
|
||||
head = "\n".join(lines[:head_lines])
|
||||
tail = "\n".join(lines[len(lines) - tail_lines :]) if tail_lines > 0 else ""
|
||||
|
||||
half_bytes = max(1, byte_budget // 2)
|
||||
if _byte_len(head) > half_bytes:
|
||||
head = _take_prefix(head, half_bytes)
|
||||
if tail and _byte_len(tail) > half_bytes:
|
||||
tail = _take_suffix(tail, half_bytes)
|
||||
|
||||
# Count from the final slices; the byte pass may have dropped whole lines.
|
||||
kept_lines = len(head.split("\n")) + (len(tail.split("\n")) if tail else 0)
|
||||
dropped_lines = max(0, len(lines) - kept_lines)
|
||||
dropped_bytes = max(0, total_bytes - _byte_len(head) - _byte_len(tail))
|
||||
notice = _TRUNCATION_NOTICE.format(lines=dropped_lines, bytes=dropped_bytes)
|
||||
return f"{head}\n\n{notice}\n\n{tail}" if tail else f"{head}\n\n{notice}"
|
||||
@@ -207,6 +207,26 @@ def otp_verify(email: str, code: str) -> dict[str, Any]:
|
||||
raise RelayError("unavailable")
|
||||
|
||||
|
||||
def feedback_submit(email: str, message: str) -> None:
|
||||
"""Relay a feedback message + email to Strix. No verification is required;
|
||||
the email is taken as given. Raises RelayError on failure."""
|
||||
status, data = _post_json(
|
||||
"/api/oss/feedback",
|
||||
{"email": email, "message": message},
|
||||
timeout=_OTP_TIMEOUT,
|
||||
)
|
||||
if status == 200:
|
||||
return
|
||||
if status == 429:
|
||||
raise RelayError("rate_limited")
|
||||
if status == 400:
|
||||
code = data.get("error")
|
||||
if code in ("invalid_email", "invalid_message"):
|
||||
raise RelayError(str(code))
|
||||
raise RelayError("invalid_message")
|
||||
raise RelayError("unavailable")
|
||||
|
||||
|
||||
def report_send(
|
||||
token: str,
|
||||
pdf_bytes: bytes,
|
||||
@@ -241,6 +261,7 @@ def report_send(
|
||||
__all__ = [
|
||||
"AUTH_PATH",
|
||||
"RelayError",
|
||||
"feedback_submit",
|
||||
"forget",
|
||||
"is_verified",
|
||||
"otp_start",
|
||||
|
||||
Generated
+10
-63
@@ -16,6 +16,7 @@
|
||||
"lucide-react": "^0.563.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-icons": "^5.7.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"tailwind-merge": "^3.4.0"
|
||||
@@ -919,9 +920,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -936,9 +934,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -953,9 +948,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -970,9 +962,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -987,9 +976,6 @@
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1004,9 +990,6 @@
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1021,9 +1004,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1038,9 +1018,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1055,9 +1032,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1072,9 +1046,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1089,9 +1060,6 @@
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1106,9 +1074,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1123,9 +1088,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1349,9 +1311,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1369,9 +1328,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1389,9 +1345,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1409,9 +1362,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2543,9 +2493,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2567,9 +2514,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2591,9 +2535,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2615,9 +2556,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3695,6 +3633,15 @@
|
||||
"react": "^19.2.7"
|
||||
}
|
||||
},
|
||||
"node_modules/react-icons": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.7.0.tgz",
|
||||
"integrity": "sha512-LBLy340Rzqy6+/yVhZKT3B/QpP1BZaesGqasf09HPOBzRarcDIFH0WwXlXQfE7q7ipxK4MSiC5DIBWURCny6fw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-markdown": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz",
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"lucide-react": "^0.563.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-icons": "^5.7.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"tailwind-merge": "^3.4.0"
|
||||
|
||||
@@ -2,14 +2,10 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
ArrowLeft,
|
||||
AlertCircle,
|
||||
Waypoints,
|
||||
Bot,
|
||||
Mail,
|
||||
ChevronDown,
|
||||
Wrench,
|
||||
FileCheck2,
|
||||
CalendarClock,
|
||||
Radar,
|
||||
GitPullRequest,
|
||||
Rocket,
|
||||
ArrowUpRight,
|
||||
History,
|
||||
@@ -45,11 +41,10 @@ import PastRunsView from "@/components/PastRunsView";
|
||||
import EmailReportView from "@/components/EmailReportView";
|
||||
import { RunDetails } from "@/components/RunDetails";
|
||||
import { TrustToast } from "@/components/TrustToast";
|
||||
import FeatureDetail from "@/components/FeatureDetail";
|
||||
import { ProTile, ProInlineCta, type ProItem } from "@/components/ProCta";
|
||||
import { FEATURES } from "@/lib/pro-features";
|
||||
import FeedbackView from "@/components/FeedbackView";
|
||||
import { ProInlineCta } from "@/components/ProCta";
|
||||
|
||||
export type View = "overview" | "issues" | "agents" | "history" | "feature" | "email";
|
||||
export type View = "overview" | "issues" | "agents" | "history" | "email" | "feedback";
|
||||
|
||||
const TRUST_BANNER =
|
||||
"Your findings stay on your machine. They're rendered here locally in your browser and never uploaded or stored by Strix.";
|
||||
@@ -57,25 +52,12 @@ const TRUST_BANNER =
|
||||
const SEVERITY_ORDER: VulnerabilitySeverity[] = ["critical", "high", "medium", "low"];
|
||||
const POLL_MS = 500;
|
||||
|
||||
// Curated inline CTAs. Continuous-coverage row on Overview (the restyled upsell
|
||||
// tiles), plus the recommendations pairing.
|
||||
const RECOMMENDATION_CTAS: ProItem[] = [
|
||||
{ title: "One-click autofix + open a fix PR", desc: "Fix it for you and open a PR, retested.", slug: "autofix", icon: Wrench },
|
||||
{ title: "Export SOC 2 / ISO 27001 report", desc: "Share an auditor-ready report with your team.", slug: "compliance", icon: FileCheck2 },
|
||||
];
|
||||
const COVERAGE_CTAS: ProItem[] = [
|
||||
{ title: "Scheduled pentesting", desc: "Continuous coverage for your whole org.", slug: "scheduled", icon: CalendarClock },
|
||||
{ title: "Attack surface monitoring", desc: "Continuous coverage for your whole org.", slug: "asm", icon: Radar },
|
||||
{ title: "PR reviews", desc: "Pentest every pull request your team opens.", slug: "pr_reviews", icon: GitPullRequest },
|
||||
];
|
||||
|
||||
export default function App() {
|
||||
const [activeRun, setActiveRun] = useState<string | null>(null);
|
||||
const [run, setRun] = useState<LoadedRun | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const [view, setView] = useState<View>("overview");
|
||||
const [activeFeature, setActiveFeature] = useState<string | null>(null);
|
||||
const [auth, setAuth] = useState<AuthStatus | null>(null);
|
||||
const [runs, setRuns] = useState<RunsPayload | null>(null);
|
||||
const [emailPurpose, setEmailPurpose] = useState<"report" | "verify">("report");
|
||||
@@ -249,12 +231,6 @@ export default function App() {
|
||||
await refreshRuns();
|
||||
}, [refreshAuth, refreshRuns]);
|
||||
|
||||
const selectFeature = useCallback((slug: string) => {
|
||||
trackCta(slug, "sidebar_nav");
|
||||
setActiveFeature(slug);
|
||||
userSetView("feature");
|
||||
}, [userSetView]);
|
||||
|
||||
const onForget = useCallback(async () => {
|
||||
await forgetAuth();
|
||||
await refreshAuth();
|
||||
@@ -266,11 +242,13 @@ export default function App() {
|
||||
<Sidebar
|
||||
view={view}
|
||||
onSelectView={(v) => {
|
||||
// Clicking a sidebar view always lands on that section's top level,
|
||||
// so leaving a specific issue's detail view and clicking "Issues"
|
||||
// returns to the full findings list.
|
||||
setSelectedId(null);
|
||||
if (v === "history") openHistory();
|
||||
else userSetView(v);
|
||||
}}
|
||||
activeFeature={activeFeature}
|
||||
onSelectFeature={selectFeature}
|
||||
issuesCount={run?.vulnerabilities.length ?? 0}
|
||||
agentCount={agentCount}
|
||||
runCount={runs?.count ?? 0}
|
||||
@@ -285,7 +263,7 @@ export default function App() {
|
||||
<div className="flex-1 min-w-0">
|
||||
{/* Top bar */}
|
||||
<div className="border-b border-[#222]">
|
||||
<div className="max-w-[72rem] mx-auto px-6 py-4 flex items-center gap-1.5">
|
||||
<div className="max-w-[88rem] mx-auto px-3 sm:px-6 py-4 flex items-center gap-1.5">
|
||||
<a
|
||||
href={ctaUrl("https://app.strix.ai", "logo")}
|
||||
target="_blank"
|
||||
@@ -297,7 +275,6 @@ export default function App() {
|
||||
<img src="./logo.png" alt="Strix" className="w-10 h-8 object-cover" />
|
||||
<div className="text-base text-white font-medium tracking-tight">Strix</div>
|
||||
</a>
|
||||
<span className="text-xs text-[#666]">Local results</span>
|
||||
{run && <LiveIndicator finished={run.finished} />}
|
||||
<div className="ml-auto flex items-center gap-3">
|
||||
{verified && runs && !runs.locked && runs.runs.length > 0 && (
|
||||
@@ -322,14 +299,20 @@ export default function App() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-[72rem] mx-auto px-6 py-8 space-y-6">
|
||||
{error && !run && view !== "history" && view !== "email" && view !== "feature" && (
|
||||
<div className="max-w-[88rem] mx-auto px-3 sm:px-6 py-8 sm:py-12 space-y-6">
|
||||
{error && !run && view !== "history" && view !== "email" && (
|
||||
<div className="rounded-lg px-4 py-3 flex gap-3 items-start border border-red-500/30 bg-red-500/5">
|
||||
<AlertCircle className="w-5 h-5 flex-shrink-0 mt-0.5 text-red-400" aria-hidden="true" />
|
||||
<p className="text-sm text-red-300">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Keyed wrapper: re-mounts on every view / finding / run change so the
|
||||
page-in transition replays. */}
|
||||
<div
|
||||
key={`${activeRun ?? "launched"}:${view}:${selectedId ?? ""}`}
|
||||
className="animate-page-in space-y-6"
|
||||
>
|
||||
{view === "email" ? (
|
||||
<EmailReportView
|
||||
activeRun={activeRun}
|
||||
@@ -342,8 +325,11 @@ export default function App() {
|
||||
}}
|
||||
onExit={(dest) => setView(dest === "history" ? "history" : "overview")}
|
||||
/>
|
||||
) : view === "feature" && activeFeature && FEATURES[activeFeature] ? (
|
||||
<FeatureDetail feature={FEATURES[activeFeature]} />
|
||||
) : view === "feedback" ? (
|
||||
<FeedbackView
|
||||
defaultEmail={auth?.email ?? null}
|
||||
onExit={(dest) => setView(dest)}
|
||||
/>
|
||||
) : view === "history" ? (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -369,7 +355,7 @@ export default function App() {
|
||||
{/* Tab strip: shown on small screens where the sidebar is hidden. */}
|
||||
<div className="flex gap-5 border-b border-[#2a2a2a] lg:hidden">
|
||||
<TabButton active={view === "overview"} onClick={() => userSetView("overview")}>
|
||||
Overview
|
||||
Pentest Overview
|
||||
</TabButton>
|
||||
<TabButton active={view === "issues"} onClick={() => userSetView("issues")}>
|
||||
Issues{run.vulnerabilities.length > 0 ? ` (${run.vulnerabilities.length})` : ""}
|
||||
@@ -412,6 +398,7 @@ export default function App() {
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TrustToast message={TRUST_BANNER} />
|
||||
@@ -438,33 +425,37 @@ function RunSwitcher({
|
||||
<button
|
||||
onClick={() => setOpen((o) => !o)}
|
||||
onBlur={() => setTimeout(() => setOpen(false), 150)}
|
||||
className="flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs text-[#aaa] transition-colors hover:text-white"
|
||||
style={{ border: "1px solid #2a2a2a" }}
|
||||
aria-label="Switch pentest"
|
||||
className="flex items-center gap-2 rounded-lg border border-[#3a3a3a] bg-[rgba(255,255,255,0.05)] px-3 py-2 text-sm text-white transition-colors hover:border-[#555] hover:bg-[rgba(255,255,255,0.09)]"
|
||||
>
|
||||
<History className="w-3.5 h-3.5" aria-hidden="true" />
|
||||
<span className="max-w-[160px] truncate">{current}</span>
|
||||
<ChevronDown className="w-3.5 h-3.5" aria-hidden="true" />
|
||||
<History className="h-4 w-4 flex-shrink-0 text-[#888]" aria-hidden="true" />
|
||||
<span className="flex-shrink-0 text-[#888]">Pentest</span>
|
||||
<span className="max-w-[260px] truncate font-medium">{current}</span>
|
||||
<ChevronDown className="h-4 w-4 flex-shrink-0 text-[#aaa]" aria-hidden="true" />
|
||||
</button>
|
||||
{open && (
|
||||
<div
|
||||
className="absolute right-0 z-50 mt-1.5 max-h-80 w-64 overflow-y-auto rounded-lg py-1 shadow-xl"
|
||||
style={{ border: "1px solid #2a2a2a", background: "#0a0a0a" }}
|
||||
className="absolute right-0 z-50 mt-2 max-h-96 w-96 overflow-y-auto rounded-xl py-1.5 shadow-2xl"
|
||||
style={{ border: "1px solid #3a3a3a", background: "#0a0a0a" }}
|
||||
>
|
||||
<div className="border-b border-[#222] px-3 py-2 text-[11px] font-semibold uppercase tracking-wide text-[#666]">
|
||||
Switch pentest
|
||||
</div>
|
||||
{runs.runs.map((r) => {
|
||||
const active = r.name === activeRun;
|
||||
return (
|
||||
<button
|
||||
key={r.name}
|
||||
onMouseDown={() => onSelect(r.name)}
|
||||
className={`flex w-full items-center gap-2 px-3 py-2 text-left text-xs transition-colors hover:bg-[rgba(255,255,255,0.06)] ${
|
||||
active ? "text-white" : "text-[#aaa]"
|
||||
className={`flex w-full items-center gap-2 px-3 py-2.5 text-left text-sm transition-colors hover:bg-[rgba(255,255,255,0.06)] ${
|
||||
active ? "bg-[rgba(255,255,255,0.04)] text-white" : "text-[#aaa]"
|
||||
}`}
|
||||
>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block truncate">{runTitle(r.target, r.name)}</span>
|
||||
{r.target && <span className="block truncate font-mono text-[#666]">{r.target}</span>}
|
||||
<span className="block truncate font-medium">{runTitle(r.target, r.name)}</span>
|
||||
{r.target && <span className="block truncate font-mono text-xs text-[#666]">{r.target}</span>}
|
||||
</span>
|
||||
{active && <span className="h-1.5 w-1.5 flex-shrink-0 rounded-full bg-emerald-400" />}
|
||||
{active && <span className="h-2 w-2 flex-shrink-0 rounded-full bg-emerald-400" />}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
@@ -508,7 +499,7 @@ function SummaryHeader({ summary }: { summary: ParsedRunSummary }) {
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold text-white">
|
||||
{runTitle(summary.targets[0] ?? null, summary.runName ?? summary.runId ?? "Scan results")}
|
||||
{runTitle(summary.targets[0] ?? null, summary.runName ?? summary.runId ?? "Pentest results")}
|
||||
</h1>
|
||||
<div className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-[#888]">
|
||||
{summary.targets.length > 0 && (
|
||||
@@ -547,7 +538,7 @@ function FindingsList({
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-8 text-center text-sm text-[#888]">
|
||||
{finished ? "No findings in this run." : "No findings yet. The scan is still running…"}
|
||||
{finished ? "No findings in this run." : "No findings yet. The pentest is still running…"}
|
||||
</div>
|
||||
{finished && (
|
||||
<div className="rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5">
|
||||
@@ -573,7 +564,7 @@ function FindingsList({
|
||||
<button
|
||||
key={v.id}
|
||||
onClick={() => onSelect(v.id)}
|
||||
className="cursor-pointer w-full text-left rounded-lg border border-[#222] hover:border-[#444] bg-[rgba(255,255,255,0.02)] px-4 py-3 transition-colors flex items-center gap-3"
|
||||
className="animate-card-in cursor-pointer w-full text-left rounded-lg border border-[#222] hover:border-[#444] bg-[rgba(255,255,255,0.02)] px-4 py-3 transition-colors flex items-center gap-3"
|
||||
>
|
||||
<span className={`w-2.5 h-2.5 rounded-full flex-shrink-0 ${getSeverityDot(v.severity)}`} aria-hidden="true" />
|
||||
<span className="flex-1 min-w-0">
|
||||
@@ -636,7 +627,7 @@ function EmailReportCta({ onOpenEmail }: { onOpenEmail: () => void }) {
|
||||
</p>
|
||||
</div>
|
||||
<span className="flex-shrink-0 rounded-lg bg-white px-3 py-1.5 text-xs font-semibold text-black transition-opacity group-hover:opacity-90">
|
||||
Email report
|
||||
Export report to PDF
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
@@ -673,26 +664,32 @@ function OverviewTab({
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<RunDetails raw={raw} durationSeconds={summary.durationSeconds} />
|
||||
<div className="animate-card-in">
|
||||
<RunDetails raw={raw} durationSeconds={summary.durationSeconds} />
|
||||
</div>
|
||||
|
||||
{total > 0 && (
|
||||
<div className="rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5">
|
||||
<div className="animate-card-in rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5">
|
||||
<IssueSeveritySummary findings={{ total, ...counts }} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Primary CTA: the one primary on Overview. Hidden until the run is
|
||||
finished, since a live scan would only email a partial report. */}
|
||||
{finished && <EmailReportCta onOpenEmail={onOpenEmail} />}
|
||||
{finished && (
|
||||
<div className="animate-card-in">
|
||||
<EmailReportCta onOpenEmail={onOpenEmail} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{sections.length > 0 ? (
|
||||
<div className="rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5 space-y-8">
|
||||
<div className="animate-card-in rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5 space-y-8">
|
||||
{sections.map((s) => (
|
||||
<ContentSection key={s.title} title={s.title} content={s.content} />
|
||||
))}
|
||||
</div>
|
||||
) : reportMarkdown ? (
|
||||
<div className="rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5">
|
||||
<div className="animate-card-in rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5">
|
||||
<ContentSection content={dedupeHeadings(reportMarkdown)} />
|
||||
</div>
|
||||
) : (
|
||||
@@ -701,22 +698,6 @@ function OverviewTab({
|
||||
)
|
||||
)}
|
||||
|
||||
{/* Near Recommendations: act on the fixes. */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{RECOMMENDATION_CTAS.map((item) => (
|
||||
<ProTile key={item.slug} item={item} surface="overview" />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Continuous coverage for your org (restyled upsell tiles). */}
|
||||
<div>
|
||||
<p className="mb-2 text-sm font-semibold text-white">Continuous coverage for your org</p>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||
{COVERAGE_CTAS.map((item) => (
|
||||
<ProTile key={item.slug} item={item} surface="overview" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -746,8 +727,7 @@ function TabButton({
|
||||
function AgentsTab({ run, canSteer }: { run: LoadedRun; canSteer: boolean }) {
|
||||
const { agents, events } = run.transcript;
|
||||
const graphAgents = useMemo(() => buildGraphAgents(agents, events), [agents, events]);
|
||||
// Clicking a graph node opens the agent's transcript in a modal (matching the
|
||||
// cloud app); no node selected means no modal.
|
||||
// Clicking a graph node opens the agent's transcript in a modal; no node selected means no modal.
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const selectedAgent = selectedId ? (agents.find((a) => a.id === selectedId) ?? null) : null;
|
||||
|
||||
@@ -758,7 +738,7 @@ function AgentsTab({ run, canSteer }: { run: LoadedRun; canSteer: boolean }) {
|
||||
<div className="space-y-5">
|
||||
<div className="rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5">
|
||||
<div className="flex items-center gap-2">
|
||||
<Waypoints className="w-4 h-4 text-[#888]" aria-hidden="true" />
|
||||
<Bot className="w-4 h-4 text-[#888]" aria-hidden="true" />
|
||||
<h2 className="text-sm font-semibold text-white">Agent graph</h2>
|
||||
<span className="text-xs text-[#666]">
|
||||
{agents.length} agent{agents.length === 1 ? "" : "s"}
|
||||
@@ -784,12 +764,12 @@ function AgentsTab({ run, canSteer }: { run: LoadedRun; canSteer: boolean }) {
|
||||
|
||||
{/* Re-run always routes to Strix Cloud. */}
|
||||
<div className="rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5">
|
||||
<p className="text-sm font-semibold text-white">Run this scan with more depth</p>
|
||||
<p className="mt-0.5 text-xs text-[#666]">Re-run this scan on managed infra in the cloud.</p>
|
||||
<p className="text-sm font-semibold text-white">Run this pentest with more depth</p>
|
||||
<p className="mt-0.5 text-xs text-[#666]">Re-run this pentest on managed infra in the cloud.</p>
|
||||
<div className="mt-3 flex flex-wrap gap-2.5">
|
||||
<ProInlineCta
|
||||
label="Re-run in Strix Cloud with more depth"
|
||||
desc="Run this scan on managed infra with more depth."
|
||||
label="Re-run in Strix Pro with more depth"
|
||||
desc="Run this pentest on managed infra with more depth."
|
||||
slug="live_scan"
|
||||
surface="agents"
|
||||
icon={Rocket}
|
||||
@@ -797,14 +777,13 @@ function AgentsTab({ run, canSteer }: { run: LoadedRun; canSteer: boolean }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedAgent && (
|
||||
<AgentDetailModal
|
||||
agent={selectedAgent}
|
||||
events={events}
|
||||
steerable={steerable}
|
||||
onClose={() => setSelectedId(null)}
|
||||
/>
|
||||
)}
|
||||
<AgentDetailModal
|
||||
open={selectedAgent !== null}
|
||||
agent={selectedAgent}
|
||||
events={events}
|
||||
steerable={steerable}
|
||||
onClose={() => setSelectedId(null)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -180,7 +180,7 @@ export default function EmailReportView({
|
||||
const confirmationEmail = sentTo || auth?.email || email.trim();
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-md space-y-4">
|
||||
<div className="mx-auto max-w-xl space-y-4">
|
||||
<button
|
||||
onClick={() => onExit(verifyOnly ? "history" : "overview")}
|
||||
className="cursor-pointer inline-flex items-center gap-1.5 text-sm text-[#888] transition-colors hover:text-white"
|
||||
@@ -192,7 +192,7 @@ export default function EmailReportView({
|
||||
<div className="flex items-center gap-2">
|
||||
<Mail className="h-5 w-5 text-[#888]" aria-hidden="true" />
|
||||
<h1 className="text-2xl font-semibold text-white">
|
||||
{verifyOnly ? "Verify your email" : "Email report"}
|
||||
{verifyOnly ? "Verify your email" : "Export report to PDF"}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
@@ -225,16 +225,13 @@ export default function EmailReportView({
|
||||
<div className="flex items-start gap-2.5">
|
||||
<ShieldCheck className="mt-0.5 h-4 w-4 flex-shrink-0 text-emerald-400" aria-hidden="true" />
|
||||
<p className="text-xs leading-relaxed text-[#aaa]">
|
||||
Viewing stays local and nothing is uploaded. Emailing is an explicit
|
||||
opt-in: we send an <span className="text-white">encrypted PDF</span>.
|
||||
We email an <span className="text-white">encrypted PDF</span>. Nothing else leaves your machine.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start gap-2.5">
|
||||
<Lock className="mt-0.5 h-4 w-4 flex-shrink-0 text-[#888]" aria-hidden="true" />
|
||||
<p className="text-xs leading-relaxed text-[#aaa]">
|
||||
The report is encrypted with a password that only you hold. Strix
|
||||
cannot read it and never stores it. We collect only your email so we
|
||||
can send it.
|
||||
Only you hold the password; Strix can't read it.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -242,7 +239,7 @@ export default function EmailReportView({
|
||||
onClick={startFlow}
|
||||
className="w-full cursor-pointer rounded-lg bg-white px-4 py-2.5 text-sm font-semibold text-black transition-opacity hover:opacity-90"
|
||||
>
|
||||
{verified ? "Email me the encrypted PDF" : "Continue with your email"}
|
||||
Export report
|
||||
</button>
|
||||
{verified && auth?.email && (
|
||||
<p className="text-center text-xs text-[#666]">Sending to {auth.email}</p>
|
||||
@@ -269,7 +266,6 @@ export default function EmailReportView({
|
||||
className="w-full rounded-lg bg-black px-3 py-2.5 text-sm text-white outline-none transition-colors focus:border-[#444]"
|
||||
style={{ border: "1px solid #2a2a2a" }}
|
||||
/>
|
||||
<span className="mt-1.5 block text-[11px] text-[#666]">Use your work email.</span>
|
||||
</label>
|
||||
<button
|
||||
type="submit"
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
import {
|
||||
CalendarClock,
|
||||
WandSparkles,
|
||||
Puzzle,
|
||||
Users,
|
||||
ArrowUpRight,
|
||||
} from "lucide-react";
|
||||
import { SIGNUP_URL, PRICING_URL, ctaUrl, trackCta } from "@/lib/cta";
|
||||
import type { ProFeature } from "@/lib/pro-features";
|
||||
import { ProTag } from "@/components/ProCta";
|
||||
|
||||
/**
|
||||
* In-app upsell page for a single platform feature. Modeled on the cloud app's
|
||||
* Networks upsell: a centered bordered card with an icon medallion, tier pill,
|
||||
* headline, one-line description, a shared "Included in Strix Pro" bullet list,
|
||||
* then a primary sign-up CTA and a secondary link to all plans.
|
||||
*/
|
||||
|
||||
const INCLUDED = [
|
||||
{
|
||||
icon: CalendarClock,
|
||||
text: "Continuous coverage: scheduled pentests and attack surface monitoring",
|
||||
},
|
||||
{ icon: WandSparkles, text: "One-click autofix that opens a retested pull request" },
|
||||
{ icon: Puzzle, text: "Two-way sync to Jira, Linear, and Slack" },
|
||||
{ icon: Users, text: "Your whole team, with roles and shared history" },
|
||||
];
|
||||
|
||||
export default function FeatureDetail({ feature }: { feature: ProFeature }) {
|
||||
const Icon = feature.icon;
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-lg">
|
||||
<div className="rounded-2xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-8 text-center">
|
||||
<div
|
||||
className="mx-auto flex h-12 w-12 items-center justify-center rounded-xl"
|
||||
style={{ border: "1px solid #2a2a2a", background: "rgba(255,255,255,0.04)" }}
|
||||
>
|
||||
<Icon className="h-5 w-5 text-[#888]" aria-hidden="true" />
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex justify-center">
|
||||
<ProTag label={feature.tier} />
|
||||
</div>
|
||||
|
||||
<h2 className="mt-3 text-2xl font-semibold text-white">{feature.headline}</h2>
|
||||
<p className="mx-auto mt-2 max-w-md text-sm text-[#888]">{feature.description}</p>
|
||||
|
||||
<div
|
||||
className="mt-6 rounded-xl p-4 text-left"
|
||||
style={{ border: "1px solid #222", background: "rgba(255,255,255,0.02)" }}
|
||||
>
|
||||
<p className="mb-3 text-xs font-semibold uppercase tracking-wide text-[#666]">
|
||||
Included in Strix Pro
|
||||
</p>
|
||||
<ul className="space-y-2.5">
|
||||
{INCLUDED.map((item) => {
|
||||
const BulletIcon = item.icon;
|
||||
return (
|
||||
<li key={item.text} className="flex items-start gap-2.5">
|
||||
<BulletIcon
|
||||
className="mt-0.5 h-4 w-4 flex-shrink-0 text-[#888]"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="text-sm text-[#aaa]">{item.text}</span>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex flex-col items-center gap-3">
|
||||
<a
|
||||
href={ctaUrl(SIGNUP_URL, feature.slug)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => trackCta(feature.slug, "feature_page")}
|
||||
className="inline-flex w-full items-center justify-center gap-1.5 rounded-lg bg-white px-4 py-2.5 text-sm font-semibold text-black transition-opacity hover:opacity-90"
|
||||
>
|
||||
Start free
|
||||
<ArrowUpRight className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
</a>
|
||||
<a
|
||||
href={ctaUrl(PRICING_URL, feature.slug)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => trackCta(feature.slug, "feature_page_plans")}
|
||||
className="inline-flex items-center gap-1 text-xs text-[#888] transition-colors hover:text-white"
|
||||
>
|
||||
View all plans
|
||||
<ArrowUpRight className="h-3 w-3" aria-hidden="true" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import { useState } from "react";
|
||||
import { ArrowLeft, AlertCircle, CheckCircle2 } from "lucide-react";
|
||||
import { IoChatbubblesOutline } from "react-icons/io5";
|
||||
import { submitFeedback } from "@/data/serverSource";
|
||||
import type { View } from "@/App";
|
||||
|
||||
const MAX_MESSAGE = 5000;
|
||||
|
||||
const ERROR_COPY: Record<string, string> = {
|
||||
invalid_email: "That email doesn't look right.",
|
||||
invalid_message: "Please write a little more.",
|
||||
unavailable: "Couldn't send that just now. Try again.",
|
||||
};
|
||||
|
||||
/**
|
||||
* Feedback & support form. Collects a message plus a work email (no
|
||||
* verification — the email is taken as-is) and relays it to Strix via the local
|
||||
* server. Mirrors EmailReportView's centered-card styling and palette.
|
||||
*/
|
||||
export default function FeedbackView({
|
||||
defaultEmail,
|
||||
onExit,
|
||||
}: {
|
||||
defaultEmail: string | null;
|
||||
onExit: (dest: View) => void;
|
||||
}) {
|
||||
const [message, setMessage] = useState("");
|
||||
const [email, setEmail] = useState(defaultEmail ?? "");
|
||||
const [step, setStep] = useState<"form" | "sending" | "sent">("form");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const canSend = message.trim().length > 0 && email.trim().length > 0 && step !== "sending";
|
||||
|
||||
const send = async () => {
|
||||
if (!canSend) return;
|
||||
setStep("sending");
|
||||
setError(null);
|
||||
const result = await submitFeedback(message.trim(), email.trim());
|
||||
if (result.ok) {
|
||||
setStep("sent");
|
||||
return;
|
||||
}
|
||||
setStep("form");
|
||||
setError(ERROR_COPY[result.error] ?? ERROR_COPY.unavailable);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-xl space-y-4">
|
||||
<button
|
||||
onClick={() => onExit("overview")}
|
||||
className="cursor-pointer inline-flex items-center gap-1.5 text-sm text-[#888] transition-colors hover:text-white"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
Back to results
|
||||
</button>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<IoChatbubblesOutline className="h-5 w-5 text-[#888]" aria-hidden="true" />
|
||||
<h1 className="text-2xl font-semibold text-white">Feedback & support</h1>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="w-full rounded-2xl bg-[rgba(255,255,255,0.02)] p-6"
|
||||
style={{ border: "1px solid #2a2a2a" }}
|
||||
>
|
||||
{step === "sent" ? (
|
||||
<div className="flex items-start gap-3">
|
||||
<CheckCircle2 className="mt-0.5 h-5 w-5 flex-shrink-0 text-emerald-400" aria-hidden="true" />
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-semibold text-white">Thanks, we got it.</p>
|
||||
<p className="mt-1 text-xs text-[#888]">
|
||||
We read every message. If it needs a reply, we'll reach out to the email you gave.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => {
|
||||
setMessage("");
|
||||
setStep("form");
|
||||
}}
|
||||
className="mt-4 cursor-pointer text-xs text-[#888] transition-colors hover:text-white"
|
||||
>
|
||||
Send more feedback
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<p className="mb-4 text-xs text-[#666]">
|
||||
Bugs, feature requests, or anything else. Tell us what's on your mind.
|
||||
</p>
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 flex items-start gap-2 rounded-lg border border-red-500/30 bg-red-500/5 px-3 py-2">
|
||||
<AlertCircle className="mt-0.5 h-4 w-4 flex-shrink-0 text-red-400" aria-hidden="true" />
|
||||
<p className="text-xs text-red-300">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<label className="block">
|
||||
<span className="mb-1.5 block text-xs text-[#888]">Your feedback</span>
|
||||
<textarea
|
||||
autoFocus
|
||||
value={message}
|
||||
maxLength={MAX_MESSAGE}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
rows={5}
|
||||
placeholder="What's working, what's not, what you'd love to see…"
|
||||
className="w-full resize-y rounded-lg border border-[#2a2a2a] bg-black px-3 py-2.5 text-sm text-white outline-none transition-colors focus:border-white/50 focus:ring-2 focus:ring-white/10"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="mt-4 block">
|
||||
<span className="mb-1.5 block text-xs text-[#888]">Your work email</span>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="[email protected]"
|
||||
className="w-full rounded-lg border border-[#2a2a2a] bg-black px-3 py-2.5 text-sm text-white outline-none transition-colors focus:border-white/50 focus:ring-2 focus:ring-white/10"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<button
|
||||
onClick={() => void send()}
|
||||
disabled={!canSend}
|
||||
className="mt-4 flex w-full cursor-pointer items-center justify-center gap-2 rounded-lg bg-white px-4 py-2.5 text-sm font-semibold text-black transition-opacity hover:opacity-90 disabled:opacity-60"
|
||||
>
|
||||
{step === "sending" ? "Sending…" : "Send feedback"}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -143,7 +143,7 @@ export default function PastRunsView({
|
||||
<button
|
||||
key={run.name}
|
||||
onClick={() => onSelectRun(run.name)}
|
||||
className={`group flex w-full cursor-pointer items-center gap-4 rounded-lg border px-4 py-3 text-left transition-colors ${
|
||||
className={`animate-card-in group flex w-full cursor-pointer items-center gap-4 rounded-lg border px-4 py-3 text-left transition-colors ${
|
||||
active
|
||||
? "border-[#444] bg-[rgba(255,255,255,0.04)]"
|
||||
: "border-[#222] bg-[rgba(255,255,255,0.02)] hover:border-[#444]"
|
||||
|
||||
@@ -1,26 +1,5 @@
|
||||
import React, { useState } from "react";
|
||||
import { ArrowUpRight } from "lucide-react";
|
||||
import { SIGNUP_URL, ctaUrl, trackCta } from "@/lib/cta";
|
||||
import type { ProFeature } from "@/lib/pro-features";
|
||||
|
||||
/**
|
||||
* Shared Pro CTA primitives. Every Pro item is a direct link-out to the cloud
|
||||
* sign-up in a new tab with a hover tooltip one-liner (no modal, no lock icon).
|
||||
* Built once here and reused by the sidebar Platform section, the top upsell
|
||||
* row, and the inline CTAs in the tabs.
|
||||
*/
|
||||
|
||||
/** Small tier pill ("Pro" or "Enterprise"). Deliberately not a padlock. */
|
||||
export function ProTag({ label = "Pro", className = "" }: { label?: string; className?: string }) {
|
||||
return (
|
||||
<span
|
||||
className={`inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-[#aaa] ${className}`}
|
||||
style={{ border: "1px solid #2a2a2a", background: "rgba(255,255,255,0.04)" }}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight hover tooltip. Wraps a trigger and reveals `text` above it on
|
||||
@@ -58,100 +37,10 @@ export function Tooltip({
|
||||
);
|
||||
}
|
||||
|
||||
export interface ProItem {
|
||||
title: string;
|
||||
desc: string;
|
||||
slug: string;
|
||||
icon: React.ElementType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Card-style Pro feature tile: icon + name + one-liner + Pro tag + arrow.
|
||||
* Used in the top upsell row and inline CTA grids.
|
||||
*/
|
||||
export function ProTile({ item, surface }: { item: ProItem; surface?: string }) {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<a
|
||||
href={ctaUrl(SIGNUP_URL, item.slug)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => trackCta(item.slug, surface)}
|
||||
title={item.desc}
|
||||
className="group block cursor-pointer rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-4 text-left transition-colors hover:border-[#444]"
|
||||
>
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<Icon className="h-4 w-4 text-[#888] transition-colors group-hover:text-white" aria-hidden="true" />
|
||||
<div className="flex items-center gap-1.5">
|
||||
<ProTag />
|
||||
<ArrowUpRight className="h-3.5 w-3.5 text-[#555] transition-colors group-hover:text-[#aaa]" aria-hidden="true" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm font-medium text-white">{item.title}</p>
|
||||
<p className="mt-0.5 text-xs text-[#666]">{item.desc}</p>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sidebar-row Pro item: a two-line row (icon + label + short one-liner
|
||||
* underneath) with a small right-aligned tier tag. Opens the in-app
|
||||
* FeatureDetail view via onClick (no link-out) so it sits uniformly beside the
|
||||
* run/local rows in the themed nav list.
|
||||
*/
|
||||
export function ProNavItem({
|
||||
feature,
|
||||
active,
|
||||
onClick,
|
||||
collapsed = false,
|
||||
}: {
|
||||
feature: ProFeature;
|
||||
active?: boolean;
|
||||
onClick: () => void;
|
||||
collapsed?: boolean;
|
||||
}) {
|
||||
const Icon = feature.icon;
|
||||
if (collapsed) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
title={`${feature.title} (${feature.tier})`}
|
||||
className={`group flex w-full cursor-pointer items-center justify-center rounded-md px-2.5 py-2 transition-colors ${
|
||||
active
|
||||
? "text-white"
|
||||
: "text-[#888] hover:bg-[rgba(255,255,255,0.06)] hover:text-white"
|
||||
}`}
|
||||
style={active ? { background: "rgba(255,255,255,0.12)" } : undefined}
|
||||
>
|
||||
<Icon className="h-4 w-4 flex-shrink-0" aria-hidden="true" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`group flex w-full cursor-pointer items-start gap-2.5 rounded-md px-2.5 py-1.5 text-left transition-colors ${
|
||||
active
|
||||
? "text-white"
|
||||
: "text-[#888] hover:bg-[rgba(255,255,255,0.06)] hover:text-white"
|
||||
}`}
|
||||
style={active ? { background: "rgba(255,255,255,0.12)" } : undefined}
|
||||
>
|
||||
<Icon className="mt-0.5 h-4 w-4 flex-shrink-0" aria-hidden="true" />
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className="flex-1 truncate text-sm">{feature.title}</span>
|
||||
<ProTag label={feature.tier} />
|
||||
</span>
|
||||
<span className="mt-0.5 block text-[11px] leading-snug text-[#666]">{feature.navDesc}</span>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inline Pro CTA button (compact). Used in the finding detail and per-surface
|
||||
* rows where a full card is too heavy.
|
||||
* Compact inline CTA button that links out to sign-up in a new tab, with a
|
||||
* hover tooltip one-liner. Used in per-surface rows where a full card is too
|
||||
* heavy.
|
||||
*/
|
||||
export function ProInlineCta({
|
||||
label,
|
||||
@@ -177,7 +66,6 @@ export function ProInlineCta({
|
||||
>
|
||||
<Icon className="h-4 w-4 text-[#888] transition-colors group-hover:text-white" aria-hidden="true" />
|
||||
<span>{label}</span>
|
||||
<ProTag className="ml-0.5" />
|
||||
</a>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -72,7 +72,13 @@ export function RunDetails({
|
||||
const diffMode = str(diff.mode);
|
||||
const diffBase = str(raw.diff_base);
|
||||
const nonInteractive = raw.non_interactive === true;
|
||||
const localSources = arr(raw.local_sources).map((x) => String(x)).filter(Boolean);
|
||||
const localSources = arr(raw.local_sources)
|
||||
.map((x) => {
|
||||
if (typeof x === "string") return x;
|
||||
const o = rec(x);
|
||||
return str(o.source_path) ?? str(o.target_path) ?? "";
|
||||
})
|
||||
.filter(Boolean);
|
||||
const status = cap(str(raw.status));
|
||||
|
||||
let scope = scopeMode ?? "auto";
|
||||
@@ -94,6 +100,7 @@ export function RunDetails({
|
||||
const reasoning = num(rec(arr(usage.output_tokens_details)[0]).reasoning_tokens);
|
||||
const totalTokens = num(usage.total_tokens);
|
||||
const cost = num(usage.cost);
|
||||
const subscription = str(raw.auth_mode) === "subscription";
|
||||
|
||||
const sub = (n: number, word: string) => (
|
||||
<span className="text-[#666]"> ({formatNumber(n)} {word})</span>
|
||||
@@ -146,7 +153,7 @@ export function RunDetails({
|
||||
<span className="text-[#666]">None</span>
|
||||
)}
|
||||
</Field>
|
||||
{scanMode && <Field label="Scan mode">{scanMode}</Field>}
|
||||
{scanMode && <Field label="Pentest mode">{scanMode}</Field>}
|
||||
<Field label="Scope">{scope}</Field>
|
||||
<Field label="Mode">{nonInteractive ? "Non-interactive" : "Interactive"}</Field>
|
||||
{localSources.length > 0 && (
|
||||
@@ -169,6 +176,15 @@ export function RunDetails({
|
||||
{hasUsage ? (
|
||||
<dl className="space-y-2.5 tabular-nums">
|
||||
<Field label="Model">{models.length ? models.join(", ") : "n/a"}</Field>
|
||||
{subscription && (
|
||||
<Field label="Provider">
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<span className="rounded-full border border-[#22c55e]/40 bg-[#22c55e]/10 px-2 py-0.5 text-[11px] text-[#22c55e]">
|
||||
ChatGPT subscription
|
||||
</span>
|
||||
</span>
|
||||
</Field>
|
||||
)}
|
||||
<Field label="Run time">{fmtDuration(durationSeconds)}</Field>
|
||||
{requests != null && <Field label="Requests">{formatNumber(requests)}</Field>}
|
||||
{inputTokens != null && (
|
||||
@@ -184,7 +200,14 @@ export function RunDetails({
|
||||
</Field>
|
||||
)}
|
||||
{totalTokens != null && <Field label="Total tokens">{formatNumber(totalTokens)}</Field>}
|
||||
{cost != null && <Field label="Cost">${cost.toFixed(2)}</Field>}
|
||||
{subscription ? (
|
||||
<Field label="Cost">
|
||||
<span className="text-[#22c55e]">$0.00</span>
|
||||
<span className="text-[#666]"> (subscription)</span>
|
||||
</Field>
|
||||
) : (
|
||||
cost != null && <Field label="Cost">${cost.toFixed(2)}</Field>
|
||||
)}
|
||||
{agents.length > 0 && <Field label="Agents">{formatNumber(agents.length)}</Field>}
|
||||
</dl>
|
||||
) : (
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
FileText,
|
||||
Bug,
|
||||
Waypoints,
|
||||
AlertTriangle,
|
||||
Bot,
|
||||
Users,
|
||||
History,
|
||||
Mail,
|
||||
ArrowUpRight,
|
||||
LogOut,
|
||||
ShieldCheck,
|
||||
PanelLeftClose,
|
||||
PanelLeft,
|
||||
ChevronsUpDown,
|
||||
} from "lucide-react";
|
||||
import { SIGNUP_URL, ctaUrl, trackCta } from "@/lib/cta";
|
||||
import { ProNavItem } from "@/components/ProCta";
|
||||
import { FEATURES, PLATFORM_ORDER } from "@/lib/pro-features";
|
||||
import { LuGitPullRequestArrow } from "react-icons/lu";
|
||||
import { VscExtensions } from "react-icons/vsc";
|
||||
import { IoChatbubblesOutline } from "react-icons/io5";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ctaUrl, trackCta } from "@/lib/cta";
|
||||
import { UpgradeModal } from "@/components/UpgradeModal";
|
||||
import type { View } from "@/App";
|
||||
|
||||
/**
|
||||
* Persistent left rail. A single, ungrouped, ordered list of uniform two-line
|
||||
* rows (icon + label + short one-liner): the current run's views, the local
|
||||
* run-history + email-report actions, then the platform features. No section
|
||||
* headers. Tier is shown only by the inline Pro/Enterprise tag on platform
|
||||
* rows. Matches App.tsx's dark palette.
|
||||
*
|
||||
* Can collapse to a narrow icon-only rail; the collapsed state persists in
|
||||
* localStorage and each icon row keeps a `title` tooltip so the labels stay
|
||||
* discoverable.
|
||||
* Persistent left rail: a black rail with a right hairline border, an
|
||||
* account-switcher-style header, a single ungrouped list of h-9 nav rows (36px
|
||||
* icon slot, 14px label, rgba(255,255,255,0.12) active fill), a hairline
|
||||
* separator, and a user footer. Drag the right edge to resize; drag past the
|
||||
* collapse threshold to hide it, then click the left pull-zone to bring it back.
|
||||
*/
|
||||
|
||||
const MIN_WIDTH = 160;
|
||||
const DEFAULT_WIDTH = 260;
|
||||
const MAX_WIDTH = 400;
|
||||
const COLLAPSE_THRESHOLD = 140;
|
||||
|
||||
const WIDTH_KEY = "strix_viewer_sidebar_width";
|
||||
const COLLAPSE_KEY = "strix_viewer_sidebar_collapsed";
|
||||
|
||||
interface SidebarProps {
|
||||
view: View;
|
||||
onSelectView: (view: View) => void;
|
||||
activeFeature: string | null;
|
||||
onSelectFeature: (slug: string) => void;
|
||||
issuesCount: number;
|
||||
agentCount: number;
|
||||
runCount: number;
|
||||
@@ -46,11 +46,19 @@ interface SidebarProps {
|
||||
onForget: () => void;
|
||||
}
|
||||
|
||||
function readInt(key: string, fallback: number): number {
|
||||
try {
|
||||
const raw = localStorage.getItem(key);
|
||||
const n = raw ? parseInt(raw, 10) : NaN;
|
||||
return Number.isFinite(n) ? n : fallback;
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
export default function Sidebar({
|
||||
view,
|
||||
onSelectView,
|
||||
activeFeature,
|
||||
onSelectFeature,
|
||||
issuesCount,
|
||||
agentCount,
|
||||
runCount,
|
||||
@@ -61,229 +69,367 @@ export default function Sidebar({
|
||||
onOpenHistory,
|
||||
onForget,
|
||||
}: SidebarProps) {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const [width, setWidth] = useState(() => {
|
||||
const w = readInt(WIDTH_KEY, DEFAULT_WIDTH);
|
||||
return Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, w));
|
||||
});
|
||||
const [collapsed, setCollapsed] = useState(() => {
|
||||
try {
|
||||
setCollapsed(localStorage.getItem(COLLAPSE_KEY) === "1");
|
||||
return localStorage.getItem(COLLAPSE_KEY) === "1";
|
||||
} catch {
|
||||
/* localStorage may be unavailable; default to expanded */
|
||||
return false;
|
||||
}
|
||||
});
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
const [showUserMenu, setShowUserMenu] = useState(false);
|
||||
const [upgradeFeature, setUpgradeFeature] = useState<string | null>(null);
|
||||
const userMenuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Open the upgrade dialog for a platform feature, recording which feature
|
||||
// drove the open (the dialog's own CTAs track the deeper conversion).
|
||||
const openUpgrade = (slug: string, description: string) => {
|
||||
trackCta(slug, "sidebar");
|
||||
setUpgradeFeature(description);
|
||||
};
|
||||
|
||||
const persistWidth = useCallback((w: number) => {
|
||||
setWidth(w);
|
||||
try {
|
||||
localStorage.setItem(WIDTH_KEY, String(w));
|
||||
} catch {
|
||||
/* best-effort persistence */
|
||||
}
|
||||
}, []);
|
||||
|
||||
const toggleCollapsed = () => {
|
||||
setCollapsed((prev) => {
|
||||
const next = !prev;
|
||||
try {
|
||||
localStorage.setItem(COLLAPSE_KEY, next ? "1" : "0");
|
||||
} catch {
|
||||
/* best-effort persistence */
|
||||
const persistCollapsed = useCallback((c: boolean) => {
|
||||
setCollapsed(c);
|
||||
try {
|
||||
localStorage.setItem(COLLAPSE_KEY, c ? "1" : "0");
|
||||
} catch {
|
||||
/* best-effort persistence */
|
||||
}
|
||||
}, []);
|
||||
|
||||
const expandSidebar = useCallback(() => {
|
||||
persistCollapsed(false);
|
||||
persistWidth(DEFAULT_WIDTH);
|
||||
}, [persistCollapsed, persistWidth]);
|
||||
|
||||
const handleResizeStart = useCallback((e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
setIsResizing(true);
|
||||
}, []);
|
||||
|
||||
// Global drag handlers for the resize handle. Dragging below the collapse
|
||||
// threshold hides the rail entirely.
|
||||
useEffect(() => {
|
||||
if (!isResizing || collapsed) return;
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
const newWidth = e.clientX;
|
||||
if (newWidth >= MIN_WIDTH && newWidth <= MAX_WIDTH) {
|
||||
setWidth(newWidth);
|
||||
} else if (newWidth > MAX_WIDTH) {
|
||||
setWidth(MAX_WIDTH);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
};
|
||||
};
|
||||
const handleMouseUp = (e: MouseEvent) => {
|
||||
const finalWidth = e.clientX;
|
||||
if (finalWidth < COLLAPSE_THRESHOLD) {
|
||||
persistCollapsed(true);
|
||||
persistWidth(DEFAULT_WIDTH);
|
||||
} else {
|
||||
persistWidth(Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, finalWidth)));
|
||||
}
|
||||
setIsResizing(false);
|
||||
};
|
||||
|
||||
window.addEventListener("mousemove", handleMouseMove);
|
||||
window.addEventListener("mouseup", handleMouseUp);
|
||||
return () => {
|
||||
window.removeEventListener("mousemove", handleMouseMove);
|
||||
window.removeEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
}, [isResizing, collapsed, persistCollapsed, persistWidth]);
|
||||
|
||||
// Close the user menu when clicking outside it.
|
||||
useEffect(() => {
|
||||
if (!showUserMenu) return;
|
||||
const onDown = (e: MouseEvent) => {
|
||||
if (userMenuRef.current && !userMenuRef.current.contains(e.target as Node)) {
|
||||
setShowUserMenu(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", onDown);
|
||||
return () => document.removeEventListener("mousedown", onDown);
|
||||
}, [showUserMenu]);
|
||||
|
||||
return (
|
||||
<aside
|
||||
className={`hidden flex-shrink-0 border-r border-[#222] lg:block ${collapsed ? "w-14" : "w-72"}`}
|
||||
>
|
||||
<div className="sticky top-0 flex h-screen flex-col overflow-y-auto px-3 py-4">
|
||||
{/* Header: wordmark + Explore full platform + signed-in chip */}
|
||||
<div className="px-1.5">
|
||||
<div className={`flex items-center ${collapsed ? "flex-col gap-2" : "justify-between"}`}>
|
||||
<a
|
||||
href={ctaUrl("https://app.strix.ai", "logo")}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => trackCta("logo", "sidebar")}
|
||||
className="flex items-center gap-1.5 opacity-90 transition-opacity hover:opacity-100"
|
||||
title="Open Strix Cloud"
|
||||
>
|
||||
<img src="./logo.png" alt="Strix" className="h-8 w-10 object-cover" />
|
||||
{!collapsed && (
|
||||
<span className="text-base font-medium tracking-tight text-white">Strix</span>
|
||||
)}
|
||||
</a>
|
||||
<button
|
||||
onClick={toggleCollapsed}
|
||||
title={collapsed ? "Expand sidebar" : "Collapse sidebar"}
|
||||
aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"}
|
||||
className="flex-shrink-0 cursor-pointer rounded-md p-1.5 text-[#666] transition-colors hover:bg-[rgba(255,255,255,0.06)] hover:text-white"
|
||||
>
|
||||
{collapsed ? (
|
||||
<PanelLeft className="h-4 w-4" aria-hidden="true" />
|
||||
) : (
|
||||
<PanelLeftClose className="h-4 w-4" aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
<>
|
||||
{/* Left-edge pull zone: click to bring the rail back when collapsed. */}
|
||||
{collapsed && (
|
||||
<div
|
||||
className="fixed left-0 top-0 z-40 hidden h-full w-4 cursor-pointer transition-colors hover:bg-[rgba(255,255,255,0.08)] lg:block"
|
||||
onClick={expandSidebar}
|
||||
title="Expand sidebar"
|
||||
/>
|
||||
)}
|
||||
|
||||
<aside
|
||||
className={cn(
|
||||
"sticky top-0 z-20 hidden h-screen flex-shrink-0 flex-col overflow-hidden border-r border-[rgba(255,255,255,0.08)] bg-black lg:flex",
|
||||
!isResizing && "transition-[width] duration-200 ease-out"
|
||||
)}
|
||||
style={{ width: collapsed ? 0 : width }}
|
||||
>
|
||||
{/* Header — account-switcher stand-in (links out to Strix Cloud). */}
|
||||
<header className="relative flex flex-col gap-1 pt-1 min-w-[160px]">
|
||||
<div className="flex flex-row py-1 px-2">
|
||||
<div className="flex h-10 w-full flex-row items-center">
|
||||
<a
|
||||
href={ctaUrl("https://app.strix.ai", "logo")}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => trackCta("logo", "sidebar")}
|
||||
className="flex flex-1 flex-row items-center gap-2 rounded-md py-2 pl-2.5 pr-1 min-w-0 transition-colors hover:bg-[rgba(255,255,255,0.06)]"
|
||||
title="Open Strix Cloud"
|
||||
>
|
||||
<span
|
||||
className="flex flex-shrink-0 items-center justify-center rounded-full bg-gradient-to-br from-emerald-400 to-cyan-500"
|
||||
style={{ width: 20, height: 20 }}
|
||||
>
|
||||
<span className="text-[10px] font-semibold text-white">S</span>
|
||||
</span>
|
||||
<span className="flex flex-1 flex-row items-center gap-2 min-w-0">
|
||||
<span className="truncate min-w-0 text-[14px] font-medium text-[#ededed]">Strix</span>
|
||||
<span className="flex h-5 flex-shrink-0 items-center rounded px-2 text-[11px] font-medium text-[#888] bg-[rgba(255,255,255,0.08)]">
|
||||
Local
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
href={ctaUrl("https://app.strix.ai", "logo")}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => trackCta("logo", "sidebar")}
|
||||
className="flex flex-none items-center rounded-md px-1.5 py-2 transition-colors hover:bg-[rgba(255,255,255,0.06)]"
|
||||
aria-label="Open Strix Cloud"
|
||||
>
|
||||
<ChevronsUpDown className="h-4 w-4 text-[#666]" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href={ctaUrl(SIGNUP_URL, "sidebar_start_free")}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => trackCta("sidebar_start_free", "sidebar")}
|
||||
title="Explore full platform"
|
||||
className={`mt-3 flex w-full cursor-pointer items-center justify-center gap-1.5 rounded-lg bg-white font-semibold text-black transition-opacity hover:opacity-90 ${
|
||||
collapsed ? "px-0 py-2" : "px-3 py-2 text-sm"
|
||||
}`}
|
||||
>
|
||||
{!collapsed && "Explore full platform"}
|
||||
<ArrowUpRight className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
</a>
|
||||
{verified && email && (
|
||||
collapsed ? (
|
||||
<div
|
||||
className="mt-2.5 flex items-center justify-center rounded-lg py-2"
|
||||
style={{ border: "1px solid #222", background: "rgba(255,255,255,0.02)" }}
|
||||
title={`Linked email: ${email}`}
|
||||
</header>
|
||||
|
||||
{/* Navigation */}
|
||||
<nav className="relative min-w-[160px] flex-1 overflow-y-auto overflow-x-clip scrollbar-thin pb-10 pt-2">
|
||||
<div className="relative flex flex-col gap-px px-2">
|
||||
<NavItem
|
||||
icon={<ProjectsIcon />}
|
||||
label="Pentest Overview"
|
||||
active={view === "overview"}
|
||||
onClick={() => onSelectView("overview")}
|
||||
/>
|
||||
<NavItem
|
||||
icon={<AlertTriangle className="h-4 w-4" />}
|
||||
label="Issues"
|
||||
count={issuesCount > 0 ? issuesCount : undefined}
|
||||
active={view === "issues"}
|
||||
onClick={() => onSelectView("issues")}
|
||||
/>
|
||||
{agentCount > 0 && (
|
||||
<NavItem
|
||||
icon={<Bot className="h-4 w-4" />}
|
||||
label="Agents"
|
||||
count={agentCount}
|
||||
active={view === "agents"}
|
||||
onClick={() => onSelectView("agents")}
|
||||
/>
|
||||
)}
|
||||
<NavItem
|
||||
icon={<History className="h-4 w-4" />}
|
||||
label="Past runs"
|
||||
count={runCount > 0 ? runCount : undefined}
|
||||
active={view === "history"}
|
||||
onClick={onOpenHistory}
|
||||
/>
|
||||
{finished && (
|
||||
<NavItem
|
||||
icon={<Mail className="h-4 w-4" />}
|
||||
label="Export report"
|
||||
active={view === "email"}
|
||||
onClick={onOpenEmail}
|
||||
/>
|
||||
)}
|
||||
<NavItem
|
||||
icon={<IoChatbubblesOutline className="h-4 w-4" />}
|
||||
label="Feedback & support"
|
||||
active={view === "feedback"}
|
||||
onClick={() => onSelectView("feedback")}
|
||||
/>
|
||||
|
||||
<hr className="mx-0 my-1 h-px w-full border-0 bg-[rgba(255,255,255,0.08)]" />
|
||||
|
||||
<NavItem
|
||||
icon={<LuGitPullRequestArrow className="h-4 w-4" />}
|
||||
label="PR Security Reviews"
|
||||
active={false}
|
||||
onClick={() =>
|
||||
openUpgrade(
|
||||
"pr_reviews",
|
||||
"Strix reviews every pull request and flags exploitable changes before they merge."
|
||||
)
|
||||
}
|
||||
/>
|
||||
<NavItem
|
||||
icon={<VscExtensions className="h-4 w-4" />}
|
||||
label="Integrations"
|
||||
active={false}
|
||||
onClick={() =>
|
||||
openUpgrade(
|
||||
"integrations",
|
||||
"Sync findings to Jira, Linear, and Slack so fixes happen where your team already works."
|
||||
)
|
||||
}
|
||||
/>
|
||||
<NavItem
|
||||
icon={<Users className="h-4 w-4" />}
|
||||
label="Members"
|
||||
active={false}
|
||||
onClick={() =>
|
||||
openUpgrade(
|
||||
"members",
|
||||
"Invite your team, set roles, and share findings and run history across your org."
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* User footer — verified-email footer. */}
|
||||
<section className="flex min-w-[160px] flex-col gap-0.5" ref={userMenuRef}>
|
||||
<div className="relative p-2">
|
||||
{verified && email ? (
|
||||
<button
|
||||
onClick={() => setShowUserMenu((v) => !v)}
|
||||
className="relative flex w-full cursor-pointer items-center gap-2 rounded-md bg-transparent px-2.5 py-2 transition-colors hover:bg-[rgba(255,255,255,0.06)]"
|
||||
>
|
||||
<ShieldCheck className="h-3.5 w-3.5 flex-shrink-0 text-emerald-400" aria-hidden="true" />
|
||||
</div>
|
||||
<span
|
||||
className="flex flex-shrink-0 items-center justify-center rounded-full bg-gradient-to-br from-emerald-400 to-cyan-500"
|
||||
style={{ width: 20, height: 20 }}
|
||||
>
|
||||
<span className="text-[9px] font-semibold text-white">
|
||||
{email[0]?.toUpperCase() || "U"}
|
||||
</span>
|
||||
</span>
|
||||
<span className="flex min-w-0 flex-1 flex-col text-left">
|
||||
<span className="truncate text-[13px] font-medium text-[#ededed]">{email}</span>
|
||||
<span className="truncate text-[11px] text-[#555]">Linked to this machine</span>
|
||||
</span>
|
||||
</button>
|
||||
) : (
|
||||
<div
|
||||
className="mt-2.5 flex items-center gap-2 rounded-lg px-2.5 py-2"
|
||||
style={{ border: "1px solid #222", background: "rgba(255,255,255,0.02)" }}
|
||||
>
|
||||
<ShieldCheck className="h-3.5 w-3.5 flex-shrink-0 text-emerald-400" aria-hidden="true" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-[11px] text-[#666]">Linked email</p>
|
||||
<p className="truncate text-xs text-[#aaa]" title={email}>{email}</p>
|
||||
<div className="flex items-center gap-2 rounded-md px-2.5 py-2">
|
||||
<span
|
||||
className="flex flex-shrink-0 items-center justify-center rounded-full bg-gradient-to-br from-emerald-400 to-cyan-500"
|
||||
style={{ width: 20, height: 20 }}
|
||||
>
|
||||
<span className="text-[9px] font-semibold text-white">S</span>
|
||||
</span>
|
||||
<span className="flex min-w-0 flex-1 flex-col text-left">
|
||||
<span className="truncate text-[13px] font-medium text-[#ededed]">Local viewer</span>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showUserMenu && verified && email && (
|
||||
<div className="absolute bottom-full left-2 right-2 z-50 mb-1 overflow-hidden rounded-lg border border-[#333] bg-black shadow-xl">
|
||||
<div className="border-b border-[#333] px-3 py-2">
|
||||
<p className="truncate text-[13px] font-medium text-white">Linked email</p>
|
||||
<p className="truncate text-[11px] text-[#666]">{email}</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={onForget}
|
||||
title="Forget this email on this machine"
|
||||
className="flex-shrink-0 cursor-pointer text-[#666] transition-colors hover:text-white"
|
||||
aria-label="Forget"
|
||||
onClick={() => {
|
||||
setShowUserMenu(false);
|
||||
onForget();
|
||||
}}
|
||||
className="flex w-full items-center gap-2 px-3 py-2 text-[13px] text-[#888] transition-colors hover:bg-[rgba(255,255,255,0.06)] hover:text-red-400"
|
||||
>
|
||||
<LogOut className="h-3.5 w-3.5" />
|
||||
<LogOut className="h-4 w-4" />
|
||||
Forget this email
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* One single ordered list, no section headers. */}
|
||||
<div className="mt-6 space-y-0.5">
|
||||
<NavItem
|
||||
icon={FileText}
|
||||
label="Overview"
|
||||
desc="This run's executive report"
|
||||
active={view === "overview"}
|
||||
onClick={() => onSelectView("overview")}
|
||||
collapsed={collapsed}
|
||||
{/* Resize handle */}
|
||||
<div
|
||||
className="group absolute right-0 top-0 z-30 h-full w-1 cursor-col-resize"
|
||||
onMouseDown={handleResizeStart}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"absolute right-0 top-0 h-full w-px bg-[rgba(255,255,255,0.08)] transition-all duration-100",
|
||||
isResizing ? "w-0.5 bg-[rgba(255,255,255,0.3)]" : "group-hover:bg-[rgba(255,255,255,0.2)]"
|
||||
)}
|
||||
/>
|
||||
<NavItem
|
||||
icon={Bug}
|
||||
label="Issues"
|
||||
desc="Findings from this run"
|
||||
count={issuesCount > 0 ? issuesCount : undefined}
|
||||
active={view === "issues"}
|
||||
onClick={() => onSelectView("issues")}
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
{agentCount > 0 && (
|
||||
<NavItem
|
||||
icon={Waypoints}
|
||||
label="Agents"
|
||||
desc="What each agent did"
|
||||
count={agentCount}
|
||||
active={view === "agents"}
|
||||
onClick={() => onSelectView("agents")}
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
)}
|
||||
<NavItem
|
||||
icon={History}
|
||||
label="Past runs"
|
||||
desc="Every run on this machine"
|
||||
count={runCount > 0 ? runCount : undefined}
|
||||
active={view === "history"}
|
||||
onClick={onOpenHistory}
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
{/* Emailing a report only makes sense once the run is complete; a
|
||||
live scan would send a partial report, so hide it until finished. */}
|
||||
{finished && (
|
||||
<NavItem
|
||||
icon={Mail}
|
||||
label="Email report"
|
||||
desc="Get an encrypted PDF by email"
|
||||
active={view === "email"}
|
||||
onClick={onOpenEmail}
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
)}
|
||||
|
||||
{PLATFORM_ORDER.map((slug) => {
|
||||
const feature = FEATURES[slug];
|
||||
if (!feature) return null;
|
||||
return (
|
||||
<ProNavItem
|
||||
key={slug}
|
||||
feature={feature}
|
||||
active={view === "feature" && activeFeature === slug}
|
||||
onClick={() => onSelectFeature(slug)}
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</aside>
|
||||
|
||||
{/* Overlay during resize to prevent text selection. */}
|
||||
{isResizing && <div className="fixed inset-0 z-10 cursor-col-resize" />}
|
||||
|
||||
<UpgradeModal
|
||||
open={upgradeFeature !== null}
|
||||
description={upgradeFeature ?? ""}
|
||||
source="sidebar"
|
||||
onClose={() => setUpgradeFeature(null)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function NavItem({
|
||||
icon: Icon,
|
||||
label,
|
||||
desc,
|
||||
count,
|
||||
active,
|
||||
onClick,
|
||||
collapsed = false,
|
||||
}: {
|
||||
icon: React.ElementType;
|
||||
interface NavItemProps {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
desc: string;
|
||||
count?: number;
|
||||
active?: boolean;
|
||||
active: boolean;
|
||||
onClick: () => void;
|
||||
collapsed?: boolean;
|
||||
}) {
|
||||
if (collapsed) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
title={count != null ? `${label} (${count})` : label}
|
||||
className={`flex w-full cursor-pointer items-center justify-center rounded-md px-2.5 py-2 transition-colors ${
|
||||
active
|
||||
? "text-white"
|
||||
: "text-[#888] hover:bg-[rgba(255,255,255,0.06)] hover:text-white"
|
||||
}`}
|
||||
style={active ? { background: "rgba(255,255,255,0.12)" } : undefined}
|
||||
>
|
||||
<Icon className="h-4 w-4 flex-shrink-0" aria-hidden="true" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
count?: number;
|
||||
}
|
||||
|
||||
function NavItem({ icon, label, active, onClick, count }: NavItemProps) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`flex w-full cursor-pointer items-start gap-2.5 rounded-md px-2.5 py-1.5 text-left transition-colors ${
|
||||
className={cn(
|
||||
"group flex h-9 w-full origin-left flex-row items-center rounded-md transition-colors",
|
||||
active
|
||||
? "text-white"
|
||||
: "text-[#888] hover:bg-[rgba(255,255,255,0.06)] hover:text-white"
|
||||
}`}
|
||||
style={active ? { background: "rgba(255,255,255,0.12)" } : undefined}
|
||||
? "bg-[rgba(255,255,255,0.12)] text-white"
|
||||
: "text-[#888] hover:bg-[rgba(255,255,255,0.06)] hover:text-[#ededed]"
|
||||
)}
|
||||
>
|
||||
<Icon className="mt-0.5 h-4 w-4 flex-shrink-0" aria-hidden="true" />
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className="flex-1 truncate text-sm">{label}</span>
|
||||
{count != null && <span className="text-xs text-[#666] tabular-nums">{count}</span>}
|
||||
<div className="grid flex-none place-content-center" style={{ width: 36, height: 36 }}>
|
||||
{icon}
|
||||
</div>
|
||||
<span className="min-w-0 flex-1 truncate text-left text-[14px] font-medium">{label}</span>
|
||||
{count != null && (
|
||||
<span className="mr-2 flex-none rounded-full border border-white/10 px-2 py-0.5 text-[10px] tabular-nums leading-none text-[#777]">
|
||||
{count}
|
||||
</span>
|
||||
<span className="mt-0.5 block text-[11px] leading-snug text-[#666]">{desc}</span>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// Overview icon: a dashboard grid glyph (16x16 viewBox).
|
||||
function ProjectsIcon() {
|
||||
return (
|
||||
<svg style={{ width: 16, height: 16, color: "currentcolor" }} viewBox="0 0 16 16" fill="currentColor">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M2.5 5.5V2.5H5.5V5.5H2.5ZM1 2C1 1.44772 1.44772 1 2 1H6C6.55228 1 7 1.44772 7 2V6C7 6.55228 6.55228 7 6 7H2C1.44772 7 1 6.55228 1 6V2ZM2.5 13.5V10.5H5.5V13.5H2.5ZM1 10C1 9.44772 1.44772 9 2 9H6C6.55228 9 7 9.44772 7 10V14C7 14.5523 6.55228 15 6 15H2C1.44772 15 1 14.5523 1 14V10ZM10.5 2.5V5.5H13.5V2.5H10.5ZM10 1C9.44772 1 9 1.44772 9 2V6C9 6.55228 9.44772 7 10 7H14C14.5523 7 15 6.55228 15 6V2C15 1.44772 14.5523 1 14 1H10ZM10.5 13.5V10.5H13.5V13.5H10.5ZM9 10C9 9.44772 9.44772 9 10 9H14C14.5523 9 15 9.44772 15 10V14C15 14.5523 14.5523 15 14 15H10C9.44772 15 9 14.5523 9 14V10Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
X,
|
||||
Sparkles,
|
||||
ExternalLink,
|
||||
GitPullRequest,
|
||||
Shield,
|
||||
Zap,
|
||||
CalendarClock,
|
||||
WandSparkles,
|
||||
Plug,
|
||||
} from "lucide-react";
|
||||
import { SIGNUP_URL, PRICING_URL, ctaUrl, trackCta } from "@/lib/cta";
|
||||
|
||||
/**
|
||||
* Dialog shown when a platform feature is clicked in the sidebar: a short
|
||||
* description of the feature plus what Strix Cloud includes. The local viewer
|
||||
* has no billing, so both CTAs link out to the public sign-up / pricing pages.
|
||||
*/
|
||||
|
||||
const CLOUD_HIGHLIGHTS: { icon: React.ElementType; label: string }[] = [
|
||||
{ icon: GitPullRequest, label: "PR security reviews" },
|
||||
{ icon: Shield, label: "Attack surface monitoring" },
|
||||
{ icon: Zap, label: "Real-time threat intelligence" },
|
||||
{ icon: CalendarClock, label: "Scheduled pentesting" },
|
||||
{ icon: WandSparkles, label: "One-click autofix" },
|
||||
{ icon: Plug, label: "Jira, Linear & Slack integrations" },
|
||||
];
|
||||
|
||||
export function UpgradeModal({
|
||||
open,
|
||||
onClose,
|
||||
description,
|
||||
source = "sidebar",
|
||||
}: {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
/** A short sentence describing what the clicked feature does. */
|
||||
description: string;
|
||||
source?: string;
|
||||
}) {
|
||||
// Keep the dialog mounted through its exit animation: `render` controls
|
||||
// presence in the DOM and `state` ("open"/"closed") drives the keyframe. On
|
||||
// close we flip to "closed", let the 200ms animation play, then unmount --
|
||||
// the same lifecycle Radix gives shadcn's Dialog.
|
||||
const [render, setRender] = useState(open);
|
||||
const [state, setState] = useState<"open" | "closed">(open ? "open" : "closed");
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setRender(true);
|
||||
setState("open");
|
||||
return;
|
||||
}
|
||||
setState("closed");
|
||||
const t = setTimeout(() => setRender(false), 200);
|
||||
return () => clearTimeout(t);
|
||||
}, [open]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!render) return;
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
};
|
||||
document.addEventListener("keydown", onKey);
|
||||
const prevOverflow = document.body.style.overflow;
|
||||
document.body.style.overflow = "hidden";
|
||||
return () => {
|
||||
document.removeEventListener("keydown", onKey);
|
||||
document.body.style.overflow = prevOverflow;
|
||||
};
|
||||
}, [render, onClose]);
|
||||
|
||||
if (!render) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-state={state}
|
||||
className="dialog-overlay fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-4"
|
||||
onClick={onClose}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Upgrade your plan"
|
||||
>
|
||||
<div
|
||||
data-state={state}
|
||||
className="dialog-panel relative w-full max-w-md rounded-2xl border border-[#222] bg-black p-6 shadow-lg sm:rounded-lg"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
aria-label="Close"
|
||||
className="absolute right-4 top-4 rounded-md p-1 text-[#888] transition-colors hover:bg-[#1a1a1a] hover:text-white"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
<div>
|
||||
<h2 className="text-lg text-white">Available in Strix Cloud</h2>
|
||||
{description && (
|
||||
<p className="mt-2 text-base leading-relaxed text-[#e5e5e5]">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 pt-4">
|
||||
<div className="rounded-xl border border-[#333] bg-[#0a0a0a] p-4 sm:rounded-lg">
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
<Sparkles className="h-4 w-4 text-blue-400" />
|
||||
<span className="text-sm font-medium text-white">Strix Cloud also includes</span>
|
||||
</div>
|
||||
<ul className="space-y-2 text-sm text-[#888]">
|
||||
{CLOUD_HIGHLIGHTS.map((f) => (
|
||||
<li key={f.label} className="flex items-center gap-2">
|
||||
<f.icon className="h-3.5 w-3.5 text-[#555]" />
|
||||
{f.label}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<a
|
||||
href={ctaUrl(SIGNUP_URL, "upgrade_try_free")}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => trackCta("upgrade_try_free", source)}
|
||||
className="flex h-10 w-full items-center justify-center gap-2 rounded-lg bg-white px-4 text-sm font-semibold text-black transition-colors hover:bg-neutral-200"
|
||||
>
|
||||
Open Strix Cloud
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
</a>
|
||||
<a
|
||||
href={ctaUrl(PRICING_URL, "upgrade_view_plans")}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => trackCta("upgrade_view_plans", source)}
|
||||
className="flex h-9 w-full items-center justify-center gap-1.5 rounded-lg border border-[#333] px-4 text-sm font-medium text-[#888] transition-colors hover:border-[#555] hover:text-white"
|
||||
>
|
||||
Learn more
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UpgradeModal;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { X } from "lucide-react";
|
||||
import { AgentTranscript } from "./AgentTranscript";
|
||||
import { ScanPromptComposer } from "./ScanPromptComposer";
|
||||
@@ -18,19 +18,27 @@ const STATUS_DOT: Record<string, string> = {
|
||||
const NEAR_BOTTOM_PX = 80;
|
||||
|
||||
/**
|
||||
* Overlay modal showing a single agent's full transcript. Matches the cloud
|
||||
* app: a fixed-size panel with a pinned header (status dot + agent name), the
|
||||
* transcript scrolling beneath it, and a footer. Auto-scrolls to follow new
|
||||
* activity while the user is near the bottom (so a live run trails). Closes on
|
||||
* backdrop click, the X button, or Escape.
|
||||
* Overlay modal showing a single agent's full transcript. A centered
|
||||
* ``max-w-6xl`` / ``60vh`` panel that animates in and out via the shared
|
||||
* ``agent-modal`` data-state keyframes (fade), with a pinned header
|
||||
* (status dot + agent name),
|
||||
* the transcript scrolling beneath it, and a footer. Auto-scrolls to follow new
|
||||
* activity while the user is near the bottom. Closes on backdrop click, the X
|
||||
* button, or Escape.
|
||||
*
|
||||
* Driven by an ``open`` prop (rather than conditional mounting) so the exit
|
||||
* animation can play before unmount; the last agent is retained through the
|
||||
* close so content doesn't blank out mid-animation.
|
||||
*/
|
||||
export function AgentDetailModal({
|
||||
open,
|
||||
agent,
|
||||
events,
|
||||
steerable,
|
||||
onClose,
|
||||
}: {
|
||||
agent: TranscriptAgent;
|
||||
open: boolean;
|
||||
agent: TranscriptAgent | null;
|
||||
events: TranscriptEvent[];
|
||||
steerable: boolean;
|
||||
onClose: () => void;
|
||||
@@ -38,6 +46,42 @@ export function AgentDetailModal({
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const nearBottom = useRef(false);
|
||||
|
||||
// Keep the modal mounted through its exit animation (see UpgradeModal).
|
||||
const [render, setRender] = useState(open);
|
||||
const [state, setState] = useState<"open" | "closed">(open ? "open" : "closed");
|
||||
// Defer the (heavy) transcript one frame so the shell + fade paint instantly
|
||||
// instead of waiting on the full event list to render.
|
||||
const [contentReady, setContentReady] = useState(false);
|
||||
|
||||
// Retain the last non-null agent so the panel keeps rendering its content
|
||||
// during the close animation, after the parent has cleared the selection.
|
||||
const lastAgentRef = useRef<TranscriptAgent | null>(agent);
|
||||
useEffect(() => {
|
||||
if (agent) lastAgentRef.current = agent;
|
||||
}, [agent]);
|
||||
const shownAgent = agent ?? lastAgentRef.current;
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setRender(true);
|
||||
setState("open");
|
||||
return;
|
||||
}
|
||||
setState("closed");
|
||||
const t = setTimeout(() => setRender(false), 140);
|
||||
return () => clearTimeout(t);
|
||||
}, [open]);
|
||||
|
||||
// Mount the transcript a frame after the shell is on screen.
|
||||
useEffect(() => {
|
||||
if (!render) {
|
||||
setContentReady(false);
|
||||
return;
|
||||
}
|
||||
const id = requestAnimationFrame(() => setContentReady(true));
|
||||
return () => cancelAnimationFrame(id);
|
||||
}, [render]);
|
||||
|
||||
const handleScroll = useCallback(() => {
|
||||
const el = scrollRef.current;
|
||||
if (!el) return;
|
||||
@@ -54,6 +98,7 @@ export function AgentDetailModal({
|
||||
}, [events]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!render) return;
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
};
|
||||
@@ -64,27 +109,30 @@ export function AgentDetailModal({
|
||||
document.removeEventListener("keydown", onKey);
|
||||
document.body.style.overflow = prevOverflow;
|
||||
};
|
||||
}, [onClose]);
|
||||
}, [render, onClose]);
|
||||
|
||||
if (!render || !shownAgent) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-4 sm:p-8"
|
||||
data-state={state}
|
||||
className="agent-modal fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-4 sm:p-8"
|
||||
onClick={onClose}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={`Agent ${agent.name}`}
|
||||
aria-label={`Agent ${shownAgent.name}`}
|
||||
>
|
||||
<div
|
||||
className="relative flex h-[80vh] w-full max-w-5xl flex-col rounded-xl border border-[#222] bg-[#0a0a0a] shadow-2xl"
|
||||
className="relative flex h-[60vh] w-[calc(100vw-4rem)] max-w-6xl flex-col overflow-hidden rounded-xl border border-[#222] bg-[#0a0a0a] shadow-2xl"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3 border-b border-[#222] px-5 py-3.5">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<span
|
||||
className={`h-2 w-2 flex-shrink-0 rounded-full ${STATUS_DOT[agent.status] ?? "bg-[#888]"}`}
|
||||
className={`h-2 w-2 flex-shrink-0 rounded-full ${STATUS_DOT[shownAgent.status] ?? "bg-[#888]"}`}
|
||||
/>
|
||||
<span className="truncate text-sm font-semibold text-white">{agent.name}</span>
|
||||
<span className="flex-shrink-0 font-mono text-xs text-[#555]">{agent.id}</span>
|
||||
<span className="truncate text-sm font-semibold text-white">{shownAgent.name}</span>
|
||||
<span className="flex-shrink-0 font-mono text-xs text-[#555]">{shownAgent.id}</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@@ -97,14 +145,16 @@ export function AgentDetailModal({
|
||||
</div>
|
||||
|
||||
<div ref={scrollRef} onScroll={handleScroll} className="flex-1 overflow-y-auto p-5">
|
||||
<AgentTranscript agent={agent} events={events} showHeader={false} />
|
||||
{contentReady && (
|
||||
<AgentTranscript agent={shownAgent} events={events} showHeader={false} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{steerable && (
|
||||
<div className="border-t border-[#222] px-5 py-3">
|
||||
<ScanPromptComposer
|
||||
agents={[agent]}
|
||||
fixedAgentId={agent.id}
|
||||
agents={[shownAgent]}
|
||||
fixedAgentId={shownAgent.id}
|
||||
className="mt-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -248,7 +248,7 @@ export function ScanPromptComposer({
|
||||
void handleSend();
|
||||
}
|
||||
}}
|
||||
placeholder="Send a live prompt to the running scan…"
|
||||
placeholder="Send a live prompt to the running pentest…"
|
||||
maxLength={4000}
|
||||
disabled={sending}
|
||||
className="block w-full resize-none border-0 bg-transparent p-0 text-[15px] leading-6 text-white placeholder:text-[#444] focus:outline-none disabled:opacity-60 max-h-[160px] overflow-y-auto"
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
import type { ToolRendererProps } from "@/types/events";
|
||||
|
||||
export default function LoadSkillRenderer({ args }: ToolRendererProps) {
|
||||
const requestedRaw = (args.skills as string) ?? "";
|
||||
const requestedSkills = requestedRaw
|
||||
.split(",")
|
||||
.map((skill) => skill.trim())
|
||||
// `skills` may arrive as an array of names or a comma-separated string
|
||||
// depending on the tool call, so normalize both to a clean list.
|
||||
const raw = args.skills;
|
||||
const requestedSkills = (Array.isArray(raw) ? raw : String(raw ?? "").split(","))
|
||||
.map((skill) => String(skill).trim())
|
||||
.filter(Boolean);
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import type { ToolRendererProps } from "@/types/events";
|
||||
import { TruncatedText } from "./ToolCard";
|
||||
import { MdCodeBlock } from "@/components/vulnerability/MdCodeBlock";
|
||||
import { parseFencedCode } from "@/lib/fenced-code";
|
||||
import Markdown from "./Markdown";
|
||||
|
||||
const SEVERITY_COLORS: Record<string, string> = {
|
||||
@@ -19,7 +20,7 @@ export default function VulnReportRenderer({ args, result }: ToolRendererProps)
|
||||
const method = (args.method as string) ?? "";
|
||||
const technicalAnalysis = (args.technical_analysis as string) ?? "";
|
||||
const pocDescription = (args.poc_description as string) ?? "";
|
||||
const pocCode = (args.poc_script_code as string) ?? "";
|
||||
const { language: pocLang, code: pocCode } = parseFencedCode((args.poc_script_code as string) ?? "");
|
||||
const remediation = (args.remediation_steps as string) ?? "";
|
||||
const cve = (args.cve as string) ?? "";
|
||||
const cwe = (args.cwe as string) ?? "";
|
||||
@@ -59,7 +60,7 @@ export default function VulnReportRenderer({ args, result }: ToolRendererProps)
|
||||
<div>
|
||||
<span className="text-emerald-400/60 text-sm font-semibold">Proof of Concept</span>
|
||||
{pocDescription && <div className="mt-1"><Markdown text={pocDescription} /></div>}
|
||||
{pocCode && <MdCodeBlock>{pocCode}</MdCodeBlock>}
|
||||
{pocCode && <MdCodeBlock className={pocLang ? `language-${pocLang}` : undefined}>{pocCode}</MdCodeBlock>}
|
||||
</div>
|
||||
)}
|
||||
{remediation && (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import hljs from "@/lib/hljs";
|
||||
import { highlightCode } from "@/lib/hljs";
|
||||
import "highlight.js/styles/github-dark.css";
|
||||
import { Copy, Check } from "lucide-react";
|
||||
import { copyToClipboard } from "@/lib/vulnerability-utils";
|
||||
@@ -17,7 +17,7 @@ export function MdCodeBlock({
|
||||
}) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const raw = String(children).replace(/\n$/, "");
|
||||
const match = /language-(\w+)/.exec(className || "");
|
||||
const match = /language-(\S+)/.exec(className || "");
|
||||
const isBlock = raw.includes("\n") || match;
|
||||
|
||||
if (!isBlock) {
|
||||
@@ -35,16 +35,7 @@ export function MdCodeBlock({
|
||||
: fileName
|
||||
: null;
|
||||
|
||||
let highlighted: string;
|
||||
if (match) {
|
||||
try {
|
||||
highlighted = hljs.highlight(raw, { language: match[1], ignoreIllegals: true }).value;
|
||||
} catch {
|
||||
highlighted = hljs.highlightAuto(raw).value;
|
||||
}
|
||||
} else {
|
||||
highlighted = hljs.highlightAuto(raw).value;
|
||||
}
|
||||
const highlighted = highlightCode(raw, match?.[1]);
|
||||
|
||||
const lines = highlighted.split("\n");
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import hljs from "@/lib/hljs";
|
||||
import { highlightCode } from "@/lib/hljs";
|
||||
import "highlight.js/styles/github-dark.css";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { Copy, Check } from "lucide-react";
|
||||
import { copyToClipboard } from "@/lib/vulnerability-utils";
|
||||
import { parseFencedCode } from "@/lib/fenced-code";
|
||||
import { rehypeCodeMeta, mdComponents } from "./MdCodeBlock";
|
||||
|
||||
interface PocBlockProps {
|
||||
@@ -20,9 +21,12 @@ export function PocBlock({ description, scriptCode, onCopy }: PocBlockProps) {
|
||||
|
||||
if (!description && !scriptCode) return null;
|
||||
|
||||
const { language, code } = parseFencedCode(scriptCode);
|
||||
const highlighted = highlightCode(code, language);
|
||||
|
||||
const copy = () => {
|
||||
if (!scriptCode) return;
|
||||
copyToClipboard(scriptCode);
|
||||
if (!code) return;
|
||||
copyToClipboard(code);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
onCopy?.();
|
||||
@@ -43,7 +47,7 @@ export function PocBlock({ description, scriptCode, onCopy }: PocBlockProps) {
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
)}
|
||||
{scriptCode && (
|
||||
{code && (
|
||||
<div className="group/poc relative rounded-md border border-[#2a2a2a] overflow-hidden">
|
||||
<div className="flex items-stretch">
|
||||
<span className="relative flex items-center text-[13px] text-[#999] font-mono px-4 py-2 border-r border-[#2a2a2a]">PoC Script<span className="absolute top-0 inset-x-0 h-0.5 bg-white/60 rounded-full" /></span>
|
||||
@@ -64,7 +68,7 @@ export function PocBlock({ description, scriptCode, onCopy }: PocBlockProps) {
|
||||
<pre className="font-mono text-[12px] leading-[22px] whitespace-pre [font-variant-ligatures:none]">
|
||||
<code
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: hljs.highlight(scriptCode, { language: "python" }).value,
|
||||
__html: highlighted,
|
||||
}}
|
||||
/>
|
||||
</pre>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { Clock, CheckCircle2, Ban, History, BellOff, Wrench, GitMerge, GitPullRequest } from "lucide-react";
|
||||
import { ProInlineCta } from "@/components/ProCta";
|
||||
import { Clock, CheckCircle2, Ban, History, BellOff, Wrench, GitMerge } from "lucide-react";
|
||||
import { SIGNUP_URL, ctaUrl, trackCta } from "@/lib/cta";
|
||||
import { Vulnerability, VulnerabilityStatus, SEVERITY_COLORS, STATUS_META, isSeverityOverridden } from "@/types/issues";
|
||||
import { formatTimeAgo } from "@/lib/utils";
|
||||
import { getSeverityDot } from "@/lib/vulnerability-utils";
|
||||
@@ -29,6 +29,15 @@ const STATUS_BANNER: Record<VulnerabilityStatus, { icon: React.ElementType; labe
|
||||
|
||||
type BottomTab = "fix" | "reproduction";
|
||||
|
||||
// Team-workflow actions shown top-right of the finding header; each links out
|
||||
// to sign-up. `requiresCode` actions only appear when the finding has concrete
|
||||
// code locations to act on -- an autofix PR makes no sense for a black-box
|
||||
// finding with no code to change.
|
||||
const WORKFLOW_CTAS: { label: string; slug: string; icon: React.ElementType; requiresCode?: boolean }[] = [
|
||||
{ label: "Auto-fix & open a PR", slug: "autofix", icon: Wrench, requiresCode: true },
|
||||
{ label: "Sync to Jira / Linear", slug: "integrations", icon: GitMerge },
|
||||
];
|
||||
|
||||
interface VulnerabilityDetailProps {
|
||||
vulnerability: Vulnerability;
|
||||
}
|
||||
@@ -54,8 +63,9 @@ export default function VulnerabilityDetail({ vulnerability }: VulnerabilityDeta
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div>
|
||||
{/* Header: title/badges on the left, workflow actions top-right. */}
|
||||
<div className="flex flex-wrap items-start justify-between gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="mb-2">
|
||||
{vulnerability.display_number && (
|
||||
<span className="text-xs font-mono text-[#555] block mb-1">
|
||||
@@ -88,6 +98,26 @@ export default function VulnerabilityDetail({ vulnerability }: VulnerabilityDeta
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-shrink-0 flex-wrap items-center gap-2">
|
||||
{WORKFLOW_CTAS.filter((cta) => !cta.requiresCode || hasCodeLocations).map((cta) => {
|
||||
const Icon = cta.icon;
|
||||
return (
|
||||
<a
|
||||
key={cta.slug}
|
||||
href={ctaUrl(SIGNUP_URL, cta.slug)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => trackCta(cta.slug, "finding_detail")}
|
||||
className="inline-flex items-center gap-1.5 rounded-lg bg-white px-3 py-1.5 text-xs font-semibold text-black transition-opacity hover:opacity-90"
|
||||
>
|
||||
<Icon className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
{cta.label}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Status banner */}
|
||||
@@ -230,36 +260,6 @@ export default function VulnerabilityDetail({ vulnerability }: VulnerabilityDeta
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Team-workflow CTAs (Pro). Highest-intent surface: act on this finding. */}
|
||||
<div className="rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5">
|
||||
<p className="text-sm font-semibold text-white">Ship the fix with your team</p>
|
||||
<p className="mt-0.5 text-xs text-[#666]">
|
||||
Take this finding into your team's workflow.
|
||||
</p>
|
||||
<div className="mt-3 flex flex-wrap gap-2.5">
|
||||
<ProInlineCta
|
||||
label="Auto-fix & open a PR"
|
||||
desc="Fix it for you and open a PR, retested."
|
||||
slug="autofix"
|
||||
icon={Wrench}
|
||||
surface="finding_detail"
|
||||
/>
|
||||
<ProInlineCta
|
||||
label="Sync to Jira / Linear"
|
||||
desc="Two-way sync findings to Jira, Linear, and Slack."
|
||||
slug="integrations"
|
||||
icon={GitMerge}
|
||||
surface="finding_detail"
|
||||
/>
|
||||
<ProInlineCta
|
||||
label="Catch this in PR reviews"
|
||||
desc="Pentest every pull request your team opens."
|
||||
slug="pr_reviews"
|
||||
icon={GitPullRequest}
|
||||
surface="finding_detail"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,10 +6,9 @@ import {
|
||||
} from "@/lib/local-run-parser";
|
||||
|
||||
/**
|
||||
* Data seam for the local viewer. Replaces strix-app's browser file-picker
|
||||
* (`loadFromTexts`) with fetches against the local Python server's JSON
|
||||
* endpoints (same origin, relative URLs). Produces the same in-memory
|
||||
* `LoadedRun` shape the UI renders, plus a `finished` flag driving live polling.
|
||||
* Data seam for the local viewer: fetches against the local Python server's
|
||||
* JSON endpoints (same origin, relative URLs), producing the in-memory
|
||||
* `LoadedRun` shape the UI renders plus a `finished` flag driving live polling.
|
||||
*
|
||||
* The server serves a live in-progress run and a finished one identically; the
|
||||
* only signal is `run.finished`.
|
||||
@@ -201,6 +200,21 @@ export async function steerAgent(agentId: string, message: string): Promise<Stee
|
||||
return { ok: false, error: String(data.error ?? "unavailable") };
|
||||
}
|
||||
|
||||
export type SubmitFeedbackResult = { ok: true } | { ok: false; error: string };
|
||||
|
||||
/**
|
||||
* POST /api/feedback. Sends a feedback message plus a work email (no
|
||||
* verification) to the local server, which relays it to Strix.
|
||||
*/
|
||||
export async function submitFeedback(
|
||||
message: string,
|
||||
email: string
|
||||
): Promise<SubmitFeedbackResult> {
|
||||
const { ok, data } = await postJson("/api/feedback", { message, email });
|
||||
if (ok && data.ok === true) return { ok: true };
|
||||
return { ok: false, error: String(data.error ?? "unavailable") };
|
||||
}
|
||||
|
||||
export async function fetchAuthStatus(): Promise<AuthStatus> {
|
||||
const obj = (await getJson("/api/auth/status")) as Partial<AuthStatus>;
|
||||
return { verified: obj?.verified === true, email: obj?.email ?? null };
|
||||
|
||||
@@ -19,7 +19,161 @@ body {
|
||||
font-family: var(--font-geist-sans);
|
||||
}
|
||||
|
||||
/* Tab content transition (lifted from strix-app globals.css) */
|
||||
/* Thin sidebar scrollbar */
|
||||
.scrollbar-thin {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.15) transparent;
|
||||
}
|
||||
.scrollbar-thin::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
.scrollbar-thin::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.scrollbar-thin::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Motion vocabulary --------------------- */
|
||||
|
||||
/* Page transition: replayed on every view change via a keyed wrapper. */
|
||||
@keyframes page-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
filter: blur(8px);
|
||||
transform: translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
filter: blur(0px);
|
||||
transform: translateY(0px);
|
||||
}
|
||||
}
|
||||
.animate-page-in {
|
||||
animation: page-in 150ms ease-out;
|
||||
}
|
||||
|
||||
/* Plain fade. */
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.animate-fade-in {
|
||||
animation: fade-in 350ms ease-out;
|
||||
}
|
||||
|
||||
/* Staggered card entrance for lists/grids (first four cascade). */
|
||||
@keyframes cardIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
filter: blur(4px);
|
||||
transform: translateY(8px) scale(0.97);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
filter: blur(0px);
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
.animate-card-in {
|
||||
opacity: 0;
|
||||
animation: cardIn 300ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
.animate-card-in:nth-child(1) {
|
||||
animation-delay: 0ms;
|
||||
}
|
||||
.animate-card-in:nth-child(2) {
|
||||
animation-delay: 50ms;
|
||||
}
|
||||
.animate-card-in:nth-child(3) {
|
||||
animation-delay: 100ms;
|
||||
}
|
||||
.animate-card-in:nth-child(4) {
|
||||
animation-delay: 150ms;
|
||||
}
|
||||
|
||||
/* Shimmer sweep for progress indicators. */
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(400%);
|
||||
}
|
||||
}
|
||||
.animate-shimmer {
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
/* Dialog enter/exit — mirrors shadcn's data-[state]:animate-in/animate-out
|
||||
(fade-in-0/zoom-in-95 in, fade-out-0/zoom-out-95 out) driven off a
|
||||
data-state attribute rather than a transition, so the enter always plays. */
|
||||
@keyframes dialog-overlay-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes dialog-overlay-out {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes dialog-panel-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes dialog-panel-out {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
.dialog-overlay[data-state="open"] {
|
||||
animation: dialog-overlay-in 200ms ease;
|
||||
}
|
||||
.dialog-overlay[data-state="closed"] {
|
||||
animation: dialog-overlay-out 200ms ease forwards;
|
||||
}
|
||||
.dialog-panel[data-state="open"] {
|
||||
animation: dialog-panel-in 200ms ease;
|
||||
}
|
||||
.dialog-panel[data-state="closed"] {
|
||||
animation: dialog-panel-out 200ms ease forwards;
|
||||
}
|
||||
|
||||
/* Agent detail modal: fade only (no scale) and faster. Its panel holds the full
|
||||
transcript, and animating a transform on that much DOM janks; fading the
|
||||
overlay (the panel inherits its opacity) stays cheap and snappy. */
|
||||
.agent-modal[data-state="open"] {
|
||||
animation: dialog-overlay-in 140ms ease;
|
||||
}
|
||||
.agent-modal[data-state="closed"] {
|
||||
animation: dialog-overlay-out 140ms ease forwards;
|
||||
}
|
||||
|
||||
/* Tab content transition. */
|
||||
@keyframes tab-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
@@ -32,12 +186,11 @@ body {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-tab-in {
|
||||
animation: tab-in 200ms ease-out;
|
||||
}
|
||||
|
||||
/* Markdown prose styling (lifted from strix-app globals.css) */
|
||||
/* Markdown prose styling */
|
||||
.prose-markdown {
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
export interface ParsedFencedCode {
|
||||
language?: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
const FENCE_RE = /^```([^\n`]*)\r?\n([\s\S]*?)\r?\n?```$/;
|
||||
|
||||
/**
|
||||
* Agent-generated `poc_script_code` is stored wrapped in a markdown code fence
|
||||
* that carries the language, e.g.
|
||||
*
|
||||
* ```python
|
||||
* import requests
|
||||
* ```
|
||||
*
|
||||
* Renderers that show the value as bare code must not display the fence lines
|
||||
* literally. This extracts the inner code and the fence's language tag. Returns
|
||||
* the input unchanged (no language) when it isn't fenced.
|
||||
*/
|
||||
export function parseFencedCode(raw: string | null | undefined): ParsedFencedCode {
|
||||
if (!raw) return { code: "" };
|
||||
const match = FENCE_RE.exec(raw.trim());
|
||||
if (!match) return { code: raw };
|
||||
const info = match[1].trim();
|
||||
const language = info ? info.split(/\s+/)[0] : undefined;
|
||||
return { language: language || undefined, code: match[2] };
|
||||
}
|
||||
@@ -11,4 +11,22 @@ hljs.registerLanguage("apache", apache);
|
||||
hljs.registerLanguage("dockerfile", dockerfile);
|
||||
hljs.registerLanguage("properties", properties);
|
||||
|
||||
/**
|
||||
* Highlight code, preferring an explicit language when it's recognized,
|
||||
* otherwise auto-detecting. Falls back to Python when auto-detection is
|
||||
* inconclusive, since legacy (unfenced) PoC scripts are Python.
|
||||
*/
|
||||
export function highlightCode(code: string, language?: string | null): string {
|
||||
try {
|
||||
if (language && hljs.getLanguage(language)) {
|
||||
return hljs.highlight(code, { language, ignoreIllegals: true }).value;
|
||||
}
|
||||
const auto = hljs.highlightAuto(code);
|
||||
if (auto.language) return auto.value;
|
||||
return hljs.highlight(code, { language: "python", ignoreIllegals: true }).value;
|
||||
} catch {
|
||||
return hljs.highlight(code, { language: "python", ignoreIllegals: true }).value;
|
||||
}
|
||||
}
|
||||
|
||||
export default hljs;
|
||||
@@ -1,174 +0,0 @@
|
||||
import type React from "react";
|
||||
import {
|
||||
GitPullRequest,
|
||||
Layers,
|
||||
Globe,
|
||||
Puzzle,
|
||||
Users,
|
||||
Search,
|
||||
LayoutDashboard,
|
||||
AlertTriangle,
|
||||
MessageSquare,
|
||||
Network,
|
||||
Database,
|
||||
} from "lucide-react";
|
||||
|
||||
/**
|
||||
* Platform (Pro / Enterprise) feature catalog. Powers both the unified sidebar
|
||||
* nav rows and the in-app FeatureDetail upsell view. Everything is "Pro" except
|
||||
* Networks, which is "Enterprise". No lock icons anywhere.
|
||||
*/
|
||||
|
||||
export type FeatureTier = "Pro" | "Enterprise";
|
||||
|
||||
export interface ProFeature {
|
||||
slug: string;
|
||||
title: string;
|
||||
icon: React.ElementType;
|
||||
tier: FeatureTier;
|
||||
/** Short one-liner for the sidebar nav row (two-line layout). */
|
||||
navDesc: string;
|
||||
/** Headline shown on the FeatureDetail upsell page. */
|
||||
headline: string;
|
||||
/** Longer sentence shown on the FeatureDetail upsell page. */
|
||||
description: string;
|
||||
}
|
||||
|
||||
// Flat catalog of every platform feature, keyed by slug for routing. The
|
||||
// sidebar groups these into capability themes (see PLATFORM_THEMES); nothing
|
||||
// here implies a tier ordering.
|
||||
export const PLATFORM_FEATURES: ProFeature[] = [
|
||||
{
|
||||
slug: "pr_reviews",
|
||||
title: "PR Reviews",
|
||||
icon: GitPullRequest,
|
||||
tier: "Pro",
|
||||
navDesc: "Pentest every pull request",
|
||||
headline: "Pentest every pull request",
|
||||
description:
|
||||
"Strix reviews every pull request your team opens and catches exploitable changes before they merge.",
|
||||
},
|
||||
{
|
||||
slug: "repositories",
|
||||
title: "Repositories",
|
||||
icon: Layers,
|
||||
tier: "Pro",
|
||||
navDesc: "Connect your team's repos",
|
||||
headline: "Connect your team's repositories",
|
||||
description:
|
||||
"Link your org's repositories so Strix can scan them continuously and track findings over time.",
|
||||
},
|
||||
{
|
||||
slug: "domains",
|
||||
title: "Domains",
|
||||
icon: Globe,
|
||||
tier: "Pro",
|
||||
navDesc: "Cover the domains you own",
|
||||
headline: "Cover every domain you own",
|
||||
description:
|
||||
"Add the domains your team owns and let Strix watch them for newly exposed paths and drift.",
|
||||
},
|
||||
{
|
||||
slug: "integrations",
|
||||
title: "Integrations",
|
||||
icon: Puzzle,
|
||||
tier: "Pro",
|
||||
navDesc: "Sync to Jira, Linear, Slack",
|
||||
headline: "Sync findings to your tools",
|
||||
description:
|
||||
"Two-way sync findings to Jira, Linear, and Slack so fixes happen where your team already works.",
|
||||
},
|
||||
{
|
||||
slug: "members",
|
||||
title: "Members",
|
||||
icon: Users,
|
||||
tier: "Pro",
|
||||
navDesc: "Invite your team, set roles",
|
||||
headline: "Bring your whole team",
|
||||
description:
|
||||
"Invite your team, set roles, and share findings and run history across your org.",
|
||||
},
|
||||
{
|
||||
slug: "pentests",
|
||||
title: "Pentests",
|
||||
icon: Search,
|
||||
tier: "Pro",
|
||||
navDesc: "Deeper scans on managed infra",
|
||||
headline: "Launch deeper pentests",
|
||||
description:
|
||||
"Run deeper, longer pentests on managed infrastructure whenever you need them.",
|
||||
},
|
||||
{
|
||||
slug: "dashboard",
|
||||
title: "Dashboard",
|
||||
icon: LayoutDashboard,
|
||||
tier: "Pro",
|
||||
navDesc: "Everything in one place",
|
||||
headline: "See everything in one place",
|
||||
description:
|
||||
"Track every project, run, and finding across your org from a single dashboard.",
|
||||
},
|
||||
{
|
||||
slug: "platform_issues",
|
||||
title: "Issues",
|
||||
icon: AlertTriangle,
|
||||
tier: "Pro",
|
||||
navDesc: "Triage across your org",
|
||||
headline: "Triage findings across your org",
|
||||
description:
|
||||
"Manage and triage findings across every project and repository in one queue.",
|
||||
},
|
||||
{
|
||||
slug: "chat",
|
||||
title: "Chat",
|
||||
icon: MessageSquare,
|
||||
tier: "Pro",
|
||||
navDesc: "Ask about any finding",
|
||||
headline: "Ask Strix anything",
|
||||
description:
|
||||
"Ask the agents about any finding, run, or part of your app in natural language.",
|
||||
},
|
||||
{
|
||||
slug: "networks",
|
||||
title: "Networks",
|
||||
icon: Network,
|
||||
tier: "Enterprise",
|
||||
navDesc: "Reach internal, VPN-only targets",
|
||||
headline: "Scan internal networks",
|
||||
description:
|
||||
"Connect private networks to scan internal applications, VPN-only services, and RFC1918 targets.",
|
||||
},
|
||||
{
|
||||
slug: "knowledge",
|
||||
title: "Knowledge",
|
||||
icon: Database,
|
||||
tier: "Pro",
|
||||
navDesc: "Give agents context",
|
||||
headline: "Give agents context",
|
||||
description:
|
||||
"Teach Strix about your systems and business logic so every run gets smarter.",
|
||||
},
|
||||
];
|
||||
|
||||
export const FEATURES: Record<string, ProFeature> = Object.fromEntries(
|
||||
PLATFORM_FEATURES.map((f) => [f.slug, f])
|
||||
);
|
||||
|
||||
/**
|
||||
* Order the platform rows appear in the sidebar's single, ungrouped nav list
|
||||
* (after the run/local rows). No section headers; tier is shown only by each
|
||||
* row's inline tag.
|
||||
*/
|
||||
export const PLATFORM_ORDER: string[] = [
|
||||
"pr_reviews",
|
||||
"repositories",
|
||||
"domains",
|
||||
"integrations",
|
||||
"members",
|
||||
"pentests",
|
||||
"networks",
|
||||
"chat",
|
||||
"dashboard",
|
||||
"platform_issues",
|
||||
"knowledge",
|
||||
];
|
||||
@@ -57,5 +57,5 @@ export function parseTarget(target: string): ParsedTarget {
|
||||
*/
|
||||
export function runTitle(target: string | null, fallback: string): string {
|
||||
if (target) return parseTarget(target).display.replace(/\/$/, "");
|
||||
return fallback || "Untitled scan";
|
||||
return fallback || "Untitled pentest";
|
||||
}
|
||||
+72
-18
@@ -151,19 +151,35 @@ def _styles() -> dict[str, ParagraphStyle]:
|
||||
"Finding", fontName=_SANS_BOLD, fontSize=13, leading=17, textColor=_INK, spaceBefore=6
|
||||
)
|
||||
styles["field_label"] = ParagraphStyle(
|
||||
"FieldLabel", fontName=_SANS_BOLD, fontSize=8.5, leading=12, textColor=_MUTED,
|
||||
spaceBefore=10, spaceAfter=2,
|
||||
"FieldLabel",
|
||||
fontName=_SANS_BOLD,
|
||||
fontSize=8.5,
|
||||
leading=12,
|
||||
textColor=_MUTED,
|
||||
spaceBefore=10,
|
||||
spaceAfter=2,
|
||||
)
|
||||
styles["body"] = ParagraphStyle(
|
||||
"Body", fontName=_SANS, fontSize=10, leading=15, textColor=_TEXT, spaceAfter=8
|
||||
)
|
||||
styles["md_heading"] = ParagraphStyle(
|
||||
"MdHeading", fontName=_SANS_BOLD, fontSize=11, leading=15, textColor=_INK,
|
||||
spaceBefore=10, spaceAfter=4,
|
||||
"MdHeading",
|
||||
fontName=_SANS_BOLD,
|
||||
fontSize=11,
|
||||
leading=15,
|
||||
textColor=_INK,
|
||||
spaceBefore=10,
|
||||
spaceAfter=4,
|
||||
)
|
||||
styles["bullet"] = ParagraphStyle(
|
||||
"Bullet", fontName=_SANS, fontSize=10, leading=15, textColor=_TEXT,
|
||||
leftIndent=16, firstLineIndent=-11, spaceAfter=3,
|
||||
"Bullet",
|
||||
fontName=_SANS,
|
||||
fontSize=10,
|
||||
leading=15,
|
||||
textColor=_TEXT,
|
||||
leftIndent=16,
|
||||
firstLineIndent=-11,
|
||||
spaceAfter=3,
|
||||
)
|
||||
styles["meta_inline"] = ParagraphStyle(
|
||||
"MetaInline", fontName=_SANS, fontSize=9, leading=13, textColor=_MUTED, spaceBefore=4
|
||||
@@ -172,23 +188,44 @@ def _styles() -> dict[str, ParagraphStyle]:
|
||||
# a bordered paragraph's top padding, so too small a gap lets the background
|
||||
# box bleed up over the field label above it.
|
||||
styles["code"] = ParagraphStyle(
|
||||
"Code", fontName=_MONO, fontSize=8, leading=11, textColor=_TEXT,
|
||||
backColor=_LIGHT_BG, borderColor=_BORDER, borderWidth=0.5, borderPadding=8,
|
||||
spaceBefore=12, spaceAfter=12,
|
||||
"Code",
|
||||
fontName=_MONO,
|
||||
fontSize=8,
|
||||
leading=11,
|
||||
textColor=_TEXT,
|
||||
backColor=_LIGHT_BG,
|
||||
borderColor=_BORDER,
|
||||
borderWidth=0.5,
|
||||
borderPadding=8,
|
||||
spaceBefore=12,
|
||||
spaceAfter=12,
|
||||
)
|
||||
styles["count"] = ParagraphStyle(
|
||||
"Count", fontName=_SANS_BOLD, fontSize=30, leading=32, alignment=TA_CENTER
|
||||
)
|
||||
styles["count_label"] = ParagraphStyle(
|
||||
"CountLabel", fontName=_SANS_BOLD, fontSize=8, leading=12, textColor=_MUTED,
|
||||
alignment=TA_CENTER, spaceBefore=4,
|
||||
"CountLabel",
|
||||
fontName=_SANS_BOLD,
|
||||
fontSize=8,
|
||||
leading=12,
|
||||
textColor=_MUTED,
|
||||
alignment=TA_CENTER,
|
||||
spaceBefore=4,
|
||||
)
|
||||
styles["badge"] = ParagraphStyle(
|
||||
"Badge", fontName=_SANS_BOLD, fontSize=9, leading=11, textColor=colors.white,
|
||||
"Badge",
|
||||
fontName=_SANS_BOLD,
|
||||
fontSize=9,
|
||||
leading=11,
|
||||
textColor=colors.white,
|
||||
alignment=TA_CENTER,
|
||||
)
|
||||
styles["confidential"] = ParagraphStyle(
|
||||
"Confidential", fontName=_SANS_BOLD, fontSize=9, leading=12, textColor=colors.white,
|
||||
"Confidential",
|
||||
fontName=_SANS_BOLD,
|
||||
fontSize=9,
|
||||
leading=12,
|
||||
textColor=colors.white,
|
||||
alignment=TA_CENTER,
|
||||
)
|
||||
return styles
|
||||
@@ -256,8 +293,10 @@ def _severity_grid(styles: dict[str, ParagraphStyle], counts: dict[str, int]) ->
|
||||
color = _SEVERITY_COLORS[name]
|
||||
count_style = ParagraphStyle(f"Count{name}", parent=styles["count"], textColor=color)
|
||||
cells.append(
|
||||
[Paragraph(str(counts.get(name, 0)), count_style),
|
||||
Paragraph(name.upper(), styles["count_label"])]
|
||||
[
|
||||
Paragraph(str(counts.get(name, 0)), count_style),
|
||||
Paragraph(name.upper(), styles["count_label"]),
|
||||
]
|
||||
)
|
||||
col = (_PAGE_W - 40 * mm) / 4
|
||||
table = Table([cells], colWidths=[col] * 4)
|
||||
@@ -320,8 +359,10 @@ def _cover(
|
||||
("DURATION", _duration(record.get("start_time"), record.get("end_time"))),
|
||||
]
|
||||
meta_table = Table(
|
||||
[[Paragraph(label, styles["meta_label"]), Paragraph(_esc(value), styles["meta_value"])]
|
||||
for label, value in meta_rows],
|
||||
[
|
||||
[Paragraph(label, styles["meta_label"]), Paragraph(_esc(value), styles["meta_value"])]
|
||||
for label, value in meta_rows
|
||||
],
|
||||
colWidths=[38 * mm, _PAGE_W - 40 * mm - 38 * mm],
|
||||
)
|
||||
meta_table.setStyle(
|
||||
@@ -462,6 +503,18 @@ def _markdown_flowables( # noqa: PLR0915 - cohesive block parser, splitting hur
|
||||
return flow
|
||||
|
||||
|
||||
_FENCE_RE = re.compile(r"^```([^\n`]*)\n(.*?)\n?```$", re.DOTALL)
|
||||
|
||||
|
||||
def _strip_code_fence(value: Any) -> Any:
|
||||
"""Drop a wrapping markdown code fence so raw-code fields don't show the
|
||||
``` ```lang ``` marker lines literally in the PDF."""
|
||||
if not isinstance(value, str):
|
||||
return value
|
||||
match = _FENCE_RE.match(value.strip())
|
||||
return match.group(2) if match else value
|
||||
|
||||
|
||||
def _field_block(
|
||||
styles: dict[str, ParagraphStyle], label: str, value: Any, *, code: bool = False
|
||||
) -> list[Flowable]:
|
||||
@@ -503,7 +556,8 @@ def _finding_flowables(
|
||||
story.extend(_field_block(styles, "Impact", vuln.get("impact")))
|
||||
story.extend(_field_block(styles, "Technical analysis", vuln.get("technical_analysis")))
|
||||
story.extend(_field_block(styles, "Proof of concept", vuln.get("poc_description")))
|
||||
story.extend(_field_block(styles, "PoC script", vuln.get("poc_script_code"), code=True))
|
||||
poc_script = _strip_code_fence(vuln.get("poc_script_code"))
|
||||
story.extend(_field_block(styles, "PoC script", poc_script, code=True))
|
||||
story.extend(_field_block(styles, "Evidence", vuln.get("evidence"), code=True))
|
||||
|
||||
remediation = vuln.get("remediation_steps")
|
||||
|
||||
@@ -174,6 +174,8 @@ def _make_handler(state: _ViewerState) -> type[BaseHTTPRequestHandler]:
|
||||
self._handle_forget()
|
||||
elif path == "/api/report/send":
|
||||
self._handle_report_send()
|
||||
elif path == "/api/feedback":
|
||||
self._handle_feedback()
|
||||
elif path == "/api/agents/steer":
|
||||
self._handle_steer()
|
||||
else:
|
||||
@@ -218,6 +220,10 @@ def _make_handler(state: _ViewerState) -> type[BaseHTTPRequestHandler]:
|
||||
|
||||
purpose = body.get("purpose")
|
||||
posthog.viewer_email_event(str(event), purpose=str(purpose) if purpose else None)
|
||||
elif event == "agent_steered":
|
||||
from strix.telemetry import posthog
|
||||
|
||||
posthog.viewer_agent_steered()
|
||||
self.send_response(HTTPStatus.NO_CONTENT)
|
||||
self.end_headers()
|
||||
|
||||
@@ -379,6 +385,37 @@ def _make_handler(state: _ViewerState) -> type[BaseHTTPRequestHandler]:
|
||||
{"ok": True, "password": password, "filename": filename},
|
||||
)
|
||||
|
||||
# Cap on a feedback message so a runaway client cannot flood the relay.
|
||||
_FEEDBACK_MESSAGE_MAX = 5000
|
||||
|
||||
def _handle_feedback(self) -> None:
|
||||
# Requires this process's session capability, like the other POSTs,
|
||||
# so an exposed --host port can't be used to spam the relay.
|
||||
if not self._has_session():
|
||||
self._send_json(HTTPStatus.FORBIDDEN, {"error": "forbidden"})
|
||||
return
|
||||
body = self._read_body()
|
||||
email = str(body.get("email") or "").strip()
|
||||
message = str(body.get("message") or "").strip()
|
||||
if not email:
|
||||
self._send_json(HTTPStatus.BAD_REQUEST, {"error": "invalid_email"})
|
||||
return
|
||||
if not message:
|
||||
self._send_json(HTTPStatus.BAD_REQUEST, {"error": "invalid_message"})
|
||||
return
|
||||
message = message[: self._FEEDBACK_MESSAGE_MAX]
|
||||
try:
|
||||
auth.feedback_submit(email, message)
|
||||
except auth.RelayError as exc:
|
||||
self._send_relay_error(exc)
|
||||
return
|
||||
# Server-authoritative: fire only after a successful relay (respects
|
||||
# the telemetry opt-out; no message/email content is sent).
|
||||
from strix.telemetry import posthog
|
||||
|
||||
posthog.viewer_feedback_submitted()
|
||||
self._send_json(HTTPStatus.OK, {"ok": True})
|
||||
|
||||
# Cap on a steering message so a runaway client cannot flood the agent.
|
||||
_STEER_MESSAGE_MAX = 4000
|
||||
|
||||
@@ -413,6 +450,7 @@ def _make_handler(state: _ViewerState) -> type[BaseHTTPRequestHandler]:
|
||||
status_by_code = {
|
||||
"rate_limited": HTTPStatus.TOO_MANY_REQUESTS,
|
||||
"invalid_email": HTTPStatus.BAD_REQUEST,
|
||||
"invalid_message": HTTPStatus.BAD_REQUEST,
|
||||
"work_email_required": HTTPStatus.BAD_REQUEST,
|
||||
"invalid_code": HTTPStatus.FORBIDDEN,
|
||||
"reverify": HTTPStatus.UNAUTHORIZED,
|
||||
|
||||
@@ -1,517 +0,0 @@
|
||||
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))a(s);new MutationObserver(s=>{for(const o of s)if(o.type==="childList")for(const c of o.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&a(c)}).observe(document,{childList:!0,subtree:!0});function i(s){const o={};return s.integrity&&(o.integrity=s.integrity),s.referrerPolicy&&(o.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?o.credentials="include":s.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function a(s){if(s.ep)return;s.ep=!0;const o=i(s);fetch(s.href,o)}})();function ko(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var lh={exports:{}},Gl={};/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var $0;function u2(){if($0)return Gl;$0=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.fragment");function i(a,s,o){var c=null;if(o!==void 0&&(c=""+o),s.key!==void 0&&(c=""+s.key),"key"in s){o={};for(var d in s)d!=="key"&&(o[d]=s[d])}else o=s;return s=o.ref,{$$typeof:e,type:a,key:c,ref:s!==void 0?s:null,props:o}}return Gl.Fragment=t,Gl.jsx=i,Gl.jsxs=i,Gl}var q0;function d2(){return q0||(q0=1,lh.exports=u2()),lh.exports}var g=d2(),oh={exports:{}},Ve={};/**
|
||||
* @license React
|
||||
* react.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var P0;function f2(){if(P0)return Ve;P0=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.portal"),i=Symbol.for("react.fragment"),a=Symbol.for("react.strict_mode"),s=Symbol.for("react.profiler"),o=Symbol.for("react.consumer"),c=Symbol.for("react.context"),d=Symbol.for("react.forward_ref"),f=Symbol.for("react.suspense"),h=Symbol.for("react.memo"),m=Symbol.for("react.lazy"),p=Symbol.for("react.activity"),y=Symbol.iterator;function x(L){return L===null||typeof L!="object"?null:(L=y&&L[y]||L["@@iterator"],typeof L=="function"?L:null)}var _={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},N=Object.assign,S={};function w(L,Y,D){this.props=L,this.context=Y,this.refs=S,this.updater=D||_}w.prototype.isReactComponent={},w.prototype.setState=function(L,Y){if(typeof L!="object"&&typeof L!="function"&&L!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,L,Y,"setState")},w.prototype.forceUpdate=function(L){this.updater.enqueueForceUpdate(this,L,"forceUpdate")};function k(){}k.prototype=w.prototype;function E(L,Y,D){this.props=L,this.context=Y,this.refs=S,this.updater=D||_}var M=E.prototype=new k;M.constructor=E,N(M,w.prototype),M.isPureReactComponent=!0;var U=Array.isArray;function R(){}var B={H:null,A:null,T:null,S:null},I=Object.prototype.hasOwnProperty;function Z(L,Y,D){var F=D.ref;return{$$typeof:e,type:L,key:Y,ref:F!==void 0?F:null,props:D}}function j(L,Y){return Z(L.type,Y,L.props)}function z(L){return typeof L=="object"&&L!==null&&L.$$typeof===e}function V(L){var Y={"=":"=0",":":"=2"};return"$"+L.replace(/[=:]/g,function(D){return Y[D]})}var G=/\/+/g;function A(L,Y){return typeof L=="object"&&L!==null&&L.key!=null?V(""+L.key):Y.toString(36)}function q(L){switch(L.status){case"fulfilled":return L.value;case"rejected":throw L.reason;default:switch(typeof L.status=="string"?L.then(R,R):(L.status="pending",L.then(function(Y){L.status==="pending"&&(L.status="fulfilled",L.value=Y)},function(Y){L.status==="pending"&&(L.status="rejected",L.reason=Y)})),L.status){case"fulfilled":return L.value;case"rejected":throw L.reason}}throw L}function O(L,Y,D,F,$){var Q=typeof L;(Q==="undefined"||Q==="boolean")&&(L=null);var J=!1;if(L===null)J=!0;else switch(Q){case"bigint":case"string":case"number":J=!0;break;case"object":switch(L.$$typeof){case e:case t:J=!0;break;case m:return J=L._init,O(J(L._payload),Y,D,F,$)}}if(J)return $=$(L),J=F===""?"."+A(L,0):F,U($)?(D="",J!=null&&(D=J.replace(G,"$&/")+"/"),O($,Y,D,"",function(oe){return oe})):$!=null&&(z($)&&($=j($,D+($.key==null||L&&L.key===$.key?"":(""+$.key).replace(G,"$&/")+"/")+J)),Y.push($)),1;J=0;var W=F===""?".":F+":";if(U(L))for(var te=0;te<L.length;te++)F=L[te],Q=W+A(F,te),J+=O(F,Y,D,Q,$);else if(te=x(L),typeof te=="function")for(L=te.call(L),te=0;!(F=L.next()).done;)F=F.value,Q=W+A(F,te++),J+=O(F,Y,D,Q,$);else if(Q==="object"){if(typeof L.then=="function")return O(q(L),Y,D,F,$);throw Y=String(L),Error("Objects are not valid as a React child (found: "+(Y==="[object Object]"?"object with keys {"+Object.keys(L).join(", ")+"}":Y)+"). If you meant to render a collection of children, use an array instead.")}return J}function H(L,Y,D){if(L==null)return L;var F=[],$=0;return O(L,F,"","",function(Q){return Y.call(D,Q,$++)}),F}function K(L){if(L._status===-1){var Y=L._result;Y=Y(),Y.then(function(D){(L._status===0||L._status===-1)&&(L._status=1,L._result=D)},function(D){(L._status===0||L._status===-1)&&(L._status=2,L._result=D)}),L._status===-1&&(L._status=0,L._result=Y)}if(L._status===1)return L._result.default;throw L._result}var X=typeof reportError=="function"?reportError:function(L){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var Y=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof L=="object"&&L!==null&&typeof L.message=="string"?String(L.message):String(L),error:L});if(!window.dispatchEvent(Y))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",L);return}console.error(L)},C={map:H,forEach:function(L,Y,D){H(L,function(){Y.apply(this,arguments)},D)},count:function(L){var Y=0;return H(L,function(){Y++}),Y},toArray:function(L){return H(L,function(Y){return Y})||[]},only:function(L){if(!z(L))throw Error("React.Children.only expected to receive a single React element child.");return L}};return Ve.Activity=p,Ve.Children=C,Ve.Component=w,Ve.Fragment=i,Ve.Profiler=s,Ve.PureComponent=E,Ve.StrictMode=a,Ve.Suspense=f,Ve.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=B,Ve.__COMPILER_RUNTIME={__proto__:null,c:function(L){return B.H.useMemoCache(L)}},Ve.cache=function(L){return function(){return L.apply(null,arguments)}},Ve.cacheSignal=function(){return null},Ve.cloneElement=function(L,Y,D){if(L==null)throw Error("The argument must be a React element, but you passed "+L+".");var
Line truncated
|
||||
* @license React
|
||||
* scheduler.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var G0;function m2(){return G0||(G0=1,(function(e){function t(O,H){var K=O.length;O.push(H);e:for(;0<K;){var X=K-1>>>1,C=O[X];if(0<s(C,H))O[X]=H,O[K]=C,K=X;else break e}}function i(O){return O.length===0?null:O[0]}function a(O){if(O.length===0)return null;var H=O[0],K=O.pop();if(K!==H){O[0]=K;e:for(var X=0,C=O.length,L=C>>>1;X<L;){var Y=2*(X+1)-1,D=O[Y],F=Y+1,$=O[F];if(0>s(D,K))F<C&&0>s($,D)?(O[X]=$,O[F]=K,X=F):(O[X]=D,O[Y]=K,X=Y);else if(F<C&&0>s($,K))O[X]=$,O[F]=K,X=F;else break e}}return H}function s(O,H){var K=O.sortIndex-H.sortIndex;return K!==0?K:O.id-H.id}if(e.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var o=performance;e.unstable_now=function(){return o.now()}}else{var c=Date,d=c.now();e.unstable_now=function(){return c.now()-d}}var f=[],h=[],m=1,p=null,y=3,x=!1,_=!1,N=!1,S=!1,w=typeof setTimeout=="function"?setTimeout:null,k=typeof clearTimeout=="function"?clearTimeout:null,E=typeof setImmediate<"u"?setImmediate:null;function M(O){for(var H=i(h);H!==null;){if(H.callback===null)a(h);else if(H.startTime<=O)a(h),H.sortIndex=H.expirationTime,t(f,H);else break;H=i(h)}}function U(O){if(N=!1,M(O),!_)if(i(f)!==null)_=!0,R||(R=!0,V());else{var H=i(h);H!==null&&q(U,H.startTime-O)}}var R=!1,B=-1,I=5,Z=-1;function j(){return S?!0:!(e.unstable_now()-Z<I)}function z(){if(S=!1,R){var O=e.unstable_now();Z=O;var H=!0;try{e:{_=!1,N&&(N=!1,k(B),B=-1),x=!0;var K=y;try{t:{for(M(O),p=i(f);p!==null&&!(p.expirationTime>O&&j());){var X=p.callback;if(typeof X=="function"){p.callback=null,y=p.priorityLevel;var C=X(p.expirationTime<=O);if(O=e.unstable_now(),typeof C=="function"){p.callback=C,M(O),H=!0;break t}p===i(f)&&a(f),M(O)}else a(f);p=i(f)}if(p!==null)H=!0;else{var L=i(h);L!==null&&q(U,L.startTime-O),H=!1}}break e}finally{p=null,y=K,x=!1}H=void 0}}finally{H?V():R=!1}}}var V;if(typeof E=="function")V=function(){E(z)};else if(typeof MessageChannel<"u"){var G=new MessageChannel,A=G.port2;G.port1.onmessage=z,V=function(){A.postMessage(null)}}else V=function(){w(z,0)};function q(O,H){B=w(function(){O(e.unstable_now())},H)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(O){O.callback=null},e.unstable_forceFrameRate=function(O){0>O||125<O?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):I=0<O?Math.floor(1e3/O):5},e.unstable_getCurrentPriorityLevel=function(){return y},e.unstable_next=function(O){switch(y){case 1:case 2:case 3:var H=3;break;default:H=y}var K=y;y=H;try{return O()}finally{y=K}},e.unstable_requestPaint=function(){S=!0},e.unstable_runWithPriority=function(O,H){switch(O){case 1:case 2:case 3:case 4:case 5:break;default:O=3}var K=y;y=O;try{return H()}finally{y=K}},e.unstable_scheduleCallback=function(O,H,K){var X=e.unstable_now();switch(typeof K=="object"&&K!==null?(K=K.delay,K=typeof K=="number"&&0<K?X+K:X):K=X,O){case 1:var C=-1;break;case 2:C=250;break;case 5:C=1073741823;break;case 4:C=1e4;break;default:C=5e3}return C=K+C,O={id:m++,callback:H,priorityLevel:O,startTime:K,expirationTime:C,sortIndex:-1},K>X?(O.sortIndex=K,t(h,O),i(f)===null&&O===i(h)&&(N?(k(B),B=-1):N=!0,q(U,K-X))):(O.sortIndex=C,t(f,O),_||x||(_=!0,R||(R=!0,V()))),O},e.unstable_shouldYield=j,e.unstable_wrapCallback=function(O){var H=y;return function(){var K=y;y=H;try{return O.apply(this,arguments)}finally{y=K}}}})(dh)),dh}var V0;function p2(){return V0||(V0=1,uh.exports=m2()),uh.exports}var fh={exports:{}},kn={};/**
|
||||
* @license React
|
||||
* react-dom.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var Y0;function g2(){if(Y0)return kn;Y0=1;var e=To();function t(f){var h="https://react.dev/errors/"+f;if(1<arguments.length){h+="?args[]="+encodeURIComponent(arguments[1]);for(var m=2;m<arguments.length;m++)h+="&args[]="+encodeURIComponent(arguments[m])}return"Minified React error #"+f+"; visit "+h+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function i(){}var a={d:{f:i,r:function(){throw Error(t(522))},D:i,C:i,L:i,m:i,X:i,S:i,M:i},p:0,findDOMNode:null},s=Symbol.for("react.portal");function o(f,h,m){var p=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null;return{$$typeof:s,key:p==null?null:""+p,children:f,containerInfo:h,implementation:m}}var c=e.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function d(f,h){if(f==="font")return"";if(typeof h=="string")return h==="use-credentials"?h:""}return kn.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=a,kn.createPortal=function(f,h){var m=2<arguments.length&&arguments[2]!==void 0?arguments[2]:null;if(!h||h.nodeType!==1&&h.nodeType!==9&&h.nodeType!==11)throw Error(t(299));return o(f,h,null,m)},kn.flushSync=function(f){var h=c.T,m=a.p;try{if(c.T=null,a.p=2,f)return f()}finally{c.T=h,a.p=m,a.d.f()}},kn.preconnect=function(f,h){typeof f=="string"&&(h?(h=h.crossOrigin,h=typeof h=="string"?h==="use-credentials"?h:"":void 0):h=null,a.d.C(f,h))},kn.prefetchDNS=function(f){typeof f=="string"&&a.d.D(f)},kn.preinit=function(f,h){if(typeof f=="string"&&h&&typeof h.as=="string"){var m=h.as,p=d(m,h.crossOrigin),y=typeof h.integrity=="string"?h.integrity:void 0,x=typeof h.fetchPriority=="string"?h.fetchPriority:void 0;m==="style"?a.d.S(f,typeof h.precedence=="string"?h.precedence:void 0,{crossOrigin:p,integrity:y,fetchPriority:x}):m==="script"&&a.d.X(f,{crossOrigin:p,integrity:y,fetchPriority:x,nonce:typeof h.nonce=="string"?h.nonce:void 0})}},kn.preinitModule=function(f,h){if(typeof f=="string")if(typeof h=="object"&&h!==null){if(h.as==null||h.as==="script"){var m=d(h.as,h.crossOrigin);a.d.M(f,{crossOrigin:m,integrity:typeof h.integrity=="string"?h.integrity:void 0,nonce:typeof h.nonce=="string"?h.nonce:void 0})}}else h==null&&a.d.M(f)},kn.preload=function(f,h){if(typeof f=="string"&&typeof h=="object"&&h!==null&&typeof h.as=="string"){var m=h.as,p=d(m,h.crossOrigin);a.d.L(f,m,{crossOrigin:p,integrity:typeof h.integrity=="string"?h.integrity:void 0,nonce:typeof h.nonce=="string"?h.nonce:void 0,type:typeof h.type=="string"?h.type:void 0,fetchPriority:typeof h.fetchPriority=="string"?h.fetchPriority:void 0,referrerPolicy:typeof h.referrerPolicy=="string"?h.referrerPolicy:void 0,imageSrcSet:typeof h.imageSrcSet=="string"?h.imageSrcSet:void 0,imageSizes:typeof h.imageSizes=="string"?h.imageSizes:void 0,media:typeof h.media=="string"?h.media:void 0})}},kn.preloadModule=function(f,h){if(typeof f=="string")if(h){var m=d(h.as,h.crossOrigin);a.d.m(f,{as:typeof h.as=="string"&&h.as!=="script"?h.as:void 0,crossOrigin:m,integrity:typeof h.integrity=="string"?h.integrity:void 0})}else a.d.m(f)},kn.requestFormReset=function(f){a.d.r(f)},kn.unstable_batchedUpdates=function(f,h){return f(h)},kn.useFormState=function(f,h,m){return c.H.useFormState(f,h,m)},kn.useFormStatus=function(){return c.H.useHostTransitionStatus()},kn.version="19.2.7",kn}var X0;function x_(){if(X0)return fh.exports;X0=1;function e(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(t){console.error(t)}}return e(),fh.exports=g2(),fh.exports}/**
|
||||
* @license React
|
||||
* react-dom-client.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var K0;function b2(){if(K0)return Vl;K0=1;var e=p2(),t=To(),i=x_();function a(n){var r="https://react.dev/errors/"+n;if(1<arguments.length){r+="?args[]="+encodeURIComponent(arguments[1]);for(var l=2;l<arguments.length;l++)r+="&args[]="+encodeURIComponent(arguments[l])}return"Minified React error #"+n+"; visit "+r+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function s(n){return!(!n||n.nodeType!==1&&n.nodeType!==9&&n.nodeType!==11)}function o(n){var r=n,l=n;if(n.alternate)for(;r.return;)r=r.return;else{n=r;do r=n,(r.flags&4098)!==0&&(l=r.return),n=r.return;while(n)}return r.tag===3?l:null}function c(n){if(n.tag===13){var r=n.memoizedState;if(r===null&&(n=n.alternate,n!==null&&(r=n.memoizedState)),r!==null)return r.dehydrated}return null}function d(n){if(n.tag===31){var r=n.memoizedState;if(r===null&&(n=n.alternate,n!==null&&(r=n.memoizedState)),r!==null)return r.dehydrated}return null}function f(n){if(o(n)!==n)throw Error(a(188))}function h(n){var r=n.alternate;if(!r){if(r=o(n),r===null)throw Error(a(188));return r!==n?null:n}for(var l=n,u=r;;){var b=l.return;if(b===null)break;var v=b.alternate;if(v===null){if(u=b.return,u!==null){l=u;continue}break}if(b.child===v.child){for(v=b.child;v;){if(v===l)return f(b),n;if(v===u)return f(b),r;v=v.sibling}throw Error(a(188))}if(l.return!==u.return)l=b,u=v;else{for(var T=!1,P=b.child;P;){if(P===l){T=!0,l=b,u=v;break}if(P===u){T=!0,u=b,l=v;break}P=P.sibling}if(!T){for(P=v.child;P;){if(P===l){T=!0,l=v,u=b;break}if(P===u){T=!0,u=v,l=b;break}P=P.sibling}if(!T)throw Error(a(189))}}if(l.alternate!==u)throw Error(a(190))}if(l.tag!==3)throw Error(a(188));return l.stateNode.current===l?n:r}function m(n){var r=n.tag;if(r===5||r===26||r===27||r===6)return n;for(n=n.child;n!==null;){if(r=m(n),r!==null)return r;n=n.sibling}return null}var p=Object.assign,y=Symbol.for("react.element"),x=Symbol.for("react.transitional.element"),_=Symbol.for("react.portal"),N=Symbol.for("react.fragment"),S=Symbol.for("react.strict_mode"),w=Symbol.for("react.profiler"),k=Symbol.for("react.consumer"),E=Symbol.for("react.context"),M=Symbol.for("react.forward_ref"),U=Symbol.for("react.suspense"),R=Symbol.for("react.suspense_list"),B=Symbol.for("react.memo"),I=Symbol.for("react.lazy"),Z=Symbol.for("react.activity"),j=Symbol.for("react.memo_cache_sentinel"),z=Symbol.iterator;function V(n){return n===null||typeof n!="object"?null:(n=z&&n[z]||n["@@iterator"],typeof n=="function"?n:null)}var G=Symbol.for("react.client.reference");function A(n){if(n==null)return null;if(typeof n=="function")return n.$$typeof===G?null:n.displayName||n.name||null;if(typeof n=="string")return n;switch(n){case N:return"Fragment";case w:return"Profiler";case S:return"StrictMode";case U:return"Suspense";case R:return"SuspenseList";case Z:return"Activity"}if(typeof n=="object")switch(n.$$typeof){case _:return"Portal";case E:return n.displayName||"Context";case k:return(n._context.displayName||"Context")+".Consumer";case M:var r=n.render;return n=n.displayName,n||(n=r.displayName||r.name||"",n=n!==""?"ForwardRef("+n+")":"ForwardRef"),n;case B:return r=n.displayName||null,r!==null?r:A(n.type)||"Memo";case I:r=n._payload,n=n._init;try{return A(n(r))}catch{}}return null}var q=Array.isArray,O=t.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,H=i.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,K={pending:!1,data:null,method:null,action:null},X=[],C=-1;function L(n){return{current:n}}function Y(n){0>C||(n.current=X[C],X[C]=null,C--)}function D(n,r){C++,X[C]=n.current,n.current=r}var F=L(null),$=L(null),Q=L(null),J=L(null);function W(n,r){switch(D(Q,r),D($,n),D(F,null),r.nodeType){case 9:case 11:n=(n=r.documentElement)&&(n=n.namespaceURI)?u0(n):0;break;default:if(n=r.tagName,r=r.namespaceURI)r=u0(r),n=d0(r,n);else switch(n){case"svg":n=1;break;case"math":n=2;break;default:n=0}}Y(F),D(F,n)}function te(){Y(F),Y($),Y(Q)}function oe(n){n.memoizedState!==null&&D(J,n);var r=F.current,l=d0(r,n.type);r!==l&&(D($,n),D(F,l))}function fe(n){$.current===n&&(Y(F),Y($)),J.current===n&&(Y(J),$l._currentValue=K)}var be,we;function Ne(n){if(be===void 0)try{throw Error()}catch(l){var r=l.stack.trim().match(/\n( *(at )?)/);be=r&&r[1]||"",we=-1<l.stack.indexOf(`
|
||||
at`)?" (<anonymous>)":-1<l.stack.indexOf("@")?"@unknown:0:0":""}return`
|
||||
`+be+n+we}var je=!1;function $e(n,r){if(!n||je)return"";je=!0;var l=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var u={DetermineComponentFrameRoot:function(){try{if(r){var ge=function(){throw Error()};if(Object.defineProperty(ge.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(ge,[])}catch(de){var ce=de}Reflect.construct(n,[],ge)}else{try{ge.call()}catch(de){ce=de}n.call(ge.prototype)}}else{try{throw Error()}catch(de){ce=de}(ge=n())&&typeof ge.catch=="function"&&ge.catch(function(){})}}catch(de){if(de&&ce&&typeof de.stack=="string")return[de.stack,ce.stack]}return[null,null]}};u.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var b=Object.getOwnPropertyDescriptor(u.DetermineComponentFrameRoot,"name");b&&b.configurable&&Object.defineProperty(u.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var v=u.DetermineComponentFrameRoot(),T=v[0],P=v[1];if(T&&P){var ne=T.split(`
|
||||
`),le=P.split(`
|
||||
`);for(b=u=0;u<ne.length&&!ne[u].includes("DetermineComponentFrameRoot");)u++;for(;b<le.length&&!le[b].includes("DetermineComponentFrameRoot");)b++;if(u===ne.length||b===le.length)for(u=ne.length-1,b=le.length-1;1<=u&&0<=b&&ne[u]!==le[b];)b--;for(;1<=u&&0<=b;u--,b--)if(ne[u]!==le[b]){if(u!==1||b!==1)do if(u--,b--,0>b||ne[u]!==le[b]){var he=`
|
||||
`+ne[u].replace(" at new "," at ");return n.displayName&&he.includes("<anonymous>")&&(he=he.replace("<anonymous>",n.displayName)),he}while(1<=u&&0<=b);break}}}finally{je=!1,Error.prepareStackTrace=l}return(l=n?n.displayName||n.name:"")?Ne(l):""}function st(n,r){switch(n.tag){case 26:case 27:case 5:return Ne(n.type);case 16:return Ne("Lazy");case 13:return n.child!==r&&r!==null?Ne("Suspense Fallback"):Ne("Suspense");case 19:return Ne("SuspenseList");case 0:case 15:return $e(n.type,!1);case 11:return $e(n.type.render,!1);case 1:return $e(n.type,!0);case 31:return Ne("Activity");default:return""}}function Rt(n){try{var r="",l=null;do r+=st(n,l),l=n,n=n.return;while(n);return r}catch(u){return`
|
||||
Error generating stack: `+u.message+`
|
||||
`+u.stack}}var Yt=Object.prototype.hasOwnProperty,Pt=e.unstable_scheduleCallback,Xt=e.unstable_cancelCallback,Yn=e.unstable_shouldYield,wn=e.unstable_requestPaint,ct=e.unstable_now,It=e.unstable_getCurrentPriorityLevel,ue=e.unstable_ImmediatePriority,xe=e.unstable_UserBlockingPriority,Oe=e.unstable_NormalPriority,Fe=e.unstable_LowPriority,Ze=e.unstable_IdlePriority,on=e.log,En=e.unstable_setDisableYieldValue,Kt=null,Ct=null;function Wt(n){if(typeof on=="function"&&En(n),Ct&&typeof Ct.setStrictMode=="function")try{Ct.setStrictMode(Kt,n)}catch{}}var ut=Math.clz32?Math.clz32:wr,zn=Math.log,cn=Math.LN2;function wr(n){return n>>>=0,n===0?32:31-(zn(n)/cn|0)|0}var nt=256,Xn=262144,Cn=4194304;function hn(n){var r=n&42;if(r!==0)return r;switch(n&-n){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return n&261888;case 262144:case 524288:case 1048576:case 2097152:return n&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return n&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return n}}function ie(n,r,l){var u=n.pendingLanes;if(u===0)return 0;var b=0,v=n.suspendedLanes,T=n.pingedLanes;n=n.warmLanes;var P=u&134217727;return P!==0?(u=P&~v,u!==0?b=hn(u):(T&=P,T!==0?b=hn(T):l||(l=P&~n,l!==0&&(b=hn(l))))):(P=u&~v,P!==0?b=hn(P):T!==0?b=hn(T):l||(l=u&~n,l!==0&&(b=hn(l)))),b===0?0:r!==0&&r!==b&&(r&v)===0&&(v=b&-b,l=r&-r,v>=l||v===32&&(l&4194048)!==0)?r:b}function me(n,r){return(n.pendingLanes&~(n.suspendedLanes&~n.pingedLanes)&r)===0}function Ee(n,r){switch(n){case 1:case 2:case 4:case 8:case 64:return r+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return r+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Pe(){var n=Cn;return Cn<<=1,(Cn&62914560)===0&&(Cn=4194304),n}function St(n){for(var r=[],l=0;31>l;l++)r.push(n);return r}function gt(n,r){n.pendingLanes|=r,r!==268435456&&(n.suspendedLanes=0,n.pingedLanes=0,n.warmLanes=0)}function Me(n,r,l,u,b,v){var T=n.pendingLanes;n.pendingLanes=l,n.suspendedLanes=0,n.pingedLanes=0,n.warmLanes=0,n.expiredLanes&=l,n.entangledLanes&=l,n.errorRecoveryDisabledLanes&=l,n.shellSuspendCounter=0;var P=n.entanglements,ne=n.expirationTimes,le=n.hiddenUpdates;for(l=T&~l;0<l;){var he=31-ut(l),ge=1<<he;P[he]=0,ne[he]=-1;var ce=le[he];if(ce!==null)for(le[he]=null,he=0;he<ce.length;he++){var de=ce[he];de!==null&&(de.lane&=-536870913)}l&=~ge}u!==0&&Se(n,u,0),v!==0&&b===0&&n.tag!==0&&(n.suspendedLanes|=v&~(T&~r))}function Se(n,r,l){n.pendingLanes|=r,n.suspendedLanes&=~r;var u=31-ut(r);n.entangledLanes|=r,n.entanglements[u]=n.entanglements[u]|1073741824|l&261930}function Ue(n,r){var l=n.entangledLanes|=r;for(n=n.entanglements;l;){var u=31-ut(l),b=1<<u;b&r|n[u]&r&&(n[u]|=r),l&=~b}}function Bt(n,r){var l=r&-r;return l=(l&42)!==0?1:Mt(l),(l&(n.suspendedLanes|r))!==0?0:l}function Mt(n){switch(n){case 2:n=1;break;case 8:n=4;break;case 32:n=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:n=128;break;case 268435456:n=134217728;break;default:n=0}return n}function bi(n){return n&=-n,2<n?8<n?(n&134217727)!==0?32:268435456:8:2}function Er(){var n=H.p;return n!==0?n:(n=window.event,n===void 0?32:j0(n.type))}function Nr(n,r){var l=H.p;try{return H.p=n,r()}finally{H.p=l}}var si=Math.random().toString(36).slice(2),Ut="__reactFiber$"+si,mn="__reactProps$"+si,xi="__reactContainer$"+si,Sr="__reactEvents$"+si,fa="__reactListeners$"+si,kr="__reactHandles$"+si,Qa="__reactResources$"+si,Qi="__reactMarker$"+si;function ha(n){delete n[Ut],delete n[mn],delete n[Sr],delete n[fa],delete n[kr]}function bn(n){var r=n[Ut];if(r)return r;for(var l=n.parentNode;l;){if(r=l[xi]||l[Ut]){if(l=r.alternate,r.child!==null||l!==null&&l.child!==null)for(n=x0(n);n!==null;){if(l=n[Ut])return l;n=x0(n)}return r}n=l,l=n.parentNode}return null}function yi(n){if(n=n[Ut]||n[xi]){var r=n.tag;if(r===5||r===6||r===13||r===31||r===26||r===27||r===3)return n}return null}function vi(n){var r=n.tag;if(r===5||r===26||r===27||r===6)return n.stateNode;throw Error(a(33))}function zi(n){var r=n[Qa];return r||(r=n[Qa]={hoistableStyles:new Map,hoistableScripts:new Map}),r}function Ft(n){n[Qi]=!0}var Wa=new Set,Wi={};function _i(n,r){ye(n,r),ye(n+"Capture",r)}function ye(n,r){for(Wi[n]=r,n=0;n<r.length;n++)Wa.add(r[n])}var Le=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u2
Line truncated
|
||||
`).replace(Rk,"")}function o0(n,r){return r=l0(r),l0(n)===r}function Et(n,r,l,u,b,v){switch(l){case"children":typeof u=="string"?r==="body"||r==="textarea"&&u===""||qe(n,u):(typeof u=="number"||typeof u=="bigint")&&r!=="body"&&qe(n,""+u);break;case"className":Mn(n,"class",u);break;case"tabIndex":Mn(n,"tabindex",u);break;case"dir":case"role":case"viewBox":case"width":case"height":Mn(n,l,u);break;case"style":bt(n,u,v);break;case"data":if(r!=="object"){Mn(n,"data",u);break}case"src":case"href":if(u===""&&(r!=="a"||l!=="href")){n.removeAttribute(l);break}if(u==null||typeof u=="function"||typeof u=="symbol"||typeof u=="boolean"){n.removeAttribute(l);break}u=Or(""+u),n.setAttribute(l,u);break;case"action":case"formAction":if(typeof u=="function"){n.setAttribute(l,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}else typeof v=="function"&&(l==="formAction"?(r!=="input"&&Et(n,r,"name",b.name,b,null),Et(n,r,"formEncType",b.formEncType,b,null),Et(n,r,"formMethod",b.formMethod,b,null),Et(n,r,"formTarget",b.formTarget,b,null)):(Et(n,r,"encType",b.encType,b,null),Et(n,r,"method",b.method,b,null),Et(n,r,"target",b.target,b,null)));if(u==null||typeof u=="symbol"||typeof u=="boolean"){n.removeAttribute(l);break}u=Or(""+u),n.setAttribute(l,u);break;case"onClick":u!=null&&(n.onclick=_e);break;case"onScroll":u!=null&&et("scroll",n);break;case"onScrollEnd":u!=null&&et("scrollend",n);break;case"dangerouslySetInnerHTML":if(u!=null){if(typeof u!="object"||!("__html"in u))throw Error(a(61));if(l=u.__html,l!=null){if(b.children!=null)throw Error(a(60));n.innerHTML=l}}break;case"multiple":n.multiple=u&&typeof u!="function"&&typeof u!="symbol";break;case"muted":n.muted=u&&typeof u!="function"&&typeof u!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(u==null||typeof u=="function"||typeof u=="boolean"||typeof u=="symbol"){n.removeAttribute("xlink:href");break}l=Or(""+u),n.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",l);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":u!=null&&typeof u!="function"&&typeof u!="symbol"?n.setAttribute(l,""+u):n.removeAttribute(l);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":u&&typeof u!="function"&&typeof u!="symbol"?n.setAttribute(l,""):n.removeAttribute(l);break;case"capture":case"download":u===!0?n.setAttribute(l,""):u!==!1&&u!=null&&typeof u!="function"&&typeof u!="symbol"?n.setAttribute(l,u):n.removeAttribute(l);break;case"cols":case"rows":case"size":case"span":u!=null&&typeof u!="function"&&typeof u!="symbol"&&!isNaN(u)&&1<=u?n.setAttribute(l,u):n.removeAttribute(l);break;case"rowSpan":case"start":u==null||typeof u=="function"||typeof u=="symbol"||isNaN(u)?n.removeAttribute(l):n.setAttribute(l,u);break;case"popover":et("beforetoggle",n),et("toggle",n),pn(n,"popover",u);break;case"xlinkActuate":Nn(n,"http://www.w3.org/1999/xlink","xlink:actuate",u);break;case"xlinkArcrole":Nn(n,"http://www.w3.org/1999/xlink","xlink:arcrole",u);break;case"xlinkRole":Nn(n,"http://www.w3.org/1999/xlink","xlink:role",u);break;case"xlinkShow":Nn(n,"http://www.w3.org/1999/xlink","xlink:show",u);break;case"xlinkTitle":Nn(n,"http://www.w3.org/1999/xlink","xlink:title",u);break;case"xlinkType":Nn(n,"http://www.w3.org/1999/xlink","xlink:type",u);break;case"xmlBase":Nn(n,"http://www.w3.org/XML/1998/namespace","xml:base",u);break;case"xmlLang":Nn(n,"http://www.w3.org/XML/1998/namespace","xml:lang",u);break;case"xmlSpace":Nn(n,"http://www.w3.org/XML/1998/namespace","xml:space",u);break;case"is":pn(n,"is",u);break;case"innerText":case"textContent":break;default:(!(2<l.length)||l[0]!=="o"&&l[0]!=="O"||l[1]!=="n"&&l[1]!=="N")&&(l=Ji.get(l)||l,pn(n,l,u))}}function Pf(n,r,l,u,b,v){switch(l){case"style":bt(n,u,v);break;case"dangerouslySetInnerHTML":if(u!=null){if(typeof u!="object"||!("__html"in u))throw Error(a(61));if(l=u.__html,l!=null){if(b.children!=null)throw Error(a(60));n.innerHTML=l}}break;case"children":typeof u=="string"?qe(n,u):(typeof u=="number"||typeof u=="bigint")&&qe(n,""+u);break;case"onScroll":u!=null&&et("scroll",n);break;case"onScrollEnd":u!=null&&et("scrollend",n);break;case"onClick":u!=null&&(n.onclick=_e);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"inne
Line truncated
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const y_=(...e)=>e.filter((t,i,a)=>!!t&&t.trim()!==""&&a.indexOf(t)===i).join(" ").trim();/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const v2=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase();/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const _2=e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(t,i,a)=>a?a.toUpperCase():i.toLowerCase());/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Q0=e=>{const t=_2(e);return t.charAt(0).toUpperCase()+t.slice(1)};/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/var w2={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const E2=e=>{for(const t in e)if(t.startsWith("aria-")||t==="role"||t==="title")return!0;return!1};/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const N2=ee.forwardRef(({color:e="currentColor",size:t=24,strokeWidth:i=2,absoluteStrokeWidth:a,className:s="",children:o,iconNode:c,...d},f)=>ee.createElement("svg",{ref:f,...w2,width:t,height:t,stroke:e,strokeWidth:a?Number(i)*24/Number(t):i,className:y_("lucide",s),...!o&&!E2(d)&&{"aria-hidden":"true"},...d},[...c.map(([h,m])=>ee.createElement(h,m)),...Array.isArray(o)?o:[o]]));/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const ke=(e,t)=>{const i=ee.forwardRef(({className:a,...s},o)=>ee.createElement(N2,{ref:o,iconNode:t,className:y_(`lucide-${v2(Q0(e))}`,`lucide-${e}`,a),...s}));return i.displayName=Q0(e),i};/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const S2=[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]],v_=ke("arrow-left",S2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const k2=[["path",{d:"M7 7h10v10",key:"1tivn9"}],["path",{d:"M7 17 17 7",key:"1vkiza"}]],Ps=ke("arrow-up-right",k2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const T2=[["path",{d:"m5 12 7-7 7 7",key:"hav0vg"}],["path",{d:"M12 19V5",key:"x0mq9r"}]],A2=ke("arrow-up",T2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const C2=[["path",{d:"M4.929 4.929 19.07 19.071",key:"196cmz"}],["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],__=ke("ban",C2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const M2=[["path",{d:"M10.268 21a2 2 0 0 0 3.464 0",key:"vwvbt9"}],["path",{d:"M17 17H4a1 1 0 0 1-.74-1.673C4.59 13.956 6 12.499 6 8a6 6 0 0 1 .258-1.742",key:"178tsu"}],["path",{d:"m2 2 20 20",key:"1ooewy"}],["path",{d:"M8.668 3.01A6 6 0 0 1 18 8c0 2.687.77 4.653 1.707 6.05",key:"1hqiys"}]],O2=ke("bell-off",M2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const R2=[["path",{d:"M12 8V4H8",key:"hb8ula"}],["rect",{width:"16",height:"12",x:"4",y:"8",rx:"2",key:"enze0r"}],["path",{d:"M2 14h2",key:"vft8re"}],["path",{d:"M20 14h2",key:"4cs60a"}],["path",{d:"M15 13v2",key:"1xurst"}],["path",{d:"M9 13v2",key:"rq6x2g"}]],fp=ke("bot",R2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const D2=[["path",{d:"M12 18V5",key:"adv99a"}],["path",{d:"M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4",key:"1e3is1"}],["path",{d:"M17.598 6.5A3 3 0 1 0 12 5a3 3 0 1 0-5.598 1.5",key:"1gqd8o"}],["path",{d:"M17.997 5.125a4 4 0 0 1 2.526 5.77",key:"iwvgf7"}],["path",{d:"M18 18a4 4 0 0 0 2-7.464",key:"efp6ie"}],["path",{d:"M19.967 17.483A4 4 0 1 1 12 18a4 4 0 1 1-7.967-.517",key:"1gq6am"}],["path",{d:"M6 18a4 4 0 0 1-2-7.464",key:"k1g0md"}],["path",{d:"M6.003 5.125a4 4 0 0 0-2.526 5.77",key:"q97ue3"}]],w_=ke("brain",D2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const j2=[["path",{d:"M12 20v-9",key:"1qisl0"}],["path",{d:"M14 7a4 4 0 0 1 4 4v3a6 6 0 0 1-12 0v-3a4 4 0 0 1 4-4z",key:"uouzyp"}],["path",{d:"M14.12 3.88 16 2",key:"qol33r"}],["path",{d:"M21 21a4 4 0 0 0-3.81-4",key:"1b0z45"}],["path",{d:"M21 5a4 4 0 0 1-3.55 3.97",key:"5cxbf6"}],["path",{d:"M22 13h-4",key:"1jl80f"}],["path",{d:"M3 21a4 4 0 0 1 3.81-4",key:"1fjd4g"}],["path",{d:"M3 5a4 4 0 0 0 3.55 3.97",key:"1d7oge"}],["path",{d:"M6 13H2",key:"82j7cp"}],["path",{d:"m8 2 1.88 1.88",key:"fmnt4t"}],["path",{d:"M9 7.13V6a3 3 0 1 1 6 0v1.13",key:"1vgav8"}]],L2=ke("bug",j2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const z2=[["path",{d:"M16 14v2.2l1.6 1",key:"fo4ql5"}],["path",{d:"M16 2v4",key:"4m81vk"}],["path",{d:"M21 7.5V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h3.5",key:"1osxxc"}],["path",{d:"M3 10h5",key:"r794hk"}],["path",{d:"M8 2v4",key:"1cmpym"}],["circle",{cx:"16",cy:"16",r:"6",key:"qoo3c4"}]],E_=ke("calendar-clock",z2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const I2=[["path",{d:"M18 6 7 17l-5-5",key:"116fxf"}],["path",{d:"m22 10-7.5 7.5L13 16",key:"ke71qq"}]],B2=ke("check-check",I2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const U2=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],Fs=ke("check",U2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const H2=[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]],fo=ke("chevron-down",H2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const $2=[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]],q2=ke("chevron-right",$2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const P2=[["path",{d:"m18 15-6-6-6 6",key:"153udz"}]],N_=ke("chevron-up",P2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const F2=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12",key:"1pkeuh"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16",key:"4dfq90"}]],hp=ke("circle-alert",F2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const G2=[["path",{d:"M21.801 10A10 10 0 1 1 17 3.335",key:"yps3ct"}],["path",{d:"m9 11 3 3L22 4",key:"1pflzl"}]],S_=ke("circle-check-big",G2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const V2=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],Y2=ke("circle-check",V2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const X2=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["circle",{cx:"12",cy:"12",r:"1",key:"41hilf"}]],K2=ke("circle-dot",X2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Z2=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],Q2=ke("circle",Z2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const W2=[["path",{d:"M12 6v6l4 2",key:"mmk7yg"}],["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],k_=ke("clock",W2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const J2=[["path",{d:"m16 18 6-6-6-6",key:"eg8j8"}],["path",{d:"m8 6-6 6 6 6",key:"ppft3o"}]],eT=ke("code",J2);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const tT=[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]],ho=ke("copy",tT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const nT=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"22",x2:"18",y1:"12",y2:"12",key:"l9bcsi"}],["line",{x1:"6",x2:"2",y1:"12",y2:"12",key:"13hhkx"}],["line",{x1:"12",x2:"12",y1:"6",y2:"2",key:"10w3f3"}],["line",{x1:"12",x2:"12",y1:"22",y2:"18",key:"15g9kq"}]],iT=ke("crosshair",nT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const rT=[["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3",key:"msslwz"}],["path",{d:"M3 5V19A9 3 0 0 0 21 19V5",key:"1wlel7"}],["path",{d:"M3 12A9 3 0 0 0 21 12",key:"mv7ke4"}]],aT=ke("database",rT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const sT=[["path",{d:"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0",key:"1nclc0"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]],lT=ke("eye",sT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const oT=[["path",{d:"M10.5 22H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v6",key:"g5mvt7"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}],["path",{d:"m14 20 2 2 4-4",key:"15kota"}]],cT=ke("file-check-corner",oT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const uT=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",key:"1oefj6"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}],["path",{d:"M10 9H8",key:"b1mrlr"}],["path",{d:"M16 13H8",key:"t4e002"}],["path",{d:"M16 17H8",key:"z1uh3a"}]],T_=ke("file-text",uT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const dT=[["path",{d:"M4 22V4a1 1 0 0 1 .4-.8A6 6 0 0 1 8 2c3 0 5 2 7.333 2q2 0 3.067-.8A1 1 0 0 1 20 4v10a1 1 0 0 1-.4.8A6 6 0 0 1 16 16c-3 0-5-2-8-2a6 6 0 0 0-4 1.528",key:"1jaruq"}]],A_=ke("flag",dT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const fT=[["circle",{cx:"18",cy:"18",r:"3",key:"1xkwt0"}],["circle",{cx:"6",cy:"6",r:"3",key:"1lh9wr"}],["path",{d:"M6 21V9a9 9 0 0 0 9 9",key:"7kw0sc"}]],hT=ke("git-merge",fT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const mT=[["circle",{cx:"18",cy:"18",r:"3",key:"1xkwt0"}],["circle",{cx:"6",cy:"6",r:"3",key:"1lh9wr"}],["path",{d:"M13 6h3a2 2 0 0 1 2 2v7",key:"1yeb86"}],["line",{x1:"6",x2:"6",y1:"9",y2:"21",key:"rroup"}]],mp=ke("git-pull-request",mT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const pT=[["path",{d:"M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4",key:"tonef"}],["path",{d:"M9 18c-4.51 2-5-2-7-2",key:"9comsn"}]],gT=ke("github",pT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const bT=[["path",{d:"m22 13.29-3.33-10a.42.42 0 0 0-.14-.18.38.38 0 0 0-.22-.11.39.39 0 0 0-.23.07.42.42 0 0 0-.14.18l-2.26 6.67H8.32L6.1 3.26a.42.42 0 0 0-.1-.18.38.38 0 0 0-.26-.08.39.39 0 0 0-.23.07.42.42 0 0 0-.14.18L2 13.29a.74.74 0 0 0 .27.83L12 21l9.69-6.88a.71.71 0 0 0 .31-.83Z",key:"148pdi"}]],xT=ke("gitlab",bT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const yT=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20",key:"13o1zl"}],["path",{d:"M2 12h20",key:"9i4pu4"}]],pp=ke("globe",yT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const vT=[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}],["path",{d:"M12 7v5l4 2",key:"1fdv2h"}]],Gs=ke("history",vT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const _T=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2",key:"1m3agn"}],["circle",{cx:"9",cy:"9",r:"2",key:"af1f0g"}],["path",{d:"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21",key:"1xmnt7"}]],wT=ke("image",_T);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const ET=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 16v-4",key:"1dtifu"}],["path",{d:"M12 8h.01",key:"e9boi3"}]],NT=ke("info",ET);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const ST=[["path",{d:"M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z",key:"zw3jo"}],["path",{d:"M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12",key:"1wduqc"}],["path",{d:"M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17",key:"kqbvx6"}]],kT=ke("layers",ST);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const TT=[["rect",{width:"7",height:"9",x:"3",y:"3",rx:"1",key:"10lvy0"}],["rect",{width:"7",height:"5",x:"14",y:"3",rx:"1",key:"16une8"}],["rect",{width:"7",height:"9",x:"14",y:"12",rx:"1",key:"1hutg5"}],["rect",{width:"7",height:"5",x:"3",y:"16",rx:"1",key:"ldoo1y"}]],AT=ke("layout-dashboard",TT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const CT=[["path",{d:"M13 5h8",key:"a7qcls"}],["path",{d:"M13 12h8",key:"h98zly"}],["path",{d:"M13 19h8",key:"c3s6r1"}],["path",{d:"m3 17 2 2 4-4",key:"1jhpwq"}],["rect",{x:"3",y:"4",width:"6",height:"6",rx:"1",key:"cif1o7"}]],MT=ke("list-todo",CT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const OT=[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]],Hs=ke("loader-circle",OT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const RT=[["rect",{width:"18",height:"11",x:"3",y:"11",rx:"2",ry:"2",key:"1w4ew1"}],["path",{d:"M7 11V7a5 5 0 0 1 10 0v4",key:"fwvmzm"}]],DT=ke("lock",RT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const jT=[["path",{d:"m16 17 5-5-5-5",key:"1bji2h"}],["path",{d:"M21 12H9",key:"dn1m92"}],["path",{d:"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4",key:"1uf3rs"}]],LT=ke("log-out",jT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const zT=[["path",{d:"m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7",key:"132q7q"}],["rect",{x:"2",y:"4",width:"20",height:"16",rx:"2",key:"izxlao"}]],gp=ke("mail",zT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const IT=[["path",{d:"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719",key:"1sd12s"}]],W0=ke("message-circle",IT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const BT=[["path",{d:"M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z",key:"18887p"}]],UT=ke("message-square",BT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const HT=[["rect",{x:"16",y:"16",width:"6",height:"6",rx:"1",key:"4q2zg0"}],["rect",{x:"2",y:"16",width:"6",height:"6",rx:"1",key:"8cvhb9"}],["rect",{x:"9",y:"2",width:"6",height:"6",rx:"1",key:"1egb70"}],["path",{d:"M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3",key:"1jsf9p"}],["path",{d:"M12 12V8",key:"2874zd"}]],$T=ke("network",HT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const qT=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M9 3v18",key:"fh3hqa"}],["path",{d:"m16 15-3-3 3-3",key:"14y99z"}]],PT=ke("panel-left-close",qT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const FT=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M9 3v18",key:"fh3hqa"}]],GT=ke("panel-left",FT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const VT=[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",key:"1a8usu"}],["path",{d:"m15 5 4 4",key:"1mk7zo"}]],YT=ke("pencil",VT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const XT=[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]],KT=ke("plus",XT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const ZT=[["path",{d:"M15.39 4.39a1 1 0 0 0 1.68-.474 2.5 2.5 0 1 1 3.014 3.015 1 1 0 0 0-.474 1.68l1.683 1.682a2.414 2.414 0 0 1 0 3.414L19.61 15.39a1 1 0 0 1-1.68-.474 2.5 2.5 0 1 0-3.014 3.015 1 1 0 0 1 .474 1.68l-1.683 1.682a2.414 2.414 0 0 1-3.414 0L8.61 19.61a1 1 0 0 0-1.68.474 2.5 2.5 0 1 1-3.014-3.015 1 1 0 0 0 .474-1.68l-1.683-1.682a2.414 2.414 0 0 1 0-3.414L4.39 8.61a1 1 0 0 1 1.68.474 2.5 2.5 0 1 0 3.014-3.015 1 1 0 0 1-.474-1.68l1.683-1.682a2.414 2.414 0 0 1 3.414 0z",key:"w46dr5"}]],C_=ke("puzzle",ZT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const QT=[["path",{d:"M19.07 4.93A10 10 0 0 0 6.99 3.34",key:"z3du51"}],["path",{d:"M4 6h.01",key:"oypzma"}],["path",{d:"M2.29 9.62A10 10 0 1 0 21.31 8.35",key:"qzzz0"}],["path",{d:"M16.24 7.76A6 6 0 1 0 8.23 16.67",key:"1yjesh"}],["path",{d:"M12 18h.01",key:"mhygvu"}],["path",{d:"M17.99 11.66A6 6 0 0 1 15.77 16.67",key:"1u2y91"}],["circle",{cx:"12",cy:"12",r:"2",key:"1c9p78"}],["path",{d:"m13.41 10.59 5.66-5.66",key:"mhq4k0"}]],M_=ke("radar",QT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const WT=[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]],JT=ke("refresh-cw",WT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const eA=[["path",{d:"M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z",key:"m3kijz"}],["path",{d:"m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z",key:"1fmvmk"}],["path",{d:"M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0",key:"1f8sc4"}],["path",{d:"M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5",key:"qeys4"}]],tA=ke("rocket",eA);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const nA=[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}]],iA=ke("rotate-ccw",nA);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const rA=[["path",{d:"m21 21-4.34-4.34",key:"14j7rj"}],["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}]],O_=ke("search",rA);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const aA=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"M12 8v4",key:"1got3b"}],["path",{d:"M12 16h.01",key:"1drbdi"}]],sA=ke("shield-alert",aA);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const lA=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],vu=ke("shield-check",lA);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const oA=[["path",{d:"M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z",key:"1s2grr"}],["path",{d:"M20 2v4",key:"1rf3ol"}],["path",{d:"M22 4h-4",key:"gwowj6"}],["circle",{cx:"4",cy:"20",r:"2",key:"6kqj1y"}]],J0=ke("sparkles",oA);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const cA=[["path",{d:"M21 9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z",key:"1dfntj"}],["path",{d:"M15 3v5a1 1 0 0 0 1 1h5",key:"6s6qgf"}]],uA=ke("sticky-note",cA);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const dA=[["path",{d:"M12 19h8",key:"baeox8"}],["path",{d:"m4 17 6-6-6-6",key:"1yngyt"}]],R_=ke("terminal",dA);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const fA=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],hA=ke("trash-2",fA);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const mA=[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]],pA=ke("triangle-alert",mA);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const gA=[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["path",{d:"M16 3.128a4 4 0 0 1 0 7.744",key:"16gr8j"}],["path",{d:"M22 21v-2a4 4 0 0 0-3-3.87",key:"kshegd"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}]],D_=ke("users",gA);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const bA=[["path",{d:"m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72",key:"ul74o6"}],["path",{d:"m14 7 3 3",key:"1r5n42"}],["path",{d:"M5 6v4",key:"ilb8ba"}],["path",{d:"M19 14v4",key:"blhpug"}],["path",{d:"M10 2v2",key:"7u0qdc"}],["path",{d:"M7 8H3",key:"zfb6yr"}],["path",{d:"M21 16h-4",key:"1cnmox"}],["path",{d:"M11 3H9",key:"1obp7u"}]],xA=ke("wand-sparkles",bA);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const yA=[["path",{d:"m10.586 5.414-5.172 5.172",key:"4mc350"}],["path",{d:"m18.586 13.414-5.172 5.172",key:"8c96vv"}],["path",{d:"M6 12h12",key:"8npq4p"}],["circle",{cx:"12",cy:"20",r:"2",key:"144qzu"}],["circle",{cx:"12",cy:"4",r:"2",key:"muu5ef"}],["circle",{cx:"20",cy:"12",r:"2",key:"1xzzfp"}],["circle",{cx:"4",cy:"12",r:"2",key:"1hvhnz"}]],j_=ke("waypoints",yA);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const vA=[["path",{d:"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z",key:"1ngwbx"}]],_u=ke("wrench",vA);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const _A=[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]],L_=ke("x",_A),wA={open:{label:"Open",color:"bg-red-500/10 text-red-400 border-red-500/20",dotColor:"bg-red-500",description:"Newly discovered, awaiting triage"},in_progress:{label:"In Progress",color:"bg-blue-500/10 text-blue-400 border-blue-500/20",dotColor:"bg-blue-500",description:"Someone is working on this"},snoozed:{label:"Snoozed",color:"bg-purple-500/10 text-purple-400 border-purple-500/20",dotColor:"bg-purple-500",description:"Temporarily hidden until a follow-up date"},fixed:{label:"Fixed",color:"bg-emerald-500/10 text-emerald-400 border-emerald-500/20",dotColor:"bg-emerald-500",description:"This vulnerability has been fixed"},ignored:{label:"Ignored",color:"bg-gray-500/10 text-gray-400 border-gray-500/20",dotColor:"bg-gray-500",description:"Acknowledged but accepted"}},EA={trivial:{label:"Trivial",color:"bg-emerald-500/10 text-emerald-400 border-emerald-500/20"},low:{label:"Low",color:"bg-blue-500/10 text-blue-400 border-blue-500/20"},medium:{label:"Medium",color:"bg-yellow-500/10 text-yellow-400 border-yellow-500/20"},high:{label:"High",color:"bg-orange-500/10 text-orange-400 border-orange-500/20"}},z_={critical:"bg-red-500/20 text-red-500 border-red-500/30",high:"bg-orange-500/20 text-orange-500 border-orange-500/30",medium:"bg-yellow-500/20 text-yellow-500 border-yellow-500/30",low:"bg-blue-500/20 text-blue-500 border-blue-500/30"};function Zc(e){return e.original_severity!=null&&e.original_severity!==e.severity}const NA={js:"javascript",ts:"typescript",tsx:"typescript",jsx:"javascript",py:"python",rb:"ruby",go:"go",rs:"rust",java:"java",php:"php",cs:"csharp",cpp:"cpp",c:"c",sh:"bash",bash:"bash",sql:"sql",html:"html",css:"css",json:"json",yaml:"yaml",yml:"yaml",xml:"xml"};function SA(e){var i;if(!e)return null;const t=(i=e.split(".").pop())==null?void 0:i.toLowerCase();return t&&NA[t]||null}function bp(e){switch(e){case"critical":return"bg-red-500";case"high":return"bg-orange-500";case"medium":return"bg-yellow-500";default:return"bg-blue-500"}}async function xp(e){try{await navigator.clipboard.writeText(e)}catch{const t=document.createElement("textarea");t.value=e,t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}}const Ao="https://app.strix.ai/api/auth/signup",kA="https://strix.ai/pricing",TA="ref=oss_viewer&utm_source=oss_viewer&utm_medium=local_viewer&utm_campaign=oss_viewer";function ca(e,t){const i=e.includes("?")?"&":"?";return`${e}${i}${TA}&utm_content=${encodeURIComponent(t)}`}function Ti(e,t={}){try{const i={event:e};for(const[s,o]of Object.entries(t))o!==void 0&&(i[s]=o);const a=JSON.stringify(i);typeof navigator<"u"&&navigator.sendBeacon?navigator.sendBeacon("/api/event",a):fetch("/api/event",{method:"POST",body:a,keepalive:!0})}catch{}}function Ci(e,t){Ti("cta_clicked",{cta:e,surface:t})}function Hu({label:e="Pro",className:t=""}){return g.jsx("span",{className:`inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-[#aaa] ${t}`,style:{border:"1px solid #2a2a2a",background:"rgba(255,255,255,0.04)"},children:e})}function AA({text:e,children:t,className:i=""}){const[a,s]=ee.useState(!1);return g.jsxs("span",{className:`relative inline-flex ${i}`,onMouseEnter:()=>s(!0),onMouseLeave:()=>s(!1),onFocus:()=>s(!0),onBlur:()=>s(!1),children:[t,a&&g.jsx("span",{role:"tooltip",className:"pointer-events-none absolute bottom-full left-1/2 z-50 mb-2 w-max max-w-[240px] -translate-x-1/2 rounded-md px-2.5 py-1.5 text-xs text-[#ddd] shadow-lg",style:{border:"1px solid #2a2a2a",background:"#0a0a0a"},children:e})]})}function ey({item:e,surface:t}){const i=e.icon;return g.jsxs("a",{href:ca(Ao,e.slug),target:"_blank",rel:"noopener noreferrer",onClick:()=>Ci(e.slug,t),title:e.desc,className:"group block cursor-pointer rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-4 text-left transition-colors hover:border-[#444]",children:[g.jsxs("div",{className:"mb-2 flex items-center justify-between",children:[g.jsx(i,{className:"h-4 w-4 text-[#888] transition-colors group-hover:text-white","aria-hidden":"true"}),g.jsxs("div",{className:"flex items-center gap-1.5",children:[g.jsx(Hu,{}),g.jsx(Ps,{className:"h-3.5 w-3.5 text-[#555] transition-colors group-hover:text-[#aaa]","aria-hidden":"true"})]})]}),g.jsx("p",{className:"text-sm font-medium text-white",children:e.title}),g.jsx("p",{className:"mt-0.5 text-xs text-[#666]",children:e.desc})]})}function CA({feature:e,active:t,onClick:i,collapsed:a=!1}){const s=e.icon;return a?g.jsx("button",{onClick:i,title:`${e.title} (${e.tier})`,className:`group flex w-full cursor-pointer items-center justify-center rounded-md px-2.5 py-2 transition-colors ${t?"text-white":"text-[#888] hover:bg-[rgba(255,255,255,0.06)] hover:text-white"}`,style:t?{background:"rgba(255,255,255,0.12)"}:void 0,children:g.jsx(s,{className:"h-4 w-4Line truncated
|
||||
`,h="/",m="*",p="",y="comment",x="declaration";function _(S,w){if(typeof S!="string")throw new TypeError("First argument must be a string");if(!S)return[];w=w||{};var k=1,E=1;function M(A){var q=A.match(t);q&&(k+=q.length);var O=A.lastIndexOf(f);E=~O?A.length-O:E+A.length}function U(){var A={line:k,column:E};return function(q){return q.position=new R(A),Z(),q}}function R(A){this.start=A,this.end={line:k,column:E},this.source=w.source}R.prototype.content=S;function B(A){var q=new Error(w.source+":"+k+":"+E+": "+A);if(q.reason=A,q.filename=w.source,q.line=k,q.column=E,q.source=S,!w.silent)throw q}function I(A){var q=A.exec(S);if(q){var O=q[0];return M(O),S=S.slice(O.length),q}}function Z(){I(i)}function j(A){var q;for(A=A||[];q=z();)q!==!1&&A.push(q);return A}function z(){var A=U();if(!(h!=S.charAt(0)||m!=S.charAt(1))){for(var q=2;p!=S.charAt(q)&&(m!=S.charAt(q)||h!=S.charAt(q+1));)++q;if(q+=2,p===S.charAt(q-1))return B("End of comment missing");var O=S.slice(2,q-2);return E+=2,M(O),S=S.slice(q),E+=2,A({type:y,comment:O})}}function V(){var A=U(),q=I(a);if(q){if(z(),!I(s))return B("property missing ':'");var O=I(o),H=A({type:x,property:N(q[0].replace(e,p)),value:O?N(O[0].replace(e,p)):p});return I(c),H}}function G(){var A=[];j(A);for(var q;q=V();)q!==!1&&(A.push(q),j(A));return A}return Z(),G()}function N(S){return S?S.replace(d,p):p}return ph=_,ph}var fy;function qC(){if(fy)return Rs;fy=1;var e=Rs&&Rs.__importDefault||function(a){return a&&a.__esModule?a:{default:a}};Object.defineProperty(Rs,"__esModule",{value:!0}),Rs.default=i;const t=e($C());function i(a,s){let o=null;if(!a||typeof a!="string")return o;const c=(0,t.default)(a),d=typeof s=="function";return c.forEach(f=>{if(f.type!=="declaration")return;const{property:h,value:m}=f;d?s(h,m,f):m&&(o=o||{},o[h]=m)}),o}return Rs}var Xl={},hy;function PC(){if(hy)return Xl;hy=1,Object.defineProperty(Xl,"__esModule",{value:!0}),Xl.camelCase=void 0;var e=/^--[a-zA-Z0-9_-]+$/,t=/-([a-z])/g,i=/^[^-]+$/,a=/^-(webkit|moz|ms|o|khtml)-/,s=/^-(ms)-/,o=function(h){return!h||i.test(h)||e.test(h)},c=function(h,m){return m.toUpperCase()},d=function(h,m){return"".concat(m,"-")},f=function(h,m){return m===void 0&&(m={}),o(h)?h:(h=h.toLowerCase(),m.reactCompat?h=h.replace(s,d):h=h.replace(a,d),h.replace(t,c))};return Xl.camelCase=f,Xl}var Kl,my;function FC(){if(my)return Kl;my=1;var e=Kl&&Kl.__importDefault||function(s){return s&&s.__esModule?s:{default:s}},t=e(qC()),i=PC();function a(s,o){var c={};return!s||typeof s!="string"||(0,t.default)(s,function(d,f){d&&f&&(c[(0,i.camelCase)(d,o)]=f)}),c}return a.default=a,Kl=a,Kl}var GC=FC();const VC=ko(GC),aw=sw("end"),Ep=sw("start");function sw(e){return t;function t(i){const a=i&&i.position&&i.position[e]||{};if(typeof a.line=="number"&&a.line>0&&typeof a.column=="number"&&a.column>0)return{line:a.line,column:a.column,offset:typeof a.offset=="number"&&a.offset>-1?a.offset:void 0}}}function YC(e){const t=Ep(e),i=aw(e);if(t&&i)return{start:t,end:i}}function so(e){return!e||typeof e!="object"?"":"position"in e||"type"in e?py(e.position):"start"in e||"end"in e?py(e):"line"in e||"column"in e?Um(e):""}function Um(e){return gy(e&&e.line)+":"+gy(e&&e.column)}function py(e){return Um(e&&e.start)+"-"+Um(e&&e.end)}function gy(e){return e&&typeof e=="number"?e:1}class An extends Error{constructor(t,i,a){super(),typeof i=="string"&&(a=i,i=void 0);let s="",o={},c=!1;if(i&&("line"in i&&"column"in i?o={place:i}:"start"in i&&"end"in i?o={place:i}:"type"in i?o={ancestors:[i],place:i.position}:o={...i}),typeof t=="string"?s=t:!o.cause&&t&&(c=!0,s=t.message,o.cause=t),!o.ruleId&&!o.source&&typeof a=="string"){const f=a.indexOf(":");f===-1?o.ruleId=a:(o.source=a.slice(0,f),o.ruleId=a.slice(f+1))}if(!o.place&&o.ancestors&&o.ancestors){const f=o.ancestors[o.ancestors.length-1];f&&(o.place=f.position)}const d=o.place&&"start"in o.place?o.place.start:o.place;this.ancestors=o.ancestors||void 0,this.cause=o.cause||void 0,this.column=d?d.column:void 0,this.fatal=void 0,this.file="",this.message=s,this.line=d?d.line:void 0,this.name=so(o.place)||"1:1",this.place=o.place||void 0,this.reason=this.message,this.ruleId=o.ruleId||void 0,this.source=o.source||void 0,this.stack=c&&o.cause&&typeof o.cause.stack=="string"?o.cause.stack:"",this.actual=void 0,this.expected=void 0,this.note=void 0,this.url=void 0}}An.prototype.file="";An.prototype.name="";An.prototype.reason="";An.prototype.message="";An.prototype.stack="";An.prototype.column=void 0;An.prototype.line=void 0;An.prototype.ancestors=void 0;An.prototype.cause=void 0;An.prototype.fatal=void 0;An.prototype.place=void 0;An.prototype.ruleId=void 0;An.prototype.source=void 0;const Np={}.hasOwnProperty,XC=new Map,KC=/[A-Z]/g,ZC=new Set(["table","tbody","thead","tfoot","tr"]),QC=new Set(["td","th"]),lw="https://github.com/syntax-tree/hast-util-to-jsx-runtime";function WC(e,t){if(!t||t.Fragment===void 0)throw new TypeError("Expected `Fragment` in options");const i=t.filePath||void 0;let a;if(t.development){if(typeof t.j
Line truncated
|
||||
`;break}case-3:{c=`\r
|
||||
`;break}case-2:{c=t?" ":" ";break}case-1:{if(!t&&s)continue;c=" ";break}default:c=String.fromCharCode(o)}s=o===-2,a.push(c)}return a.join("")}function s5(e){const a={constructs:fw([n5,...(e||{}).extensions||[]]),content:s(_M),defined:[],document:s(EM),flow:s($O),lazy:{},string:s(FO),text:s(GO)};return a;function s(o){return c;function c(d){return i5(a,o,d)}}}function l5(e){for(;!bw(e););return e}const ky=/[\0\t\n\r]/g;function o5(){let e=1,t="",i=!0,a;return s;function s(o,c,d){const f=[];let h,m,p,y,x;for(o=t+(typeof o=="string"?o.toString():new TextDecoder(c||void 0).decode(o)),p=0,t="",i&&(o.charCodeAt(0)===65279&&p++,i=void 0);p<o.length;){if(ky.lastIndex=p,h=ky.exec(o),y=h&&h.index!==void 0?h.index:o.length,x=o.charCodeAt(y),!h){t=o.slice(p);break}if(x===10&&p===y&&a)f.push(-3),a=void 0;else switch(a&&(f.push(-5),a=void 0),p<y&&(f.push(o.slice(p,y)),e+=y-p),x){case 0:{f.push(65533),e++;break}case 9:{for(m=Math.ceil(e/4)*4,f.push(-2);e++<m;)f.push(-1);break}case 10:{f.push(-4),e=1;break}default:a=!0,e=1}p=y+1}return d&&(a&&f.push(-5),t&&f.push(t),f.push(null)),f}}const c5=/\\([!-/:-@[-`{-~])|&(#(?:\d{1,7}|x[\da-f]{1,6})|[\da-z]{1,31});/gi;function u5(e){return e.replace(c5,d5)}function d5(e,t,i){if(t)return t;if(i.charCodeAt(0)===35){const s=i.charCodeAt(1),o=s===120||s===88;return hw(i.slice(o?2:1),o?16:10)}return Ap(i)||e}const Ew={}.hasOwnProperty;function f5(e,t,i){return t&&typeof t=="object"&&(i=t,t=void 0),h5(i)(l5(s5(i).document().write(o5()(e,t,!0))))}function h5(e){const t={transforms:[],canContainEols:["emphasis","fragment","heading","paragraph","strong"],enter:{autolink:o(Yt),autolinkProtocol:Z,autolinkEmail:Z,atxHeading:o(je),blockQuote:o(oe),characterEscape:Z,characterReference:Z,codeFenced:o(fe),codeFencedFenceInfo:c,codeFencedFenceMeta:c,codeIndented:o(fe,c),codeText:o(be,c),codeTextData:Z,data:Z,codeFlowValue:Z,definition:o(we),definitionDestinationString:c,definitionLabelString:c,definitionTitleString:c,emphasis:o(Ne),hardBreakEscape:o($e),hardBreakTrailing:o($e),htmlFlow:o(st,c),htmlFlowData:Z,htmlText:o(st,c),htmlTextData:Z,image:o(Rt),label:c,link:o(Yt),listItem:o(Xt),listItemValue:y,listOrdered:o(Pt,p),listUnordered:o(Pt),paragraph:o(Yn),reference:D,referenceString:c,resourceDestinationString:c,resourceTitleString:c,setextHeading:o(je),strong:o(wn),thematicBreak:o(It)},exit:{atxHeading:f(),atxHeadingSequence:U,autolink:f(),autolinkEmail:te,autolinkProtocol:W,blockQuote:f(),characterEscapeValue:j,characterReferenceMarkerHexadecimal:$,characterReferenceMarkerNumeric:$,characterReferenceValue:Q,characterReference:J,codeFenced:f(S),codeFencedFence:N,codeFencedFenceInfo:x,codeFencedFenceMeta:_,codeFlowValue:j,codeIndented:f(w),codeText:f(q),codeTextData:j,data:j,definition:f(),definitionDestinationString:M,definitionLabelString:k,definitionTitleString:E,emphasis:f(),hardBreakEscape:f(V),hardBreakTrailing:f(V),htmlFlow:f(G),htmlFlowData:j,htmlText:f(A),htmlTextData:j,image:f(H),label:X,labelText:K,lineEnding:z,link:f(O),listItem:f(),listOrdered:f(),listUnordered:f(),paragraph:f(),referenceString:F,resourceDestinationString:C,resourceTitleString:L,resource:Y,setextHeading:f(I),setextHeadingLineSequence:B,setextHeadingText:R,strong:f(),thematicBreak:f()}};Nw(t,(e||{}).mdastExtensions||[]);const i={};return a;function a(ue){let xe={type:"root",children:[]};const Oe={stack:[xe],tokenStack:[],config:t,enter:d,exit:h,buffer:c,resume:m,data:i},Fe=[];let Ze=-1;for(;++Ze<ue.length;)if(ue[Ze][1].type==="listOrdered"||ue[Ze][1].type==="listUnordered")if(ue[Ze][0]==="enter")Fe.push(Ze);else{const on=Fe.pop();Ze=s(ue,on,Ze)}for(Ze=-1;++Ze<ue.length;){const on=t[ue[Ze][0]];Ew.call(on,ue[Ze][1].type)&&on[ue[Ze][1].type].call(Object.assign({sliceSerialize:ue[Ze][2].sliceSerialize},Oe),ue[Ze][1])}if(Oe.tokenStack.length>0){const on=Oe.tokenStack[Oe.tokenStack.length-1];(on[1]||Ty).call(Oe,void 0,on[0])}for(xe.position={start:na(ue.length>0?ue[0][1].start:{line:1,column:1,offset:0}),end:na(ue.length>0?ue[ue.length-2][1].end:{line:1,column:1,offset:0})},Ze=-1;++Ze<t.transforms.length;)xe=t.transforms[Ze](xe)||xe;return xe}function s(ue,xe,Oe){let Fe=xe-1,Ze=-1,on=!1,En,Kt,Ct,Wt;for(;++Fe<=Oe;){const ut=ue[Fe];switch(ut[1].type){case"listUnordered":case"listOrdered":case"blockQuote":{ut[0]==="enter"?Ze++:Ze--,Wt=void 0;break}case"lineEndingBlank":{ut[0]==="enter"&&(En&&!Wt&&!Ze&&!Ct&&(Ct=Fe),Wt=void 0);break}case"linePrefix":case"listItemValue":case"listItemMarker":case"listItemPrefix":case"listItemPrefixWhitespace":break;default:Wt=void 0}if(!Ze&&ut[0]==="enter"&&ut[1].type==="listItemPrefix"||Ze===-1&&ut[0]==="exit"&&(ut[1].type==="listUnordered"||ut[1].type==="listOrdered")){if(En){let zn=Fe;for(Kt=void 0;zn--;){const cn=ue[zn];if(cn[1].type==="lineEnding"||cn[1].type==="lineEndingBlank"){if(cn[0]==="exit")continue;Kt&&(ue[Kt][1].type="lineEndingBlank",on=!0),cn[1].type="lineEnding",Kt=zn}else if(!(cn[1].type==="linePrefix"||cn[1].type==="blockQuotePrefix"||cn[1].type==="blockQuotePrefixWhitespace"||cn[Line truncated
|
||||
`}]}function x5(e,t){const i=t.value?t.value+`
|
||||
`:"",a={},s=t.lang?t.lang.split(/\s+/):[];s.length>0&&(a.className=["language-"+s[0]]);let o={type:"element",tagName:"code",properties:a,children:[{type:"text",value:i}]};return t.meta&&(o.data={meta:t.meta}),e.patch(t,o),o=e.applyData(t,o),o={type:"element",tagName:"pre",properties:{},children:[o]},e.patch(t,o),o}function y5(e,t){const i={type:"element",tagName:"del",properties:{},children:e.all(t)};return e.patch(t,i),e.applyData(t,i)}function v5(e,t){const i={type:"element",tagName:"em",properties:{},children:e.all(t)};return e.patch(t,i),e.applyData(t,i)}function _5(e,t){const i=typeof e.options.clobberPrefix=="string"?e.options.clobberPrefix:"user-content-",a=String(t.identifier).toUpperCase(),s=tl(a.toLowerCase()),o=e.footnoteOrder.indexOf(a);let c,d=e.footnoteCounts.get(a);d===void 0?(d=0,e.footnoteOrder.push(a),c=e.footnoteOrder.length):c=o+1,d+=1,e.footnoteCounts.set(a,d);const f={type:"element",tagName:"a",properties:{href:"#"+i+"fn-"+s,id:i+"fnref-"+s+(d>1?"-"+d:""),dataFootnoteRef:!0,ariaDescribedBy:["footnote-label"]},children:[{type:"text",value:String(c)}]};e.patch(t,f);const h={type:"element",tagName:"sup",properties:{},children:[f]};return e.patch(t,h),e.applyData(t,h)}function w5(e,t){const i={type:"element",tagName:"h"+t.depth,properties:{},children:e.all(t)};return e.patch(t,i),e.applyData(t,i)}function E5(e,t){if(e.options.allowDangerousHtml){const i={type:"raw",value:t.value};return e.patch(t,i),e.applyData(t,i)}}function Sw(e,t){const i=t.referenceType;let a="]";if(i==="collapsed"?a+="[]":i==="full"&&(a+="["+(t.label||t.identifier)+"]"),t.type==="imageReference")return[{type:"text",value:"!["+t.alt+a}];const s=e.all(t),o=s[0];o&&o.type==="text"?o.value="["+o.value:s.unshift({type:"text",value:"["});const c=s[s.length-1];return c&&c.type==="text"?c.value+=a:s.push({type:"text",value:a}),s}function N5(e,t){const i=String(t.identifier).toUpperCase(),a=e.definitionById.get(i);if(!a)return Sw(e,t);const s={src:tl(a.url||""),alt:t.alt};a.title!==null&&a.title!==void 0&&(s.title=a.title);const o={type:"element",tagName:"img",properties:s,children:[]};return e.patch(t,o),e.applyData(t,o)}function S5(e,t){const i={src:tl(t.url)};t.alt!==null&&t.alt!==void 0&&(i.alt=t.alt),t.title!==null&&t.title!==void 0&&(i.title=t.title);const a={type:"element",tagName:"img",properties:i,children:[]};return e.patch(t,a),e.applyData(t,a)}function k5(e,t){const i={type:"text",value:t.value.replace(/\r?\n|\r/g," ")};e.patch(t,i);const a={type:"element",tagName:"code",properties:{},children:[i]};return e.patch(t,a),e.applyData(t,a)}function T5(e,t){const i=String(t.identifier).toUpperCase(),a=e.definitionById.get(i);if(!a)return Sw(e,t);const s={href:tl(a.url||"")};a.title!==null&&a.title!==void 0&&(s.title=a.title);const o={type:"element",tagName:"a",properties:s,children:e.all(t)};return e.patch(t,o),e.applyData(t,o)}function A5(e,t){const i={href:tl(t.url)};t.title!==null&&t.title!==void 0&&(i.title=t.title);const a={type:"element",tagName:"a",properties:i,children:e.all(t)};return e.patch(t,a),e.applyData(t,a)}function C5(e,t,i){const a=e.all(t),s=i?M5(i):kw(t),o={},c=[];if(typeof t.checked=="boolean"){const m=a[0];let p;m&&m.type==="element"&&m.tagName==="p"?p=m:(p={type:"element",tagName:"p",properties:{},children:[]},a.unshift(p)),p.children.length>0&&p.children.unshift({type:"text",value:" "}),p.children.unshift({type:"element",tagName:"input",properties:{type:"checkbox",checked:t.checked,disabled:!0},children:[]}),o.className=["task-list-item"]}let d=-1;for(;++d<a.length;){const m=a[d];(s||d!==0||m.type!=="element"||m.tagName!=="p")&&c.push({type:"text",value:`
|
||||
`}),m.type==="element"&&m.tagName==="p"&&!s?c.push(...m.children):c.push(m)}const f=a[a.length-1];f&&(s||f.type!=="element"||f.tagName!=="p")&&c.push({type:"text",value:`
|
||||
`});const h={type:"element",tagName:"li",properties:o,children:c};return e.patch(t,h),e.applyData(t,h)}function M5(e){let t=!1;if(e.type==="list"){t=e.spread||!1;const i=e.children;let a=-1;for(;!t&&++a<i.length;)t=kw(i[a])}return t}function kw(e){const t=e.spread;return t??e.children.length>1}function O5(e,t){const i={},a=e.all(t);let s=-1;for(typeof t.start=="number"&&t.start!==1&&(i.start=t.start);++s<a.length;){const c=a[s];if(c.type==="element"&&c.tagName==="li"&&c.properties&&Array.isArray(c.properties.className)&&c.properties.className.includes("task-list-item")){i.className=["contains-task-list"];break}}const o={type:"element",tagName:t.ordered?"ol":"ul",properties:i,children:e.wrap(a,!0)};return e.patch(t,o),e.applyData(t,o)}function R5(e,t){const i={type:"element",tagName:"p",properties:{},children:e.all(t)};return e.patch(t,i),e.applyData(t,i)}function D5(e,t){const i={type:"root",children:e.wrap(e.all(t))};return e.patch(t,i),e.applyData(t,i)}function j5(e,t){const i={type:"element",tagName:"strong",properties:{},children:e.all(t)};return e.patch(t,i),e.applyData(t,i)}function L5(e,t){const i=e.all(t),a=i.shift(),s=[];if(a){const c={type:"element",tagName:"thead",properties:{},children:e.wrap([a],!0)};e.patch(t.children[0],c),s.push(c)}if(i.length>0){const c={type:"element",tagName:"tbody",properties:{},children:e.wrap(i,!0)},d=Ep(t.children[1]),f=aw(t.children[t.children.length-1]);d&&f&&(c.position={start:d,end:f}),s.push(c)}const o={type:"element",tagName:"table",properties:{},children:e.wrap(s,!0)};return e.patch(t,o),e.applyData(t,o)}function z5(e,t,i){const a=i?i.children:void 0,o=(a?a.indexOf(t):1)===0?"th":"td",c=i&&i.type==="table"?i.align:void 0,d=c?c.length:t.children.length;let f=-1;const h=[];for(;++f<d;){const p=t.children[f],y={},x=c?c[f]:void 0;x&&(y.align=x);let _={type:"element",tagName:o,properties:y,children:[]};p&&(_.children=e.all(p),e.patch(p,_),_=e.applyData(p,_)),h.push(_)}const m={type:"element",tagName:"tr",properties:{},children:e.wrap(h,!0)};return e.patch(t,m),e.applyData(t,m)}function I5(e,t){const i={type:"element",tagName:"td",properties:{},children:e.all(t)};return e.patch(t,i),e.applyData(t,i)}const Ay=9,Cy=32;function B5(e){const t=String(e),i=/\r?\n|\r/g;let a=i.exec(t),s=0;const o=[];for(;a;)o.push(My(t.slice(s,a.index),s>0,!0),a[0]),s=a.index+a[0].length,a=i.exec(t);return o.push(My(t.slice(s),s>0,!1)),o.join("")}function My(e,t,i){let a=0,s=e.length;if(t){let o=e.codePointAt(a);for(;o===Ay||o===Cy;)a++,o=e.codePointAt(a)}if(i){let o=e.codePointAt(s-1);for(;o===Ay||o===Cy;)s--,o=e.codePointAt(s-1)}return s>a?e.slice(a,s):""}function U5(e,t){const i={type:"text",value:B5(String(t.value))};return e.patch(t,i),e.applyData(t,i)}function H5(e,t){const i={type:"element",tagName:"hr",properties:{},children:[]};return e.patch(t,i),e.applyData(t,i)}const $5={blockquote:g5,break:b5,code:x5,delete:y5,emphasis:v5,footnoteReference:_5,heading:w5,html:E5,imageReference:N5,image:S5,inlineCode:k5,linkReference:T5,link:A5,listItem:C5,list:O5,paragraph:R5,root:D5,strong:j5,table:L5,tableCell:I5,tableRow:z5,text:U5,thematicBreak:H5,toml:Jc,yaml:Jc,definition:Jc,footnoteDefinition:Jc};function Jc(){}const Tw=-1,Pu=0,oo=1,Nu=2,Mp=3,Op=4,Rp=5,Dp=6,Aw=7,Cw=8,Mw=typeof self=="object"?self:globalThis,Oy=(e,t)=>{switch(e){case"Function":case"SharedWorker":case"Worker":case"eval":case"setInterval":case"setTimeout":throw new TypeError("unable to deserialize "+e)}return new Mw[e](t)},q5=(e,t)=>{const i=(s,o)=>(e.set(o,s),s),a=s=>{if(e.has(s))return e.get(s);const[o,c]=t[s];switch(o){case Pu:case Tw:return i(c,s);case oo:{const d=i([],s);for(const f of c)d.push(a(f));return d}case Nu:{const d=i({},s);for(const[f,h]of c)d[a(f)]=a(h);return d}case Mp:return i(new Date(c),s);case Op:{const{source:d,flags:f}=c;return i(new RegExp(d,f),s)}case Rp:{const d=i(new Map,s);for(const[f,h]of c)d.set(a(f),a(h));return d}case Dp:{const d=i(new Set,s);for(const f of c)d.add(a(f));return d}case Aw:{const{name:d,message:f}=c;return i(typeof Mw[d]=="function"?Oy(d,f):new Error(f),s)}case Cw:return i(BigInt(c),s);case"BigInt":return i(Object(BigInt(c)),s);case"ArrayBuffer":return i(new Uint8Array(c).buffer,c);case"DataView":{const{buffer:d}=new Uint8Array(c);return i(new DataView(d),c)}}return i(Oy(o,c),s)};return a},Ry=e=>q5(new Map,e)(0),za="",{toString:P5}={},{keys:F5}=Object,Ql=e=>{const t=typeof e;if(t!=="object"||!e)return[Pu,t];const i=P5.call(e).slice(8,-1);switch(i){case"Array":return[oo,za];case"Object":return[Nu,za];case"Date":return[Mp,za];case"RegExp":return[Op,za];case"Map":return[Rp,za];case"Set":return[Dp,za];case"DataView":return[oo,i]}return i.includes("Array")?[oo,i]:e instanceof Error?[Aw,e.name||"Error"]:[Nu,i]},eu=([e,t])=>e===Pu&&(t==="function"||t==="symbol"),G5=(e,t,i,a)=>{const s=(c,d)=>{const f=a.push(c)-1;return i.set(d,f),f},o=c=>{if(i.has(c))return i.get(c);let[d,f]=Ql(c);switch(d){case Pu:{let m=c;switch(f){case"bigint":d=Cw,m=c.toString();break;case"function":case"symbol":
Line truncated
|
||||
`},{type:"element",tagName:"ol",properties:{},children:e.wrap(d,!0)},{type:"text",value:`
|
||||
`}]}}const Fu=(function(e){if(e==null)return W5;if(typeof e=="function")return Gu(e);if(typeof e=="object")return Array.isArray(e)?K5(e):Z5(e);if(typeof e=="string")return Q5(e);throw new Error("Expected function, string, or object as test")});function K5(e){const t=[];let i=-1;for(;++i<e.length;)t[i]=Fu(e[i]);return Gu(a);function a(...s){let o=-1;for(;++o<t.length;)if(t[o].apply(this,s))return!0;return!1}}function Z5(e){const t=e;return Gu(i);function i(a){const s=a;let o;for(o in e)if(s[o]!==t[o])return!1;return!0}}function Q5(e){return Gu(t);function t(i){return i&&i.type===e}}function Gu(e){return t;function t(i,a,s){return!!(J5(i)&&e.call(this,i,typeof a=="number"?a:void 0,s||void 0))}}function W5(){return!0}function J5(e){return e!==null&&typeof e=="object"&&"type"in e}const Ow=[],eR=!0,qm=!1,tR="skip";function Rw(e,t,i,a){let s;typeof t=="function"&&typeof i!="function"?(a=i,i=t):s=t;const o=Fu(s),c=a?-1:1;d(e,void 0,[])();function d(f,h,m){const p=f&&typeof f=="object"?f:{};if(typeof p.type=="string"){const x=typeof p.tagName=="string"?p.tagName:typeof p.name=="string"?p.name:void 0;Object.defineProperty(y,"name",{value:"node ("+(f.type+(x?"<"+x+">":""))+")"})}return y;function y(){let x=Ow,_,N,S;if((!t||o(f,h,m[m.length-1]||void 0))&&(x=nR(i(f,m)),x[0]===qm))return x;if("children"in f&&f.children){const w=f;if(w.children&&x[0]!==tR)for(N=(a?w.children.length:-1)+c,S=m.concat(w);N>-1&&N<w.children.length;){const k=w.children[N];if(_=d(k,N,S)(),_[0]===qm)return _;N=typeof _[1]=="number"?_[1]:N+c}}return x}}}function nR(e){return Array.isArray(e)?e:typeof e=="number"?[eR,e]:e==null?Ow:[e]}function jp(e,t,i,a){let s,o,c;typeof t=="function"&&typeof i!="function"?(o=void 0,c=t,s=i):(o=t,c=i,s=a),Rw(e,o,d,s);function d(f,h){const m=h[h.length-1],p=m?m.children.indexOf(f):void 0;return c(f,p,m)}}const Pm={}.hasOwnProperty,iR={};function rR(e,t){const i=t||iR,a=new Map,s=new Map,o=new Map,c={...$5,...i.handlers},d={all:h,applyData:sR,definitionById:a,footnoteById:s,footnoteCounts:o,footnoteOrder:[],handlers:c,one:f,options:i,patch:aR,wrap:oR};return jp(e,function(m){if(m.type==="definition"||m.type==="footnoteDefinition"){const p=m.type==="definition"?a:s,y=String(m.identifier).toUpperCase();p.has(y)||p.set(y,m)}}),d;function f(m,p){const y=m.type,x=d.handlers[y];if(Pm.call(d.handlers,y)&&x)return x(d,m,p);if(d.options.passThrough&&d.options.passThrough.includes(y)){if("children"in m){const{children:N,...S}=m,w=Su(S);return w.children=d.all(m),w}return Su(m)}return(d.options.unknownHandler||lR)(d,m,p)}function h(m){const p=[];if("children"in m){const y=m.children;let x=-1;for(;++x<y.length;){const _=d.one(y[x],m);if(_){if(x&&y[x-1].type==="break"&&(!Array.isArray(_)&&_.type==="text"&&(_.value=jy(_.value)),!Array.isArray(_)&&_.type==="element")){const N=_.children[0];N&&N.type==="text"&&(N.value=jy(N.value))}Array.isArray(_)?p.push(..._):p.push(_)}}}return p}}function aR(e,t){e.position&&(t.position=YC(e))}function sR(e,t){let i=t;if(e&&e.data){const a=e.data.hName,s=e.data.hChildren,o=e.data.hProperties;if(typeof a=="string")if(i.type==="element")i.tagName=a;else{const c="children"in i?i.children:[i];i={type:"element",tagName:a,properties:{},children:c}}i.type==="element"&&o&&Object.assign(i.properties,Su(o)),"children"in i&&i.children&&s!==null&&s!==void 0&&(i.children=s)}return i}function lR(e,t){const i=t.data||{},a="value"in t&&!(Pm.call(i,"hProperties")||Pm.call(i,"hChildren"))?{type:"text",value:t.value}:{type:"element",tagName:"div",properties:{},children:e.all(t)};return e.patch(t,a),e.applyData(t,a)}function oR(e,t){const i=[];let a=-1;for(t&&i.push({type:"text",value:`
|
||||
`});++a<e.length;)a&&i.push({type:"text",value:`
|
||||
`}),i.push(e[a]);return t&&e.length>0&&i.push({type:"text",value:`
|
||||
`}),i}function jy(e){let t=0,i=e.charCodeAt(t);for(;i===9||i===32;)t++,i=e.charCodeAt(t);return e.slice(t)}function Ly(e,t){const i=rR(e,t),a=i.one(e,void 0),s=X5(i),o=Array.isArray(a)?{type:"root",children:a}:a||{type:"root",children:[]};return s&&o.children.push({type:"text",value:`
|
||||
`},s),o}function cR(e,t){return e&&"run"in e?async function(i,a){const s=Ly(i,{file:a,...t});await e.run(s,a)}:function(i,a){return Ly(i,{file:a,...e||t})}}function zy(e){if(e)throw e}var yh,Iy;function uR(){if(Iy)return yh;Iy=1;var e=Object.prototype.hasOwnProperty,t=Object.prototype.toString,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,s=function(h){return typeof Array.isArray=="function"?Array.isArray(h):t.call(h)==="[object Array]"},o=function(h){if(!h||t.call(h)!=="[object Object]")return!1;var m=e.call(h,"constructor"),p=h.constructor&&h.constructor.prototype&&e.call(h.constructor.prototype,"isPrototypeOf");if(h.constructor&&!m&&!p)return!1;var y;for(y in h);return typeof y>"u"||e.call(h,y)},c=function(h,m){i&&m.name==="__proto__"?i(h,m.name,{enumerable:!0,configurable:!0,value:m.newValue,writable:!0}):h[m.name]=m.newValue},d=function(h,m){if(m==="__proto__")if(e.call(h,m)){if(a)return a(h,m).value}else return;return h[m]};return yh=function f(){var h,m,p,y,x,_,N=arguments[0],S=1,w=arguments.length,k=!1;for(typeof N=="boolean"&&(k=N,N=arguments[1]||{},S=2),(N==null||typeof N!="object"&&typeof N!="function")&&(N={});S<w;++S)if(h=arguments[S],h!=null)for(m in h)p=d(N,m),y=d(h,m),N!==y&&(k&&y&&(o(y)||(x=s(y)))?(x?(x=!1,_=p&&s(p)?p:[]):_=p&&o(p)?p:{},c(N,{name:m,newValue:f(k,_,y)})):typeof y<"u"&&c(N,{name:m,newValue:y}));return N},yh}var dR=uR();const vh=ko(dR);function Fm(e){if(typeof e!="object"||e===null)return!1;const t=Object.getPrototypeOf(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(Symbol.toStringTag in e)&&!(Symbol.iterator in e)}function fR(){const e=[],t={run:i,use:a};return t;function i(...s){let o=-1;const c=s.pop();if(typeof c!="function")throw new TypeError("Expected function as last argument, not "+c);d(null,...s);function d(f,...h){const m=e[++o];let p=-1;if(f){c(f);return}for(;++p<s.length;)(h[p]===null||h[p]===void 0)&&(h[p]=s[p]);s=h,m?hR(m,d)(...h):c(null,...h)}}function a(s){if(typeof s!="function")throw new TypeError("Expected `middelware` to be a function, not "+s);return e.push(s),t}}function hR(e,t){let i;return a;function a(...c){const d=e.length>c.length;let f;d&&c.push(s);try{f=e.apply(this,c)}catch(h){const m=h;if(d&&i)throw m;return s(m)}d||(f&&f.then&&typeof f.then=="function"?f.then(o,s):f instanceof Error?s(f):o(f))}function s(c,...d){i||(i=!0,t(c,...d))}function o(c){s(null,c)}}const Fi={basename:mR,dirname:pR,extname:gR,join:bR,sep:"/"};function mR(e,t){if(t!==void 0&&typeof t!="string")throw new TypeError('"ext" argument must be a string');Oo(e);let i=0,a=-1,s=e.length,o;if(t===void 0||t.length===0||t.length>e.length){for(;s--;)if(e.codePointAt(s)===47){if(o){i=s+1;break}}else a<0&&(o=!0,a=s+1);return a<0?"":e.slice(i,a)}if(t===e)return"";let c=-1,d=t.length-1;for(;s--;)if(e.codePointAt(s)===47){if(o){i=s+1;break}}else c<0&&(o=!0,c=s+1),d>-1&&(e.codePointAt(s)===t.codePointAt(d--)?d<0&&(a=s):(d=-1,a=c));return i===a?a=c:a<0&&(a=e.length),e.slice(i,a)}function pR(e){if(Oo(e),e.length===0)return".";let t=-1,i=e.length,a;for(;--i;)if(e.codePointAt(i)===47){if(a){t=i;break}}else a||(a=!0);return t<0?e.codePointAt(0)===47?"/":".":t===1&&e.codePointAt(0)===47?"//":e.slice(0,t)}function gR(e){Oo(e);let t=e.length,i=-1,a=0,s=-1,o=0,c;for(;t--;){const d=e.codePointAt(t);if(d===47){if(c){a=t+1;break}continue}i<0&&(c=!0,i=t+1),d===46?s<0?s=t:o!==1&&(o=1):s>-1&&(o=-1)}return s<0||i<0||o===0||o===1&&s===i-1&&s===a+1?"":e.slice(s,i)}function bR(...e){let t=-1,i;for(;++t<e.length;)Oo(e[t]),e[t]&&(i=i===void 0?e[t]:i+"/"+e[t]);return i===void 0?".":xR(i)}function xR(e){Oo(e);const t=e.codePointAt(0)===47;let i=yR(e,!t);return i.length===0&&!t&&(i="."),i.length>0&&e.codePointAt(e.length-1)===47&&(i+="/"),t?"/"+i:i}function yR(e,t){let i="",a=0,s=-1,o=0,c=-1,d,f;for(;++c<=e.length;){if(c<e.length)d=e.codePointAt(c);else{if(d===47)break;d=47}if(d===47){if(!(s===c-1||o===1))if(s!==c-1&&o===2){if(i.length<2||a!==2||i.codePointAt(i.length-1)!==46||i.codePointAt(i.length-2)!==46){if(i.length>2){if(f=i.lastIndexOf("/"),f!==i.length-1){f<0?(i="",a=0):(i=i.slice(0,f),a=i.length-1-i.lastIndexOf("/")),s=c,o=0;continue}}else if(i.length>0){i="",a=0,s=c,o=0;continue}}t&&(i=i.length>0?i+"/..":"..",a=2)}else i.length>0?i+="/"+e.slice(s+1,c):i=e.slice(s+1,c),a=c-s-1;s=c,o=0}else d===46&&o>-1?o++:o=-1}return i}function Oo(e){if(typeof e!="string")throw new TypeError("Path must be a string. Received "+JSON.stringify(e))}const vR={cwd:_R};function _R(){return"/"}function Gm(e){return!!(e!==null&&typeof e=="object"&&"href"in e&&e.href&&"protocol"in e&&e.protocol&&e.auth===void 0)}function wR(e){if(typeof e=="string")e=new URL(e);else if(!Gm(e)){const t=new TypeError('The "path" argument must be of type string or an instance of URL. Received `'+e+"`");throw t.code="ERR_INVALID_ARG_TYPE",t}if(e.protocol!=="file:"){const t=new TypeError("The URL must be of scheme file");throw t.code="ERR_INVALID_URL_SCHEME",t}return ER(e)}function ER(e){if(e.hostname!==""){consLine truncated
|
||||
`:" ")+o.indentLines(o.containerFlow(a,d.current()),t?zw:f3))),h(),f}}function f3(e,t,i){return t===0?e:zw(e,t,i)}function zw(e,t,i){return(i?"":" ")+e}const h3=["autolink","destinationLiteral","destinationRaw","reference","titleQuote","titleApostrophe"];Iw.peek=x3;function m3(){return{canContainEols:["delete"],enter:{strikethrough:g3},exit:{strikethrough:b3}}}function p3(){return{unsafe:[{character:"~",inConstruct:"phrasing",notInConstruct:h3}],handlers:{delete:Iw}}}function g3(e){this.enter({type:"delete",children:[]},e)}function b3(e){this.exit(e)}function Iw(e,t,i,a){const s=i.createTracker(a),o=i.enter("strikethrough");let c=s.move("~~");return c+=i.containerPhrasing(e,{...s.current(),before:c,after:"~"}),c+=s.move("~~"),o(),c}function x3(){return"~"}function y3(e){return e.length}function v3(e,t){const i=t||{},a=(i.align||[]).concat(),s=i.stringLength||y3,o=[],c=[],d=[],f=[];let h=0,m=-1;for(;++m<e.length;){const N=[],S=[];let w=-1;for(e[m].length>h&&(h=e[m].length);++w<e[m].length;){const k=_3(e[m][w]);if(i.alignDelimiters!==!1){const E=s(k);S[w]=E,(f[w]===void 0||E>f[w])&&(f[w]=E)}N.push(k)}c[m]=N,d[m]=S}let p=-1;if(typeof a=="object"&&"length"in a)for(;++p<h;)o[p]=Fy(a[p]);else{const N=Fy(a);for(;++p<h;)o[p]=N}p=-1;const y=[],x=[];for(;++p<h;){const N=o[p];let S="",w="";N===99?(S=":",w=":"):N===108?S=":":N===114&&(w=":");let k=i.alignDelimiters===!1?1:Math.max(1,f[p]-S.length-w.length);const E=S+"-".repeat(k)+w;i.alignDelimiters!==!1&&(k=S.length+k+w.length,k>f[p]&&(f[p]=k),x[p]=k),y[p]=E}c.splice(1,0,y),d.splice(1,0,x),m=-1;const _=[];for(;++m<c.length;){const N=c[m],S=d[m];p=-1;const w=[];for(;++p<h;){const k=N[p]||"";let E="",M="";if(i.alignDelimiters!==!1){const U=f[p]-(S[p]||0),R=o[p];R===114?E=" ".repeat(U):R===99?U%2?(E=" ".repeat(U/2+.5),M=" ".repeat(U/2-.5)):(E=" ".repeat(U/2),M=E):M=" ".repeat(U)}i.delimiterStart!==!1&&!p&&w.push("|"),i.padding!==!1&&!(i.alignDelimiters===!1&&k==="")&&(i.delimiterStart!==!1||p)&&w.push(" "),i.alignDelimiters!==!1&&w.push(E),w.push(k),i.alignDelimiters!==!1&&w.push(M),i.padding!==!1&&w.push(" "),(i.delimiterEnd!==!1||p!==h-1)&&w.push("|")}_.push(i.delimiterEnd===!1?w.join("").replace(/ +$/,""):w.join(""))}return _.join(`
|
||||
`)}function _3(e){return e==null?"":String(e)}function Fy(e){const t=typeof e=="string"?e.codePointAt(0):0;return t===67||t===99?99:t===76||t===108?108:t===82||t===114?114:0}function w3(e,t,i,a){const s=i.enter("blockquote"),o=i.createTracker(a);o.move("> "),o.shift(2);const c=i.indentLines(i.containerFlow(e,o.current()),E3);return s(),c}function E3(e,t,i){return">"+(i?"":" ")+e}function N3(e,t){return Gy(e,t.inConstruct,!0)&&!Gy(e,t.notInConstruct,!1)}function Gy(e,t,i){if(typeof t=="string"&&(t=[t]),!t||t.length===0)return i;let a=-1;for(;++a<t.length;)if(e.includes(t[a]))return!0;return!1}function Vy(e,t,i,a){let s=-1;for(;++s<i.unsafe.length;)if(i.unsafe[s].character===`
|
||||
`&&N3(i.stack,i.unsafe[s]))return/[ \t]/.test(a.before)?"":" ";return`\\
|
||||
`}function S3(e,t){const i=String(e);let a=i.indexOf(t),s=a,o=0,c=0;if(typeof t!="string")throw new TypeError("Expected substring");for(;a!==-1;)a===s?++o>c&&(c=o):o=1,s=a+t.length,a=i.indexOf(t,s);return c}function k3(e,t){return!!(t.options.fences===!1&&e.value&&!e.lang&&/[^ \r\n]/.test(e.value)&&!/^[\t ]*(?:[\r\n]|$)|(?:^|[\r\n])[\t ]*$/.test(e.value))}function T3(e){const t=e.options.fence||"`";if(t!=="`"&&t!=="~")throw new Error("Cannot serialize code with `"+t+"` for `options.fence`, expected `` ` `` or `~`");return t}function A3(e,t,i,a){const s=T3(i),o=e.value||"",c=s==="`"?"GraveAccent":"Tilde";if(k3(e,i)){const p=i.enter("codeIndented"),y=i.indentLines(o,C3);return p(),y}const d=i.createTracker(a),f=s.repeat(Math.max(S3(o,s)+1,3)),h=i.enter("codeFenced");let m=d.move(f);if(e.lang){const p=i.enter(`codeFencedLang${c}`);m+=d.move(i.safe(e.lang,{before:m,after:" ",encode:["`"],...d.current()})),p()}if(e.lang&&e.meta){const p=i.enter(`codeFencedMeta${c}`);m+=d.move(" "),m+=d.move(i.safe(e.meta,{before:m,after:`
|
||||
`,encode:["`"],...d.current()})),p()}return m+=d.move(`
|
||||
`),o&&(m+=d.move(o+`
|
||||
`)),m+=d.move(f),h(),m}function C3(e,t,i){return(i?"":" ")+e}function Ip(e){const t=e.options.quote||'"';if(t!=='"'&&t!=="'")throw new Error("Cannot serialize title with `"+t+"` for `options.quote`, expected `\"`, or `'`");return t}function M3(e,t,i,a){const s=Ip(i),o=s==='"'?"Quote":"Apostrophe",c=i.enter("definition");let d=i.enter("label");const f=i.createTracker(a);let h=f.move("[");return h+=f.move(i.safe(i.associationId(e),{before:h,after:"]",...f.current()})),h+=f.move("]: "),d(),!e.url||/[\0- \u007F]/.test(e.url)?(d=i.enter("destinationLiteral"),h+=f.move("<"),h+=f.move(i.safe(e.url,{before:h,after:">",...f.current()})),h+=f.move(">")):(d=i.enter("destinationRaw"),h+=f.move(i.safe(e.url,{before:h,after:e.title?" ":`
|
||||
`,...f.current()}))),d(),e.title&&(d=i.enter(`title${o}`),h+=f.move(" "+s),h+=f.move(i.safe(e.title,{before:h,after:s,...f.current()})),h+=f.move(s),d()),c(),h}function O3(e){const t=e.options.emphasis||"*";if(t!=="*"&&t!=="_")throw new Error("Cannot serialize emphasis with `"+t+"` for `options.emphasis`, expected `*`, or `_`");return t}function po(e){return"&#x"+e.toString(16).toUpperCase()+";"}function ku(e,t,i){const a=Vs(e),s=Vs(t);return a===void 0?s===void 0?i==="_"?{inside:!0,outside:!0}:{inside:!1,outside:!1}:s===1?{inside:!0,outside:!0}:{inside:!1,outside:!0}:a===1?s===void 0?{inside:!1,outside:!1}:s===1?{inside:!0,outside:!0}:{inside:!1,outside:!1}:s===void 0?{inside:!1,outside:!1}:s===1?{inside:!0,outside:!1}:{inside:!1,outside:!1}}Bw.peek=R3;function Bw(e,t,i,a){const s=O3(i),o=i.enter("emphasis"),c=i.createTracker(a),d=c.move(s);let f=c.move(i.containerPhrasing(e,{after:s,before:d,...c.current()}));const h=f.charCodeAt(0),m=ku(a.before.charCodeAt(a.before.length-1),h,s);m.inside&&(f=po(h)+f.slice(1));const p=f.charCodeAt(f.length-1),y=ku(a.after.charCodeAt(0),p,s);y.inside&&(f=f.slice(0,-1)+po(p));const x=c.move(s);return o(),i.attentionEncodeSurroundingInfo={after:y.outside,before:m.outside},d+f+x}function R3(e,t,i){return i.options.emphasis||"*"}function D3(e,t){let i=!1;return jp(e,function(a){if("value"in a&&/\r?\n|\r/.test(a.value)||a.type==="break")return i=!0,qm}),!!((!e.depth||e.depth<3)&&Tp(e)&&(t.options.setext||i))}function j3(e,t,i,a){const s=Math.max(Math.min(6,e.depth||1),1),o=i.createTracker(a);if(D3(e,i)){const m=i.enter("headingSetext"),p=i.enter("phrasing"),y=i.containerPhrasing(e,{...o.current(),before:`
|
||||
`,after:`
|
||||
`});return p(),m(),y+`
|
||||
`+(s===1?"=":"-").repeat(y.length-(Math.max(y.lastIndexOf("\r"),y.lastIndexOf(`
|
||||
`))+1))}const c="#".repeat(s),d=i.enter("headingAtx"),f=i.enter("phrasing");o.move(c+" ");let h=i.containerPhrasing(e,{before:"# ",after:`
|
||||
`,...o.current()});return/^[\t ]/.test(h)&&(h=po(h.charCodeAt(0))+h.slice(1)),h=h?c+" "+h:c,i.options.closeAtx&&(h+=" "+c),f(),d(),h}Uw.peek=L3;function Uw(e){return e.value||""}function L3(){return"<"}Hw.peek=z3;function Hw(e,t,i,a){const s=Ip(i),o=s==='"'?"Quote":"Apostrophe",c=i.enter("image");let d=i.enter("label");const f=i.createTracker(a);let h=f.move("![");return h+=f.move(i.safe(e.alt,{before:h,after:"]",...f.current()})),h+=f.move("]("),d(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(d=i.enter("destinationLiteral"),h+=f.move("<"),h+=f.move(i.safe(e.url,{before:h,after:">",...f.current()})),h+=f.move(">")):(d=i.enter("destinationRaw"),h+=f.move(i.safe(e.url,{before:h,after:e.title?" ":")",...f.current()}))),d(),e.title&&(d=i.enter(`title${o}`),h+=f.move(" "+s),h+=f.move(i.safe(e.title,{before:h,after:s,...f.current()})),h+=f.move(s),d()),h+=f.move(")"),c(),h}function z3(){return"!"}$w.peek=I3;function $w(e,t,i,a){const s=e.referenceType,o=i.enter("imageReference");let c=i.enter("label");const d=i.createTracker(a);let f=d.move("![");const h=i.safe(e.alt,{before:f,after:"]",...d.current()});f+=d.move(h+"]["),c();const m=i.stack;i.stack=[],c=i.enter("reference");const p=i.safe(i.associationId(e),{before:f,after:"]",...d.current()});return c(),i.stack=m,o(),s==="full"||!h||h!==p?f+=d.move(p+"]"):s==="shortcut"?f=f.slice(0,-1):f+=d.move("]"),f}function I3(){return"!"}qw.peek=B3;function qw(e,t,i){let a=e.value||"",s="`",o=-1;for(;new RegExp("(^|[^`])"+s+"([^`]|$)").test(a);)s+="`";for(/[^ \r\n]/.test(a)&&(/^[ \r\n]/.test(a)&&/[ \r\n]$/.test(a)||/^`|`$/.test(a))&&(a=" "+a+" ");++o<i.unsafe.length;){const c=i.unsafe[o],d=i.compilePattern(c);let f;if(c.atBreak)for(;f=d.exec(a);){let h=f.index;a.charCodeAt(h)===10&&a.charCodeAt(h-1)===13&&h--,a=a.slice(0,h)+" "+a.slice(f.index+1)}}return s+a+s}function B3(){return"`"}function Pw(e,t){const i=Tp(e);return!!(!t.options.resourceLink&&e.url&&!e.title&&e.children&&e.children.length===1&&e.children[0].type==="text"&&(i===e.url||"mailto:"+i===e.url)&&/^[a-z][a-z+.-]+:/i.test(e.url)&&!/[\0- <>\u007F]/.test(e.url))}Fw.peek=U3;function Fw(e,t,i,a){const s=Ip(i),o=s==='"'?"Quote":"Apostrophe",c=i.createTracker(a);let d,f;if(Pw(e,i)){const m=i.stack;i.stack=[],d=i.enter("autolink");let p=c.move("<");return p+=c.move(i.containerPhrasing(e,{before:p,after:">",...c.current()})),p+=c.move(">"),d(),i.stack=m,p}d=i.enter("link"),f=i.enter("label");let h=c.move("[");return h+=c.move(i.containerPhrasing(e,{before:h,after:"](",...c.current()})),h+=c.move("]("),f(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(f=i.enter("destinationLiteral"),h+=c.move("<"),h+=c.move(i.safe(e.url,{before:h,after:">",...c.current()})),h+=c.move(">")):(f=i.enter("destinationRaw"),h+=c.move(i.safe(e.url,{before:h,after:e.title?" ":")",...c.current()}))),f(),e.title&&(f=i.enter(`title${o}`),h+=c.move(" "+s),h+=c.move(i.safe(e.title,{before:h,after:s,...c.current()})),h+=c.move(s),f()),h+=c.move(")"),d(),h}function U3(e,t,i){return Pw(e,i)?"<":"["}Gw.peek=H3;function Gw(e,t,i,a){const s=e.referenceType,o=i.enter("linkReference");let c=i.enter("label");const d=i.createTracker(a);let f=d.move("[");const h=i.containerPhrasing(e,{before:f,after:"]",...d.current()});f+=d.move(h+"]["),c();const m=i.stack;i.stack=[],c=i.enter("reference");const p=i.safe(i.associationId(e),{before:f,after:"]",...d.current()});return c(),i.stack=m,o(),s==="full"||!h||h!==p?f+=d.move(p+"]"):s==="shortcut"?f=f.slice(0,-1):f+=d.move("]"),f}function H3(){return"["}function Bp(e){const t=e.options.bullet||"*";if(t!=="*"&&t!=="+"&&t!=="-")throw new Error("Cannot serialize items with `"+t+"` for `options.bullet`, expected `*`, `+`, or `-`");return t}function $3(e){const t=Bp(e),i=e.options.bulletOther;if(!i)return t==="*"?"-":"*";if(i!=="*"&&i!=="+"&&i!=="-")throw new Error("Cannot serialize items with `"+i+"` for `options.bulletOther`, expected `*`, `+`, or `-`");if(i===t)throw new Error("Expected `bullet` (`"+t+"`) and `bulletOther` (`"+i+"`) to be different");return i}function q3(e){const t=e.options.bulletOrdered||".";if(t!=="."&&t!==")")throw new Error("Cannot serialize items with `"+t+"` for `options.bulletOrdered`, expected `.` or `)`");return t}function Vw(e){const t=e.options.rule||"*";if(t!=="*"&&t!=="-"&&t!=="_")throw new Error("Cannot serialize rules with `"+t+"` for `options.rule`, expected `*`, `-`, or `_`");return t}function P3(e,t,i,a){const s=i.enter("list"),o=i.bulletCurrent;let c=e.ordered?q3(i):Bp(i);const d=e.ordered?c==="."?")":".":$3(i);let f=t&&i.bulletLastUsed?c===i.bulletLastUsed:!1;if(!e.ordered){const m=e.children?e.children[0]:void 0;if((c==="*"||c==="-")&&m&&(!m.children||!m.children[0])&&i.stack[i.stack.length-1]==="list"&&i.stack[i.stack.length-2]==="listItem"&&i.stack[i.stack.length-3]==="list"&&i.stack[i.stack.length-4]==="listItem"&&i.indexStack[i.indexStack.length-1]===0&&i.indexStack[i.indexStack.length-2]===0&&i.indexStack[i.indexStack.length-3]===0&&(f=!0),Vw(i)===c&&m){let p=-1;for(;++p<e.chilLine truncated
|
||||
`,inConstruct:"tableCell"},{atBreak:!0,character:"|",after:"[ :-]"},{character:"|",inConstruct:"tableCell"},{atBreak:!0,character:":",after:"-"},{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{inlineCode:y,table:c,tableCell:f,tableRow:d}};function c(x,_,N,S){return h(m(x,N,S),x.align)}function d(x,_,N,S){const w=p(x,N,S),k=h([w]);return k.slice(0,k.indexOf(`
|
||||
`))}function f(x,_,N,S){const w=N.enter("tableCell"),k=N.enter("phrasing"),E=N.containerPhrasing(x,{...S,before:o,after:o});return k(),w(),E}function h(x,_){return v3(x,{align:_,alignDelimiters:a,padding:i,stringLength:s})}function m(x,_,N){const S=x.children;let w=-1;const k=[],E=_.enter("table");for(;++w<S.length;)k[w]=p(S[w],_,N);return E(),k}function p(x,_,N){const S=x.children;let w=-1;const k=[],E=_.enter("tableRow");for(;++w<S.length;)k[w]=f(S[w],x,_,N);return E(),k}function y(x,_,N){let S=Xw.inlineCode(x,_,N);return N.stack.includes("tableCell")&&(S=S.replace(/\|/g,"\\$&")),S}}function l4(){return{exit:{taskListCheckValueChecked:Xy,taskListCheckValueUnchecked:Xy,paragraph:c4}}}function o4(){return{unsafe:[{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{listItem:u4}}}function Xy(e){const t=this.stack[this.stack.length-2];t.type,t.checked=e.type==="taskListCheckValueChecked"}function c4(e){const t=this.stack[this.stack.length-2];if(t&&t.type==="listItem"&&typeof t.checked=="boolean"){const i=this.stack[this.stack.length-1];i.type;const a=i.children[0];if(a&&a.type==="text"){const s=t.children;let o=-1,c;for(;++o<s.length;){const d=s[o];if(d.type==="paragraph"){c=d;break}}c===i&&(a.value=a.value.slice(1),a.value.length===0?i.children.shift():i.position&&a.position&&typeof a.position.start.offset=="number"&&(a.position.start.column++,a.position.start.offset++,i.position.start=Object.assign({},a.position.start)))}}this.exit(e)}function u4(e,t,i,a){const s=e.children[0],o=typeof e.checked=="boolean"&&s&&s.type==="paragraph",c="["+(e.checked?"x":" ")+"] ",d=i.createTracker(a);o&&d.move(c);let f=Xw.listItem(e,t,i,{...a,...d.current()});return o&&(f=f.replace(/^(?:[*+-]|\d+\.)([\r\n]| {1,3})/,h)),f;function h(m){return m+c}}function d4(){return[PR(),u3(),m3(),e4(),l4()]}function f4(e){return{extensions:[FR(),d3(e),p3(),s4(e),o4()]}}const h4={tokenize:y4,partial:!0},Kw={tokenize:v4,partial:!0},Zw={tokenize:_4,partial:!0},Qw={tokenize:w4,partial:!0},m4={tokenize:E4,partial:!0},Ww={name:"wwwAutolink",tokenize:b4,previous:eE},Jw={name:"protocolAutolink",tokenize:x4,previous:tE},_r={name:"emailAutolink",tokenize:g4,previous:nE},Xi={};function p4(){return{text:Xi}}let Da=48;for(;Da<123;)Xi[Da]=_r,Da++,Da===58?Da=65:Da===91&&(Da=97);Xi[43]=_r;Xi[45]=_r;Xi[46]=_r;Xi[95]=_r;Xi[72]=[_r,Jw];Xi[104]=[_r,Jw];Xi[87]=[_r,Ww];Xi[119]=[_r,Ww];function g4(e,t,i){const a=this;let s,o;return c;function c(p){return!Vm(p)||!nE.call(a,a.previous)||Up(a.events)?i(p):(e.enter("literalAutolink"),e.enter("literalAutolinkEmail"),d(p))}function d(p){return Vm(p)?(e.consume(p),d):p===64?(e.consume(p),f):i(p)}function f(p){return p===46?e.check(m4,m,h)(p):p===45||p===95||Tn(p)?(o=!0,e.consume(p),f):m(p)}function h(p){return e.consume(p),s=!0,f}function m(p){return o&&s&&jn(a.previous)?(e.exit("literalAutolinkEmail"),e.exit("literalAutolink"),t(p)):i(p)}}function b4(e,t,i){const a=this;return s;function s(c){return c!==87&&c!==119||!eE.call(a,a.previous)||Up(a.events)?i(c):(e.enter("literalAutolink"),e.enter("literalAutolinkWww"),e.check(h4,e.attempt(Kw,e.attempt(Zw,o),i),i)(c))}function o(c){return e.exit("literalAutolinkWww"),e.exit("literalAutolink"),t(c)}}function x4(e,t,i){const a=this;let s="",o=!1;return c;function c(p){return(p===72||p===104)&&tE.call(a,a.previous)&&!Up(a.events)?(e.enter("literalAutolink"),e.enter("literalAutolinkHttp"),s+=String.fromCodePoint(p),e.consume(p),d):i(p)}function d(p){if(jn(p)&&s.length<5)return s+=String.fromCodePoint(p),e.consume(p),d;if(p===58){const y=s.toLowerCase();if(y==="http"||y==="https")return e.consume(p),f}return i(p)}function f(p){return p===47?(e.consume(p),o?h:(o=!0,f)):i(p)}function h(p){return p===null||Eu(p)||At(p)||qa(p)||$u(p)?i(p):e.attempt(Kw,e.attempt(Zw,m),i)(p)}function m(p){return e.exit("literalAutolinkHttp"),e.exit("literalAutolink"),t(p)}}function y4(e,t,i){let a=0;return s;function s(c){return(c===87||c===119)&&a<3?(a++,e.consume(c),s):c===46&&a===3?(e.consume(c),o):i(c)}function o(c){return c===null?i(c):t(c)}}function v4(e,t,i){let a,s,o;return c;function c(h){return h===46||h===95?e.check(Qw,f,d)(h):h===null||At(h)||qa(h)||h!==45&&$u(h)?f(h):(o=!0,e.consume(h),c)}function d(h){return h===95?a=!0:(s=a,a=void 0),e.consume(h),c}function f(h){return s||a||!o?i(h):t(h)}}function _4(e,t){let i=0,a=0;return s;function s(c){return c===40?(i++,e.consume(c),s):c===41&&a<i?o(c):c===33||c===34||c===38||c===39||c===41||c===42||c===44||c===46||c===58||c===59||c===60||c===63||c===93||c===95||c===126?e.check(Qw,t,o)(c):c===null||At(c)||qa(c)?t(c):(e.consume(c),s)}function o(c){return c===41&&a++,e.consume(c),s}}function w4(e,t,i){return a;function a(d){return d===33||d===34||d===39||d===41||d===42||d===44||d===46||d===58||d===59||d===63||d===95||d===126?(e.consume(d),a):d===38?(e.consume(d),o):d===93?(e.consume(d),s):d===60||d===null||At(d)||qa(d)?t(d):i(d)}function s(d){return d===null||d===40||d===91||At(d)||qa(d)?t(d):a(d)}function o(d){return jn(d)?c(d):i(d)}function c(d){re
Line truncated
|
||||
https://github.com/highlightjs/highlight.js/issues/2277`),Ht=ye,ft=Le),Qe===void 0&&(Qe=!0);const pn={code:ft,language:Ht};Wi("before:highlight",pn);const Mn=pn.result?pn.result:bi(pn.language,pn.code,Qe);return Mn.code=pn.code,Wi("after:highlight",Mn),Mn}function bi(ye,Le,Qe,ft){const Ht=Object.create(null);function pn(_e,Re){return _e.keywords[Re]}function Mn(){if(!qe.keywords){Jt.addText(bt);return}let _e=0;qe.keywordPatternRe.lastIndex=0;let Re=qe.keywordPatternRe.exec(bt),Ye="";for(;Re;){Ye+=bt.substring(_e,Re.index);const it=un.case_insensitive?Re[0].toLowerCase():Re[0],$t=pn(qe,it);if($t){const[li,il]=$t;if(Jt.addText(Ye),Ye="",Ht[it]=(Ht[it]||0)+1,Ht[it]<=Xn&&(Mr+=il),li.startsWith("_"))Ye+=Re[0];else{const $o=un.classNameAliases[li]||li;On(Re[0],$o)}}else Ye+=Re[0];_e=qe.keywordPatternRe.lastIndex,Re=qe.keywordPatternRe.exec(bt)}Ye+=bt.substring(_e),Jt.addText(Ye)}function Nn(){if(bt==="")return;let _e=null;if(typeof qe.subLanguage=="string"){if(!me[qe.subLanguage]){Jt.addText(bt);return}_e=bi(qe.subLanguage,bt,!0,Ho[qe.subLanguage]),Ho[qe.subLanguage]=_e._top}else _e=Nr(bt,qe.subLanguage.length?qe.subLanguage:null);qe.relevance>0&&(Mr+=_e.relevance),Jt.__addSublanguage(_e._emitter,_e.language)}function _t(){qe.subLanguage!=null?Nn():Mn(),bt=""}function On(_e,Re){_e!==""&&(Jt.startScope(Re),Jt.addText(_e),Jt.endScope())}function Ja(_e,Re){let Ye=1;const it=Re.length-1;for(;Ye<=it;){if(!_e._emit[Ye]){Ye++;continue}const $t=un.classNameAliases[_e[Ye]]||_e[Ye],li=Re[Ye];$t?On(li,$t):(bt=li,Mn(),bt=""),Ye++}}function Tr(_e,Re){return _e.scope&&typeof _e.scope=="string"&&Jt.openNode(un.classNameAliases[_e.scope]||_e.scope),_e.beginScope&&(_e.beginScope._wrap?(On(bt,un.classNameAliases[_e.beginScope._wrap]||_e.beginScope._wrap),bt=""):_e.beginScope._multi&&(Ja(_e.beginScope,Re),bt="")),qe=Object.create(_e,{parent:{value:qe}}),qe}function Ii(_e,Re,Ye){let it=E(_e.endRe,Ye);if(it){if(_e["on:end"]){const $t=new t(_e);_e["on:end"](Re,$t),$t.isMatchIgnored&&(it=!1)}if(it){for(;_e.endsParent&&_e.parent;)_e=_e.parent;return _e}}if(_e.endsWithParent)return Ii(_e.parent,Re,Ye)}function Ar(_e){return qe.matcher.regexIndex===0?(bt+=_e[0],1):(Or=!0,0)}function es(_e){const Re=_e[0],Ye=_e.rule,it=new t(Ye),$t=[Ye.__beforeBegin,Ye["on:begin"]];for(const li of $t)if(li&&(li(_e,it),it.isMatchIgnored))return Ar(Re);return Ye.skip?bt+=Re:(Ye.excludeBegin&&(bt+=Re),_t(),!Ye.returnBegin&&!Ye.excludeBegin&&(bt=Re)),Tr(Ye,_e),Ye.returnBegin?0:Re.length}function Sn(_e){const Re=_e[0],Ye=Le.substring(_e.index),it=Ii(qe,_e,Ye);if(!it)return nt;const $t=qe;qe.endScope&&qe.endScope._wrap?(_t(),On(Re,qe.endScope._wrap)):qe.endScope&&qe.endScope._multi?(_t(),Ja(qe.endScope,_e)):$t.skip?bt+=Re:($t.returnEnd||$t.excludeEnd||(bt+=Re),_t(),$t.excludeEnd&&(bt=Re));do qe.scope&&Jt.closeNode(),!qe.skip&&!qe.subLanguage&&(Mr+=qe.relevance),qe=qe.parent;while(qe!==it.parent);return it.starts&&Tr(it.starts,_e),$t.returnEnd?0:Re.length}function ma(){const _e=[];for(let Re=qe;Re!==un;Re=Re.parent)Re.scope&&_e.unshift(Re.scope);_e.forEach(Re=>Jt.openNode(Re))}let wi={};function Cr(_e,Re){const Ye=Re&&Re[0];if(bt+=_e,Ye==null)return _t(),0;if(wi.type==="begin"&&Re.type==="end"&&wi.index===Re.index&&Ye===""){if(bt+=Le.slice(Re.index,Re.index+1),!St){const it=new Error(`0 width match regex (${ye})`);throw it.languageName=ye,it.badRule=wi.rule,it}return 1}if(wi=Re,Re.type==="begin")return es(Re);if(Re.type==="illegal"&&!Qe){const it=new Error('Illegal lexeme "'+Ye+'" for mode "'+(qe.scope||"<unnamed>")+'"');throw it.mode=qe,it}else if(Re.type==="end"){const it=Sn(Re);if(it!==nt)return it}if(Re.type==="illegal"&&Ye==="")return bt+=`
|
||||
`,1;if(nl>1e5&&nl>Re.index*3)throw new Error("potential infinite loop, way more iterations than matches");return bt+=Ye,Ye.length}const un=bn(ye);if(!un)throw ct(gt.replace("{}",ye)),new Error('Unknown language: "'+ye+'"');const pa=Kt(un);let ts="",qe=ft||pa;const Ho={},Jt=new Se.__emitter(Se);ma();let bt="",Mr=0,Ji=0,nl=0,Or=!1;try{if(un.__emitTokens)un.__emitTokens(Le,Jt);else{for(qe.matcher.considerAll();;){nl++,Or?Or=!1:qe.matcher.considerAll(),qe.matcher.lastIndex=Ji;const _e=qe.matcher.exec(Le);if(!_e)break;const Re=Le.substring(Ji,_e.index),Ye=Cr(Re,_e);Ji=_e.index+Ye}Cr(Le.substring(Ji))}return Jt.finalize(),ts=Jt.toHTML(),{language:ye,value:ts,relevance:Mr,illegal:!1,_emitter:Jt,_top:qe}}catch(_e){if(_e.message&&_e.message.includes("Illegal"))return{language:ye,value:cn(Le),illegal:!0,relevance:0,_illegalBy:{message:_e.message,index:Ji,context:Le.slice(Ji-100,Ji+100),mode:_e.mode,resultSoFar:ts},_emitter:Jt};if(St)return{language:ye,value:cn(Le),illegal:!1,relevance:0,errorRaised:_e,_emitter:Jt,_top:qe};throw _e}}function Er(ye){const Le={value:cn(ye),illegal:!1,relevance:0,_top:Me,_emitter:new Se.__emitter(Se)};return Le._emitter.addText(ye),Le}function Nr(ye,Le){Le=Le||Se.languages||Object.keys(me);const Qe=Er(ye),ft=Le.filter(bn).filter(vi).map(_t=>bi(_t,ye,!1));ft.unshift(Qe);const Ht=ft.sort((_t,On)=>{if(_t.relevance!==On.relevance)return On.relevance-_t.relevance;if(_t.language&&On.language){if(bn(_t.language).supersetOf===On.language)return 1;if(bn(On.language).supersetOf===_t.language)return-1}return 0}),[pn,Mn]=Ht,Nn=pn;return Nn.secondBest=Mn,Nn}function si(ye,Le,Qe){const ft=Le&&Ee[Le]||Qe;ye.classList.add("hljs"),ye.classList.add(`language-${ft}`)}function Ut(ye){let Le=null;const Qe=Bt(ye);if(Ue(Qe))return;if(Wi("before:highlightElement",{el:ye,language:Qe}),ye.dataset.highlighted){console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",ye);return}if(ye.children.length>0&&(Se.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."),console.warn("https://github.com/highlightjs/highlight.js/wiki/security"),console.warn("The element with unescaped HTML:"),console.warn(ye)),Se.throwUnescapedHTML))throw new zn("One of your code blocks includes unescaped HTML.",ye.innerHTML);Le=ye;const ft=Le.textContent,Ht=Qe?Mt(ft,{language:Qe,ignoreIllegals:!0}):Nr(ft);ye.innerHTML=Ht.value,ye.dataset.highlighted="yes",si(ye,Qe,Ht.language),ye.result={language:Ht.language,re:Ht.relevance,relevance:Ht.relevance},Ht.secondBest&&(ye.secondBest={language:Ht.secondBest.language,relevance:Ht.secondBest.relevance}),Wi("after:highlightElement",{el:ye,result:Ht,text:ft})}function mn(ye){Se=wr(Se,ye)}const xi=()=>{kr(),ue("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")};function Sr(){kr(),ue("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.")}let fa=!1;function kr(){function ye(){kr()}if(document.readyState==="loading"){fa||window.addEventListener("DOMContentLoaded",ye,!1),fa=!0;return}document.querySelectorAll(Se.cssSelector).forEach(Ut)}function Qa(ye,Le){let Qe=null;try{Qe=Le(ie)}catch(ft){if(ct("Language definition for '{}' could not be registered.".replace("{}",ye)),St)ct(ft);else throw ft;Qe=Me}Qe.name||(Qe.name=ye),me[ye]=Qe,Qe.rawDefinition=Le.bind(null,ie),Qe.aliases&&yi(Qe.aliases,{languageName:ye})}function Qi(ye){delete me[ye];for(const Le of Object.keys(Ee))Ee[Le]===ye&&delete Ee[Le]}function ha(){return Object.keys(me)}function bn(ye){return ye=(ye||"").toLowerCase(),me[ye]||me[Ee[ye]]}function yi(ye,{languageName:Le}){typeof ye=="string"&&(ye=[ye]),ye.forEach(Qe=>{Ee[Qe.toLowerCase()]=Le})}function vi(ye){const Le=bn(ye);return Le&&!Le.disableAutodetect}function zi(ye){ye["before:highlightBlock"]&&!ye["before:highlightElement"]&&(ye["before:highlightElement"]=Le=>{ye["before:highlightBlock"](Object.assign({block:Le.el},Le))}),ye["after:highlightBlock"]&&!ye["after:highlightElement"]&&(ye["after:highlightElement"]=Le=>{ye["after:highlightBlock"](Object.assign({block:Le.el},Le))})}function Ft(ye){zi(ye),Pe.push(ye)}function Wa(ye){const Le=Pe.indexOf(ye);Le!==-1&&Pe.splice(Le,1)}function Wi(ye,Le){const Qe=ye;Pe.forEach(function(ft){ft[Qe]&&ft[Qe](Le)})}function _i(ye){return ue("10.7.0","highlightBlock will be removed entirely in v12.0"),ue("10.7.0","Please use highlightElement now."),Ut(ye)}Object.assign(ie,{highlight:Mt,highlightAuto:Nr,highlightAll:kr,highlightElement:Ut,highlightBlock:_i,configure:mn,initHighlighting:xi,initHighlightingOnLoad:Sr,registerLanguage:Qa,unregisterLanguage:Qi,listLanguages:ha,getLanguage:bn,registerAliases:yi,autoDetection:vi,inherit:wr,addPlugin:Ft,removePlugin:Wa}),ie.debugMode=function(){St=!1},ie.safeMode=function(){St=!0},ie.versionString=ut,ie.regex={concat:N,lookahead:y,either:w,optional:_,anyNumberOfTimes:x};for(const ye in oe)typeof oe[ye]=="object"&&e(oe[ye]);return Object.assign(ie,oe),ie},hn=Cn({})
Line truncated
|
||||
`},_]}}return Yh=s,Yh}var Xh,hv;function cD(){if(hv)return Xh;hv=1;const e=m=>({IMPORTANT:{scope:"meta",begin:"!important"},BLOCK_COMMENT:m.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:"number",begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{className:"built_in",begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[m.APOS_STRING_MODE,m.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{scope:"number",begin:m.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z_][A-Za-z0-9_-]*/}}),t=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","optgroup","option","p","picture","q","quote","samp","section","select","source","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],i=["defs","g","marker","mask","pattern","svg","switch","symbol","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feFlood","feGaussianBlur","feImage","feMerge","feMorphology","feOffset","feSpecularLighting","feTile","feTurbulence","linearGradient","radialGradient","stop","circle","ellipse","image","line","path","polygon","polyline","rect","text","use","textPath","tspan","foreignObject","clipPath"],a=[...t,...i],s=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"].sort().reverse(),o=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"].sort().reverse(),c=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"].sort().reverse(),d=["accent-color","align-content","align-items","align-self","alignment-baseline","all","anchor-name","animation","animation-composition","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-range","animation-range-end","animation-range-start","animation-timeline","animation-timing-function","appearance","aspect-ratio","backdrop-filter","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-position-x","background-position-y","background-repeat","background-size","baseline-shift","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-end-end-radius","border-end-start-radius","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-start-end-radius","border-start-start-radius","border-style","border-top","border-top
Line truncated
|
||||
]`,N={scope:"string",variants:[p,m,y,x]},S={scope:"number",variants:[{begin:"\\b0[bB][01]+(?:_[01]+)*\\b"},{begin:"\\b0[oO][0-7]+(?:_[0-7]+)*\\b"},{begin:"\\b0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*\\b"},{begin:"(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:[eE][+-]?\\d+)?"}],relevance:0},w=["false","null","true"],k=["__CLASS__","__DIR__","__FILE__","__FUNCTION__","__COMPILER_HALT_OFFSET__","__LINE__","__METHOD__","__NAMESPACE__","__TRAIT__","die","echo","exit","include","include_once","print","require","require_once","array","abstract","and","as","binary","bool","boolean","break","callable","case","catch","class","clone","const","continue","declare","default","do","double","else","elseif","empty","enddeclare","endfor","endforeach","endif","endswitch","endwhile","enum","eval","extends","final","finally","float","for","foreach","from","global","goto","if","implements","instanceof","insteadof","int","integer","interface","isset","iterable","list","match|0","mixed","new","never","object","or","private","protected","public","readonly","real","return","string","switch","throw","trait","try","unset","use","var","void","while","xor","yield"],E=["Error|0","AppendIterator","ArgumentCountError","ArithmeticError","ArrayIterator","ArrayObject","AssertionError","BadFunctionCallException","BadMethodCallException","CachingIterator","CallbackFilterIterator","CompileError","Countable","DirectoryIterator","DivisionByZeroError","DomainException","EmptyIterator","ErrorException","Exception","FilesystemIterator","FilterIterator","GlobIterator","InfiniteIterator","InvalidArgumentException","IteratorIterator","LengthException","LimitIterator","LogicException","MultipleIterator","NoRewindIterator","OutOfBoundsException","OutOfRangeException","OuterIterator","OverflowException","ParentIterator","ParseError","RangeException","RecursiveArrayIterator","RecursiveCachingIterator","RecursiveCallbackFilterIterator","RecursiveDirectoryIterator","RecursiveFilterIterator","RecursiveIterator","RecursiveIteratorIterator","RecursiveRegexIterator","RecursiveTreeIterator","RegexIterator","RuntimeException","SeekableIterator","SplDoublyLinkedList","SplFileInfo","SplFileObject","SplFixedArray","SplHeap","SplMaxHeap","SplMinHeap","SplObjectStorage","SplObserver","SplPriorityQueue","SplQueue","SplStack","SplSubject","SplTempFileObject","TypeError","UnderflowException","UnexpectedValueException","UnhandledMatchError","ArrayAccess","BackedEnum","Closure","Fiber","Generator","Iterator","IteratorAggregate","Serializable","Stringable","Throwable","Traversable","UnitEnum","WeakReference","WeakMap","Directory","__PHP_Incomplete_Class","parent","php_user_filter","self","static","stdClass"],U={keyword:k,literal:(q=>{const O=[];return q.forEach(H=>{O.push(H),H.toLowerCase()===H?O.push(H.toUpperCase()):O.push(H.toLowerCase())}),O})(w),built_in:E},R=q=>q.map(O=>O.replace(/\|\d+$/,"")),B={variants:[{match:[/new/,i.concat(_,"+"),i.concat("(?!",R(E).join("\\b|"),"\\b)"),o],scope:{1:"keyword",4:"title.class"}}]},I=i.concat(s,"\\b(?!\\()"),Z={variants:[{match:[i.concat(/::/,i.lookahead(/(?!class\b)/)),I],scope:{2:"variable.constant"}},{match:[/::/,/class/],scope:{2:"variable.language"}},{match:[o,i.concat(/::/,i.lookahead(/(?!class\b)/)),I],scope:{1:"title.class",3:"variable.constant"}},{match:[o,i.concat("::",i.lookahead(/(?!class\b)/))],scope:{1:"title.class"}},{match:[o,/::/,/class/],scope:{1:"title.class",3:"variable.language"}}]},j={scope:"attr",match:i.concat(s,i.lookahead(":"),i.lookahead(/(?!::)/))},z={relevance:0,begin:/\(/,end:/\)/,keywords:U,contains:[j,d,Z,t.C_BLOCK_COMMENT_MODE,N,S,B]},V={relevance:0,match:[/\b/,i.concat("(?!fn\\b|function\\b|",R(k).join("\\b|"),"|",R(E).join("\\b|"),"\\b)"),s,i.concat(_,"*"),i.lookahead(/(?=\()/)],scope:{3:"title.function.invoke"},contains:[z]};z.contains.push(V);const G=[j,Z,t.C_BLOCK_COMMENT_MODE,N,S,B],A={begin:i.concat(/#\[\s*\\?/,i.either(o,c)),beginScope:"meta",end:/]/,endScope:"meta",keywords:{literal:w,keyword:["new","array"]},contains:[{begin:/\[/,end:/]/,keywords:{literal:w,keyword:["new","array"]},contains:["self",...G]},...G,{scope:"meta",variants:[{match:o},{match:c}]}]};return{case_insensitive:!1,keywords:U,contains:[A,t.HASH_COMMENT_MODE,t.COMMENT("//","$"),t.COMMENT("/\\*","\\*/",{contains:[{scope:"doctag",match:"@[A-Za-z]+"}]}),{match:/__halt_compiler\(\);/,keywords:"__halt_compiler",starts:{scope:"comment",end:t.MATCH_NOTHING_RE,contains:[{match:/\?>/,scope:"meta",endsParent:!0}]}},f,{scope:"variable.language",match:/\$this\b/},d,V,Z,{match:[/const/,/\s/,s],scope:{1:"keyword",3:"variable.constant"}},B,{scope:"function",relevance:0,beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[{beginKeywords:"use"},t.UNDERSCORE_TITLE_MODE,{begin:"=>",endsParent:!0},{scope:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:U,contains:["self",A,d,Z,t.C_BLOCK_COMMENT_MODE,N,S]}]},{scope:"class",variants:[{beginKeywords:"enum",illegal:/[($"]/},{beginKeywords:"cl
Line truncated
|
||||
`)||c))return g.jsx("code",{className:`${e||""} bg-white/8 px-1.5 py-0.5 rounded text-[13px]`,children:t});const f=((w=i==null?void 0:i.data)==null?void 0:w.meta)||((k=i==null?void 0:i.properties)==null?void 0:k.metastring)||"",h=/title=["']?([^"'\s}]+)["']?/.exec(f),m=/startLineNumber=(\d+)/.exec(f),p=(h==null?void 0:h[1])||null,y=m?parseInt(m[1],10):1,x=p?m?`${p}:${y}`:p:null;let _;if(c)try{_=Gn.highlight(o,{language:c[1],ignoreIllegals:!0}).value}catch{_=Gn.highlightAuto(o).value}else _=Gn.highlightAuto(o).value;const N=_.split(`
|
||||
`),S=()=>{xp(o),s(!0),setTimeout(()=>s(!1),2e3)};return g.jsxs("div",{className:"group/code relative rounded-md border border-[#2a2a2a] my-4 text-[#ddd] overflow-hidden",children:[x?g.jsxs("div",{className:"flex items-stretch",children:[g.jsxs("span",{className:"relative flex items-center text-[13px] text-[#999] font-mono px-4 py-2 border-r border-[#2a2a2a]",children:[x,g.jsx("span",{className:"absolute top-0 inset-x-0 h-0.5 bg-white/60 rounded-full"})]}),g.jsx("div",{className:"flex-1 border-b border-[#2a2a2a]"}),g.jsx("button",{onClick:S,className:"px-3 py-2 text-[#555] hover:text-white transition-colors border-b border-[#2a2a2a]","aria-label":"Copy code",children:a?g.jsx(Fs,{className:"w-3.5 h-3.5 text-emerald-400"}):g.jsx(ho,{className:"w-3.5 h-3.5"})})]}):g.jsx("button",{onClick:S,className:"absolute top-2 right-2 z-10 p-1 rounded text-[#444] hover:text-white opacity-0 group-hover/code:opacity-100 transition-opacity","aria-label":"Copy code",children:a?g.jsx(Fs,{className:"w-3.5 h-3.5 text-emerald-400"}):g.jsx(ho,{className:"w-3.5 h-3.5"})}),g.jsx("div",{className:"overflow-auto max-h-[400px]",children:g.jsx("table",{className:"w-full border-collapse font-mono text-[12px] leading-[22px] [font-variant-ligatures:none]",children:g.jsx("tbody",{children:N.map((E,M)=>g.jsxs("tr",{children:[g.jsx("td",{className:"select-none w-[1px] whitespace-nowrap px-4 text-right text-[#555] align-top text-[12px] leading-[22px] border-r border-[#2a2a2a]",children:y+M}),g.jsx("td",{className:"pl-4 pr-4 whitespace-pre",dangerouslySetInnerHTML:{__html:E||`
|
||||
`}})]},M))})})})]})}function $p(){return e=>{const t=i=>{var a;if(i.type==="element"&&i.tagName==="pre"&&i.children){const s=i.children.find(o=>o.type==="element"&&o.tagName==="code");(a=s==null?void 0:s.data)!=null&&a.meta&&(s.properties=s.properties||{},s.properties.metastring=s.data.meta)}i.children&&i.children.forEach(s=>t(s))};t(e)}}const qp={code:iE,pre:({children:e})=>g.jsx(g.Fragment,{children:e})};function aa({title:e,content:t,action:i}){return g.jsxs("section",{children:[(e||i)&&g.jsxs("div",{className:"flex items-center justify-between gap-3 mb-3",children:[e?g.jsx("h2",{className:"text-xl font-semibold text-white",children:e}):g.jsx("span",{}),i]}),g.jsx("div",{className:"prose-markdown",children:g.jsx(zp,{remarkPlugins:[Hp],rehypePlugins:[$p],components:qp,children:t})})]})}class zD{diff(t,i,a={}){let s;typeof a=="function"?(s=a,a={}):"callback"in a&&(s=a.callback);const o=this.castInput(t,a),c=this.castInput(i,a),d=this.removeEmpty(this.tokenize(o,a)),f=this.removeEmpty(this.tokenize(c,a));return this.diffWithOptionsObj(d,f,a,s)}diffWithOptionsObj(t,i,a,s){var o;const c=k=>{if(k=this.postProcess(k,a),s){setTimeout(function(){s(k)},0);return}else return k},d=i.length,f=t.length;let h=1,m=d+f;a.maxEditLength!=null&&(m=Math.min(m,a.maxEditLength));const p=(o=a.timeout)!==null&&o!==void 0?o:1/0,y=Date.now()+p,x=[{oldPos:-1,lastComponent:void 0}];let _=this.extractCommon(x[0],i,t,0,a);if(x[0].oldPos+1>=f&&_+1>=d)return c(this.buildValues(x[0].lastComponent,i,t));let N=-1/0,S=1/0;const w=()=>{for(let k=Math.max(N,-h);k<=Math.min(S,h);k+=2){let E;const M=x[k-1],U=x[k+1];M&&(x[k-1]=void 0);let R=!1;if(U){const I=U.oldPos-k;R=U&&0<=I&&I<d}const B=M&&M.oldPos+1<f;if(!R&&!B){x[k]=void 0;continue}if(!B||R&&M.oldPos<U.oldPos?E=this.addToPath(U,!0,!1,0,a):E=this.addToPath(M,!1,!0,1,a),_=this.extractCommon(E,i,t,k,a),E.oldPos+1>=f&&_+1>=d)return c(this.buildValues(E.lastComponent,i,t))||!0;x[k]=E,E.oldPos+1>=f&&(S=Math.min(S,k-1)),_+1>=d&&(N=Math.max(N,k+1))}h++};if(s)(function k(){setTimeout(function(){if(h>m||Date.now()>y)return s(void 0);w()||k()},0)})();else for(;h<=m&&Date.now()<=y;){const k=w();if(k)return k}}addToPath(t,i,a,s,o){const c=t.lastComponent;return c&&!o.oneChangePerToken&&c.added===i&&c.removed===a?{oldPos:t.oldPos+s,lastComponent:{count:c.count+1,added:i,removed:a,previousComponent:c.previousComponent}}:{oldPos:t.oldPos+s,lastComponent:{count:1,added:i,removed:a,previousComponent:c}}}extractCommon(t,i,a,s,o){const c=i.length,d=a.length;let f=t.oldPos,h=f-s,m=0;for(;h+1<c&&f+1<d&&this.equals(a[f+1],i[h+1],o);)h++,f++,m++,o.oneChangePerToken&&(t.lastComponent={count:1,previousComponent:t.lastComponent,added:!1,removed:!1});return m&&!o.oneChangePerToken&&(t.lastComponent={count:m,previousComponent:t.lastComponent,added:!1,removed:!1}),t.oldPos=f,h}equals(t,i,a){return a.comparator?a.comparator(t,i):t===i||!!a.ignoreCase&&t.toLowerCase()===i.toLowerCase()}removeEmpty(t){const i=[];for(let a=0;a<t.length;a++)t[a]&&i.push(t[a]);return i}castInput(t,i){return t}tokenize(t,i){return Array.from(t)}join(t){return t.join("")}postProcess(t,i){return t}get useLongestToken(){return!1}buildValues(t,i,a){const s=[];let o;for(;t;)s.push(t),o=t.previousComponent,delete t.previousComponent,t=o;s.reverse();const c=s.length;let d=0,f=0,h=0;for(;d<c;d++){const m=s[d];if(m.removed)m.value=this.join(a.slice(h,h+m.count)),h+=m.count;else{if(!m.added&&this.useLongestToken){let p=i.slice(f,f+m.count);p=p.map(function(y,x){const _=a[h+x];return _.length>y.length?_:y}),m.value=this.join(p)}else m.value=this.join(i.slice(f,f+m.count));f+=m.count,m.added||(h+=m.count)}}return s}}class ID extends zD{constructor(){super(...arguments),this.tokenize=HD}equals(t,i,a){return a.ignoreWhitespace?((!a.newlineIsToken||!t.includes(`
|
||||
`))&&(t=t.trim()),(!a.newlineIsToken||!i.includes(`
|
||||
`))&&(i=i.trim())):a.ignoreNewlineAtEof&&!a.newlineIsToken&&(t.endsWith(`
|
||||
`)&&(t=t.slice(0,-1)),i.endsWith(`
|
||||
`)&&(i=i.slice(0,-1))),super.equals(t,i,a)}}const BD=new ID;function UD(e,t,i){return BD.diff(e,t,i)}function HD(e,t){t.stripTrailingCr&&(e=e.replace(/\r\n/g,`
|
||||
`));const i=[],a=e.split(/(\n|\r\n)/);a[a.length-1]||a.pop();for(let s=0;s<a.length;s++){const o=a[s];s%2&&!t.newlineIsToken?i[i.length-1]+=o:i.push(o)}return i}function $D(e,t){try{return Gn.highlight(e,{language:t,ignoreIllegals:!0}).value}catch{return Gn.highlightAuto(e).value}}function qD({file:e,startLine:t,endLine:i,before:a,after:s,onCopy:o}){const[c,d]=ee.useState(!1),f=i&&i!==t?`${t}-${i}`:`${t}`,h=SA(e)||"text",m=UD(a,s);let p=t,y=t;const x=m.flatMap(N=>N.value.replace(/\n$/,"").split(`
|
||||
`).map(S=>{const w=S===""?`
|
||||
`:h!=="text"?$D(S,h):S.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");let k="",E="";return N.removed?k=String(p++):(N.added||(k=String(p++)),E=String(y++)),{highlighted:w,added:!!N.added,removed:!!N.removed,leftNo:k,rightNo:E}})),_=()=>{xp(s),d(!0),setTimeout(()=>d(!1),2e3),o==null||o()};return g.jsxs("div",{className:"rounded-md border border-[#2a2a2a] overflow-hidden",children:[g.jsxs("div",{className:"flex items-stretch",children:[g.jsxs("span",{className:"relative flex items-center text-[13px] text-[#999] font-mono px-4 py-2 border-r border-[#2a2a2a] break-all",children:[e,":",f,g.jsx("span",{className:"absolute top-0 inset-x-0 h-0.5 bg-white/60 rounded-full"})]}),g.jsx("div",{className:"flex-1 border-b border-[#2a2a2a]"}),g.jsx("button",{onClick:_,className:"px-3 py-2 text-[#555] hover:text-white transition-colors flex-shrink-0 border-b border-[#2a2a2a]","aria-label":"Copy fixed code",children:c?g.jsx(Fs,{className:"w-3.5 h-3.5 text-emerald-400"}):g.jsx(ho,{className:"w-3.5 h-3.5"})})]}),g.jsx("div",{className:"overflow-auto max-h-[400px]",children:g.jsx("table",{className:"w-full border-collapse font-mono text-[12px] leading-[22px] [font-variant-ligatures:none]",children:g.jsx("tbody",{children:x.map((N,S)=>g.jsxs("tr",{className:N.added?"bg-blue-500/[0.12]":N.removed?"bg-red-500/[0.12]":"",children:[g.jsx("td",{className:"select-none w-[1px] whitespace-nowrap pl-4 pr-1.5 text-right text-[#555] align-top text-[12px] leading-[22px]",children:N.leftNo}),g.jsx("td",{className:"select-none w-[1px] whitespace-nowrap pl-1.5 pr-4 text-right text-[#555] align-top text-[12px] leading-[22px] border-r border-[#2a2a2a]",children:N.rightNo}),g.jsx("td",{className:"pl-4 pr-4 whitespace-pre",dangerouslySetInnerHTML:{__html:N.highlighted}})]},S))})})})]})}function PD({description:e,scriptCode:t,onCopy:i}){const[a,s]=ee.useState(!1);if(!e&&!t)return null;const o=()=>{t&&(xp(t),s(!0),setTimeout(()=>s(!1),2e3),i==null||i())};return g.jsxs("section",{children:[g.jsx("h2",{className:"text-xl font-semibold text-white mb-3",children:"Proof of Concept"}),g.jsxs("div",{className:"space-y-4",children:[e&&g.jsx("div",{className:"prose-markdown",children:g.jsx(zp,{remarkPlugins:[Hp],rehypePlugins:[$p],components:qp,children:e})}),t&&g.jsxs("div",{className:"group/poc relative rounded-md border border-[#2a2a2a] overflow-hidden",children:[g.jsxs("div",{className:"flex items-stretch",children:[g.jsxs("span",{className:"relative flex items-center text-[13px] text-[#999] font-mono px-4 py-2 border-r border-[#2a2a2a]",children:["PoC Script",g.jsx("span",{className:"absolute top-0 inset-x-0 h-0.5 bg-white/60 rounded-full"})]}),g.jsx("div",{className:"flex-1 border-b border-[#2a2a2a]"}),g.jsx("button",{onClick:o,className:"px-3 py-2 text-[#555] hover:text-white transition-colors flex-shrink-0 border-b border-[#2a2a2a]","aria-label":"Copy PoC code",children:a?g.jsx(Fs,{className:"w-3.5 h-3.5 text-emerald-400"}):g.jsx(ho,{className:"w-3.5 h-3.5"})})]}),g.jsx("div",{className:"overflow-auto max-h-[400px] px-4 py-3",children:g.jsx("pre",{className:"font-mono text-[12px] leading-[22px] whitespace-pre [font-variant-ligatures:none]",children:g.jsx("code",{dangerouslySetInnerHTML:{__html:Gn.highlight(t,{language:"python"}).value}})})})]})]})]})}function rE(e){const t=e.match(/(?:https?:\/\/)?(?:www\.)?github\.com\/([^\s/]+\/[^\s/]+)/);if(t){const s=t[1].replace(/\.git$/,"");return{display:s,href:`https://github.com/${s}`,provider:"github"}}const i=e.match(/(?:https?:\/\/)?(?:www\.)?gitlab\.com\/([^\s/]+\/[^\s/]+)/);if(i){const s=i[1].replace(/\.git$/,"");return{display:s,href:`https://gitlab.com/${s}`,provider:"gitlab"}}const a=e.match(/(?:https?:\/\/)?(?:www\.)?bitbucket\.org\/([^\s/]+\/[^\s/]+)/);if(a){const s=a[1].replace(/\.git$/,"");return{display:s,href:`https://bitbucket.org/${s}`,provider:"bitbucket"}}return/^https?:\/\//i.test(e)?{display:e.replace(/^https?:\/\/(www\.)?/,""),href:e,provider:null}:/^[a-zA-Z0-9][\w.-]*\.[a-zA-Z]{2,}/.test(e)?{display:e,href:`https://${e}`,provider:null}:{display:e,href:null,provider:null}}function go(e,t){return e?rE(e).display.replace(/\/$/,""):t||"Untitled scan"}function FD({className:e}){return g.jsx("svg",{viewBox:"0 0 24 24",fill:"currentColor",className:e,"aria-hidden":"true",children:g.jsx("path",{d:"M2.65 3a.72.72 0 0 0-.72.83l2.86 17.39a.98.98 0 0 0 .96.82h13.72a.72.72 0 0 0 .72-.6l2.86-17.4A.72.72 0 0 0 22.3 3H2.65Zm12.1 12.53H9.3L8.06 8.9h7.8l-1.11 6.63Z"})})}function GD({provider:e,className:t}){const i=t??"w-4 h-4";return e==="gitlab"?g.jsx(xT,{className:`${i} text-orange-400`}):e==="bitbucket"?g.jsx(FD,{className:`${i} text-blue-400`}):g.jsx(gT,{className:`${i} text-white`})}const VD={attack_vector:{N:"Remotely exploitable",A:"Adjacent network",L:"Local access required",P:"Physical access required"},attack_complexity:{L:"Easy to exploit",H:"Requires specific conditions"},privileges_required:{N:"No authentication needed",L:"Low privileges needed",H:"High privileges needed"}
Line truncated
|
||||
* @license React
|
||||
* use-sync-external-store-shim.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var m1;function O6(){if(m1)return Sm;m1=1;var e=To();function t(p,y){return p===y&&(p!==0||1/p===1/y)||p!==p&&y!==y}var i=typeof Object.is=="function"?Object.is:t,a=e.useState,s=e.useEffect,o=e.useLayoutEffect,c=e.useDebugValue;function d(p,y){var x=y(),_=a({inst:{value:x,getSnapshot:y}}),N=_[0].inst,S=_[1];return o(function(){N.value=x,N.getSnapshot=y,f(N)&&S({inst:N})},[p,x,y]),s(function(){return f(N)&&S({inst:N}),p(function(){f(N)&&S({inst:N})})},[p]),c(x),x}function f(p){var y=p.getSnapshot;p=p.value;try{var x=y();return!i(p,x)}catch{return!0}}function h(p,y){return y()}var m=typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"?h:d;return Sm.useSyncExternalStore=e.useSyncExternalStore!==void 0?e.useSyncExternalStore:m,Sm}var p1;function R6(){return p1||(p1=1,Nm.exports=O6()),Nm.exports}/**
|
||||
* @license React
|
||||
* use-sync-external-store-shim/with-selector.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var g1;function D6(){if(g1)return Em;g1=1;var e=To(),t=R6();function i(h,m){return h===m&&(h!==0||1/h===1/m)||h!==h&&m!==m}var a=typeof Object.is=="function"?Object.is:i,s=t.useSyncExternalStore,o=e.useRef,c=e.useEffect,d=e.useMemo,f=e.useDebugValue;return Em.useSyncExternalStoreWithSelector=function(h,m,p,y,x){var _=o(null);if(_.current===null){var N={hasValue:!1,value:null};_.current=N}else N=_.current;_=d(function(){function w(R){if(!k){if(k=!0,E=R,R=y(R),x!==void 0&&N.hasValue){var B=N.value;if(x(B,R))return M=B}return M=R}if(B=M,a(E,R))return B;var I=y(R);return x!==void 0&&x(B,I)?(E=R,B):(E=R,M=I)}var k=!1,E,M,U=p===void 0?null:p;return[function(){return w(m())},U===null?void 0:function(){return w(U())}]},[m,p,y,x]);var S=s(h,_[0],_[1]);return c(function(){N.hasValue=!0,N.value=S},[S]),f(S),S},Em}var b1;function j6(){return b1||(b1=1,wm.exports=D6()),wm.exports}var L6=j6();const z6=ko(L6),I6={},x1=e=>{let t;const i=new Set,a=(m,p)=>{const y=typeof m=="function"?m(t):m;if(!Object.is(y,t)){const x=t;t=p??(typeof y!="object"||y===null)?y:Object.assign({},t,y),i.forEach(_=>_(t,x))}},s=()=>t,f={setState:a,getState:s,getInitialState:()=>h,subscribe:m=>(i.add(m),()=>i.delete(m)),destroy:()=>{(I6?"production":void 0)!=="production"&&console.warn("[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected."),i.clear()}},h=t=e(a,s,f);return f},B6=e=>e?x1(e):x1,{useDebugValue:U6}=h2,{useSyncExternalStoreWithSelector:H6}=z6,$6=e=>e;function dN(e,t=$6,i){const a=H6(e.subscribe,e.getState,e.getServerState||e.getInitialState,t,i);return U6(a),a}const y1=(e,t)=>{const i=B6(e),a=(s,o=t)=>dN(i,s,o);return Object.assign(a,i),a},q6=(e,t)=>e?y1(e,t):y1;function qt(e,t){if(Object.is(e,t))return!0;if(typeof e!="object"||e===null||typeof t!="object"||t===null)return!1;if(e instanceof Map&&t instanceof Map){if(e.size!==t.size)return!1;for(const[a,s]of e)if(!Object.is(s,t.get(a)))return!1;return!0}if(e instanceof Set&&t instanceof Set){if(e.size!==t.size)return!1;for(const a of e)if(!t.has(a))return!1;return!0}const i=Object.keys(e);if(i.length!==Object.keys(t).length)return!1;for(const a of i)if(!Object.prototype.hasOwnProperty.call(t,a)||!Object.is(e[a],t[a]))return!1;return!0}x_();const ed=ee.createContext(null),P6=ed.Provider,fN=Di.error001("react");function dt(e,t){const i=ee.useContext(ed);if(i===null)throw new Error(fN);return dN(i,e,t)}function Lt(){const e=ee.useContext(ed);if(e===null)throw new Error(fN);return ee.useMemo(()=>({getState:e.getState,setState:e.setState,subscribe:e.subscribe}),[e])}const v1={display:"none"},F6={position:"absolute",width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0px, 0px, 0px, 0px)",clipPath:"inset(100%)"},hN="react-flow__node-desc",mN="react-flow__edge-desc",G6="react-flow__aria-live",V6=e=>e.ariaLiveMessage,Y6=e=>e.ariaLabelConfig;function X6({rfId:e}){const t=dt(V6);return g.jsx("div",{id:`${G6}-${e}`,"aria-live":"assertive","aria-atomic":"true",style:F6,children:t})}function K6({rfId:e,disableKeyboardA11y:t}){const i=dt(Y6);return g.jsxs(g.Fragment,{children:[g.jsx("div",{id:`${hN}-${e}`,style:v1,children:t?i["node.a11yDescription.default"]:i["node.a11yDescription.keyboardDisabled"]}),g.jsx("div",{id:`${mN}-${e}`,style:v1,children:i["edge.a11yDescription.default"]}),!t&&g.jsx(X6,{rfId:e})]})}const td=ee.forwardRef(({position:e="top-left",children:t,className:i,style:a,...s},o)=>{const c=`${e}`.split("-");return g.jsx("div",{className:ln(["react-flow__panel",i,...c]),style:a,ref:o,...s,children:t})});td.displayName="Panel";const _1="https://reactflow.dev?utm_source=attribution";function Z6({proOptions:e,position:t="bottom-right"}){return e!=null&&e.hideAttribution?null:g.jsx(td,{position:t,className:"react-flow__attribution","data-message":`Please only hide this attribution when you are subscribed to React Flow Pro: ${_1}`,children:g.jsx("a",{href:_1,target:"_blank",rel:"noopener noreferrer","aria-label":"React Flow attribution",children:"React Flow"})})}const Q6=e=>{const t=[],i=[];for(const[,a]of e.nodeLookup)a.selected&&t.push(a.internals.userNode);for(const[,a]of e.edgeLookup)a.selected&&i.push(a);return{selectedNodes:t,selectedEdges:i}},uu=e=>e.id;function W6(e,t){return qt(e.selectedNodes.map(uu),t.selectedNodes.map(uu))&&qt(e.selectedEdges.map(uu),t.selectedEdges.map(uu))}function J6({onSelectionChange:e}){const t=Lt(),{selectedNodes:i,selectedEdges:a}=dt(Q6,W6);return ee.useEffect(()=>{const s={nodes:i,edges:a};e==null||e(s),t.getState().onSelectionChangeHandlers.forEach(o=>o(s))},[i,a,e]),null}const e8=e=>!!e.onSelectionChangeHandlers;function t8({onSelectionChange:e}){const t=dt(e8);return e||t?g.jsx(J6,{onSelectionChange:e}):null}const pN=[0,0],n8={x:0,y:0,zoom:1},i8=["nodes","edges","defaultNodes","defaultEdges","onConnect","onConnectStart","onConnectEnd","onClickConnectStart","onClickConnectEnd","nodesDraLine truncated
|
||||
`).replace(`
|
||||
|
||||
`,`
|
||||
+`).split(`
|
||||
`)),m=h.reduce((p,y)=>p.concat(...y),[]);return[h,m]}return[[],[]]},[e]);return ee.useEffect(()=>{const f=(t==null?void 0:t.target)??S1,h=(t==null?void 0:t.actInsideInputWithModifier)??!0;if(e!==null){const m=x=>{var S,w;if(s.current=x.ctrlKey||x.metaKey||x.shiftKey||x.altKey,(!s.current||s.current&&!h)&&XE(x))return!1;const N=T1(x.code,d);if(o.current.add(x[N]),k1(c,o.current,!1)){const k=((w=(S=x.composedPath)==null?void 0:S.call(x))==null?void 0:w[0])||x.target,E=(k==null?void 0:k.nodeName)==="BUTTON"||(k==null?void 0:k.nodeName)==="A";t.preventDefault!==!1&&(s.current||!E)&&x.preventDefault(),a(!0)}},p=x=>{const _=T1(x.code,d);k1(c,o.current,!0)?(a(!1),o.current.clear()):o.current.delete(x[_]),x.key==="Meta"&&o.current.clear(),s.current=!1},y=()=>{o.current.clear(),a(!1)};return f==null||f.addEventListener("keydown",m),f==null||f.addEventListener("keyup",p),window.addEventListener("blur",y),window.addEventListener("contextmenu",y),()=>{f==null||f.removeEventListener("keydown",m),f==null||f.removeEventListener("keyup",p),window.removeEventListener("blur",y),window.removeEventListener("contextmenu",y)}}},[e,a]),i}function k1(e,t,i){return e.filter(a=>i||a.length===t.size).some(a=>a.every(s=>t.has(s)))}function T1(e,t){return t.includes(e)?"code":"key"}const l8=()=>{const e=Lt();return ee.useMemo(()=>({zoomIn:async t=>{const{panZoom:i}=e.getState();return i?i.scaleBy(1.2,t):!1},zoomOut:async t=>{const{panZoom:i}=e.getState();return i?i.scaleBy(1/1.2,t):!1},zoomTo:async(t,i)=>{const{panZoom:a}=e.getState();return a?a.scaleTo(t,i):!1},getZoom:()=>e.getState().transform[2],setViewport:async(t,i)=>{const{transform:[a,s,o],panZoom:c}=e.getState();return c?(await c.setViewport({x:t.x??a,y:t.y??s,zoom:t.zoom??o},i),!0):!1},getViewport:()=>{const[t,i,a]=e.getState().transform;return{x:t,y:i,zoom:a}},setCenter:async(t,i,a)=>e.getState().setCenter(t,i,a),fitBounds:async(t,i)=>{const{width:a,height:s,minZoom:o,maxZoom:c,panZoom:d}=e.getState(),f=Jp(t,a,s,o,c,(i==null?void 0:i.padding)??.1);return d?(await d.setViewport(f,{duration:i==null?void 0:i.duration,ease:i==null?void 0:i.ease,interpolate:i==null?void 0:i.interpolate}),!0):!1},screenToFlowPosition:(t,i={})=>{const{transform:a,snapGrid:s,snapToGrid:o,domNode:c}=e.getState();if(!c)return t;const{x:d,y:f}=c.getBoundingClientRect(),h={x:t.x-d,y:t.y-f},m=i.snapGrid??s,p=i.snapToGrid??o;return Io(h,a,p,m)},flowToScreenPosition:t=>{const{transform:i,domNode:a}=e.getState();if(!a)return t;const{x:s,y:o}=a.getBoundingClientRect(),c=Qs(t,i);return{x:c.x+s,y:c.y+o}}}),[])};function gN(e,t){const i=[],a=new Map,s=[];for(const o of e)if(o.type==="add"){s.push(o);continue}else if(o.type==="remove"||o.type==="replace")a.set(o.id,[o]);else{const c=a.get(o.id);c?c.push(o):a.set(o.id,[o])}for(const o of t){const c=a.get(o.id);if(!c){i.push(o);continue}if(c[0].type==="remove")continue;if(c[0].type==="replace"){i.push({...c[0].item});continue}const d={...o};for(const f of c)o8(f,d);i.push(d)}return s.length&&s.forEach(o=>{o.index!==void 0?i.splice(o.index,0,{...o.item}):i.push({...o.item})}),i}function o8(e,t){switch(e.type){case"select":{t.selected=e.selected;break}case"position":{typeof e.position<"u"&&(t.position=e.position),typeof e.dragging<"u"&&(t.dragging=e.dragging);break}case"dimensions":{typeof e.dimensions<"u"&&(t.measured={...e.dimensions},e.setAttributes&&((e.setAttributes===!0||e.setAttributes==="width")&&(t.width=e.dimensions.width),(e.setAttributes===!0||e.setAttributes==="height")&&(t.height=e.dimensions.height))),typeof e.resizing=="boolean"&&(t.resizing=e.resizing);break}}}function bN(e,t){return gN(e,t)}function xN(e,t){return gN(e,t)}function Ia(e,t){return{id:e,type:"select",selected:t}}function Bs(e,t=new Set,i=!1){const a=[];for(const[s,o]of e){const c=t.has(s);!(o.selected===void 0&&!c)&&o.selected!==c&&(i&&(o.selected=c),a.push(Ia(o.id,c)))}return a}function A1({items:e=[],lookup:t}){var s;const i=[],a=new Map(e.map(o=>[o.id,o]));for(const[o,c]of e.entries()){const d=t.get(c.id),f=((s=d==null?void 0:d.internals)==null?void 0:s.userNode)??d;f!==void 0&&f!==c&&i.push({id:c.id,item:c,type:"replace"}),f===void 0&&i.push({item:c,type:"add",index:o})}for(const[o]of t)a.get(o)===void 0&&i.push({id:o,type:"remove"});return i}function C1(e){return{id:e.id,type:"remove"}}const c8=FE();function u8(e,t,i={}){return KI(e,t,{...i,onError:i.onError??c8})}const M1=e=>jI(e),d8=e=>UE(e);function yN(e){return ee.forwardRef(e)}const vN=typeof window<"u"?ee.useLayoutEffect:ee.useEffect;function O1(e){const[t,i]=ee.useState(BigInt(0)),[a]=ee.useState(()=>f8(()=>i(s=>s+BigInt(1))));return vN(()=>{const s=a.get();s.length&&(e(s),a.reset())},[t]),a}function f8(e){let t=[];return{get:()=>t,reset:()=>{t=[]},push:i=>{t.push(i),e()}}}const _N=ee.createContext(null);function h8({children:e}){const t=Lt(),i=ee.useCallback(d=>{const{nodes:f=[],setNodes:h,hasDefaultNodes:m,onNodesChange:p,nodeLookup:y,fitViewQueued:x,onNodesChangeMiddlewareMap:_}=t.getState();let N=f;for(const w of d)N=
Line truncated
|
||||
M${I.x},${I.y}h${I.width}v${I.height}h${-I.width}z`,fillRule:"evenodd",pointerEvents:"none"})]})})}YN.displayName="MiniMap";const l9=ee.memo(YN),o9=e=>t=>e?`${Math.max(1/t.transform[2],1)}`:void 0,c9={[Ws.Line]:"right",[Ws.Handle]:"bottom-right"};function u9({nodeId:e,position:t,variant:i=Ws.Handle,className:a,style:s=void 0,children:o,color:c,minWidth:d=10,minHeight:f=10,maxWidth:h=Number.MAX_VALUE,maxHeight:m=Number.MAX_VALUE,keepAspectRatio:p=!1,resizeDirection:y,autoScale:x=!0,shouldResize:_,onResizeStart:N,onResize:S,onResizeEnd:w}){const k=NN(),E=typeof e=="string"?e:k,M=Lt(),U=ee.useRef(null),R=i===Ws.Handle,B=dt(ee.useCallback(o9(R&&x),[R,x]),qt),I=ee.useRef(null),Z=t??c9[i];ee.useEffect(()=>{if(!(!U.current||!E))return I.current||(I.current=M6({domNode:U.current,nodeId:E,getStoreItems:()=>{const{nodeLookup:z,transform:V,snapGrid:G,snapToGrid:A,nodeOrigin:q,domNode:O}=M.getState();return{nodeLookup:z,transform:V,snapGrid:G,snapToGrid:A,nodeOrigin:q,paneDomNode:O}},onChange:(z,V)=>{const{triggerNodeChanges:G,nodeLookup:A,parentLookup:q,nodeOrigin:O}=M.getState(),H=[],K={x:z.x,y:z.y},X=A.get(E);if(X&&X.expandParent&&X.parentId){const C=X.origin??O,L=z.width??X.measured.width??0,Y=z.height??X.measured.height??0,D={id:X.id,parentId:X.parentId,rect:{width:L,height:Y,...VE({x:z.x??X.position.x,y:z.y??X.position.y},{width:L,height:Y},X.parentId,A,C)}},F=ag([D],A,q,O);H.push(...F),K.x=z.x?Math.max(C[0]*L,z.x):void 0,K.y=z.y?Math.max(C[1]*Y,z.y):void 0}if(K.x!==void 0&&K.y!==void 0){const C={id:E,type:"position",position:{...K}};H.push(C)}if(z.width!==void 0&&z.height!==void 0){const L={id:E,type:"dimensions",resizing:!0,setAttributes:y?y==="horizontal"?"width":"height":!0,dimensions:{width:z.width,height:z.height}};H.push(L)}for(const C of V){const L={...C,type:"position"};H.push(L)}G(H)},onEnd:({width:z,height:V})=>{const G={id:E,type:"dimensions",resizing:!1,dimensions:{width:z,height:V}};M.getState().triggerNodeChanges([G])}})),I.current.update({controlPosition:Z,boundaries:{minWidth:d,minHeight:f,maxWidth:h,maxHeight:m},keepAspectRatio:p,resizeDirection:y,onResizeStart:N,onResize:S,onResizeEnd:w,shouldResize:_}),()=>{var z;(z=I.current)==null||z.destroy()}},[Z,d,f,h,m,p,N,S,w,_]);const j=Z.split("-");return g.jsx("div",{className:ln(["react-flow__resize-control","nodrag",...j,i,a]),ref:U,style:{...s,scale:B,...c&&{[R?"backgroundColor":"borderColor"]:c}},children:o})}ee.memo(u9);var vt=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),ji=vt((e,t)=>{var i=Object.defineProperty,a=(G,A,q)=>A in G?i(G,A,{enumerable:!0,configurable:!0,writable:!0,value:q}):G[A]=q,s=(G,A)=>()=>(A||G((A={exports:{}}).exports,A),A.exports),o=(G,A,q)=>a(G,typeof A!="symbol"?A+"":A,q),c=s((G,A)=>{var q="\0",O="\0",H="",K=class{constructor(F){o(this,"_isDirected",!0),o(this,"_isMultigraph",!1),o(this,"_isCompound",!1),o(this,"_label"),o(this,"_defaultNodeLabelFn",()=>{}),o(this,"_defaultEdgeLabelFn",()=>{}),o(this,"_nodes",{}),o(this,"_in",{}),o(this,"_preds",{}),o(this,"_out",{}),o(this,"_sucs",{}),o(this,"_edgeObjs",{}),o(this,"_edgeLabels",{}),o(this,"_nodeCount",0),o(this,"_edgeCount",0),o(this,"_parent"),o(this,"_children"),F&&(this._isDirected=Object.hasOwn(F,"directed")?F.directed:!0,this._isMultigraph=Object.hasOwn(F,"multigraph")?F.multigraph:!1,this._isCompound=Object.hasOwn(F,"compound")?F.compound:!1),this._isCompound&&(this._parent={},this._children={},this._children[O]={})}isDirected(){return this._isDirected}isMultigraph(){return this._isMultigraph}isCompound(){return this._isCompound}setGraph(F){return this._label=F,this}graph(){return this._label}setDefaultNodeLabel(F){return this._defaultNodeLabelFn=F,typeof F!="function"&&(this._defaultNodeLabelFn=()=>F),this}nodeCount(){return this._nodeCount}nodes(){return Object.keys(this._nodes)}sources(){var F=this;return this.nodes().filter($=>Object.keys(F._in[$]).length===0)}sinks(){var F=this;return this.nodes().filter($=>Object.keys(F._out[$]).length===0)}setNodes(F,$){var Q=arguments,J=this;return F.forEach(function(W){Q.length>1?J.setNode(W,$):J.setNode(W)}),this}setNode(F,$){return Object.hasOwn(this._nodes,F)?(arguments.length>1&&(this._nodes[F]=$),this):(this._nodes[F]=arguments.length>1?$:this._defaultNodeLabelFn(F),this._isCompound&&(this._parent[F]=O,this._children[F]={},this._children[O][F]=!0),this._in[F]={},this._preds[F]={},this._out[F]={},this._sucs[F]={},++this._nodeCount,this)}node(F){return this._nodes[F]}hasNode(F){return Object.hasOwn(this._nodes,F)}removeNode(F){var $=this;if(Object.hasOwn(this._nodes,F)){var Q=J=>$.removeEdge($._edgeObjs[J]);delete this._nodes[F],this._isCompound&&(this._removeFromParentsChildList(F),delete this._parent[F],this.children(F).forEach(function(J){$.setParent(J)}),delete this._children[F]),Object.keys(this._in[F]).forEach(Q),delete this._in[F],delete this._preds[F],Object.keys(this._out[F]).forEach(Q),delete this._out[F],delete this._sucs[F],--this._nodeCount}return this}setParent(F,$){if(!this._isCompound
Line truncated
|
||||
`).length>t;return g.jsxs("div",{children:[g.jsx("div",{className:i&&o?"max-h-[1200px] overflow-auto":"",style:!i&&o?{display:"-webkit-box",WebkitLineClamp:t,WebkitBoxOrient:"vertical",overflow:"hidden"}:void 0,children:g.jsx(Us,{text:e})}),o&&g.jsx("button",{onClick:()=>a(!i),className:"text-xs text-[#555] hover:text-[#888] mt-1",children:i?"Show less":"Show more"})]})}function vr({children:e,className:t=""}){const[i,a]=ee.useState(!1),o=typeof e=="string"?e.trimEnd().split(`
|
||||
`):null,c=o!==null&&o.length>X1,d=c&&!i?o.slice(0,X1).join(`
|
||||
`):e;return g.jsxs("div",{children:[g.jsx("pre",{className:`font-mono text-[13px] leading-relaxed whitespace-pre-wrap break-words mt-1 ${i?"overflow-auto max-h-[1200px]":"overflow-hidden"} ${t}`,children:d}),c&&g.jsx("button",{onClick:()=>a(!i),className:"text-xs text-[#555] hover:text-[#888] mt-0.5",children:i?"Show less":"Show more"})]})}function lg({code:e,language:t,className:i="",collapsible:a=!1}){const[s,o]=ee.useState(!1),c=e.trimEnd().split(`
|
||||
`),d=a&&c.length>K1,f=d&&!s?c.slice(0,K1).join(`
|
||||
`):e;let h;try{h=t?Gn.highlight(f,{language:t,ignoreIllegals:!0}).value:Gn.highlightAuto(f).value}catch{h=Gn.highlightAuto(f).value}return g.jsxs("div",{children:[g.jsx("pre",{className:`font-mono text-[12px] leading-relaxed px-0 py-1 mt-1 whitespace-pre-wrap break-all ${a?s?"overflow-auto max-h-[1200px]":"overflow-hidden":"overflow-auto max-h-[400px]"} ${i}`,children:g.jsx("code",{dangerouslySetInnerHTML:{__html:h}})}),d&&g.jsx("button",{onClick:()=>o(!s),className:"text-xs text-[#555] hover:text-[#888] mt-0.5",children:s?"Show less":"Show more"})]})}const F9=50,Z1=200,Q1=25,W1=24,G9=[/\n?\[Command still running after [\d.]+s - showing output so far\.?\s*(?:Use C-c to interrupt if needed\.)?\]/g,/^\[Below is the output of the previous command\.\]\n?/gm,/^No command is currently running\. Cannot send input\.$/gm,/^A command is already running\. Use is_input=true to send input to it, or interrupt it first \(e\.g\., with C-c\)\.$/gm],V9=/^Chunk ID: [0-9a-f]+\s*$/,Y9=[/^Wall time: [\d.]+ seconds\s*$/,/^Process exited with code -?\d+\s*$/,/^Process running with session ID \d+\s*$/,/^Original token count: \d+\s*$/];function X9(e){const t=[];for(let i=0;i<e.length;i++){if(V9.test(e[i])){let a=i+1;for(;a<e.length&&Y9.some(s=>s.test(e[a]));)a++;a<e.length&&e[a].trim()==="Output:"&&a++,i=a-1;continue}t.push(e[i])}return t}function K9(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Cm(e){return e.length>Z1?e.slice(0,Z1-3)+"...":e}function Z9(e,t=""){let i=e.replace(/\x1b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07]*\x07)/g,"").replace(/\r/g,"");for(const a of G9)i=i.replace(a,"");if(i.trim()){const a=X9(i.split(`
|
||||
`)),s=[];for(const o of a)s.length===0&&!o.trim()||/^\[STRIX_\d+\]\$\s*/.test(o)||t&&o.trim()===t.trim()||t&&new RegExp(`^[\\$#>]\\s*${K9(t.trim())}\\s*$`).test(o)||s.push(o);for(;s.length>0&&/^\[STRIX_\d+\]\$\s*/.test(s[s.length-1]);)s.pop();i=s.join(`
|
||||
`)}return i.trim()}function Q9(e){const t=e.split(`
|
||||
`);if(t.length<=F9)return t.map(Cm).join(`
|
||||
`);const i=t.length-Q1-W1;return[...t.slice(0,Q1).map(Cm),`... ${i} lines truncated ...`,...t.slice(-W1).map(Cm)].join(`
|
||||
`)}function W9({toolName:e,args:t,result:i}){const a=e==="write_stdin",s=a?t.chars??t.input??"":t.command??t.cmd??"",o=i;let c=null,d=null,f=null;if(o&&typeof o=="object"){c=typeof o.content=="string"?o.content:null,d=typeof o.error=="string"?o.error:null,f=typeof o.exit_code=="number"?o.exit_code:null;const m=typeof o.status=="string"?o.status:"";(m==="running"||m==="command still running")&&(c=null)}else typeof o=="string"&&(c=o);const h=c?Q9(Z9(c,s)):null;return g.jsxs("div",{children:[g.jsx("span",{className:"text-emerald-400/80 font-semibold text-sm",children:a?"Terminal input":"Terminal"}),s&&g.jsx(lg,{code:s,language:"bash",collapsible:!0}),d&&g.jsx(vr,{className:"text-red-400/70",children:d}),h&&g.jsx(vr,{className:"text-[#666]",children:h}),f!=null&&f!==0&&g.jsxs("div",{className:"font-mono text-[13px] text-red-400/70 mt-0.5",children:["exit code ",f]})]})}const J1={back:"going back in browser history",forward:"going forward in browser history",scroll_down:"scrolling down",scroll_up:"scrolling up",refresh:"refreshing",close_tab:"closing tab",switch_tab:"switching tab",list_tabs:"listing tabs",view_source:"viewing page source",get_console_logs:"getting console logs",screenshot:"taking screenshot",wait:"waiting...",close:"closing"},e_={click:"clicking",double_click:"double clicking",hover:"hovering"};function Mm({prefix:e,url:t,suffix:i}){return g.jsxs("span",{className:"text-[#888] text-[13px]",children:[e,t&&g.jsx("a",{href:t,target:"_blank",rel:"noopener noreferrer",className:"text-cyan-400/80 hover:underline",children:t}),i]})}function J9(e){const t=e.action??"",i=e.url??void 0;if(t in J1)return J1[t];if(t==="launch")return i?g.jsx(Mm,{prefix:"launching ",url:i}):"launching";if(t==="goto"||t==="navigate")return g.jsx(Mm,{prefix:"navigating to ",url:i});if(t==="new_tab")return g.jsx(Mm,{prefix:"opening tab ",url:i});if(t in e_)return e_[t];if(t==="type")return`typing "${(e.text??"").slice(0,40)}"`;if(t==="press_key"||t==="key_press")return`pressing key ${e.key??""}`;if(t==="save_pdf"||t==="save_as_pdf"){const a=e.file_path??"";return`saving PDF${a?` to ${a}`:""}`}return t==="execute_js"?"executing javascript":t||"browser action"}function eU({args:e}){const i=(e.action??"")==="execute_js"?e.js_code??e.code??"":"",a=J9(e);return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-center gap-2 min-w-0",children:[g.jsx("span",{className:"text-blue-400/80 font-semibold text-sm shrink-0",children:"Browser"}),g.jsx("span",{className:"min-w-0 truncate text-[#888] text-[13px]",children:a})]}),i&&g.jsx(lg,{code:i,language:"javascript",collapsible:!0})]})}function og(e){return e.length>60?"..."+e.slice(-57):e}const fu=30;function tU({toolName:e,args:t}){const i=t.path??t.file_path??"",a=t.command??"",s=t.old_str??"",o=t.new_str??"",c=t.regex??"";let d;e==="list_files"?d="list":e==="search_files"?d="search":a==="view"?d="view":a==="create"?d="create":a==="str_replace"?d="edit":a==="undo_edit"?d="undo":a==="insert"?d="insert":d="file";const f=i?og(i):"",h=c?` /${c}/`:"",m=s?s.split(`
|
||||
`):[],p=o?o.split(`
|
||||
`):[],y=m.length+p.length,x=y>fu,_=x?Math.round(fu*(m.length/y)):m.length,N=x?fu-_:p.length;return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-baseline gap-2",children:[g.jsx("span",{className:"text-sky-400/80 font-semibold text-sm shrink-0",children:d}),f&&g.jsx("span",{className:"text-[#888] font-mono text-[13px] break-all",children:f})]}),h&&g.jsx("div",{className:"text-purple-400/60 font-mono text-[13px] break-all mt-0.5",children:h}),(s||o)&&g.jsxs("div",{className:"font-mono text-[13px] leading-relaxed mt-1.5",children:[m.slice(0,_).map((S,w)=>g.jsxs("div",{className:"text-red-400/60",children:[g.jsx("span",{className:"select-none text-red-400/30 mr-1",children:"-"}),S]},`o${w}`)),p.slice(0,N).map((S,w)=>g.jsxs("div",{className:"text-emerald-400/60",children:[g.jsx("span",{className:"select-none text-emerald-400/30 mr-1",children:"+"}),S]},`n${w}`)),x&&g.jsxs("div",{className:"text-[#444] mt-0.5",children:["... ",y-fu," more lines"]})]})]})}const hu=30,nU="*** Begin Patch",iU="*** End Patch",t_="*** Add File: ",n_="*** Update File: ",i_="*** Delete File: ",rU={add:"create",update:"edit",delete:"delete"};function aU(e){const t=e.patch;return typeof t=="string"?t:t&&typeof t=="object"&&typeof t.patch=="string"?t.patch:typeof e.input=="string"?e.input:""}function sU(e){const t=[];let i=null;const a=()=>{i&&t.push(i),i=null};for(const s of e.split(`
|
||||
`))if(!(s===nU||s===iU))if(s.startsWith(t_))a(),i={kind:"add",path:s.slice(t_.length).trim(),oldLines:[],newLines:[]};else if(s.startsWith(n_))a(),i={kind:"update",path:s.slice(n_.length).trim(),oldLines:[],newLines:[]};else if(s.startsWith(i_))a(),i={kind:"delete",path:s.slice(i_.length).trim(),oldLines:[],newLines:[]};else if((i==null?void 0:i.kind)==="update"){if(s.startsWith("@@"))continue;s.startsWith("-")&&!s.startsWith("---")?i.oldLines.push(s.slice(1)):s.startsWith("+")&&!s.startsWith("+++")&&i.newLines.push(s.slice(1))}else(i==null?void 0:i.kind)==="add"&&(s.startsWith("+")?i.newLines.push(s.slice(1)):s.trim()&&i.newLines.push(s));return a(),t}function lU({op:e}){const t=rU[e.kind]??"file",i=e.oldLines.length+e.newLines.length,a=i>hu,s=a&&i>0?Math.round(hu*(e.oldLines.length/i)):e.oldLines.length,o=a?hu-s:e.newLines.length;return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-baseline gap-2",children:[g.jsx("span",{className:"text-sky-400/80 font-semibold text-sm shrink-0",children:t}),e.path&&g.jsx("span",{className:"text-[#888] font-mono text-[13px] break-all",children:og(e.path)})]}),(e.oldLines.length>0||e.newLines.length>0)&&g.jsxs("div",{className:"font-mono text-[13px] leading-relaxed mt-1.5",children:[e.oldLines.slice(0,s).map((c,d)=>g.jsxs("div",{className:"text-red-400/60",children:[g.jsx("span",{className:"select-none text-red-400/30 mr-1",children:"-"}),c]},`o${d}`)),e.newLines.slice(0,o).map((c,d)=>g.jsxs("div",{className:"text-emerald-400/60",children:[g.jsx("span",{className:"select-none text-emerald-400/30 mr-1",children:"+"}),c]},`n${d}`)),a&&g.jsxs("div",{className:"text-[#444] mt-0.5",children:["... ",i-hu," more lines"]})]})]})}function oU({args:e,result:t,status:i}){const a=sU(aU(e));return a.length===0?g.jsxs("div",{children:[g.jsx("span",{className:"text-sky-400/80 font-semibold text-sm",children:"patch"}),i==="failed"&&typeof t=="string"&&t.trim()&&g.jsx("div",{className:"text-red-400/70 text-[13px] mt-1",children:t.trim()})]}):g.jsxs("div",{className:"space-y-2",children:[a.map((s,o)=>g.jsx(lU,{op:s},o)),i==="failed"&&typeof t=="string"&&t.trim()&&g.jsx("div",{className:"text-red-400/70 text-[13px]",children:t.trim()})]})}function cU({args:e,result:t}){const i=(e.path??"").trim(),a=t;let s=null;if(typeof a=="string"){const o=a.trim();o&&!o.toLowerCase().startsWith("data:image/")&&!o.startsWith("{")&&(s=o)}return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-baseline gap-2",children:[g.jsx("span",{className:"text-sky-400/80 font-semibold text-sm shrink-0",children:"view image"}),i&&g.jsx("span",{className:"text-[#888] font-mono text-[13px] break-all",children:og(i)})]}),s&&g.jsx("div",{className:"text-red-400/70 text-[13px] mt-1",children:s})]})}const uU={critical:"text-red-400",high:"text-orange-400",medium:"text-yellow-400",low:"text-blue-400",info:"text-cyan-400"};function dU({args:e,result:t}){const i=e.title??"",a=e.description??"",s=e.impact??"",o=e.target??"",c=e.endpoint??"",d=e.method??"",f=e.technical_analysis??"",h=e.poc_description??"",m=e.poc_script_code??"",p=e.remediation_steps??"",y=e.cve??"",x=e.cwe??"",_=t,N=(_&&typeof _=="object"?_.severity:null)??e.severity??"medium",S=String(N).toLowerCase(),w=(_&&typeof _=="object"?_.cvss_score:null)??e.cvss??null,k=uU[S]??"text-yellow-400";return g.jsxs("div",{className:"space-y-3",children:[g.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[g.jsx("span",{className:`font-semibold text-sm ${k}`,children:S.toUpperCase()}),w!=null&&g.jsxs("span",{className:"text-[#888] text-[13px]",children:["CVSS ",w]}),y&&g.jsx("span",{className:"text-[#888] font-mono text-[13px]",children:y}),x&&g.jsx("span",{className:"text-[#888] font-mono text-[13px]",children:x})]}),i&&g.jsx("div",{className:"text-[15px] text-white/80 font-semibold",children:i}),(o||c)&&g.jsxs("div",{className:"text-[13px] text-[#888] font-mono",children:[o,c?` ${d} ${c}`:""]}),a&&g.jsx(Ln,{text:a,maxLines:20}),s&&g.jsxs("div",{children:[g.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Impact"}),g.jsx("div",{className:"mt-1",children:g.jsx(Ln,{text:s,maxLines:15})})]}),f&&g.jsxs("div",{children:[g.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Technical Analysis"}),g.jsx("div",{className:"mt-1",children:g.jsx(Ln,{text:f,maxLines:20})})]}),(h||m)&&g.jsxs("div",{children:[g.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Proof of Concept"}),h&&g.jsx("div",{className:"mt-1",children:g.jsx(Us,{text:h})}),m&&g.jsx(iE,{children:m})]}),p&&g.jsxs("div",{children:[g.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Remediation"}),g.jsx("div",{className:"mt-1",children:g.jsx(Ln,{text:p,maxLines:15})})]})]})}const KN=200,ZN={GET:"text-emerald-400/80",POST:"text-blue-400/80",PUT:"text-yellow-400/80",PATCH:"text-orange-400/80",DELETE:"text-red-400/80"};function cg(e){return e<300?"text-emerald-400/80":e<400?"text-yellow-4
Line truncated
|
||||
`),a=i.slice(0,t).map(s=>Yi(s,KN-5)).join(`
|
||||
`);return i.length>t?a+`
|
||||
...`:a}function fU({args:e,result:t}){const i=e.httpql_filter??"",a=t,s=a?a.requests:null,o=Array.isArray(s)?s:[];return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"listing requests"}),i&&g.jsx("span",{className:"text-[#888] text-[13px]",children:Yi(i,150)})]}),o.length>0&&g.jsxs("div",{className:"mt-1.5 font-mono text-[13px] space-y-0.5",children:[o.slice(0,20).map((c,d)=>{const f=(c.method??"GET").toUpperCase(),h=c.host??"",m=c.path??"",p=c.response,y=(p==null?void 0:p.statusCode)??null;return g.jsxs("div",{className:"flex gap-2",children:[g.jsx("span",{className:`w-10 shrink-0 font-bold ${ZN[f]??"text-[#888]"}`,children:f}),g.jsx("span",{className:"text-[#777] truncate",children:Yi(h+m,180)}),y!=null&&g.jsx("span",{className:`ml-auto shrink-0 ${cg(y)}`,children:y})]},d)}),o.length>20&&g.jsxs("div",{className:"text-[#555]",children:["... +",o.length-20," more"]})]})]})}function hU({args:e,result:t}){const i=e.request_id,a=e.part??"request",s=e.search_pattern??"",o=t,c=o?o.matches:null,d=Array.isArray(c)?c:[],f=o?o.content??null:null,h=o?!!o.has_more:!1;return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsxs("span",{className:"text-purple-400/80 font-semibold text-sm",children:[s?"searching":"viewing"," ",a]}),i!=null&&g.jsxs("span",{className:"text-[#888] text-[13px]",children:["#",i]}),s&&g.jsxs("span",{className:"text-[#666] font-mono text-[13px]",children:["/",Yi(s,100),"/"]})]}),d.length>0&&g.jsxs("div",{className:"mt-1.5 font-mono text-[13px] space-y-1",children:[d.slice(0,5).map((m,p)=>{const y=(m.before??"").replace(/\n/g," ").replace(/\r/g,"").slice(-100),x=(m.after??"").replace(/\n/g," ").replace(/\r/g,"").slice(0,100);return g.jsxs("div",{children:[y&&g.jsxs("span",{className:"text-[#555]",children:["...",y]}),g.jsx("span",{className:"text-amber-400/80 font-bold",children:m.match}),x&&g.jsxs("span",{className:"text-[#555]",children:[x,"..."]})]},p)}),d.length>5&&g.jsxs("div",{className:"text-[#555]",children:["... +",d.length-5," more matches"]})]}),f&&!d.length&&(()=>{const m=f.split(`
|
||||
`),p=m.slice(0,15).map(x=>Yi(x,KN)).join(`
|
||||
`),y=h||m.length>15;return g.jsx(vr,{className:"text-[#666]",children:p+(y?`
|
||||
... more content available`:"")})})()]})}function mU({args:e,result:t}){const i=(e.method??"GET").toUpperCase(),a=e.url??"",s=e.headers,o=e.body,c=typeof o=="string"?o:"",d=t,f=d?d.error??null:null,h=d?d.status_code??null:null,m=d?d.response_time_ms??null:null,p=d?d.body:null,y=typeof p=="string"?p:null;return g.jsxs("div",{children:[g.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"request"}),g.jsxs("div",{className:"mt-1.5 font-mono text-[13px] space-y-0.5",children:[g.jsxs("div",{children:[g.jsx("span",{className:"text-[#555] select-none mr-1",children:">>"}),g.jsx("span",{className:`font-bold ${ZN[i]??"text-[#888]"}`,children:i}),g.jsx("span",{className:"text-[#888] ml-1 break-all",children:Yi(a,180)})]}),s&&typeof s=="object"&&Object.entries(s).slice(0,5).map(([x,_])=>g.jsxs("div",{className:"text-[#555] pl-5",children:[x,": ",cp(String(_),150)]},x))]}),c&&g.jsx(vr,{className:"text-[#888]",children:up(c,4)}),f&&g.jsx("div",{className:"text-red-400/70 text-[13px] mt-1.5",children:cp(f,150)}),h!=null&&g.jsxs("div",{className:"font-mono text-[13px] mt-1.5",children:[g.jsx("span",{className:"text-[#555] select-none mr-1",children:"<<"}),g.jsx("span",{className:`font-bold ${cg(h)}`,children:h}),m!=null&&g.jsxs("span",{className:"text-[#555] ml-2",children:[m,"ms"]})]}),y&&g.jsx(vr,{className:"text-[#666]",children:up(y,6)})]})}function pU({args:e,result:t}){const i=e.request_id,a=e.modifications,s=t,o=s?s.status_code??null:null,c=s?s.response_time_ms??null:null,d=s?s.body:null,f=typeof d=="string"?d:null;return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"repeating request"}),i!=null&&g.jsxs("span",{className:"text-[#888] text-[13px]",children:["#",i]})]}),a&&typeof a=="object"&&Object.keys(a).length>0&&g.jsx("div",{className:"mt-1.5 font-mono text-[13px] space-y-0.5",children:Object.entries(a).slice(0,5).map(([h,m])=>g.jsxs("div",{children:[g.jsxs("span",{className:"text-orange-400/60",children:[h,":"]})," ",g.jsx("span",{className:"text-[#777]",children:cp(typeof m=="string"?m:JSON.stringify(m),150)})]},h))}),o!=null&&g.jsxs("div",{className:"font-mono text-[13px] mt-1.5",children:[g.jsx("span",{className:"text-[#555] select-none mr-1",children:"<<"}),g.jsx("span",{className:`font-bold ${cg(o)}`,children:o}),c!=null&&g.jsxs("span",{className:"text-[#555] ml-2",children:[c,"ms"]})]}),f&&g.jsx(vr,{className:"text-[#666]",children:up(f,5)})]})}const gU={get:"getting",list:"listing",create:"creating",update:"updating",delete:"deleting"};function bU({args:e}){const t=e.action??"",i=e.scope_name??"",a=gU[t]??(t||"managing");return g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsxs("span",{className:"text-purple-400/80 font-semibold text-sm",children:[a," proxy scope"]}),i&&g.jsx("span",{className:"text-[#888] text-[13px]",children:Yi(i,50)})]})}function xU({args:e}){const t=e.parent_id;return g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"listing sitemap"}),t&&g.jsxs("span",{className:"text-[#888] text-[13px]",children:["under #",Yi(String(t),20)]})]})}function yU({args:e}){const t=e.entry_id;return g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"viewing sitemap entry"}),t&&g.jsxs("span",{className:"text-[#888] text-[13px]",children:["#",Yi(String(t),20)]})]})}function vU(e){switch(e.toolName){case"list_requests":return g.jsx(fU,{...e});case"view_request":return g.jsx(hU,{...e});case"send_request":return g.jsx(mU,{...e});case"repeat_request":return g.jsx(pU,{...e});case"scope_rules":return g.jsx(bU,{...e});case"list_sitemap":return g.jsx(xU,{...e});case"view_sitemap_entry":return g.jsx(yU,{...e});default:return g.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:e.toolName.replace(/_/g," ")})}}function _U({args:e}){const t=e.thought??e.content??"";return t?g.jsxs("div",{children:[g.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"Agent is thinking"}),g.jsx("div",{className:"mt-1.5 italic text-[#888]",children:g.jsx(Ln,{text:t,maxLines:20})})]}):null}function wU({toolName:e,args:t}){if(e==="create_agent"){const i=t.name??t.agent_name??"",a=t.task??"";return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx("span",{className:"text-cyan-400/80 font-semibold text-sm",children:"spawning"}),i&&g.jsx("span",{className:"text-cyan-400 font-semibold text-sm",children:i})]}),a&&g.jsx("div",{className:"mt-1.5",children:g.jsx(Ln,{text:a,maxLines:15})})]})}if(e==="agent_finish"){const i=t.result_summary??"",a=t.success,s=t.findings,o=Array.isArray(s)?s:void 0;return g.jsxs("div",{children:[g.jsx("span",{className:`font-semibold text-sm ${a===!1?"text-red-400/80":"text-emerald-400/80"}`,children:a===!1?"A
Line truncated
|
||||
`);if(t.length<=NU)return t.map(Om).join(`
|
||||
`);const i=t.length-a_-s_;return[...t.slice(0,a_).map(Om),`... ${i} lines truncated ...`,...t.slice(-s_).map(Om)].join(`
|
||||
`)}function MU({args:e,result:t}){const i=e.action??"",a=e.code??e.script??"",s=t;let o=null;s&&typeof s=="object"?o=typeof s.stdout=="string"?s.stdout:null:typeof s=="string"&&(o=s);const c=i==="new_session"?"new session":i==="close"?"close session":i==="list_sessions"?"list sessions":null,d=o?CU(AU(o)):null;return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx("span",{className:"text-yellow-400/80 font-semibold text-sm",children:"Python"}),c&&g.jsx("span",{className:"text-[#888] text-[13px]",children:c})]}),a&&g.jsx(lg,{code:a,language:"python",collapsible:!0}),d&&g.jsx(vr,{className:"text-[#666]",children:d})]})}function OU({args:e}){const t=e.targets,a=(Array.isArray(t)?t:[]).map(s=>typeof s=="object"&&s?s.original??null:null).filter(Boolean);return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx("span",{className:"text-emerald-400/80 font-semibold text-sm",children:"Starting penetration test"}),a.length===1&&g.jsxs("span",{className:"text-[#888] text-[13px]",children:["on ",a[0]]})]}),a.length>1&&g.jsx("div",{className:"mt-1.5 space-y-0.5",children:a.map((s,o)=>g.jsxs("div",{className:"text-[13px] text-[#888]",children:[g.jsx("span",{className:"text-[#555] mr-1",children:"•"}),s]},o))})]})}function RU({args:e}){const t=e.name??"Unknown Agent",i=e.task??"";return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx("span",{className:"text-[#888] text-[13px]",children:"subagent"}),g.jsx("span",{className:"text-purple-400 font-semibold text-sm",children:t})]}),i&&g.jsx("div",{className:"mt-1.5",children:g.jsx(Ln,{text:i,maxLines:15})})]})}function DU(e){return e.toolName==="subagent_start_info"?g.jsx(RU,{...e}):g.jsx(OU,{...e})}function jU({args:e}){const t=e.executive_summary??"",i=e.methodology??"",a=e.technical_analysis??"",s=e.recommendations??"";return g.jsxs("div",{className:"space-y-3",children:[g.jsx("span",{className:"text-emerald-400/80 font-semibold text-sm",children:"Penetration test completed"}),t&&g.jsxs("div",{children:[g.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Executive Summary"}),g.jsx("div",{className:"mt-1",children:g.jsx(Ln,{text:t,maxLines:25})})]}),i&&g.jsxs("div",{children:[g.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Methodology"}),g.jsx("div",{className:"mt-1",children:g.jsx(Ln,{text:i,maxLines:25})})]}),a&&g.jsxs("div",{children:[g.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Technical Analysis"}),g.jsx("div",{className:"mt-1",children:g.jsx(Ln,{text:a,maxLines:25})})]}),s&&g.jsxs("div",{children:[g.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Recommendations"}),g.jsx("div",{className:"mt-1",children:g.jsx(Ln,{text:s,maxLines:25})})]}),!t&&!i&&!a&&!s&&g.jsx("div",{className:"text-[#555] text-xs",children:"Generating final report..."})]})}function LU({toolName:e,args:t,result:i}){if(e==="create_note"){const a=t.title??"",s=t.content??"",o=t.category??"general";return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx("span",{className:"text-amber-400/80 font-semibold text-sm",children:"note"}),g.jsxs("span",{className:"text-[#555] text-[13px]",children:["(",o,")"]})]}),a&&g.jsx("div",{className:"mt-1.5 text-[#999] text-[13px]",children:a}),s&&g.jsx("div",{className:"mt-1",children:g.jsx(Us,{text:s})})]})}if(e==="delete_note")return g.jsx("span",{className:"text-amber-400/80 font-semibold text-sm",children:"note removed"});if(e==="update_note"){const a=t.title??"",s=t.content??"";return g.jsxs("div",{children:[g.jsx("span",{className:"text-amber-400/80 font-semibold text-sm",children:"note updated"}),a&&g.jsx("div",{className:"mt-1.5 text-[#999] text-[13px]",children:a}),s&&g.jsx("div",{className:"mt-1",children:g.jsx(Us,{text:s})})]})}if(e==="get_note"){const a=i,s=a&&typeof a=="object"&&a.success?a.note:void 0;return g.jsxs("div",{children:[g.jsx("span",{className:"text-amber-400/80 font-semibold text-sm",children:"note read"}),s&&g.jsxs(g.Fragment,{children:[g.jsxs("div",{className:"mt-1.5 text-[#999] text-[13px]",children:[s.title??"(untitled)",g.jsxs("span",{className:"text-[#555] ml-1",children:["(",s.category??"general",")"]})]}),s.content&&g.jsx("div",{className:"mt-1",children:g.jsx(Us,{text:s.content})})]})]})}if(e==="list_notes"){const a=i;let s=[];if(a&&typeof a=="object"&&a.success){const o=a.notes;s=Array.isArray(o)?o:[]}return g.jsxs("div",{children:[g.jsx("span",{className:"text-amber-400/80 font-semibold text-sm",children:"notes"}),s.length>0?g.jsx("div",{className:"mt-1.5 space-y-0.5",children:s.map((o,c)=>g.jsxs("div",{className:"text-[13px]",children:[g.jsx("span",{className:"text-[#555] mr-1",children:"-"}),g.jsx("span",{className:"text-[#999]",children:o.title??"(untitled)"}),g.jsxs("span",{className:"text-[#555] ml-1",children:["(",o.category??"general",")"]}),o.content
Line truncated
|
||||
`)){const s=a.match(/^#{1,6}\s+(.*)$/);if(s){const o=s[1].trim().toLowerCase();if(o===i)continue;i=o}else a.trim()!==""&&(i=null);t.push(a)}return t.join(`
|
||||
`)}function J7({onOpenEmail:e}){return g.jsx("button",{onClick:e,className:"group w-full cursor-pointer rounded-xl border border-emerald-500/25 bg-emerald-500/[0.06] p-4 text-left transition-colors hover:border-emerald-500/40",children:g.jsxs("div",{className:"flex items-center gap-3",children:[g.jsx("div",{className:"flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg",style:{border:"1px solid rgba(16,185,129,0.3)",background:"rgba(16,185,129,0.08)"},children:g.jsx(gp,{className:"h-4 w-4 text-emerald-400","aria-hidden":"true"})}),g.jsxs("div",{className:"min-w-0 flex-1",children:[g.jsx("p",{className:"text-sm font-semibold text-white",children:"Email an encrypted PDF report of this run"}),g.jsx("p",{className:"mt-0.5 text-xs text-[#888]",children:"Encrypted with a key only you can see, email verified with a one-time code before sending."})]}),g.jsx("span",{className:"flex-shrink-0 rounded-lg bg-white px-3 py-1.5 text-xs font-semibold text-black transition-opacity group-hover:opacity-90",children:"Email report"})]})})}function eH({summary:e,counts:t,total:i,reportMarkdown:a,raw:s,finished:o,onOpenEmail:c}){const d=[["Executive Summary",e.executiveSummary],["Technical Analysis",e.technicalAnalysis],["Methodology",e.methodology],["Recommendations",e.recommendations]].filter(([,f])=>!!f).map(([f,h])=>({title:f,content:Q7(h)}));return g.jsxs("div",{className:"space-y-6",children:[g.jsx(I7,{raw:s,durationSeconds:e.durationSeconds}),i>0&&g.jsx("div",{className:"rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5",children:g.jsx(ej,{findings:{total:i,...t}})}),o&&g.jsx(J7,{onOpenEmail:c}),d.length>0?g.jsx("div",{className:"rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5 space-y-8",children:d.map(f=>g.jsx(aa,{title:f.title,content:f.content},f.title))}):a?g.jsx("div",{className:"rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5",children:g.jsx(aa,{content:W7(a)})}):i===0&&g.jsx("p",{className:"text-sm text-[#888]",children:"No summary available for this run yet."}),g.jsx("div",{className:"grid grid-cols-1 sm:grid-cols-2 gap-3",children:P7.map(f=>g.jsx(ey,{item:f,surface:"overview"},f.slug))}),g.jsxs("div",{children:[g.jsx("p",{className:"mb-2 text-sm font-semibold text-white",children:"Continuous coverage for your org"}),g.jsx("div",{className:"grid grid-cols-1 sm:grid-cols-3 gap-3",children:F7.map(f=>g.jsx(ey,{item:f,surface:"overview"},f.slug))})]})]})}function Dm({active:e,onClick:t,children:i}){return g.jsxs("button",{onClick:t,className:`cursor-pointer relative pb-2.5 text-sm font-semibold transition-colors ${e?"text-white":"text-[#666] hover:text-white"}`,children:[i,e&&g.jsx("span",{className:"absolute bottom-0 inset-x-0 h-0.5 bg-white rounded-full"})]})}function tH({run:e,canSteer:t}){const{agents:i,events:a}=e.transcript,s=ee.useMemo(()=>n7(i,a),[i,a]),[o,c]=ee.useState(null),d=o?i.find(h=>h.id===o)??null:null,f=t&&!e.finished;return g.jsxs("div",{className:"space-y-5",children:[g.jsxs("div",{className:"rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5",children:[g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx(j_,{className:"w-4 h-4 text-[#888]","aria-hidden":"true"}),g.jsx("h2",{className:"text-sm font-semibold text-white",children:"Agent graph"}),g.jsxs("span",{className:"text-xs text-[#666]",children:[i.length," agent",i.length===1?"":"s"]})]}),g.jsx("p",{className:"mt-1 mb-4 text-xs text-[#666]",children:"Click an agent to open its full transcript."}),g.jsx("div",{className:"h-[480px] rounded-lg border border-[#1a1a1a] overflow-hidden",children:g.jsx(P9,{agents:s,selectedAgentId:o,onSelectAgent:h=>c(h),eventsLoaded:!0,eventsEmpty:s.size===0,scanCompleted:e.finished})})]}),f&&g.jsx(lS,{agents:i}),g.jsxs("div",{className:"rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5",children:[g.jsx("p",{className:"text-sm font-semibold text-white",children:"Run this scan with more depth"}),g.jsx("p",{className:"mt-0.5 text-xs text-[#666]",children:"Re-run this scan on managed infra in the cloud."}),g.jsx("div",{className:"mt-3 flex flex-wrap gap-2.5",children:g.jsx(ao,{label:"Re-run in Strix Cloud with more depth",desc:"Run this scan on managed infra with more depth.",slug:"live_scan",surface:"agents",icon:tA})})]}),d&&g.jsx(w7,{agent:d,events:a,steerable:f,onClose:()=>c(null)})]})}y2.createRoot(document.getElementById("root")).render(g.jsx(ee.StrictMode,{children:g.jsx(G7,{})}));
|
||||
@@ -1,10 +0,0 @@
|
||||
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||
Theme: GitHub Dark
|
||||
Description: Dark theme as seen on github.com
|
||||
Author: github.com
|
||||
Maintainer: @Hirse
|
||||
Updated: 2021-05-15
|
||||
|
||||
Outdated base version: https://github.com/primer/github-syntax-dark
|
||||
Current colors taken from GitHub's CSS
|
||||
*/.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id{color:#79c0ff}.hljs-regexp,.hljs-string,.hljs-meta .hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-comment,.hljs-code,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c}.react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #555;--xy-background-pattern-lines-color-default: #333;--xy-background-pattern-cross-color-default: #333;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1;touch-action:none}.react-flow__panLine truncated
|
||||
@@ -0,0 +1,487 @@
|
||||
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))a(s);new MutationObserver(s=>{for(const o of s)if(o.type==="childList")for(const c of o.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&a(c)}).observe(document,{childList:!0,subtree:!0});function r(s){const o={};return s.integrity&&(o.integrity=s.integrity),s.referrerPolicy&&(o.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?o.credentials="include":s.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function a(s){if(s.ep)return;s.ep=!0;const o=r(s);fetch(s.href,o)}})();function Co(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var oh={exports:{}},Yl={};/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var F0;function bk(){if(F0)return Yl;F0=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.fragment");function r(a,s,o){var c=null;if(o!==void 0&&(c=""+o),s.key!==void 0&&(c=""+s.key),"key"in s){o={};for(var d in s)d!=="key"&&(o[d]=s[d])}else o=s;return s=o.ref,{$$typeof:e,type:a,key:c,ref:s!==void 0?s:null,props:o}}return Yl.Fragment=t,Yl.jsx=r,Yl.jsxs=r,Yl}var G0;function xk(){return G0||(G0=1,oh.exports=bk()),oh.exports}var g=xk(),ch={exports:{}},Ve={};/**
|
||||
* @license React
|
||||
* react.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var V0;function yk(){if(V0)return Ve;V0=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.portal"),r=Symbol.for("react.fragment"),a=Symbol.for("react.strict_mode"),s=Symbol.for("react.profiler"),o=Symbol.for("react.consumer"),c=Symbol.for("react.context"),d=Symbol.for("react.forward_ref"),f=Symbol.for("react.suspense"),h=Symbol.for("react.memo"),m=Symbol.for("react.lazy"),p=Symbol.for("react.activity"),y=Symbol.iterator;function x(j){return j===null||typeof j!="object"?null:(j=y&&j[y]||j["@@iterator"],typeof j=="function"?j:null)}var _={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},N=Object.assign,S={};function w(j,Y,L){this.props=j,this.context=Y,this.refs=S,this.updater=L||_}w.prototype.isReactComponent={},w.prototype.setState=function(j,Y){if(typeof j!="object"&&typeof j!="function"&&j!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,j,Y,"setState")},w.prototype.forceUpdate=function(j){this.updater.enqueueForceUpdate(this,j,"forceUpdate")};function k(){}k.prototype=w.prototype;function E(j,Y,L){this.props=j,this.context=Y,this.refs=S,this.updater=L||_}var M=E.prototype=new k;M.constructor=E,N(M,w.prototype),M.isPureReactComponent=!0;var I=Array.isArray;function R(){}var U={H:null,A:null,T:null,S:null},B=Object.prototype.hasOwnProperty;function Z(j,Y,L){var G=L.ref;return{$$typeof:e,type:j,key:Y,ref:G!==void 0?G:null,props:L}}function D(j,Y){return Z(j.type,Y,j.props)}function z(j){return typeof j=="object"&&j!==null&&j.$$typeof===e}function V(j){var Y={"=":"=0",":":"=2"};return"$"+j.replace(/[=:]/g,function(L){return Y[L]})}var P=/\/+/g;function T(j,Y){return typeof j=="object"&&j!==null&&j.key!=null?V(""+j.key):Y.toString(36)}function $(j){switch(j.status){case"fulfilled":return j.value;case"rejected":throw j.reason;default:switch(typeof j.status=="string"?j.then(R,R):(j.status="pending",j.then(function(Y){j.status==="pending"&&(j.status="fulfilled",j.value=Y)},function(Y){j.status==="pending"&&(j.status="rejected",j.reason=Y)})),j.status){case"fulfilled":return j.value;case"rejected":throw j.reason}}throw j}function O(j,Y,L,G,q){var Q=typeof j;(Q==="undefined"||Q==="boolean")&&(j=null);var J=!1;if(j===null)J=!0;else switch(Q){case"bigint":case"string":case"number":J=!0;break;case"object":switch(j.$$typeof){case e:case t:J=!0;break;case m:return J=j._init,O(J(j._payload),Y,L,G,q)}}if(J)return q=q(j),J=G===""?"."+T(j,0):G,I(q)?(L="",J!=null&&(L=J.replace(P,"$&/")+"/"),O(q,Y,L,"",function(ce){return ce})):q!=null&&(z(q)&&(q=D(q,L+(q.key==null||j&&j.key===q.key?"":(""+q.key).replace(P,"$&/")+"/")+J)),Y.push(q)),1;J=0;var W=G===""?".":G+":";if(I(j))for(var te=0;te<j.length;te++)G=j[te],Q=W+T(G,te),J+=O(G,Y,L,Q,q);else if(te=x(j),typeof te=="function")for(j=te.call(j),te=0;!(G=j.next()).done;)G=G.value,Q=W+T(G,te++),J+=O(G,Y,L,Q,q);else if(Q==="object"){if(typeof j.then=="function")return O($(j),Y,L,G,q);throw Y=String(j),Error("Objects are not valid as a React child (found: "+(Y==="[object Object]"?"object with keys {"+Object.keys(j).join(", ")+"}":Y)+"). If you meant to render a collection of children, use an array instead.")}return J}function H(j,Y,L){if(j==null)return j;var G=[],q=0;return O(j,G,"","",function(Q){return Y.call(L,Q,q++)}),G}function X(j){if(j._status===-1){var Y=j._result;Y=Y(),Y.then(function(L){(j._status===0||j._status===-1)&&(j._status=1,j._result=L)},function(L){(j._status===0||j._status===-1)&&(j._status=2,j._result=L)}),j._status===-1&&(j._status=0,j._result=Y)}if(j._status===1)return j._result.default;throw j._result}var K=typeof reportError=="function"?reportError:function(j){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var Y=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof j=="object"&&j!==null&&typeof j.message=="string"?String(j.message):String(j),error:j});if(!window.dispatchEvent(Y))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",j);return}console.error(j)},C={map:H,forEach:function(j,Y,L){H(j,function(){Y.apply(this,arguments)},L)},count:function(j){var Y=0;return H(j,function(){Y++}),Y},toArray:function(j){return H(j,function(Y){return Y})||[]},only:function(j){if(!z(j))throw Error("React.Children.only expected to receive a single React element child.");return j}};return Ve.Activity=p,Ve.Children=C,Ve.Component=w,Ve.Fragment=r,Ve.Profiler=s,Ve.PureComponent=E,Ve.StrictMode=a,Ve.Suspense=f,Ve.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=U,Ve.__COMPILER_RUNTIME={__proto__:null,c:function(j){return U.H.useMemoCache(j)}},Ve.cache=function(j){return function(){return j.apply(null,arguments)}},Ve.cacheSignal=function(){return null},Ve.cloneElement=function(j,Y,L){if(j==null)throw Error("The argument must be a React element, but you passed "+j+".");var
Line truncated
|
||||
* @license React
|
||||
* scheduler.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var X0;function vk(){return X0||(X0=1,(function(e){function t(O,H){var X=O.length;O.push(H);e:for(;0<X;){var K=X-1>>>1,C=O[K];if(0<s(C,H))O[K]=H,O[X]=C,X=K;else break e}}function r(O){return O.length===0?null:O[0]}function a(O){if(O.length===0)return null;var H=O[0],X=O.pop();if(X!==H){O[0]=X;e:for(var K=0,C=O.length,j=C>>>1;K<j;){var Y=2*(K+1)-1,L=O[Y],G=Y+1,q=O[G];if(0>s(L,X))G<C&&0>s(q,L)?(O[K]=q,O[G]=X,K=G):(O[K]=L,O[Y]=X,K=Y);else if(G<C&&0>s(q,X))O[K]=q,O[G]=X,K=G;else break e}}return H}function s(O,H){var X=O.sortIndex-H.sortIndex;return X!==0?X:O.id-H.id}if(e.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var o=performance;e.unstable_now=function(){return o.now()}}else{var c=Date,d=c.now();e.unstable_now=function(){return c.now()-d}}var f=[],h=[],m=1,p=null,y=3,x=!1,_=!1,N=!1,S=!1,w=typeof setTimeout=="function"?setTimeout:null,k=typeof clearTimeout=="function"?clearTimeout:null,E=typeof setImmediate<"u"?setImmediate:null;function M(O){for(var H=r(h);H!==null;){if(H.callback===null)a(h);else if(H.startTime<=O)a(h),H.sortIndex=H.expirationTime,t(f,H);else break;H=r(h)}}function I(O){if(N=!1,M(O),!_)if(r(f)!==null)_=!0,R||(R=!0,V());else{var H=r(h);H!==null&&$(I,H.startTime-O)}}var R=!1,U=-1,B=5,Z=-1;function D(){return S?!0:!(e.unstable_now()-Z<B)}function z(){if(S=!1,R){var O=e.unstable_now();Z=O;var H=!0;try{e:{_=!1,N&&(N=!1,k(U),U=-1),x=!0;var X=y;try{t:{for(M(O),p=r(f);p!==null&&!(p.expirationTime>O&&D());){var K=p.callback;if(typeof K=="function"){p.callback=null,y=p.priorityLevel;var C=K(p.expirationTime<=O);if(O=e.unstable_now(),typeof C=="function"){p.callback=C,M(O),H=!0;break t}p===r(f)&&a(f),M(O)}else a(f);p=r(f)}if(p!==null)H=!0;else{var j=r(h);j!==null&&$(I,j.startTime-O),H=!1}}break e}finally{p=null,y=X,x=!1}H=void 0}}finally{H?V():R=!1}}}var V;if(typeof E=="function")V=function(){E(z)};else if(typeof MessageChannel<"u"){var P=new MessageChannel,T=P.port2;P.port1.onmessage=z,V=function(){T.postMessage(null)}}else V=function(){w(z,0)};function $(O,H){U=w(function(){O(e.unstable_now())},H)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(O){O.callback=null},e.unstable_forceFrameRate=function(O){0>O||125<O?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):B=0<O?Math.floor(1e3/O):5},e.unstable_getCurrentPriorityLevel=function(){return y},e.unstable_next=function(O){switch(y){case 1:case 2:case 3:var H=3;break;default:H=y}var X=y;y=H;try{return O()}finally{y=X}},e.unstable_requestPaint=function(){S=!0},e.unstable_runWithPriority=function(O,H){switch(O){case 1:case 2:case 3:case 4:case 5:break;default:O=3}var X=y;y=O;try{return H()}finally{y=X}},e.unstable_scheduleCallback=function(O,H,X){var K=e.unstable_now();switch(typeof X=="object"&&X!==null?(X=X.delay,X=typeof X=="number"&&0<X?K+X:K):X=K,O){case 1:var C=-1;break;case 2:C=250;break;case 5:C=1073741823;break;case 4:C=1e4;break;default:C=5e3}return C=X+C,O={id:m++,callback:H,priorityLevel:O,startTime:X,expirationTime:C,sortIndex:-1},X>K?(O.sortIndex=X,t(h,O),r(f)===null&&O===r(h)&&(N?(k(U),U=-1):N=!0,$(I,X-K))):(O.sortIndex=C,t(f,O),_||x||(_=!0,R||(R=!0,V()))),O},e.unstable_shouldYield=D,e.unstable_wrapCallback=function(O){var H=y;return function(){var X=y;y=H;try{return O.apply(this,arguments)}finally{y=X}}}})(fh)),fh}var K0;function _k(){return K0||(K0=1,dh.exports=vk()),dh.exports}var hh={exports:{}},Cn={};/**
|
||||
* @license React
|
||||
* react-dom.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var Z0;function wk(){if(Z0)return Cn;Z0=1;var e=To();function t(f){var h="https://react.dev/errors/"+f;if(1<arguments.length){h+="?args[]="+encodeURIComponent(arguments[1]);for(var m=2;m<arguments.length;m++)h+="&args[]="+encodeURIComponent(arguments[m])}return"Minified React error #"+f+"; visit "+h+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function r(){}var a={d:{f:r,r:function(){throw Error(t(522))},D:r,C:r,L:r,m:r,X:r,S:r,M:r},p:0,findDOMNode:null},s=Symbol.for("react.portal");function o(f,h,m){var p=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null;return{$$typeof:s,key:p==null?null:""+p,children:f,containerInfo:h,implementation:m}}var c=e.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function d(f,h){if(f==="font")return"";if(typeof h=="string")return h==="use-credentials"?h:""}return Cn.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=a,Cn.createPortal=function(f,h){var m=2<arguments.length&&arguments[2]!==void 0?arguments[2]:null;if(!h||h.nodeType!==1&&h.nodeType!==9&&h.nodeType!==11)throw Error(t(299));return o(f,h,null,m)},Cn.flushSync=function(f){var h=c.T,m=a.p;try{if(c.T=null,a.p=2,f)return f()}finally{c.T=h,a.p=m,a.d.f()}},Cn.preconnect=function(f,h){typeof f=="string"&&(h?(h=h.crossOrigin,h=typeof h=="string"?h==="use-credentials"?h:"":void 0):h=null,a.d.C(f,h))},Cn.prefetchDNS=function(f){typeof f=="string"&&a.d.D(f)},Cn.preinit=function(f,h){if(typeof f=="string"&&h&&typeof h.as=="string"){var m=h.as,p=d(m,h.crossOrigin),y=typeof h.integrity=="string"?h.integrity:void 0,x=typeof h.fetchPriority=="string"?h.fetchPriority:void 0;m==="style"?a.d.S(f,typeof h.precedence=="string"?h.precedence:void 0,{crossOrigin:p,integrity:y,fetchPriority:x}):m==="script"&&a.d.X(f,{crossOrigin:p,integrity:y,fetchPriority:x,nonce:typeof h.nonce=="string"?h.nonce:void 0})}},Cn.preinitModule=function(f,h){if(typeof f=="string")if(typeof h=="object"&&h!==null){if(h.as==null||h.as==="script"){var m=d(h.as,h.crossOrigin);a.d.M(f,{crossOrigin:m,integrity:typeof h.integrity=="string"?h.integrity:void 0,nonce:typeof h.nonce=="string"?h.nonce:void 0})}}else h==null&&a.d.M(f)},Cn.preload=function(f,h){if(typeof f=="string"&&typeof h=="object"&&h!==null&&typeof h.as=="string"){var m=h.as,p=d(m,h.crossOrigin);a.d.L(f,m,{crossOrigin:p,integrity:typeof h.integrity=="string"?h.integrity:void 0,nonce:typeof h.nonce=="string"?h.nonce:void 0,type:typeof h.type=="string"?h.type:void 0,fetchPriority:typeof h.fetchPriority=="string"?h.fetchPriority:void 0,referrerPolicy:typeof h.referrerPolicy=="string"?h.referrerPolicy:void 0,imageSrcSet:typeof h.imageSrcSet=="string"?h.imageSrcSet:void 0,imageSizes:typeof h.imageSizes=="string"?h.imageSizes:void 0,media:typeof h.media=="string"?h.media:void 0})}},Cn.preloadModule=function(f,h){if(typeof f=="string")if(h){var m=d(h.as,h.crossOrigin);a.d.m(f,{as:typeof h.as=="string"&&h.as!=="script"?h.as:void 0,crossOrigin:m,integrity:typeof h.integrity=="string"?h.integrity:void 0})}else a.d.m(f)},Cn.requestFormReset=function(f){a.d.r(f)},Cn.unstable_batchedUpdates=function(f,h){return f(h)},Cn.useFormState=function(f,h,m){return c.H.useFormState(f,h,m)},Cn.useFormStatus=function(){return c.H.useHostTransitionStatus()},Cn.version="19.2.7",Cn}var Q0;function N_(){if(Q0)return hh.exports;Q0=1;function e(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(t){console.error(t)}}return e(),hh.exports=wk(),hh.exports}/**
|
||||
* @license React
|
||||
* react-dom-client.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var W0;function Ek(){if(W0)return Xl;W0=1;var e=_k(),t=To(),r=N_();function a(n){var i="https://react.dev/errors/"+n;if(1<arguments.length){i+="?args[]="+encodeURIComponent(arguments[1]);for(var l=2;l<arguments.length;l++)i+="&args[]="+encodeURIComponent(arguments[l])}return"Minified React error #"+n+"; visit "+i+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function s(n){return!(!n||n.nodeType!==1&&n.nodeType!==9&&n.nodeType!==11)}function o(n){var i=n,l=n;if(n.alternate)for(;i.return;)i=i.return;else{n=i;do i=n,(i.flags&4098)!==0&&(l=i.return),n=i.return;while(n)}return i.tag===3?l:null}function c(n){if(n.tag===13){var i=n.memoizedState;if(i===null&&(n=n.alternate,n!==null&&(i=n.memoizedState)),i!==null)return i.dehydrated}return null}function d(n){if(n.tag===31){var i=n.memoizedState;if(i===null&&(n=n.alternate,n!==null&&(i=n.memoizedState)),i!==null)return i.dehydrated}return null}function f(n){if(o(n)!==n)throw Error(a(188))}function h(n){var i=n.alternate;if(!i){if(i=o(n),i===null)throw Error(a(188));return i!==n?null:n}for(var l=n,u=i;;){var b=l.return;if(b===null)break;var v=b.alternate;if(v===null){if(u=b.return,u!==null){l=u;continue}break}if(b.child===v.child){for(v=b.child;v;){if(v===l)return f(b),n;if(v===u)return f(b),i;v=v.sibling}throw Error(a(188))}if(l.return!==u.return)l=b,u=v;else{for(var A=!1,F=b.child;F;){if(F===l){A=!0,l=b,u=v;break}if(F===u){A=!0,u=b,l=v;break}F=F.sibling}if(!A){for(F=v.child;F;){if(F===l){A=!0,l=v,u=b;break}if(F===u){A=!0,u=v,l=b;break}F=F.sibling}if(!A)throw Error(a(189))}}if(l.alternate!==u)throw Error(a(190))}if(l.tag!==3)throw Error(a(188));return l.stateNode.current===l?n:i}function m(n){var i=n.tag;if(i===5||i===26||i===27||i===6)return n;for(n=n.child;n!==null;){if(i=m(n),i!==null)return i;n=n.sibling}return null}var p=Object.assign,y=Symbol.for("react.element"),x=Symbol.for("react.transitional.element"),_=Symbol.for("react.portal"),N=Symbol.for("react.fragment"),S=Symbol.for("react.strict_mode"),w=Symbol.for("react.profiler"),k=Symbol.for("react.consumer"),E=Symbol.for("react.context"),M=Symbol.for("react.forward_ref"),I=Symbol.for("react.suspense"),R=Symbol.for("react.suspense_list"),U=Symbol.for("react.memo"),B=Symbol.for("react.lazy"),Z=Symbol.for("react.activity"),D=Symbol.for("react.memo_cache_sentinel"),z=Symbol.iterator;function V(n){return n===null||typeof n!="object"?null:(n=z&&n[z]||n["@@iterator"],typeof n=="function"?n:null)}var P=Symbol.for("react.client.reference");function T(n){if(n==null)return null;if(typeof n=="function")return n.$$typeof===P?null:n.displayName||n.name||null;if(typeof n=="string")return n;switch(n){case N:return"Fragment";case w:return"Profiler";case S:return"StrictMode";case I:return"Suspense";case R:return"SuspenseList";case Z:return"Activity"}if(typeof n=="object")switch(n.$$typeof){case _:return"Portal";case E:return n.displayName||"Context";case k:return(n._context.displayName||"Context")+".Consumer";case M:var i=n.render;return n=n.displayName,n||(n=i.displayName||i.name||"",n=n!==""?"ForwardRef("+n+")":"ForwardRef"),n;case U:return i=n.displayName||null,i!==null?i:T(n.type)||"Memo";case B:i=n._payload,n=n._init;try{return T(n(i))}catch{}}return null}var $=Array.isArray,O=t.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,H=r.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,X={pending:!1,data:null,method:null,action:null},K=[],C=-1;function j(n){return{current:n}}function Y(n){0>C||(n.current=K[C],K[C]=null,C--)}function L(n,i){C++,K[C]=n.current,n.current=i}var G=j(null),q=j(null),Q=j(null),J=j(null);function W(n,i){switch(L(Q,i),L(q,n),L(G,null),i.nodeType){case 9:case 11:n=(n=i.documentElement)&&(n=n.namespaceURI)?h0(n):0;break;default:if(n=i.tagName,i=i.namespaceURI)i=h0(i),n=m0(i,n);else switch(n){case"svg":n=1;break;case"math":n=2;break;default:n=0}}Y(G),L(G,n)}function te(){Y(G),Y(q),Y(Q)}function ce(n){n.memoizedState!==null&&L(J,n);var i=G.current,l=m0(i,n.type);i!==l&&(L(q,n),L(G,l))}function fe(n){q.current===n&&(Y(G),Y(q)),J.current===n&&(Y(J),Pl._currentValue=X)}var be,we;function Ne(n){if(be===void 0)try{throw Error()}catch(l){var i=l.stack.trim().match(/\n( *(at )?)/);be=i&&i[1]||"",we=-1<l.stack.indexOf(`
|
||||
at`)?" (<anonymous>)":-1<l.stack.indexOf("@")?"@unknown:0:0":""}return`
|
||||
`+be+n+we}var je=!1;function $e(n,i){if(!n||je)return"";je=!0;var l=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var u={DetermineComponentFrameRoot:function(){try{if(i){var ge=function(){throw Error()};if(Object.defineProperty(ge.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(ge,[])}catch(de){var oe=de}Reflect.construct(n,[],ge)}else{try{ge.call()}catch(de){oe=de}n.call(ge.prototype)}}else{try{throw Error()}catch(de){oe=de}(ge=n())&&typeof ge.catch=="function"&&ge.catch(function(){})}}catch(de){if(de&&oe&&typeof de.stack=="string")return[de.stack,oe.stack]}return[null,null]}};u.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var b=Object.getOwnPropertyDescriptor(u.DetermineComponentFrameRoot,"name");b&&b.configurable&&Object.defineProperty(u.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var v=u.DetermineComponentFrameRoot(),A=v[0],F=v[1];if(A&&F){var ne=A.split(`
|
||||
`),le=F.split(`
|
||||
`);for(b=u=0;u<ne.length&&!ne[u].includes("DetermineComponentFrameRoot");)u++;for(;b<le.length&&!le[b].includes("DetermineComponentFrameRoot");)b++;if(u===ne.length||b===le.length)for(u=ne.length-1,b=le.length-1;1<=u&&0<=b&&ne[u]!==le[b];)b--;for(;1<=u&&0<=b;u--,b--)if(ne[u]!==le[b]){if(u!==1||b!==1)do if(u--,b--,0>b||ne[u]!==le[b]){var he=`
|
||||
`+ne[u].replace(" at new "," at ");return n.displayName&&he.includes("<anonymous>")&&(he=he.replace("<anonymous>",n.displayName)),he}while(1<=u&&0<=b);break}}}finally{je=!1,Error.prepareStackTrace=l}return(l=n?n.displayName||n.name:"")?Ne(l):""}function st(n,i){switch(n.tag){case 26:case 27:case 5:return Ne(n.type);case 16:return Ne("Lazy");case 13:return n.child!==i&&i!==null?Ne("Suspense Fallback"):Ne("Suspense");case 19:return Ne("SuspenseList");case 0:case 15:return $e(n.type,!1);case 11:return $e(n.type.render,!1);case 1:return $e(n.type,!0);case 31:return Ne("Activity");default:return""}}function Rt(n){try{var i="",l=null;do i+=st(n,l),l=n,n=n.return;while(n);return i}catch(u){return`
|
||||
Error generating stack: `+u.message+`
|
||||
`+u.stack}}var Yt=Object.prototype.hasOwnProperty,Pt=e.unstable_scheduleCallback,Xt=e.unstable_cancelCallback,Yn=e.unstable_shouldYield,En=e.unstable_requestPaint,ct=e.unstable_now,It=e.unstable_getCurrentPriorityLevel,ue=e.unstable_ImmediatePriority,xe=e.unstable_UserBlockingPriority,Oe=e.unstable_NormalPriority,Fe=e.unstable_LowPriority,Ze=e.unstable_IdlePriority,on=e.log,Nn=e.unstable_setDisableYieldValue,Kt=null,At=null;function Wt(n){if(typeof on=="function"&&Nn(n),At&&typeof At.setStrictMode=="function")try{At.setStrictMode(Kt,n)}catch{}}var ut=Math.clz32?Math.clz32:Ni,In=Math.log,cn=Math.LN2;function Ni(n){return n>>>=0,n===0?32:31-(In(n)/cn|0)|0}var nt=256,Xn=262144,Mn=4194304;function hn(n){var i=n&42;if(i!==0)return i;switch(n&-n){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return n&261888;case 262144:case 524288:case 1048576:case 2097152:return n&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return n&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return n}}function re(n,i,l){var u=n.pendingLanes;if(u===0)return 0;var b=0,v=n.suspendedLanes,A=n.pingedLanes;n=n.warmLanes;var F=u&134217727;return F!==0?(u=F&~v,u!==0?b=hn(u):(A&=F,A!==0?b=hn(A):l||(l=F&~n,l!==0&&(b=hn(l))))):(F=u&~v,F!==0?b=hn(F):A!==0?b=hn(A):l||(l=u&~n,l!==0&&(b=hn(l)))),b===0?0:i!==0&&i!==b&&(i&v)===0&&(v=b&-b,l=i&-i,v>=l||v===32&&(l&4194048)!==0)?i:b}function me(n,i){return(n.pendingLanes&~(n.suspendedLanes&~n.pingedLanes)&i)===0}function Ee(n,i){switch(n){case 1:case 2:case 4:case 8:case 64:return i+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return i+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Pe(){var n=Mn;return Mn<<=1,(Mn&62914560)===0&&(Mn=4194304),n}function St(n){for(var i=[],l=0;31>l;l++)i.push(n);return i}function gt(n,i){n.pendingLanes|=i,i!==268435456&&(n.suspendedLanes=0,n.pingedLanes=0,n.warmLanes=0)}function Ae(n,i,l,u,b,v){var A=n.pendingLanes;n.pendingLanes=l,n.suspendedLanes=0,n.pingedLanes=0,n.warmLanes=0,n.expiredLanes&=l,n.entangledLanes&=l,n.errorRecoveryDisabledLanes&=l,n.shellSuspendCounter=0;var F=n.entanglements,ne=n.expirationTimes,le=n.hiddenUpdates;for(l=A&~l;0<l;){var he=31-ut(l),ge=1<<he;F[he]=0,ne[he]=-1;var oe=le[he];if(oe!==null)for(le[he]=null,he=0;he<oe.length;he++){var de=oe[he];de!==null&&(de.lane&=-536870913)}l&=~ge}u!==0&&Se(n,u,0),v!==0&&b===0&&n.tag!==0&&(n.suspendedLanes|=v&~(A&~i))}function Se(n,i,l){n.pendingLanes|=i,n.suspendedLanes&=~i;var u=31-ut(i);n.entangledLanes|=i,n.entanglements[u]=n.entanglements[u]|1073741824|l&261930}function Ue(n,i){var l=n.entangledLanes|=i;for(n=n.entanglements;l;){var u=31-ut(l),b=1<<u;b&i|n[u]&i&&(n[u]|=i),l&=~b}}function Bt(n,i){var l=i&-i;return l=(l&42)!==0?1:Mt(l),(l&(n.suspendedLanes|i))!==0?0:l}function Mt(n){switch(n){case 2:n=1;break;case 8:n=4;break;case 32:n=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:n=128;break;case 268435456:n=134217728;break;default:n=0}return n}function xr(n){return n&=-n,2<n?8<n?(n&134217727)!==0?32:268435456:8:2}function Si(){var n=H.p;return n!==0?n:(n=window.event,n===void 0?32:I0(n.type))}function ki(n,i){var l=H.p;try{return H.p=n,i()}finally{H.p=l}}var lr=Math.random().toString(36).slice(2),Ut="__reactFiber$"+lr,mn="__reactProps$"+lr,yr="__reactContainer$"+lr,Ci="__reactEvents$"+lr,pa="__reactListeners$"+lr,Ti="__reactHandles$"+lr,Ja="__reactResources$"+lr,Wr="__reactMarker$"+lr;function ga(n){delete n[Ut],delete n[mn],delete n[Ci],delete n[pa],delete n[Ti]}function bn(n){var i=n[Ut];if(i)return i;for(var l=n.parentNode;l;){if(i=l[yr]||l[Ut]){if(l=i.alternate,i.child!==null||l!==null&&l.child!==null)for(n=_0(n);n!==null;){if(l=n[Ut])return l;n=_0(n)}return i}n=l,l=n.parentNode}return null}function vr(n){if(n=n[Ut]||n[yr]){var i=n.tag;if(i===5||i===6||i===13||i===31||i===26||i===27||i===3)return n}return null}function _r(n){var i=n.tag;if(i===5||i===26||i===27||i===6)return n.stateNode;throw Error(a(33))}function Br(n){var i=n[Ja];return i||(i=n[Ja]={hoistableStyles:new Map,hoistableScripts:new Map}),i}function Ft(n){n[Wr]=!0}var es=new Set,Jr={};function wr(n,i){ye(n,i),ye(n+"Capture",i)}function ye(n,i){for(Jr[n]=i,n=0;n<i.length;n++)es.add(i[n])}var Le=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u2
Line truncated
|
||||
`).replace(U2,"")}function d0(n,i){return i=u0(i),u0(n)===i}function Et(n,i,l,u,b,v){switch(l){case"children":typeof u=="string"?i==="body"||i==="textarea"&&u===""||qe(n,u):(typeof u=="number"||typeof u=="bigint")&&i!=="body"&&qe(n,""+u);break;case"className":On(n,"class",u);break;case"tabIndex":On(n,"tabindex",u);break;case"dir":case"role":case"viewBox":case"width":case"height":On(n,l,u);break;case"style":bt(n,u,v);break;case"data":if(i!=="object"){On(n,"data",u);break}case"src":case"href":if(u===""&&(i!=="a"||l!=="href")){n.removeAttribute(l);break}if(u==null||typeof u=="function"||typeof u=="symbol"||typeof u=="boolean"){n.removeAttribute(l);break}u=Di(""+u),n.setAttribute(l,u);break;case"action":case"formAction":if(typeof u=="function"){n.setAttribute(l,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}else typeof v=="function"&&(l==="formAction"?(i!=="input"&&Et(n,i,"name",b.name,b,null),Et(n,i,"formEncType",b.formEncType,b,null),Et(n,i,"formMethod",b.formMethod,b,null),Et(n,i,"formTarget",b.formTarget,b,null)):(Et(n,i,"encType",b.encType,b,null),Et(n,i,"method",b.method,b,null),Et(n,i,"target",b.target,b,null)));if(u==null||typeof u=="symbol"||typeof u=="boolean"){n.removeAttribute(l);break}u=Di(""+u),n.setAttribute(l,u);break;case"onClick":u!=null&&(n.onclick=_e);break;case"onScroll":u!=null&&et("scroll",n);break;case"onScrollEnd":u!=null&&et("scrollend",n);break;case"dangerouslySetInnerHTML":if(u!=null){if(typeof u!="object"||!("__html"in u))throw Error(a(61));if(l=u.__html,l!=null){if(b.children!=null)throw Error(a(60));n.innerHTML=l}}break;case"multiple":n.multiple=u&&typeof u!="function"&&typeof u!="symbol";break;case"muted":n.muted=u&&typeof u!="function"&&typeof u!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(u==null||typeof u=="function"||typeof u=="boolean"||typeof u=="symbol"){n.removeAttribute("xlink:href");break}l=Di(""+u),n.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",l);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":u!=null&&typeof u!="function"&&typeof u!="symbol"?n.setAttribute(l,""+u):n.removeAttribute(l);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":u&&typeof u!="function"&&typeof u!="symbol"?n.setAttribute(l,""):n.removeAttribute(l);break;case"capture":case"download":u===!0?n.setAttribute(l,""):u!==!1&&u!=null&&typeof u!="function"&&typeof u!="symbol"?n.setAttribute(l,u):n.removeAttribute(l);break;case"cols":case"rows":case"size":case"span":u!=null&&typeof u!="function"&&typeof u!="symbol"&&!isNaN(u)&&1<=u?n.setAttribute(l,u):n.removeAttribute(l);break;case"rowSpan":case"start":u==null||typeof u=="function"||typeof u=="symbol"||isNaN(u)?n.removeAttribute(l):n.setAttribute(l,u);break;case"popover":et("beforetoggle",n),et("toggle",n),pn(n,"popover",u);break;case"xlinkActuate":Sn(n,"http://www.w3.org/1999/xlink","xlink:actuate",u);break;case"xlinkArcrole":Sn(n,"http://www.w3.org/1999/xlink","xlink:arcrole",u);break;case"xlinkRole":Sn(n,"http://www.w3.org/1999/xlink","xlink:role",u);break;case"xlinkShow":Sn(n,"http://www.w3.org/1999/xlink","xlink:show",u);break;case"xlinkTitle":Sn(n,"http://www.w3.org/1999/xlink","xlink:title",u);break;case"xlinkType":Sn(n,"http://www.w3.org/1999/xlink","xlink:type",u);break;case"xmlBase":Sn(n,"http://www.w3.org/XML/1998/namespace","xml:base",u);break;case"xmlLang":Sn(n,"http://www.w3.org/XML/1998/namespace","xml:lang",u);break;case"xmlSpace":Sn(n,"http://www.w3.org/XML/1998/namespace","xml:space",u);break;case"is":pn(n,"is",u);break;case"innerText":case"textContent":break;default:(!(2<l.length)||l[0]!=="o"&&l[0]!=="O"||l[1]!=="n"&&l[1]!=="N")&&(l=ei.get(l)||l,pn(n,l,u))}}function Ff(n,i,l,u,b,v){switch(l){case"style":bt(n,u,v);break;case"dangerouslySetInnerHTML":if(u!=null){if(typeof u!="object"||!("__html"in u))throw Error(a(61));if(l=u.__html,l!=null){if(b.children!=null)throw Error(a(60));n.innerHTML=l}}break;case"children":typeof u=="string"?qe(n,u):(typeof u=="number"||typeof u=="bigint")&&qe(n,""+u);break;case"onScroll":u!=null&&et("scroll",n);break;case"onScrollEnd":u!=null&&et("scrollend",n);break;case"onClick":u!=null&&(n.onclick=_e);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"inne
Line truncated
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const S_=(...e)=>e.filter((t,r,a)=>!!t&&t.trim()!==""&&a.indexOf(t)===r).join(" ").trim();/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const kk=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase();/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Ck=e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(t,r,a)=>a?a.toUpperCase():r.toLowerCase());/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const ey=e=>{const t=Ck(e);return t.charAt(0).toUpperCase()+t.slice(1)};/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/var Tk={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Ak=e=>{for(const t in e)if(t.startsWith("aria-")||t==="role"||t==="title")return!0;return!1};/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Mk=ee.forwardRef(({color:e="currentColor",size:t=24,strokeWidth:r=2,absoluteStrokeWidth:a,className:s="",children:o,iconNode:c,...d},f)=>ee.createElement("svg",{ref:f,...Tk,width:t,height:t,stroke:e,strokeWidth:a?Number(r)*24/Number(t):r,className:S_("lucide",s),...!o&&!Ak(d)&&{"aria-hidden":"true"},...d},[...c.map(([h,m])=>ee.createElement(h,m)),...Array.isArray(o)?o:[o]]));/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Me=(e,t)=>{const r=ee.forwardRef(({className:a,...s},o)=>ee.createElement(Mk,{ref:o,iconNode:t,className:S_(`lucide-${kk(ey(e))}`,`lucide-${e}`,a),...s}));return r.displayName=ey(e),r};/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Ok=[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]],gp=Me("arrow-left",Ok);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Rk=[["path",{d:"M7 7h10v10",key:"1tivn9"}],["path",{d:"M7 17 17 7",key:"1vkiza"}]],k_=Me("arrow-up-right",Rk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Dk=[["path",{d:"m5 12 7-7 7 7",key:"hav0vg"}],["path",{d:"M12 19V5",key:"x0mq9r"}]],jk=Me("arrow-up",Dk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Lk=[["path",{d:"M4.929 4.929 19.07 19.071",key:"196cmz"}],["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],C_=Me("ban",Lk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const zk=[["path",{d:"M10.268 21a2 2 0 0 0 3.464 0",key:"vwvbt9"}],["path",{d:"M17 17H4a1 1 0 0 1-.74-1.673C4.59 13.956 6 12.499 6 8a6 6 0 0 1 .258-1.742",key:"178tsu"}],["path",{d:"m2 2 20 20",key:"1ooewy"}],["path",{d:"M8.668 3.01A6 6 0 0 1 18 8c0 2.687.77 4.653 1.707 6.05",key:"1hqiys"}]],Ik=Me("bell-off",zk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Bk=[["path",{d:"M12 8V4H8",key:"hb8ula"}],["rect",{width:"16",height:"12",x:"4",y:"8",rx:"2",key:"enze0r"}],["path",{d:"M2 14h2",key:"vft8re"}],["path",{d:"M20 14h2",key:"4cs60a"}],["path",{d:"M15 13v2",key:"1xurst"}],["path",{d:"M9 13v2",key:"rq6x2g"}]],Ao=Me("bot",Bk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Uk=[["path",{d:"M12 18V5",key:"adv99a"}],["path",{d:"M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4",key:"1e3is1"}],["path",{d:"M17.598 6.5A3 3 0 1 0 12 5a3 3 0 1 0-5.598 1.5",key:"1gqd8o"}],["path",{d:"M17.997 5.125a4 4 0 0 1 2.526 5.77",key:"iwvgf7"}],["path",{d:"M18 18a4 4 0 0 0 2-7.464",key:"efp6ie"}],["path",{d:"M19.967 17.483A4 4 0 1 1 12 18a4 4 0 1 1-7.967-.517",key:"1gq6am"}],["path",{d:"M6 18a4 4 0 0 1-2-7.464",key:"k1g0md"}],["path",{d:"M6.003 5.125a4 4 0 0 0-2.526 5.77",key:"q97ue3"}]],T_=Me("brain",Uk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Hk=[["path",{d:"M16 14v2.2l1.6 1",key:"fo4ql5"}],["path",{d:"M16 2v4",key:"4m81vk"}],["path",{d:"M21 7.5V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h3.5",key:"1osxxc"}],["path",{d:"M3 10h5",key:"r794hk"}],["path",{d:"M8 2v4",key:"1cmpym"}],["circle",{cx:"16",cy:"16",r:"6",key:"qoo3c4"}]],$k=Me("calendar-clock",Hk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const qk=[["path",{d:"M18 6 7 17l-5-5",key:"116fxf"}],["path",{d:"m22 10-7.5 7.5L13 16",key:"ke71qq"}]],Pk=Me("check-check",qk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Fk=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],Gs=Me("check",Fk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Gk=[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]],ho=Me("chevron-down",Gk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Vk=[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]],Yk=Me("chevron-right",Vk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Xk=[["path",{d:"m18 15-6-6-6 6",key:"153udz"}]],A_=Me("chevron-up",Xk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Kk=[["path",{d:"m7 15 5 5 5-5",key:"1hf1tw"}],["path",{d:"m7 9 5-5 5 5",key:"sgt6xg"}]],Zk=Me("chevrons-up-down",Kk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Qk=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12",key:"1pkeuh"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16",key:"4dfq90"}]],Hu=Me("circle-alert",Qk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Wk=[["path",{d:"M21.801 10A10 10 0 1 1 17 3.335",key:"yps3ct"}],["path",{d:"m9 11 3 3L22 4",key:"1pflzl"}]],M_=Me("circle-check-big",Wk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Jk=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],O_=Me("circle-check",Jk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const eC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["circle",{cx:"12",cy:"12",r:"1",key:"41hilf"}]],tC=Me("circle-dot",eC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const nC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],rC=Me("circle",nC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const iC=[["path",{d:"M12 6v6l4 2",key:"mmk7yg"}],["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],R_=Me("clock",iC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const aC=[["path",{d:"m16 18 6-6-6-6",key:"eg8j8"}],["path",{d:"m8 6-6 6 6 6",key:"ppft3o"}]],sC=Me("code",aC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const lC=[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]],mo=Me("copy",lC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const oC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"22",x2:"18",y1:"12",y2:"12",key:"l9bcsi"}],["line",{x1:"6",x2:"2",y1:"12",y2:"12",key:"13hhkx"}],["line",{x1:"12",x2:"12",y1:"6",y2:"2",key:"10w3f3"}],["line",{x1:"12",x2:"12",y1:"22",y2:"18",key:"15g9kq"}]],cC=Me("crosshair",oC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const uC=[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]],ty=Me("external-link",uC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const dC=[["path",{d:"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0",key:"1nclc0"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]],fC=Me("eye",dC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const hC=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",key:"1oefj6"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}],["path",{d:"M10 9H8",key:"b1mrlr"}],["path",{d:"M16 13H8",key:"t4e002"}],["path",{d:"M16 17H8",key:"z1uh3a"}]],mC=Me("file-text",hC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const pC=[["path",{d:"M4 22V4a1 1 0 0 1 .4-.8A6 6 0 0 1 8 2c3 0 5 2 7.333 2q2 0 3.067-.8A1 1 0 0 1 20 4v10a1 1 0 0 1-.4.8A6 6 0 0 1 16 16c-3 0-5-2-8-2a6 6 0 0 0-4 1.528",key:"1jaruq"}]],D_=Me("flag",pC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const gC=[["circle",{cx:"18",cy:"18",r:"3",key:"1xkwt0"}],["circle",{cx:"6",cy:"6",r:"3",key:"1lh9wr"}],["path",{d:"M6 21V9a9 9 0 0 0 9 9",key:"7kw0sc"}]],bC=Me("git-merge",gC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const xC=[["circle",{cx:"18",cy:"18",r:"3",key:"1xkwt0"}],["circle",{cx:"6",cy:"6",r:"3",key:"1lh9wr"}],["path",{d:"M13 6h3a2 2 0 0 1 2 2v7",key:"1yeb86"}],["line",{x1:"6",x2:"6",y1:"9",y2:"21",key:"rroup"}]],yC=Me("git-pull-request",xC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const vC=[["path",{d:"M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4",key:"tonef"}],["path",{d:"M9 18c-4.51 2-5-2-7-2",key:"9comsn"}]],_C=Me("github",vC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const wC=[["path",{d:"m22 13.29-3.33-10a.42.42 0 0 0-.14-.18.38.38 0 0 0-.22-.11.39.39 0 0 0-.23.07.42.42 0 0 0-.14.18l-2.26 6.67H8.32L6.1 3.26a.42.42 0 0 0-.1-.18.38.38 0 0 0-.26-.08.39.39 0 0 0-.23.07.42.42 0 0 0-.14.18L2 13.29a.74.74 0 0 0 .27.83L12 21l9.69-6.88a.71.71 0 0 0 .31-.83Z",key:"148pdi"}]],EC=Me("gitlab",wC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const NC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20",key:"13o1zl"}],["path",{d:"M2 12h20",key:"9i4pu4"}]],j_=Me("globe",NC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const SC=[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}],["path",{d:"M12 7v5l4 2",key:"1fdv2h"}]],Vs=Me("history",SC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const kC=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2",key:"1m3agn"}],["circle",{cx:"9",cy:"9",r:"2",key:"af1f0g"}],["path",{d:"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21",key:"1xmnt7"}]],CC=Me("image",kC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const TC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 16v-4",key:"1dtifu"}],["path",{d:"M12 8h.01",key:"e9boi3"}]],AC=Me("info",TC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const MC=[["path",{d:"M13 5h8",key:"a7qcls"}],["path",{d:"M13 12h8",key:"h98zly"}],["path",{d:"M13 19h8",key:"c3s6r1"}],["path",{d:"m3 17 2 2 4-4",key:"1jhpwq"}],["rect",{x:"3",y:"4",width:"6",height:"6",rx:"1",key:"cif1o7"}]],OC=Me("list-todo",MC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const RC=[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]],qs=Me("loader-circle",RC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const DC=[["rect",{width:"18",height:"11",x:"3",y:"11",rx:"2",ry:"2",key:"1w4ew1"}],["path",{d:"M7 11V7a5 5 0 0 1 10 0v4",key:"fwvmzm"}]],jC=Me("lock",DC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const LC=[["path",{d:"m16 17 5-5-5-5",key:"1bji2h"}],["path",{d:"M21 12H9",key:"dn1m92"}],["path",{d:"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4",key:"1uf3rs"}]],zC=Me("log-out",LC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const IC=[["path",{d:"m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7",key:"132q7q"}],["rect",{x:"2",y:"4",width:"20",height:"16",rx:"2",key:"izxlao"}]],bp=Me("mail",IC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const BC=[["path",{d:"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719",key:"1sd12s"}]],ny=Me("message-circle",BC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const UC=[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",key:"1a8usu"}],["path",{d:"m15 5 4 4",key:"1mk7zo"}]],HC=Me("pencil",UC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const $C=[["path",{d:"M12 22v-5",key:"1ega77"}],["path",{d:"M15 8V2",key:"18g5xt"}],["path",{d:"M17 8a1 1 0 0 1 1 1v4a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1z",key:"1xoxul"}],["path",{d:"M9 8V2",key:"14iosj"}]],qC=Me("plug",$C);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const PC=[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]],FC=Me("plus",PC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const GC=[["path",{d:"M19.07 4.93A10 10 0 0 0 6.99 3.34",key:"z3du51"}],["path",{d:"M4 6h.01",key:"oypzma"}],["path",{d:"M2.29 9.62A10 10 0 1 0 21.31 8.35",key:"qzzz0"}],["path",{d:"M16.24 7.76A6 6 0 1 0 8.23 16.67",key:"1yjesh"}],["path",{d:"M12 18h.01",key:"mhygvu"}],["path",{d:"M17.99 11.66A6 6 0 0 1 15.77 16.67",key:"1u2y91"}],["circle",{cx:"12",cy:"12",r:"2",key:"1c9p78"}],["path",{d:"m13.41 10.59 5.66-5.66",key:"mhq4k0"}]],VC=Me("radar",GC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const YC=[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]],XC=Me("refresh-cw",YC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const KC=[["path",{d:"M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z",key:"m3kijz"}],["path",{d:"m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z",key:"1fmvmk"}],["path",{d:"M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0",key:"1f8sc4"}],["path",{d:"M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5",key:"qeys4"}]],ZC=Me("rocket",KC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const QC=[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}]],WC=Me("rotate-ccw",QC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const JC=[["path",{d:"m21 21-4.34-4.34",key:"14j7rj"}],["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}]],eT=Me("search",JC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const tT=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"M12 8v4",key:"1got3b"}],["path",{d:"M12 16h.01",key:"1drbdi"}]],nT=Me("shield-alert",tT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const rT=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],L_=Me("shield-check",rT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const iT=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}]],aT=Me("shield",iT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const sT=[["path",{d:"M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z",key:"1s2grr"}],["path",{d:"M20 2v4",key:"1rf3ol"}],["path",{d:"M22 4h-4",key:"gwowj6"}],["circle",{cx:"4",cy:"20",r:"2",key:"6kqj1y"}]],Im=Me("sparkles",sT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const lT=[["path",{d:"M21 9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z",key:"1dfntj"}],["path",{d:"M15 3v5a1 1 0 0 0 1 1h5",key:"6s6qgf"}]],oT=Me("sticky-note",lT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const cT=[["path",{d:"M12 19h8",key:"baeox8"}],["path",{d:"m4 17 6-6-6-6",key:"1yngyt"}]],z_=Me("terminal",cT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const uT=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],dT=Me("trash-2",uT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const fT=[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]],hT=Me("triangle-alert",fT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const mT=[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["path",{d:"M16 3.128a4 4 0 0 1 0 7.744",key:"16gr8j"}],["path",{d:"M22 21v-2a4 4 0 0 0-3-3.87",key:"kshegd"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}]],pT=Me("users",mT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const gT=[["path",{d:"m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72",key:"ul74o6"}],["path",{d:"m14 7 3 3",key:"1r5n42"}],["path",{d:"M5 6v4",key:"ilb8ba"}],["path",{d:"M19 14v4",key:"blhpug"}],["path",{d:"M10 2v2",key:"7u0qdc"}],["path",{d:"M7 8H3",key:"zfb6yr"}],["path",{d:"M21 16h-4",key:"1cnmox"}],["path",{d:"M11 3H9",key:"1obp7u"}]],bT=Me("wand-sparkles",gT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const xT=[["path",{d:"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z",key:"1ngwbx"}]],Bm=Me("wrench",xT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const yT=[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]],xp=Me("x",yT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const vT=[["path",{d:"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z",key:"1xq2db"}]],_T=Me("zap",vT),wT={open:{label:"Open",color:"bg-red-500/10 text-red-400 border-red-500/20",dotColor:"bg-red-500",description:"Newly discovered, awaiting triage"},in_progress:{label:"In Progress",color:"bg-blue-500/10 text-blue-400 border-blue-500/20",dotColor:"bg-blue-500",description:"Someone is working on this"},snoozed:{label:"Snoozed",color:"bg-purple-500/10 text-purple-400 border-purple-500/20",dotColor:"bg-purple-500",description:"Temporarily hidden until a follow-up date"},fixed:{label:"Fixed",color:"bg-emerald-500/10 text-emerald-400 border-emerald-500/20",dotColor:"bg-emerald-500",description:"This vulnerability has been fixed"},ignored:{label:"Ignored",color:"bg-gray-500/10 text-gray-400 border-gray-500/20",dotColor:"bg-gray-500",description:"Acknowledged but accepted"}},ET={trivial:{label:"Trivial",color:"bg-emerald-500/10 text-emerald-400 border-emerald-500/20"},low:{label:"Low",color:"bg-blue-500/10 text-blue-400 border-blue-500/20"},medium:{label:"Medium",color:"bg-yellow-500/10 text-yellow-400 border-yellow-500/20"},high:{label:"High",color:"bg-orange-500/10 text-orange-400 border-orange-500/20"}},I_={critical:"bg-red-500/20 text-red-500 border-red-500/30",high:"bg-orange-500/20 text-orange-500 border-orange-500/30",medium:"bg-yellow-500/20 text-yellow-500 border-yellow-500/30",low:"bg-blue-500/20 text-blue-500 border-blue-500/30"};function Zc(e){return e.original_severity!=null&&e.original_severity!==e.severity}const NT={js:"javascript",ts:"typescript",tsx:"typescript",jsx:"javascript",py:"python",rb:"ruby",go:"go",rs:"rust",java:"java",php:"php",cs:"csharp",cpp:"cpp",c:"c",sh:"bash",bash:"bash",sql:"sql",html:"html",css:"css",json:"json",yaml:"yaml",yml:"yaml",xml:"xml"};function ST(e){var r;if(!e)return null;const t=(r=e.split(".").pop())==null?void 0:r.toLowerCase();return t&&NT[t]||null}function yp(e){switch(e){case"critical":return"bg-red-500";case"high":return"bg-orange-500";case"medium":return"bg-yellow-500";default:return"bg-blue-500"}}async function vp(e){try{await navigator.clipboard.writeText(e)}catch{const t=document.createElement("textarea");t.value=e,t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}}const $u="https://app.strix.ai/api/auth/signup",kT="https://strix.ai/pricing",CT="ref=oss_viewer&utm_source=oss_viewer&utm_medium=local_viewer&utm_campaign=oss_viewer";function fa(e,t){const r=e.includes("?")?"&":"?";return`${e}${r}${CT}&utm_content=${encodeURIComponent(t)}`}function Tr(e,t={}){try{const r={event:e};for(const[s,o]of Object.entries(t))o!==void 0&&(r[s]=o);const a=JSON.stringify(r);typeof navigator<"u"&&navigator.sendBeacon?navigator.sendBeacon("/api/event",a):fetch("/api/event",{method:"POST",body:a,keepalive:!0})}catch{}}function Dr(e,t){Tr("cta_clicked",{cta:e,surface:t})}function B_(e){var t,r,a="";if(typeof e=="string"||typeof e=="number")a+=e;else if(typeof e=="object")if(Array.isArray(e)){var s=e.length;for(t=0;t<s;t++)e[t]&&(r=B_(e[t]))&&(a&&(a+=" "),a+=r)}else for(r in e)e[r]&&(a&&(a+=" "),a+=r);return a}function TT(){for(var e,t,r=0,a="",s=arguments.length;r<s;r++)(e=arguments[r])&&(t=B_(e))&&(a&&(a+=" "),a+=t);return a}const AT=(e,t)=>{const r=new Array(e.length+t.length);for(let a=0;a<e.length;a++)r[a]=e[a];for(let a=0;a<t.length;a++)r[e.length+a]=t[a];return r},MT=(e,t)=>({classGroupId:e,validator:t}),U_=(e=new Map,t=null,r)=>({nextPart:e,validators:t,classGroupId:r}),vu="-",ry=[],OT="arbitrary..",RT=e=>{const t=jT(e),{conflictingClassGroups:r,conflictingClassGroupModifiers:a}=e;return{getClassGroupId:c=>{if(c.startsWith("[")&&c.endsWith("]"))return DT(c);const d=c.split(vu),f=d[0]===""&&d.length>1?1:0;return H_(d,f,t)},getConflictingClassGroupIds:(c,d)=>{if(d){const f=a[c],h=r[c];return f?h?AT(h,f):f:h||ry}return r[c]||ry}}},H_=(e,t,r)=>{if(e.length-t===0)return r.classGroupId;const s=e[t],o=r.nextPart.get(s);if(o){const h=H_(e,t+1,o);if(h)return h}const c=r.validators;if(c===null)return;const d=t===0?e.join(vu):e.slice(t).join(vu),f=c.length;for(let h=0;h<f;h++){const m=c[h];if(m.validator(d))return m.classGroupId}},DT=e=>e.slice(1,-1).indexOf(":")===-1?void 0:(()=>{const t=e.slice(1,-1),r=t.indexOf(":"),a=t.slice(0,r);return a?OT+a:void 0})(),jT=e=>{const{theme:t,classGroups:r}=e;return LT(r,t)},LT=(e,t)=>{const r=U_();for(const a in e){const s=e[a];_p(s,r,a,t)}return r},_p=(e,t,r,a)=>{const s=e.length;for(let o=0;o<s;o++){const c=e[o];zT(c,t,r,a)}},zT=(e,t,r,a)=>{if(typeof e=="string"){IT(e,t,r);return}if(typeof e=="function"){BT(e,t,r,a);return}UT(e,t,r,a)},IT=(e,t,r)=>{const a=e===""?t:$_(t,e);a.classGroupId=r},BT=(e,t,r,a)=>{if(HT(e)){_p(e(a),t,r,a);return}t.validators===null&&(t.validators=[]),t.validators.push(MT(r,e))},UT=(e,t,r,a)=>{const s=Object.enLine truncated
|
||||
`,h="/",m="*",p="",y="comment",x="declaration";function _(S,w){if(typeof S!="string")throw new TypeError("First argument must be a string");if(!S)return[];w=w||{};var k=1,E=1;function M(T){var $=T.match(t);$&&(k+=$.length);var O=T.lastIndexOf(f);E=~O?T.length-O:E+T.length}function I(){var T={line:k,column:E};return function($){return $.position=new R(T),Z(),$}}function R(T){this.start=T,this.end={line:k,column:E},this.source=w.source}R.prototype.content=S;function U(T){var $=new Error(w.source+":"+k+":"+E+": "+T);if($.reason=T,$.filename=w.source,$.line=k,$.column=E,$.source=S,!w.silent)throw $}function B(T){var $=T.exec(S);if($){var O=$[0];return M(O),S=S.slice(O.length),$}}function Z(){B(r)}function D(T){var $;for(T=T||[];$=z();)$!==!1&&T.push($);return T}function z(){var T=I();if(!(h!=S.charAt(0)||m!=S.charAt(1))){for(var $=2;p!=S.charAt($)&&(m!=S.charAt($)||h!=S.charAt($+1));)++$;if($+=2,p===S.charAt($-1))return U("End of comment missing");var O=S.slice(2,$-2);return E+=2,M(O),S=S.slice($),E+=2,T({type:y,comment:O})}}function V(){var T=I(),$=B(a);if($){if(z(),!B(s))return U("property missing ':'");var O=B(o),H=T({type:x,property:N($[0].replace(e,p)),value:O?N(O[0].replace(e,p)):p});return B(c),H}}function P(){var T=[];D(T);for(var $;$=V();)$!==!1&&(T.push($),D(T));return T}return Z(),P()}function N(S){return S?S.replace(d,p):p}return gh=_,gh}var my;function HA(){if(my)return js;my=1;var e=js&&js.__importDefault||function(a){return a&&a.__esModule?a:{default:a}};Object.defineProperty(js,"__esModule",{value:!0}),js.default=r;const t=e(UA());function r(a,s){let o=null;if(!a||typeof a!="string")return o;const c=(0,t.default)(a),d=typeof s=="function";return c.forEach(f=>{if(f.type!=="declaration")return;const{property:h,value:m}=f;d?s(h,m,f):m&&(o=o||{},o[h]=m)}),o}return js}var Zl={},py;function $A(){if(py)return Zl;py=1,Object.defineProperty(Zl,"__esModule",{value:!0}),Zl.camelCase=void 0;var e=/^--[a-zA-Z0-9_-]+$/,t=/-([a-z])/g,r=/^[^-]+$/,a=/^-(webkit|moz|ms|o|khtml)-/,s=/^-(ms)-/,o=function(h){return!h||r.test(h)||e.test(h)},c=function(h,m){return m.toUpperCase()},d=function(h,m){return"".concat(m,"-")},f=function(h,m){return m===void 0&&(m={}),o(h)?h:(h=h.toLowerCase(),m.reactCompat?h=h.replace(s,d):h=h.replace(a,d),h.replace(t,c))};return Zl.camelCase=f,Zl}var Ql,gy;function qA(){if(gy)return Ql;gy=1;var e=Ql&&Ql.__importDefault||function(s){return s&&s.__esModule?s:{default:s}},t=e(HA()),r=$A();function a(s,o){var c={};return!s||typeof s!="string"||(0,t.default)(s,function(d,f){d&&f&&(c[(0,r.camelCase)(d,o)]=f)}),c}return a.default=a,Ql=a,Ql}var PA=qA();const FA=Co(PA),sw=lw("end"),Sp=lw("start");function lw(e){return t;function t(r){const a=r&&r.position&&r.position[e]||{};if(typeof a.line=="number"&&a.line>0&&typeof a.column=="number"&&a.column>0)return{line:a.line,column:a.column,offset:typeof a.offset=="number"&&a.offset>-1?a.offset:void 0}}}function GA(e){const t=Sp(e),r=sw(e);if(t&&r)return{start:t,end:r}}function lo(e){return!e||typeof e!="object"?"":"position"in e||"type"in e?by(e.position):"start"in e||"end"in e?by(e):"line"in e||"column"in e?Fm(e):""}function Fm(e){return xy(e&&e.line)+":"+xy(e&&e.column)}function by(e){return Fm(e&&e.start)+"-"+Fm(e&&e.end)}function xy(e){return e&&typeof e=="number"?e:1}class An extends Error{constructor(t,r,a){super(),typeof r=="string"&&(a=r,r=void 0);let s="",o={},c=!1;if(r&&("line"in r&&"column"in r?o={place:r}:"start"in r&&"end"in r?o={place:r}:"type"in r?o={ancestors:[r],place:r.position}:o={...r}),typeof t=="string"?s=t:!o.cause&&t&&(c=!0,s=t.message,o.cause=t),!o.ruleId&&!o.source&&typeof a=="string"){const f=a.indexOf(":");f===-1?o.ruleId=a:(o.source=a.slice(0,f),o.ruleId=a.slice(f+1))}if(!o.place&&o.ancestors&&o.ancestors){const f=o.ancestors[o.ancestors.length-1];f&&(o.place=f.position)}const d=o.place&&"start"in o.place?o.place.start:o.place;this.ancestors=o.ancestors||void 0,this.cause=o.cause||void 0,this.column=d?d.column:void 0,this.fatal=void 0,this.file="",this.message=s,this.line=d?d.line:void 0,this.name=lo(o.place)||"1:1",this.place=o.place||void 0,this.reason=this.message,this.ruleId=o.ruleId||void 0,this.source=o.source||void 0,this.stack=c&&o.cause&&typeof o.cause.stack=="string"?o.cause.stack:"",this.actual=void 0,this.expected=void 0,this.note=void 0,this.url=void 0}}An.prototype.file="";An.prototype.name="";An.prototype.reason="";An.prototype.message="";An.prototype.stack="";An.prototype.column=void 0;An.prototype.line=void 0;An.prototype.ancestors=void 0;An.prototype.cause=void 0;An.prototype.fatal=void 0;An.prototype.place=void 0;An.prototype.ruleId=void 0;An.prototype.source=void 0;const kp={}.hasOwnProperty,VA=new Map,YA=/[A-Z]/g,XA=new Set(["table","tbody","thead","tfoot","tr"]),KA=new Set(["td","th"]),ow="https://github.com/syntax-tree/hast-util-to-jsx-runtime";function ZA(e,t){if(!t||t.Fragment===void 0)throw new TypeError("Expected `Fragment` in options");const r=t.filePath||void 0;let a;if(t.development){if(typeof t.j
Line truncated
|
||||
`;break}case-3:{c=`\r
|
||||
`;break}case-2:{c=t?" ":" ";break}case-1:{if(!t&&s)continue;c=" ";break}default:c=String.fromCharCode(o)}s=o===-2,a.push(c)}return a.join("")}function iO(e){const a={constructs:hw([eO,...(e||{}).extensions||[]]),content:s(yM),defined:[],document:s(_M),flow:s(U5),lazy:{},string:s(q5),text:s(P5)};return a;function s(o){return c;function c(d){return tO(a,o,d)}}}function aO(e){for(;!xw(e););return e}const Ty=/[\0\t\n\r]/g;function sO(){let e=1,t="",r=!0,a;return s;function s(o,c,d){const f=[];let h,m,p,y,x;for(o=t+(typeof o=="string"?o.toString():new TextDecoder(c||void 0).decode(o)),p=0,t="",r&&(o.charCodeAt(0)===65279&&p++,r=void 0);p<o.length;){if(Ty.lastIndex=p,h=Ty.exec(o),y=h&&h.index!==void 0?h.index:o.length,x=o.charCodeAt(y),!h){t=o.slice(p);break}if(x===10&&p===y&&a)f.push(-3),a=void 0;else switch(a&&(f.push(-5),a=void 0),p<y&&(f.push(o.slice(p,y)),e+=y-p),x){case 0:{f.push(65533),e++;break}case 9:{for(m=Math.ceil(e/4)*4,f.push(-2);e++<m;)f.push(-1);break}case 10:{f.push(-4),e=1;break}default:a=!0,e=1}p=y+1}return d&&(a&&f.push(-5),t&&f.push(t),f.push(null)),f}}const lO=/\\([!-/:-@[-`{-~])|&(#(?:\d{1,7}|x[\da-f]{1,6})|[\da-z]{1,31});/gi;function oO(e){return e.replace(lO,cO)}function cO(e,t,r){if(t)return t;if(r.charCodeAt(0)===35){const s=r.charCodeAt(1),o=s===120||s===88;return mw(r.slice(o?2:1),o?16:10)}return Mp(r)||e}const Nw={}.hasOwnProperty;function uO(e,t,r){return t&&typeof t=="object"&&(r=t,t=void 0),dO(r)(aO(iO(r).document().write(sO()(e,t,!0))))}function dO(e){const t={transforms:[],canContainEols:["emphasis","fragment","heading","paragraph","strong"],enter:{autolink:o(Yt),autolinkProtocol:Z,autolinkEmail:Z,atxHeading:o(je),blockQuote:o(ce),characterEscape:Z,characterReference:Z,codeFenced:o(fe),codeFencedFenceInfo:c,codeFencedFenceMeta:c,codeIndented:o(fe,c),codeText:o(be,c),codeTextData:Z,data:Z,codeFlowValue:Z,definition:o(we),definitionDestinationString:c,definitionLabelString:c,definitionTitleString:c,emphasis:o(Ne),hardBreakEscape:o($e),hardBreakTrailing:o($e),htmlFlow:o(st,c),htmlFlowData:Z,htmlText:o(st,c),htmlTextData:Z,image:o(Rt),label:c,link:o(Yt),listItem:o(Xt),listItemValue:y,listOrdered:o(Pt,p),listUnordered:o(Pt),paragraph:o(Yn),reference:L,referenceString:c,resourceDestinationString:c,resourceTitleString:c,setextHeading:o(je),strong:o(En),thematicBreak:o(It)},exit:{atxHeading:f(),atxHeadingSequence:I,autolink:f(),autolinkEmail:te,autolinkProtocol:W,blockQuote:f(),characterEscapeValue:D,characterReferenceMarkerHexadecimal:q,characterReferenceMarkerNumeric:q,characterReferenceValue:Q,characterReference:J,codeFenced:f(S),codeFencedFence:N,codeFencedFenceInfo:x,codeFencedFenceMeta:_,codeFlowValue:D,codeIndented:f(w),codeText:f($),codeTextData:D,data:D,definition:f(),definitionDestinationString:M,definitionLabelString:k,definitionTitleString:E,emphasis:f(),hardBreakEscape:f(V),hardBreakTrailing:f(V),htmlFlow:f(P),htmlFlowData:D,htmlText:f(T),htmlTextData:D,image:f(H),label:K,labelText:X,lineEnding:z,link:f(O),listItem:f(),listOrdered:f(),listUnordered:f(),paragraph:f(),referenceString:G,resourceDestinationString:C,resourceTitleString:j,resource:Y,setextHeading:f(B),setextHeadingLineSequence:U,setextHeadingText:R,strong:f(),thematicBreak:f()}};Sw(t,(e||{}).mdastExtensions||[]);const r={};return a;function a(ue){let xe={type:"root",children:[]};const Oe={stack:[xe],tokenStack:[],config:t,enter:d,exit:h,buffer:c,resume:m,data:r},Fe=[];let Ze=-1;for(;++Ze<ue.length;)if(ue[Ze][1].type==="listOrdered"||ue[Ze][1].type==="listUnordered")if(ue[Ze][0]==="enter")Fe.push(Ze);else{const on=Fe.pop();Ze=s(ue,on,Ze)}for(Ze=-1;++Ze<ue.length;){const on=t[ue[Ze][0]];Nw.call(on,ue[Ze][1].type)&&on[ue[Ze][1].type].call(Object.assign({sliceSerialize:ue[Ze][2].sliceSerialize},Oe),ue[Ze][1])}if(Oe.tokenStack.length>0){const on=Oe.tokenStack[Oe.tokenStack.length-1];(on[1]||Ay).call(Oe,void 0,on[0])}for(xe.position={start:ia(ue.length>0?ue[0][1].start:{line:1,column:1,offset:0}),end:ia(ue.length>0?ue[ue.length-2][1].end:{line:1,column:1,offset:0})},Ze=-1;++Ze<t.transforms.length;)xe=t.transforms[Ze](xe)||xe;return xe}function s(ue,xe,Oe){let Fe=xe-1,Ze=-1,on=!1,Nn,Kt,At,Wt;for(;++Fe<=Oe;){const ut=ue[Fe];switch(ut[1].type){case"listUnordered":case"listOrdered":case"blockQuote":{ut[0]==="enter"?Ze++:Ze--,Wt=void 0;break}case"lineEndingBlank":{ut[0]==="enter"&&(Nn&&!Wt&&!Ze&&!At&&(At=Fe),Wt=void 0);break}case"linePrefix":case"listItemValue":case"listItemMarker":case"listItemPrefix":case"listItemPrefixWhitespace":break;default:Wt=void 0}if(!Ze&&ut[0]==="enter"&&ut[1].type==="listItemPrefix"||Ze===-1&&ut[0]==="exit"&&(ut[1].type==="listUnordered"||ut[1].type==="listOrdered")){if(Nn){let In=Fe;for(Kt=void 0;In--;){const cn=ue[In];if(cn[1].type==="lineEnding"||cn[1].type==="lineEndingBlank"){if(cn[0]==="exit")continue;Kt&&(ue[Kt][1].type="lineEndingBlank",on=!0),cn[1].type="lineEnding",Kt=In}else if(!(cn[1].type==="linePrefix"||cn[1].type==="blockQuotePrefix"||cn[1].type==="blockQuotePrefixWhitespace"||cn[Line truncated
|
||||
`}]}function gO(e,t){const r=t.value?t.value+`
|
||||
`:"",a={},s=t.lang?t.lang.split(/\s+/):[];s.length>0&&(a.className=["language-"+s[0]]);let o={type:"element",tagName:"code",properties:a,children:[{type:"text",value:r}]};return t.meta&&(o.data={meta:t.meta}),e.patch(t,o),o=e.applyData(t,o),o={type:"element",tagName:"pre",properties:{},children:[o]},e.patch(t,o),o}function bO(e,t){const r={type:"element",tagName:"del",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function xO(e,t){const r={type:"element",tagName:"em",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function yO(e,t){const r=typeof e.options.clobberPrefix=="string"?e.options.clobberPrefix:"user-content-",a=String(t.identifier).toUpperCase(),s=nl(a.toLowerCase()),o=e.footnoteOrder.indexOf(a);let c,d=e.footnoteCounts.get(a);d===void 0?(d=0,e.footnoteOrder.push(a),c=e.footnoteOrder.length):c=o+1,d+=1,e.footnoteCounts.set(a,d);const f={type:"element",tagName:"a",properties:{href:"#"+r+"fn-"+s,id:r+"fnref-"+s+(d>1?"-"+d:""),dataFootnoteRef:!0,ariaDescribedBy:["footnote-label"]},children:[{type:"text",value:String(c)}]};e.patch(t,f);const h={type:"element",tagName:"sup",properties:{},children:[f]};return e.patch(t,h),e.applyData(t,h)}function vO(e,t){const r={type:"element",tagName:"h"+t.depth,properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function _O(e,t){if(e.options.allowDangerousHtml){const r={type:"raw",value:t.value};return e.patch(t,r),e.applyData(t,r)}}function kw(e,t){const r=t.referenceType;let a="]";if(r==="collapsed"?a+="[]":r==="full"&&(a+="["+(t.label||t.identifier)+"]"),t.type==="imageReference")return[{type:"text",value:"!["+t.alt+a}];const s=e.all(t),o=s[0];o&&o.type==="text"?o.value="["+o.value:s.unshift({type:"text",value:"["});const c=s[s.length-1];return c&&c.type==="text"?c.value+=a:s.push({type:"text",value:a}),s}function wO(e,t){const r=String(t.identifier).toUpperCase(),a=e.definitionById.get(r);if(!a)return kw(e,t);const s={src:nl(a.url||""),alt:t.alt};a.title!==null&&a.title!==void 0&&(s.title=a.title);const o={type:"element",tagName:"img",properties:s,children:[]};return e.patch(t,o),e.applyData(t,o)}function EO(e,t){const r={src:nl(t.url)};t.alt!==null&&t.alt!==void 0&&(r.alt=t.alt),t.title!==null&&t.title!==void 0&&(r.title=t.title);const a={type:"element",tagName:"img",properties:r,children:[]};return e.patch(t,a),e.applyData(t,a)}function NO(e,t){const r={type:"text",value:t.value.replace(/\r?\n|\r/g," ")};e.patch(t,r);const a={type:"element",tagName:"code",properties:{},children:[r]};return e.patch(t,a),e.applyData(t,a)}function SO(e,t){const r=String(t.identifier).toUpperCase(),a=e.definitionById.get(r);if(!a)return kw(e,t);const s={href:nl(a.url||"")};a.title!==null&&a.title!==void 0&&(s.title=a.title);const o={type:"element",tagName:"a",properties:s,children:e.all(t)};return e.patch(t,o),e.applyData(t,o)}function kO(e,t){const r={href:nl(t.url)};t.title!==null&&t.title!==void 0&&(r.title=t.title);const a={type:"element",tagName:"a",properties:r,children:e.all(t)};return e.patch(t,a),e.applyData(t,a)}function CO(e,t,r){const a=e.all(t),s=r?TO(r):Cw(t),o={},c=[];if(typeof t.checked=="boolean"){const m=a[0];let p;m&&m.type==="element"&&m.tagName==="p"?p=m:(p={type:"element",tagName:"p",properties:{},children:[]},a.unshift(p)),p.children.length>0&&p.children.unshift({type:"text",value:" "}),p.children.unshift({type:"element",tagName:"input",properties:{type:"checkbox",checked:t.checked,disabled:!0},children:[]}),o.className=["task-list-item"]}let d=-1;for(;++d<a.length;){const m=a[d];(s||d!==0||m.type!=="element"||m.tagName!=="p")&&c.push({type:"text",value:`
|
||||
`}),m.type==="element"&&m.tagName==="p"&&!s?c.push(...m.children):c.push(m)}const f=a[a.length-1];f&&(s||f.type!=="element"||f.tagName!=="p")&&c.push({type:"text",value:`
|
||||
`});const h={type:"element",tagName:"li",properties:o,children:c};return e.patch(t,h),e.applyData(t,h)}function TO(e){let t=!1;if(e.type==="list"){t=e.spread||!1;const r=e.children;let a=-1;for(;!t&&++a<r.length;)t=Cw(r[a])}return t}function Cw(e){const t=e.spread;return t??e.children.length>1}function AO(e,t){const r={},a=e.all(t);let s=-1;for(typeof t.start=="number"&&t.start!==1&&(r.start=t.start);++s<a.length;){const c=a[s];if(c.type==="element"&&c.tagName==="li"&&c.properties&&Array.isArray(c.properties.className)&&c.properties.className.includes("task-list-item")){r.className=["contains-task-list"];break}}const o={type:"element",tagName:t.ordered?"ol":"ul",properties:r,children:e.wrap(a,!0)};return e.patch(t,o),e.applyData(t,o)}function MO(e,t){const r={type:"element",tagName:"p",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function OO(e,t){const r={type:"root",children:e.wrap(e.all(t))};return e.patch(t,r),e.applyData(t,r)}function RO(e,t){const r={type:"element",tagName:"strong",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function DO(e,t){const r=e.all(t),a=r.shift(),s=[];if(a){const c={type:"element",tagName:"thead",properties:{},children:e.wrap([a],!0)};e.patch(t.children[0],c),s.push(c)}if(r.length>0){const c={type:"element",tagName:"tbody",properties:{},children:e.wrap(r,!0)},d=Sp(t.children[1]),f=sw(t.children[t.children.length-1]);d&&f&&(c.position={start:d,end:f}),s.push(c)}const o={type:"element",tagName:"table",properties:{},children:e.wrap(s,!0)};return e.patch(t,o),e.applyData(t,o)}function jO(e,t,r){const a=r?r.children:void 0,o=(a?a.indexOf(t):1)===0?"th":"td",c=r&&r.type==="table"?r.align:void 0,d=c?c.length:t.children.length;let f=-1;const h=[];for(;++f<d;){const p=t.children[f],y={},x=c?c[f]:void 0;x&&(y.align=x);let _={type:"element",tagName:o,properties:y,children:[]};p&&(_.children=e.all(p),e.patch(p,_),_=e.applyData(p,_)),h.push(_)}const m={type:"element",tagName:"tr",properties:{},children:e.wrap(h,!0)};return e.patch(t,m),e.applyData(t,m)}function LO(e,t){const r={type:"element",tagName:"td",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}const My=9,Oy=32;function zO(e){const t=String(e),r=/\r?\n|\r/g;let a=r.exec(t),s=0;const o=[];for(;a;)o.push(Ry(t.slice(s,a.index),s>0,!0),a[0]),s=a.index+a[0].length,a=r.exec(t);return o.push(Ry(t.slice(s),s>0,!1)),o.join("")}function Ry(e,t,r){let a=0,s=e.length;if(t){let o=e.codePointAt(a);for(;o===My||o===Oy;)a++,o=e.codePointAt(a)}if(r){let o=e.codePointAt(s-1);for(;o===My||o===Oy;)s--,o=e.codePointAt(s-1)}return s>a?e.slice(a,s):""}function IO(e,t){const r={type:"text",value:zO(String(t.value))};return e.patch(t,r),e.applyData(t,r)}function BO(e,t){const r={type:"element",tagName:"hr",properties:{},children:[]};return e.patch(t,r),e.applyData(t,r)}const UO={blockquote:mO,break:pO,code:gO,delete:bO,emphasis:xO,footnoteReference:yO,heading:vO,html:_O,imageReference:wO,image:EO,inlineCode:NO,linkReference:SO,link:kO,listItem:CO,list:AO,paragraph:MO,root:OO,strong:RO,table:DO,tableCell:LO,tableRow:jO,text:IO,thematicBreak:BO,toml:Jc,yaml:Jc,definition:Jc,footnoteDefinition:Jc};function Jc(){}const Tw=-1,Fu=0,co=1,wu=2,Rp=3,Dp=4,jp=5,Lp=6,Aw=7,Mw=8,Ow=typeof self=="object"?self:globalThis,Dy=(e,t)=>{switch(e){case"Function":case"SharedWorker":case"Worker":case"eval":case"setInterval":case"setTimeout":throw new TypeError("unable to deserialize "+e)}return new Ow[e](t)},HO=(e,t)=>{const r=(s,o)=>(e.set(o,s),s),a=s=>{if(e.has(s))return e.get(s);const[o,c]=t[s];switch(o){case Fu:case Tw:return r(c,s);case co:{const d=r([],s);for(const f of c)d.push(a(f));return d}case wu:{const d=r({},s);for(const[f,h]of c)d[a(f)]=a(h);return d}case Rp:return r(new Date(c),s);case Dp:{const{source:d,flags:f}=c;return r(new RegExp(d,f),s)}case jp:{const d=r(new Map,s);for(const[f,h]of c)d.set(a(f),a(h));return d}case Lp:{const d=r(new Set,s);for(const f of c)d.add(a(f));return d}case Aw:{const{name:d,message:f}=c;return r(typeof Ow[d]=="function"?Dy(d,f):new Error(f),s)}case Mw:return r(BigInt(c),s);case"BigInt":return r(Object(BigInt(c)),s);case"ArrayBuffer":return r(new Uint8Array(c).buffer,c);case"DataView":{const{buffer:d}=new Uint8Array(c);return r(new DataView(d),c)}}return r(Dy(o,c),s)};return a},jy=e=>HO(new Map,e)(0),Ba="",{toString:$O}={},{keys:qO}=Object,Jl=e=>{const t=typeof e;if(t!=="object"||!e)return[Fu,t];const r=$O.call(e).slice(8,-1);switch(r){case"Array":return[co,Ba];case"Object":return[wu,Ba];case"Date":return[Rp,Ba];case"RegExp":return[Dp,Ba];case"Map":return[jp,Ba];case"Set":return[Lp,Ba];case"DataView":return[co,r]}return r.includes("Array")?[co,r]:e instanceof Error?[Aw,e.name||"Error"]:[wu,r]},eu=([e,t])=>e===Fu&&(t==="function"||t==="symbol"),PO=(e,t,r,a)=>{const s=(c,d)=>{const f=a.push(c)-1;return r.set(d,f),f},o=c=>{if(r.has(c))return r.get(c);let[d,f]=Jl(c);switch(d){case Fu:{let m=c;switch(f){case"bigint":d=Mw,m=c.toString();break;case"function":case"symbol":
Line truncated
|
||||
`},{type:"element",tagName:"ol",properties:{},children:e.wrap(d,!0)},{type:"text",value:`
|
||||
`}]}}const Gu=(function(e){if(e==null)return ZO;if(typeof e=="function")return Vu(e);if(typeof e=="object")return Array.isArray(e)?YO(e):XO(e);if(typeof e=="string")return KO(e);throw new Error("Expected function, string, or object as test")});function YO(e){const t=[];let r=-1;for(;++r<e.length;)t[r]=Gu(e[r]);return Vu(a);function a(...s){let o=-1;for(;++o<t.length;)if(t[o].apply(this,s))return!0;return!1}}function XO(e){const t=e;return Vu(r);function r(a){const s=a;let o;for(o in e)if(s[o]!==t[o])return!1;return!0}}function KO(e){return Vu(t);function t(r){return r&&r.type===e}}function Vu(e){return t;function t(r,a,s){return!!(QO(r)&&e.call(this,r,typeof a=="number"?a:void 0,s||void 0))}}function ZO(){return!0}function QO(e){return e!==null&&typeof e=="object"&&"type"in e}const Rw=[],WO=!0,Ym=!1,JO="skip";function Dw(e,t,r,a){let s;typeof t=="function"&&typeof r!="function"?(a=r,r=t):s=t;const o=Gu(s),c=a?-1:1;d(e,void 0,[])();function d(f,h,m){const p=f&&typeof f=="object"?f:{};if(typeof p.type=="string"){const x=typeof p.tagName=="string"?p.tagName:typeof p.name=="string"?p.name:void 0;Object.defineProperty(y,"name",{value:"node ("+(f.type+(x?"<"+x+">":""))+")"})}return y;function y(){let x=Rw,_,N,S;if((!t||o(f,h,m[m.length-1]||void 0))&&(x=eR(r(f,m)),x[0]===Ym))return x;if("children"in f&&f.children){const w=f;if(w.children&&x[0]!==JO)for(N=(a?w.children.length:-1)+c,S=m.concat(w);N>-1&&N<w.children.length;){const k=w.children[N];if(_=d(k,N,S)(),_[0]===Ym)return _;N=typeof _[1]=="number"?_[1]:N+c}}return x}}}function eR(e){return Array.isArray(e)?e:typeof e=="number"?[WO,e]:e==null?Rw:[e]}function zp(e,t,r,a){let s,o,c;typeof t=="function"&&typeof r!="function"?(o=void 0,c=t,s=r):(o=t,c=r,s=a),Dw(e,o,d,s);function d(f,h){const m=h[h.length-1],p=m?m.children.indexOf(f):void 0;return c(f,p,m)}}const Xm={}.hasOwnProperty,tR={};function nR(e,t){const r=t||tR,a=new Map,s=new Map,o=new Map,c={...UO,...r.handlers},d={all:h,applyData:iR,definitionById:a,footnoteById:s,footnoteCounts:o,footnoteOrder:[],handlers:c,one:f,options:r,patch:rR,wrap:sR};return zp(e,function(m){if(m.type==="definition"||m.type==="footnoteDefinition"){const p=m.type==="definition"?a:s,y=String(m.identifier).toUpperCase();p.has(y)||p.set(y,m)}}),d;function f(m,p){const y=m.type,x=d.handlers[y];if(Xm.call(d.handlers,y)&&x)return x(d,m,p);if(d.options.passThrough&&d.options.passThrough.includes(y)){if("children"in m){const{children:N,...S}=m,w=Eu(S);return w.children=d.all(m),w}return Eu(m)}return(d.options.unknownHandler||aR)(d,m,p)}function h(m){const p=[];if("children"in m){const y=m.children;let x=-1;for(;++x<y.length;){const _=d.one(y[x],m);if(_){if(x&&y[x-1].type==="break"&&(!Array.isArray(_)&&_.type==="text"&&(_.value=zy(_.value)),!Array.isArray(_)&&_.type==="element")){const N=_.children[0];N&&N.type==="text"&&(N.value=zy(N.value))}Array.isArray(_)?p.push(..._):p.push(_)}}}return p}}function rR(e,t){e.position&&(t.position=GA(e))}function iR(e,t){let r=t;if(e&&e.data){const a=e.data.hName,s=e.data.hChildren,o=e.data.hProperties;if(typeof a=="string")if(r.type==="element")r.tagName=a;else{const c="children"in r?r.children:[r];r={type:"element",tagName:a,properties:{},children:c}}r.type==="element"&&o&&Object.assign(r.properties,Eu(o)),"children"in r&&r.children&&s!==null&&s!==void 0&&(r.children=s)}return r}function aR(e,t){const r=t.data||{},a="value"in t&&!(Xm.call(r,"hProperties")||Xm.call(r,"hChildren"))?{type:"text",value:t.value}:{type:"element",tagName:"div",properties:{},children:e.all(t)};return e.patch(t,a),e.applyData(t,a)}function sR(e,t){const r=[];let a=-1;for(t&&r.push({type:"text",value:`
|
||||
`});++a<e.length;)a&&r.push({type:"text",value:`
|
||||
`}),r.push(e[a]);return t&&e.length>0&&r.push({type:"text",value:`
|
||||
`}),r}function zy(e){let t=0,r=e.charCodeAt(t);for(;r===9||r===32;)t++,r=e.charCodeAt(t);return e.slice(t)}function Iy(e,t){const r=nR(e,t),a=r.one(e,void 0),s=VO(r),o=Array.isArray(a)?{type:"root",children:a}:a||{type:"root",children:[]};return s&&o.children.push({type:"text",value:`
|
||||
`},s),o}function lR(e,t){return e&&"run"in e?async function(r,a){const s=Iy(r,{file:a,...t});await e.run(s,a)}:function(r,a){return Iy(r,{file:a,...e||t})}}function By(e){if(e)throw e}var vh,Uy;function oR(){if(Uy)return vh;Uy=1;var e=Object.prototype.hasOwnProperty,t=Object.prototype.toString,r=Object.defineProperty,a=Object.getOwnPropertyDescriptor,s=function(h){return typeof Array.isArray=="function"?Array.isArray(h):t.call(h)==="[object Array]"},o=function(h){if(!h||t.call(h)!=="[object Object]")return!1;var m=e.call(h,"constructor"),p=h.constructor&&h.constructor.prototype&&e.call(h.constructor.prototype,"isPrototypeOf");if(h.constructor&&!m&&!p)return!1;var y;for(y in h);return typeof y>"u"||e.call(h,y)},c=function(h,m){r&&m.name==="__proto__"?r(h,m.name,{enumerable:!0,configurable:!0,value:m.newValue,writable:!0}):h[m.name]=m.newValue},d=function(h,m){if(m==="__proto__")if(e.call(h,m)){if(a)return a(h,m).value}else return;return h[m]};return vh=function f(){var h,m,p,y,x,_,N=arguments[0],S=1,w=arguments.length,k=!1;for(typeof N=="boolean"&&(k=N,N=arguments[1]||{},S=2),(N==null||typeof N!="object"&&typeof N!="function")&&(N={});S<w;++S)if(h=arguments[S],h!=null)for(m in h)p=d(N,m),y=d(h,m),N!==y&&(k&&y&&(o(y)||(x=s(y)))?(x?(x=!1,_=p&&s(p)?p:[]):_=p&&o(p)?p:{},c(N,{name:m,newValue:f(k,_,y)})):typeof y<"u"&&c(N,{name:m,newValue:y}));return N},vh}var cR=oR();const _h=Co(cR);function Km(e){if(typeof e!="object"||e===null)return!1;const t=Object.getPrototypeOf(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(Symbol.toStringTag in e)&&!(Symbol.iterator in e)}function uR(){const e=[],t={run:r,use:a};return t;function r(...s){let o=-1;const c=s.pop();if(typeof c!="function")throw new TypeError("Expected function as last argument, not "+c);d(null,...s);function d(f,...h){const m=e[++o];let p=-1;if(f){c(f);return}for(;++p<s.length;)(h[p]===null||h[p]===void 0)&&(h[p]=s[p]);s=h,m?dR(m,d)(...h):c(null,...h)}}function a(s){if(typeof s!="function")throw new TypeError("Expected `middelware` to be a function, not "+s);return e.push(s),t}}function dR(e,t){let r;return a;function a(...c){const d=e.length>c.length;let f;d&&c.push(s);try{f=e.apply(this,c)}catch(h){const m=h;if(d&&r)throw m;return s(m)}d||(f&&f.then&&typeof f.then=="function"?f.then(o,s):f instanceof Error?s(f):o(f))}function s(c,...d){r||(r=!0,t(c,...d))}function o(c){s(null,c)}}const Gr={basename:fR,dirname:hR,extname:mR,join:pR,sep:"/"};function fR(e,t){if(t!==void 0&&typeof t!="string")throw new TypeError('"ext" argument must be a string');Ro(e);let r=0,a=-1,s=e.length,o;if(t===void 0||t.length===0||t.length>e.length){for(;s--;)if(e.codePointAt(s)===47){if(o){r=s+1;break}}else a<0&&(o=!0,a=s+1);return a<0?"":e.slice(r,a)}if(t===e)return"";let c=-1,d=t.length-1;for(;s--;)if(e.codePointAt(s)===47){if(o){r=s+1;break}}else c<0&&(o=!0,c=s+1),d>-1&&(e.codePointAt(s)===t.codePointAt(d--)?d<0&&(a=s):(d=-1,a=c));return r===a?a=c:a<0&&(a=e.length),e.slice(r,a)}function hR(e){if(Ro(e),e.length===0)return".";let t=-1,r=e.length,a;for(;--r;)if(e.codePointAt(r)===47){if(a){t=r;break}}else a||(a=!0);return t<0?e.codePointAt(0)===47?"/":".":t===1&&e.codePointAt(0)===47?"//":e.slice(0,t)}function mR(e){Ro(e);let t=e.length,r=-1,a=0,s=-1,o=0,c;for(;t--;){const d=e.codePointAt(t);if(d===47){if(c){a=t+1;break}continue}r<0&&(c=!0,r=t+1),d===46?s<0?s=t:o!==1&&(o=1):s>-1&&(o=-1)}return s<0||r<0||o===0||o===1&&s===r-1&&s===a+1?"":e.slice(s,r)}function pR(...e){let t=-1,r;for(;++t<e.length;)Ro(e[t]),e[t]&&(r=r===void 0?e[t]:r+"/"+e[t]);return r===void 0?".":gR(r)}function gR(e){Ro(e);const t=e.codePointAt(0)===47;let r=bR(e,!t);return r.length===0&&!t&&(r="."),r.length>0&&e.codePointAt(e.length-1)===47&&(r+="/"),t?"/"+r:r}function bR(e,t){let r="",a=0,s=-1,o=0,c=-1,d,f;for(;++c<=e.length;){if(c<e.length)d=e.codePointAt(c);else{if(d===47)break;d=47}if(d===47){if(!(s===c-1||o===1))if(s!==c-1&&o===2){if(r.length<2||a!==2||r.codePointAt(r.length-1)!==46||r.codePointAt(r.length-2)!==46){if(r.length>2){if(f=r.lastIndexOf("/"),f!==r.length-1){f<0?(r="",a=0):(r=r.slice(0,f),a=r.length-1-r.lastIndexOf("/")),s=c,o=0;continue}}else if(r.length>0){r="",a=0,s=c,o=0;continue}}t&&(r=r.length>0?r+"/..":"..",a=2)}else r.length>0?r+="/"+e.slice(s+1,c):r=e.slice(s+1,c),a=c-s-1;s=c,o=0}else d===46&&o>-1?o++:o=-1}return r}function Ro(e){if(typeof e!="string")throw new TypeError("Path must be a string. Received "+JSON.stringify(e))}const xR={cwd:yR};function yR(){return"/"}function Zm(e){return!!(e!==null&&typeof e=="object"&&"href"in e&&e.href&&"protocol"in e&&e.protocol&&e.auth===void 0)}function vR(e){if(typeof e=="string")e=new URL(e);else if(!Zm(e)){const t=new TypeError('The "path" argument must be of type string or an instance of URL. Received `'+e+"`");throw t.code="ERR_INVALID_ARG_TYPE",t}if(e.protocol!=="file:"){const t=new TypeError("The URL must be of scheme file");throw t.code="ERR_INVALID_URL_SCHEME",t}return _R(e)}function _R(e){if(e.hostname!==""){consLine truncated
|
||||
`:" ")+o.indentLines(o.containerFlow(a,d.current()),t?Iw:u3))),h(),f}}function u3(e,t,r){return t===0?e:Iw(e,t,r)}function Iw(e,t,r){return(r?"":" ")+e}const d3=["autolink","destinationLiteral","destinationRaw","reference","titleQuote","titleApostrophe"];Bw.peek=g3;function f3(){return{canContainEols:["delete"],enter:{strikethrough:m3},exit:{strikethrough:p3}}}function h3(){return{unsafe:[{character:"~",inConstruct:"phrasing",notInConstruct:d3}],handlers:{delete:Bw}}}function m3(e){this.enter({type:"delete",children:[]},e)}function p3(e){this.exit(e)}function Bw(e,t,r,a){const s=r.createTracker(a),o=r.enter("strikethrough");let c=s.move("~~");return c+=r.containerPhrasing(e,{...s.current(),before:c,after:"~"}),c+=s.move("~~"),o(),c}function g3(){return"~"}function b3(e){return e.length}function x3(e,t){const r=t||{},a=(r.align||[]).concat(),s=r.stringLength||b3,o=[],c=[],d=[],f=[];let h=0,m=-1;for(;++m<e.length;){const N=[],S=[];let w=-1;for(e[m].length>h&&(h=e[m].length);++w<e[m].length;){const k=y3(e[m][w]);if(r.alignDelimiters!==!1){const E=s(k);S[w]=E,(f[w]===void 0||E>f[w])&&(f[w]=E)}N.push(k)}c[m]=N,d[m]=S}let p=-1;if(typeof a=="object"&&"length"in a)for(;++p<h;)o[p]=Vy(a[p]);else{const N=Vy(a);for(;++p<h;)o[p]=N}p=-1;const y=[],x=[];for(;++p<h;){const N=o[p];let S="",w="";N===99?(S=":",w=":"):N===108?S=":":N===114&&(w=":");let k=r.alignDelimiters===!1?1:Math.max(1,f[p]-S.length-w.length);const E=S+"-".repeat(k)+w;r.alignDelimiters!==!1&&(k=S.length+k+w.length,k>f[p]&&(f[p]=k),x[p]=k),y[p]=E}c.splice(1,0,y),d.splice(1,0,x),m=-1;const _=[];for(;++m<c.length;){const N=c[m],S=d[m];p=-1;const w=[];for(;++p<h;){const k=N[p]||"";let E="",M="";if(r.alignDelimiters!==!1){const I=f[p]-(S[p]||0),R=o[p];R===114?E=" ".repeat(I):R===99?I%2?(E=" ".repeat(I/2+.5),M=" ".repeat(I/2-.5)):(E=" ".repeat(I/2),M=E):M=" ".repeat(I)}r.delimiterStart!==!1&&!p&&w.push("|"),r.padding!==!1&&!(r.alignDelimiters===!1&&k==="")&&(r.delimiterStart!==!1||p)&&w.push(" "),r.alignDelimiters!==!1&&w.push(E),w.push(k),r.alignDelimiters!==!1&&w.push(M),r.padding!==!1&&w.push(" "),(r.delimiterEnd!==!1||p!==h-1)&&w.push("|")}_.push(r.delimiterEnd===!1?w.join("").replace(/ +$/,""):w.join(""))}return _.join(`
|
||||
`)}function y3(e){return e==null?"":String(e)}function Vy(e){const t=typeof e=="string"?e.codePointAt(0):0;return t===67||t===99?99:t===76||t===108?108:t===82||t===114?114:0}function v3(e,t,r,a){const s=r.enter("blockquote"),o=r.createTracker(a);o.move("> "),o.shift(2);const c=r.indentLines(r.containerFlow(e,o.current()),_3);return s(),c}function _3(e,t,r){return">"+(r?"":" ")+e}function w3(e,t){return Yy(e,t.inConstruct,!0)&&!Yy(e,t.notInConstruct,!1)}function Yy(e,t,r){if(typeof t=="string"&&(t=[t]),!t||t.length===0)return r;let a=-1;for(;++a<t.length;)if(e.includes(t[a]))return!0;return!1}function Xy(e,t,r,a){let s=-1;for(;++s<r.unsafe.length;)if(r.unsafe[s].character===`
|
||||
`&&w3(r.stack,r.unsafe[s]))return/[ \t]/.test(a.before)?"":" ";return`\\
|
||||
`}function E3(e,t){const r=String(e);let a=r.indexOf(t),s=a,o=0,c=0;if(typeof t!="string")throw new TypeError("Expected substring");for(;a!==-1;)a===s?++o>c&&(c=o):o=1,s=a+t.length,a=r.indexOf(t,s);return c}function N3(e,t){return!!(t.options.fences===!1&&e.value&&!e.lang&&/[^ \r\n]/.test(e.value)&&!/^[\t ]*(?:[\r\n]|$)|(?:^|[\r\n])[\t ]*$/.test(e.value))}function S3(e){const t=e.options.fence||"`";if(t!=="`"&&t!=="~")throw new Error("Cannot serialize code with `"+t+"` for `options.fence`, expected `` ` `` or `~`");return t}function k3(e,t,r,a){const s=S3(r),o=e.value||"",c=s==="`"?"GraveAccent":"Tilde";if(N3(e,r)){const p=r.enter("codeIndented"),y=r.indentLines(o,C3);return p(),y}const d=r.createTracker(a),f=s.repeat(Math.max(E3(o,s)+1,3)),h=r.enter("codeFenced");let m=d.move(f);if(e.lang){const p=r.enter(`codeFencedLang${c}`);m+=d.move(r.safe(e.lang,{before:m,after:" ",encode:["`"],...d.current()})),p()}if(e.lang&&e.meta){const p=r.enter(`codeFencedMeta${c}`);m+=d.move(" "),m+=d.move(r.safe(e.meta,{before:m,after:`
|
||||
`,encode:["`"],...d.current()})),p()}return m+=d.move(`
|
||||
`),o&&(m+=d.move(o+`
|
||||
`)),m+=d.move(f),h(),m}function C3(e,t,r){return(r?"":" ")+e}function Up(e){const t=e.options.quote||'"';if(t!=='"'&&t!=="'")throw new Error("Cannot serialize title with `"+t+"` for `options.quote`, expected `\"`, or `'`");return t}function T3(e,t,r,a){const s=Up(r),o=s==='"'?"Quote":"Apostrophe",c=r.enter("definition");let d=r.enter("label");const f=r.createTracker(a);let h=f.move("[");return h+=f.move(r.safe(r.associationId(e),{before:h,after:"]",...f.current()})),h+=f.move("]: "),d(),!e.url||/[\0- \u007F]/.test(e.url)?(d=r.enter("destinationLiteral"),h+=f.move("<"),h+=f.move(r.safe(e.url,{before:h,after:">",...f.current()})),h+=f.move(">")):(d=r.enter("destinationRaw"),h+=f.move(r.safe(e.url,{before:h,after:e.title?" ":`
|
||||
`,...f.current()}))),d(),e.title&&(d=r.enter(`title${o}`),h+=f.move(" "+s),h+=f.move(r.safe(e.title,{before:h,after:s,...f.current()})),h+=f.move(s),d()),c(),h}function A3(e){const t=e.options.emphasis||"*";if(t!=="*"&&t!=="_")throw new Error("Cannot serialize emphasis with `"+t+"` for `options.emphasis`, expected `*`, or `_`");return t}function go(e){return"&#x"+e.toString(16).toUpperCase()+";"}function Nu(e,t,r){const a=Ys(e),s=Ys(t);return a===void 0?s===void 0?r==="_"?{inside:!0,outside:!0}:{inside:!1,outside:!1}:s===1?{inside:!0,outside:!0}:{inside:!1,outside:!0}:a===1?s===void 0?{inside:!1,outside:!1}:s===1?{inside:!0,outside:!0}:{inside:!1,outside:!1}:s===void 0?{inside:!1,outside:!1}:s===1?{inside:!0,outside:!1}:{inside:!1,outside:!1}}Uw.peek=M3;function Uw(e,t,r,a){const s=A3(r),o=r.enter("emphasis"),c=r.createTracker(a),d=c.move(s);let f=c.move(r.containerPhrasing(e,{after:s,before:d,...c.current()}));const h=f.charCodeAt(0),m=Nu(a.before.charCodeAt(a.before.length-1),h,s);m.inside&&(f=go(h)+f.slice(1));const p=f.charCodeAt(f.length-1),y=Nu(a.after.charCodeAt(0),p,s);y.inside&&(f=f.slice(0,-1)+go(p));const x=c.move(s);return o(),r.attentionEncodeSurroundingInfo={after:y.outside,before:m.outside},d+f+x}function M3(e,t,r){return r.options.emphasis||"*"}function O3(e,t){let r=!1;return zp(e,function(a){if("value"in a&&/\r?\n|\r/.test(a.value)||a.type==="break")return r=!0,Ym}),!!((!e.depth||e.depth<3)&&Ap(e)&&(t.options.setext||r))}function R3(e,t,r,a){const s=Math.max(Math.min(6,e.depth||1),1),o=r.createTracker(a);if(O3(e,r)){const m=r.enter("headingSetext"),p=r.enter("phrasing"),y=r.containerPhrasing(e,{...o.current(),before:`
|
||||
`,after:`
|
||||
`});return p(),m(),y+`
|
||||
`+(s===1?"=":"-").repeat(y.length-(Math.max(y.lastIndexOf("\r"),y.lastIndexOf(`
|
||||
`))+1))}const c="#".repeat(s),d=r.enter("headingAtx"),f=r.enter("phrasing");o.move(c+" ");let h=r.containerPhrasing(e,{before:"# ",after:`
|
||||
`,...o.current()});return/^[\t ]/.test(h)&&(h=go(h.charCodeAt(0))+h.slice(1)),h=h?c+" "+h:c,r.options.closeAtx&&(h+=" "+c),f(),d(),h}Hw.peek=D3;function Hw(e){return e.value||""}function D3(){return"<"}$w.peek=j3;function $w(e,t,r,a){const s=Up(r),o=s==='"'?"Quote":"Apostrophe",c=r.enter("image");let d=r.enter("label");const f=r.createTracker(a);let h=f.move("![");return h+=f.move(r.safe(e.alt,{before:h,after:"]",...f.current()})),h+=f.move("]("),d(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(d=r.enter("destinationLiteral"),h+=f.move("<"),h+=f.move(r.safe(e.url,{before:h,after:">",...f.current()})),h+=f.move(">")):(d=r.enter("destinationRaw"),h+=f.move(r.safe(e.url,{before:h,after:e.title?" ":")",...f.current()}))),d(),e.title&&(d=r.enter(`title${o}`),h+=f.move(" "+s),h+=f.move(r.safe(e.title,{before:h,after:s,...f.current()})),h+=f.move(s),d()),h+=f.move(")"),c(),h}function j3(){return"!"}qw.peek=L3;function qw(e,t,r,a){const s=e.referenceType,o=r.enter("imageReference");let c=r.enter("label");const d=r.createTracker(a);let f=d.move("![");const h=r.safe(e.alt,{before:f,after:"]",...d.current()});f+=d.move(h+"]["),c();const m=r.stack;r.stack=[],c=r.enter("reference");const p=r.safe(r.associationId(e),{before:f,after:"]",...d.current()});return c(),r.stack=m,o(),s==="full"||!h||h!==p?f+=d.move(p+"]"):s==="shortcut"?f=f.slice(0,-1):f+=d.move("]"),f}function L3(){return"!"}Pw.peek=z3;function Pw(e,t,r){let a=e.value||"",s="`",o=-1;for(;new RegExp("(^|[^`])"+s+"([^`]|$)").test(a);)s+="`";for(/[^ \r\n]/.test(a)&&(/^[ \r\n]/.test(a)&&/[ \r\n]$/.test(a)||/^`|`$/.test(a))&&(a=" "+a+" ");++o<r.unsafe.length;){const c=r.unsafe[o],d=r.compilePattern(c);let f;if(c.atBreak)for(;f=d.exec(a);){let h=f.index;a.charCodeAt(h)===10&&a.charCodeAt(h-1)===13&&h--,a=a.slice(0,h)+" "+a.slice(f.index+1)}}return s+a+s}function z3(){return"`"}function Fw(e,t){const r=Ap(e);return!!(!t.options.resourceLink&&e.url&&!e.title&&e.children&&e.children.length===1&&e.children[0].type==="text"&&(r===e.url||"mailto:"+r===e.url)&&/^[a-z][a-z+.-]+:/i.test(e.url)&&!/[\0- <>\u007F]/.test(e.url))}Gw.peek=I3;function Gw(e,t,r,a){const s=Up(r),o=s==='"'?"Quote":"Apostrophe",c=r.createTracker(a);let d,f;if(Fw(e,r)){const m=r.stack;r.stack=[],d=r.enter("autolink");let p=c.move("<");return p+=c.move(r.containerPhrasing(e,{before:p,after:">",...c.current()})),p+=c.move(">"),d(),r.stack=m,p}d=r.enter("link"),f=r.enter("label");let h=c.move("[");return h+=c.move(r.containerPhrasing(e,{before:h,after:"](",...c.current()})),h+=c.move("]("),f(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(f=r.enter("destinationLiteral"),h+=c.move("<"),h+=c.move(r.safe(e.url,{before:h,after:">",...c.current()})),h+=c.move(">")):(f=r.enter("destinationRaw"),h+=c.move(r.safe(e.url,{before:h,after:e.title?" ":")",...c.current()}))),f(),e.title&&(f=r.enter(`title${o}`),h+=c.move(" "+s),h+=c.move(r.safe(e.title,{before:h,after:s,...c.current()})),h+=c.move(s),f()),h+=c.move(")"),d(),h}function I3(e,t,r){return Fw(e,r)?"<":"["}Vw.peek=B3;function Vw(e,t,r,a){const s=e.referenceType,o=r.enter("linkReference");let c=r.enter("label");const d=r.createTracker(a);let f=d.move("[");const h=r.containerPhrasing(e,{before:f,after:"]",...d.current()});f+=d.move(h+"]["),c();const m=r.stack;r.stack=[],c=r.enter("reference");const p=r.safe(r.associationId(e),{before:f,after:"]",...d.current()});return c(),r.stack=m,o(),s==="full"||!h||h!==p?f+=d.move(p+"]"):s==="shortcut"?f=f.slice(0,-1):f+=d.move("]"),f}function B3(){return"["}function Hp(e){const t=e.options.bullet||"*";if(t!=="*"&&t!=="+"&&t!=="-")throw new Error("Cannot serialize items with `"+t+"` for `options.bullet`, expected `*`, `+`, or `-`");return t}function U3(e){const t=Hp(e),r=e.options.bulletOther;if(!r)return t==="*"?"-":"*";if(r!=="*"&&r!=="+"&&r!=="-")throw new Error("Cannot serialize items with `"+r+"` for `options.bulletOther`, expected `*`, `+`, or `-`");if(r===t)throw new Error("Expected `bullet` (`"+t+"`) and `bulletOther` (`"+r+"`) to be different");return r}function H3(e){const t=e.options.bulletOrdered||".";if(t!=="."&&t!==")")throw new Error("Cannot serialize items with `"+t+"` for `options.bulletOrdered`, expected `.` or `)`");return t}function Yw(e){const t=e.options.rule||"*";if(t!=="*"&&t!=="-"&&t!=="_")throw new Error("Cannot serialize rules with `"+t+"` for `options.rule`, expected `*`, `-`, or `_`");return t}function $3(e,t,r,a){const s=r.enter("list"),o=r.bulletCurrent;let c=e.ordered?H3(r):Hp(r);const d=e.ordered?c==="."?")":".":U3(r);let f=t&&r.bulletLastUsed?c===r.bulletLastUsed:!1;if(!e.ordered){const m=e.children?e.children[0]:void 0;if((c==="*"||c==="-")&&m&&(!m.children||!m.children[0])&&r.stack[r.stack.length-1]==="list"&&r.stack[r.stack.length-2]==="listItem"&&r.stack[r.stack.length-3]==="list"&&r.stack[r.stack.length-4]==="listItem"&&r.indexStack[r.indexStack.length-1]===0&&r.indexStack[r.indexStack.length-2]===0&&r.indexStack[r.indexStack.length-3]===0&&(f=!0),Yw(r)===c&&m){let p=-1;for(;++p<e.chilLine truncated
|
||||
`,inConstruct:"tableCell"},{atBreak:!0,character:"|",after:"[ :-]"},{character:"|",inConstruct:"tableCell"},{atBreak:!0,character:":",after:"-"},{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{inlineCode:y,table:c,tableCell:f,tableRow:d}};function c(x,_,N,S){return h(m(x,N,S),x.align)}function d(x,_,N,S){const w=p(x,N,S),k=h([w]);return k.slice(0,k.indexOf(`
|
||||
`))}function f(x,_,N,S){const w=N.enter("tableCell"),k=N.enter("phrasing"),E=N.containerPhrasing(x,{...S,before:o,after:o});return k(),w(),E}function h(x,_){return x3(x,{align:_,alignDelimiters:a,padding:r,stringLength:s})}function m(x,_,N){const S=x.children;let w=-1;const k=[],E=_.enter("table");for(;++w<S.length;)k[w]=p(S[w],_,N);return E(),k}function p(x,_,N){const S=x.children;let w=-1;const k=[],E=_.enter("tableRow");for(;++w<S.length;)k[w]=f(S[w],x,_,N);return E(),k}function y(x,_,N){let S=Kw.inlineCode(x,_,N);return N.stack.includes("tableCell")&&(S=S.replace(/\|/g,"\\$&")),S}}function a4(){return{exit:{taskListCheckValueChecked:Zy,taskListCheckValueUnchecked:Zy,paragraph:l4}}}function s4(){return{unsafe:[{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{listItem:o4}}}function Zy(e){const t=this.stack[this.stack.length-2];t.type,t.checked=e.type==="taskListCheckValueChecked"}function l4(e){const t=this.stack[this.stack.length-2];if(t&&t.type==="listItem"&&typeof t.checked=="boolean"){const r=this.stack[this.stack.length-1];r.type;const a=r.children[0];if(a&&a.type==="text"){const s=t.children;let o=-1,c;for(;++o<s.length;){const d=s[o];if(d.type==="paragraph"){c=d;break}}c===r&&(a.value=a.value.slice(1),a.value.length===0?r.children.shift():r.position&&a.position&&typeof a.position.start.offset=="number"&&(a.position.start.column++,a.position.start.offset++,r.position.start=Object.assign({},a.position.start)))}}this.exit(e)}function o4(e,t,r,a){const s=e.children[0],o=typeof e.checked=="boolean"&&s&&s.type==="paragraph",c="["+(e.checked?"x":" ")+"] ",d=r.createTracker(a);o&&d.move(c);let f=Kw.listItem(e,t,r,{...a,...d.current()});return o&&(f=f.replace(/^(?:[*+-]|\d+\.)([\r\n]| {1,3})/,h)),f;function h(m){return m+c}}function c4(){return[$R(),o3(),f3(),W3(),a4()]}function u4(e){return{extensions:[qR(),c3(e),h3(),i4(e),s4()]}}const d4={tokenize:b4,partial:!0},Zw={tokenize:x4,partial:!0},Qw={tokenize:y4,partial:!0},Ww={tokenize:v4,partial:!0},f4={tokenize:_4,partial:!0},Jw={name:"wwwAutolink",tokenize:p4,previous:tE},eE={name:"protocolAutolink",tokenize:g4,previous:nE},Ei={name:"emailAutolink",tokenize:m4,previous:rE},Kr={};function h4(){return{text:Kr}}let za=48;for(;za<123;)Kr[za]=Ei,za++,za===58?za=65:za===91&&(za=97);Kr[43]=Ei;Kr[45]=Ei;Kr[46]=Ei;Kr[95]=Ei;Kr[72]=[Ei,eE];Kr[104]=[Ei,eE];Kr[87]=[Ei,Jw];Kr[119]=[Ei,Jw];function m4(e,t,r){const a=this;let s,o;return c;function c(p){return!Qm(p)||!rE.call(a,a.previous)||$p(a.events)?r(p):(e.enter("literalAutolink"),e.enter("literalAutolinkEmail"),d(p))}function d(p){return Qm(p)?(e.consume(p),d):p===64?(e.consume(p),f):r(p)}function f(p){return p===46?e.check(f4,m,h)(p):p===45||p===95||Tn(p)?(o=!0,e.consume(p),f):m(p)}function h(p){return e.consume(p),s=!0,f}function m(p){return o&&s&&jn(a.previous)?(e.exit("literalAutolinkEmail"),e.exit("literalAutolink"),t(p)):r(p)}}function p4(e,t,r){const a=this;return s;function s(c){return c!==87&&c!==119||!tE.call(a,a.previous)||$p(a.events)?r(c):(e.enter("literalAutolink"),e.enter("literalAutolinkWww"),e.check(d4,e.attempt(Zw,e.attempt(Qw,o),r),r)(c))}function o(c){return e.exit("literalAutolinkWww"),e.exit("literalAutolink"),t(c)}}function g4(e,t,r){const a=this;let s="",o=!1;return c;function c(p){return(p===72||p===104)&&nE.call(a,a.previous)&&!$p(a.events)?(e.enter("literalAutolink"),e.enter("literalAutolinkHttp"),s+=String.fromCodePoint(p),e.consume(p),d):r(p)}function d(p){if(jn(p)&&s.length<5)return s+=String.fromCodePoint(p),e.consume(p),d;if(p===58){const y=s.toLowerCase();if(y==="http"||y==="https")return e.consume(p),f}return r(p)}function f(p){return p===47?(e.consume(p),o?h:(o=!0,f)):r(p)}function h(p){return p===null||_u(p)||Tt(p)||Fa(p)||qu(p)?r(p):e.attempt(Zw,e.attempt(Qw,m),r)(p)}function m(p){return e.exit("literalAutolinkHttp"),e.exit("literalAutolink"),t(p)}}function b4(e,t,r){let a=0;return s;function s(c){return(c===87||c===119)&&a<3?(a++,e.consume(c),s):c===46&&a===3?(e.consume(c),o):r(c)}function o(c){return c===null?r(c):t(c)}}function x4(e,t,r){let a,s,o;return c;function c(h){return h===46||h===95?e.check(Ww,f,d)(h):h===null||Tt(h)||Fa(h)||h!==45&&qu(h)?f(h):(o=!0,e.consume(h),c)}function d(h){return h===95?a=!0:(s=a,a=void 0),e.consume(h),c}function f(h){return s||a||!o?r(h):t(h)}}function y4(e,t){let r=0,a=0;return s;function s(c){return c===40?(r++,e.consume(c),s):c===41&&a<r?o(c):c===33||c===34||c===38||c===39||c===41||c===42||c===44||c===46||c===58||c===59||c===60||c===63||c===93||c===95||c===126?e.check(Ww,t,o)(c):c===null||Tt(c)||Fa(c)?t(c):(e.consume(c),s)}function o(c){return c===41&&a++,e.consume(c),s}}function v4(e,t,r){return a;function a(d){return d===33||d===34||d===39||d===41||d===42||d===44||d===46||d===58||d===59||d===63||d===95||d===126?(e.consume(d),a):d===38?(e.consume(d),o):d===93?(e.consume(d),s):d===60||d===null||Tt(d)||Fa(d)?t(d):r(d)}function s(d){return d===null||d===40||d===91||Tt(d)||Fa(d)?t(d):a(d)}function o(d){return jn(d)?c(d):r(d)}function c(d){re
Line truncated
|
||||
https://github.com/highlightjs/highlight.js/issues/2277`),Ht=ye,ft=Le),Qe===void 0&&(Qe=!0);const pn={code:ft,language:Ht};Jr("before:highlight",pn);const On=pn.result?pn.result:xr(pn.language,pn.code,Qe);return On.code=pn.code,Jr("after:highlight",On),On}function xr(ye,Le,Qe,ft){const Ht=Object.create(null);function pn(_e,Re){return _e.keywords[Re]}function On(){if(!qe.keywords){Jt.addText(bt);return}let _e=0;qe.keywordPatternRe.lastIndex=0;let Re=qe.keywordPatternRe.exec(bt),Ye="";for(;Re;){Ye+=bt.substring(_e,Re.index);const rt=un.case_insensitive?Re[0].toLowerCase():Re[0],$t=pn(qe,rt);if($t){const[or,al]=$t;if(Jt.addText(Ye),Ye="",Ht[rt]=(Ht[rt]||0)+1,Ht[rt]<=Xn&&(Ri+=al),or.startsWith("_"))Ye+=Re[0];else{const $o=un.classNameAliases[or]||or;Rn(Re[0],$o)}}else Ye+=Re[0];_e=qe.keywordPatternRe.lastIndex,Re=qe.keywordPatternRe.exec(bt)}Ye+=bt.substring(_e),Jt.addText(Ye)}function Sn(){if(bt==="")return;let _e=null;if(typeof qe.subLanguage=="string"){if(!me[qe.subLanguage]){Jt.addText(bt);return}_e=xr(qe.subLanguage,bt,!0,Ho[qe.subLanguage]),Ho[qe.subLanguage]=_e._top}else _e=ki(bt,qe.subLanguage.length?qe.subLanguage:null);qe.relevance>0&&(Ri+=_e.relevance),Jt.__addSublanguage(_e._emitter,_e.language)}function _t(){qe.subLanguage!=null?Sn():On(),bt=""}function Rn(_e,Re){_e!==""&&(Jt.startScope(Re),Jt.addText(_e),Jt.endScope())}function ts(_e,Re){let Ye=1;const rt=Re.length-1;for(;Ye<=rt;){if(!_e._emit[Ye]){Ye++;continue}const $t=un.classNameAliases[_e[Ye]]||_e[Ye],or=Re[Ye];$t?Rn(or,$t):(bt=or,On(),bt=""),Ye++}}function Ai(_e,Re){return _e.scope&&typeof _e.scope=="string"&&Jt.openNode(un.classNameAliases[_e.scope]||_e.scope),_e.beginScope&&(_e.beginScope._wrap?(Rn(bt,un.classNameAliases[_e.beginScope._wrap]||_e.beginScope._wrap),bt=""):_e.beginScope._multi&&(ts(_e.beginScope,Re),bt="")),qe=Object.create(_e,{parent:{value:qe}}),qe}function Ur(_e,Re,Ye){let rt=E(_e.endRe,Ye);if(rt){if(_e["on:end"]){const $t=new t(_e);_e["on:end"](Re,$t),$t.isMatchIgnored&&(rt=!1)}if(rt){for(;_e.endsParent&&_e.parent;)_e=_e.parent;return _e}}if(_e.endsWithParent)return Ur(_e.parent,Re,Ye)}function Mi(_e){return qe.matcher.regexIndex===0?(bt+=_e[0],1):(Di=!0,0)}function ns(_e){const Re=_e[0],Ye=_e.rule,rt=new t(Ye),$t=[Ye.__beforeBegin,Ye["on:begin"]];for(const or of $t)if(or&&(or(_e,rt),rt.isMatchIgnored))return Mi(Re);return Ye.skip?bt+=Re:(Ye.excludeBegin&&(bt+=Re),_t(),!Ye.returnBegin&&!Ye.excludeBegin&&(bt=Re)),Ai(Ye,_e),Ye.returnBegin?0:Re.length}function kn(_e){const Re=_e[0],Ye=Le.substring(_e.index),rt=Ur(qe,_e,Ye);if(!rt)return nt;const $t=qe;qe.endScope&&qe.endScope._wrap?(_t(),Rn(Re,qe.endScope._wrap)):qe.endScope&&qe.endScope._multi?(_t(),ts(qe.endScope,_e)):$t.skip?bt+=Re:($t.returnEnd||$t.excludeEnd||(bt+=Re),_t(),$t.excludeEnd&&(bt=Re));do qe.scope&&Jt.closeNode(),!qe.skip&&!qe.subLanguage&&(Ri+=qe.relevance),qe=qe.parent;while(qe!==rt.parent);return rt.starts&&Ai(rt.starts,_e),$t.returnEnd?0:Re.length}function ba(){const _e=[];for(let Re=qe;Re!==un;Re=Re.parent)Re.scope&&_e.unshift(Re.scope);_e.forEach(Re=>Jt.openNode(Re))}let Er={};function Oi(_e,Re){const Ye=Re&&Re[0];if(bt+=_e,Ye==null)return _t(),0;if(Er.type==="begin"&&Re.type==="end"&&Er.index===Re.index&&Ye===""){if(bt+=Le.slice(Re.index,Re.index+1),!St){const rt=new Error(`0 width match regex (${ye})`);throw rt.languageName=ye,rt.badRule=Er.rule,rt}return 1}if(Er=Re,Re.type==="begin")return ns(Re);if(Re.type==="illegal"&&!Qe){const rt=new Error('Illegal lexeme "'+Ye+'" for mode "'+(qe.scope||"<unnamed>")+'"');throw rt.mode=qe,rt}else if(Re.type==="end"){const rt=kn(Re);if(rt!==nt)return rt}if(Re.type==="illegal"&&Ye==="")return bt+=`
|
||||
`,1;if(il>1e5&&il>Re.index*3)throw new Error("potential infinite loop, way more iterations than matches");return bt+=Ye,Ye.length}const un=bn(ye);if(!un)throw ct(gt.replace("{}",ye)),new Error('Unknown language: "'+ye+'"');const xa=Kt(un);let rs="",qe=ft||xa;const Ho={},Jt=new Se.__emitter(Se);ba();let bt="",Ri=0,ei=0,il=0,Di=!1;try{if(un.__emitTokens)un.__emitTokens(Le,Jt);else{for(qe.matcher.considerAll();;){il++,Di?Di=!1:qe.matcher.considerAll(),qe.matcher.lastIndex=ei;const _e=qe.matcher.exec(Le);if(!_e)break;const Re=Le.substring(ei,_e.index),Ye=Oi(Re,_e);ei=_e.index+Ye}Oi(Le.substring(ei))}return Jt.finalize(),rs=Jt.toHTML(),{language:ye,value:rs,relevance:Ri,illegal:!1,_emitter:Jt,_top:qe}}catch(_e){if(_e.message&&_e.message.includes("Illegal"))return{language:ye,value:cn(Le),illegal:!0,relevance:0,_illegalBy:{message:_e.message,index:ei,context:Le.slice(ei-100,ei+100),mode:_e.mode,resultSoFar:rs},_emitter:Jt};if(St)return{language:ye,value:cn(Le),illegal:!1,relevance:0,errorRaised:_e,_emitter:Jt,_top:qe};throw _e}}function Si(ye){const Le={value:cn(ye),illegal:!1,relevance:0,_top:Ae,_emitter:new Se.__emitter(Se)};return Le._emitter.addText(ye),Le}function ki(ye,Le){Le=Le||Se.languages||Object.keys(me);const Qe=Si(ye),ft=Le.filter(bn).filter(_r).map(_t=>xr(_t,ye,!1));ft.unshift(Qe);const Ht=ft.sort((_t,Rn)=>{if(_t.relevance!==Rn.relevance)return Rn.relevance-_t.relevance;if(_t.language&&Rn.language){if(bn(_t.language).supersetOf===Rn.language)return 1;if(bn(Rn.language).supersetOf===_t.language)return-1}return 0}),[pn,On]=Ht,Sn=pn;return Sn.secondBest=On,Sn}function lr(ye,Le,Qe){const ft=Le&&Ee[Le]||Qe;ye.classList.add("hljs"),ye.classList.add(`language-${ft}`)}function Ut(ye){let Le=null;const Qe=Bt(ye);if(Ue(Qe))return;if(Jr("before:highlightElement",{el:ye,language:Qe}),ye.dataset.highlighted){console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",ye);return}if(ye.children.length>0&&(Se.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."),console.warn("https://github.com/highlightjs/highlight.js/wiki/security"),console.warn("The element with unescaped HTML:"),console.warn(ye)),Se.throwUnescapedHTML))throw new In("One of your code blocks includes unescaped HTML.",ye.innerHTML);Le=ye;const ft=Le.textContent,Ht=Qe?Mt(ft,{language:Qe,ignoreIllegals:!0}):ki(ft);ye.innerHTML=Ht.value,ye.dataset.highlighted="yes",lr(ye,Qe,Ht.language),ye.result={language:Ht.language,re:Ht.relevance,relevance:Ht.relevance},Ht.secondBest&&(ye.secondBest={language:Ht.secondBest.language,relevance:Ht.secondBest.relevance}),Jr("after:highlightElement",{el:ye,result:Ht,text:ft})}function mn(ye){Se=Ni(Se,ye)}const yr=()=>{Ti(),ue("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")};function Ci(){Ti(),ue("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.")}let pa=!1;function Ti(){function ye(){Ti()}if(document.readyState==="loading"){pa||window.addEventListener("DOMContentLoaded",ye,!1),pa=!0;return}document.querySelectorAll(Se.cssSelector).forEach(Ut)}function Ja(ye,Le){let Qe=null;try{Qe=Le(re)}catch(ft){if(ct("Language definition for '{}' could not be registered.".replace("{}",ye)),St)ct(ft);else throw ft;Qe=Ae}Qe.name||(Qe.name=ye),me[ye]=Qe,Qe.rawDefinition=Le.bind(null,re),Qe.aliases&&vr(Qe.aliases,{languageName:ye})}function Wr(ye){delete me[ye];for(const Le of Object.keys(Ee))Ee[Le]===ye&&delete Ee[Le]}function ga(){return Object.keys(me)}function bn(ye){return ye=(ye||"").toLowerCase(),me[ye]||me[Ee[ye]]}function vr(ye,{languageName:Le}){typeof ye=="string"&&(ye=[ye]),ye.forEach(Qe=>{Ee[Qe.toLowerCase()]=Le})}function _r(ye){const Le=bn(ye);return Le&&!Le.disableAutodetect}function Br(ye){ye["before:highlightBlock"]&&!ye["before:highlightElement"]&&(ye["before:highlightElement"]=Le=>{ye["before:highlightBlock"](Object.assign({block:Le.el},Le))}),ye["after:highlightBlock"]&&!ye["after:highlightElement"]&&(ye["after:highlightElement"]=Le=>{ye["after:highlightBlock"](Object.assign({block:Le.el},Le))})}function Ft(ye){Br(ye),Pe.push(ye)}function es(ye){const Le=Pe.indexOf(ye);Le!==-1&&Pe.splice(Le,1)}function Jr(ye,Le){const Qe=ye;Pe.forEach(function(ft){ft[Qe]&&ft[Qe](Le)})}function wr(ye){return ue("10.7.0","highlightBlock will be removed entirely in v12.0"),ue("10.7.0","Please use highlightElement now."),Ut(ye)}Object.assign(re,{highlight:Mt,highlightAuto:ki,highlightAll:Ti,highlightElement:Ut,highlightBlock:wr,configure:mn,initHighlighting:yr,initHighlightingOnLoad:Ci,registerLanguage:Ja,unregisterLanguage:Wr,listLanguages:ga,getLanguage:bn,registerAliases:vr,autoDetection:_r,inherit:Ni,addPlugin:Ft,removePlugin:es}),re.debugMode=function(){St=!1},re.safeMode=function(){St=!0},re.versionString=ut,re.regex={concat:N,lookahead:y,either:w,optional:_,anyNumberOfTimes:x};for(const ye in ce)typeof ce[ye]=="object"&&e(ce[ye]);return Object.assign(re,ce),re},hn=Mn({})
Line truncated
|
||||
`},_]}}return Xh=s,Xh}var Kh,pv;function lD(){if(pv)return Kh;pv=1;const e=m=>({IMPORTANT:{scope:"meta",begin:"!important"},BLOCK_COMMENT:m.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:"number",begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{className:"built_in",begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[m.APOS_STRING_MODE,m.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{scope:"number",begin:m.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z_][A-Za-z0-9_-]*/}}),t=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","optgroup","option","p","picture","q","quote","samp","section","select","source","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],r=["defs","g","marker","mask","pattern","svg","switch","symbol","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feFlood","feGaussianBlur","feImage","feMerge","feMorphology","feOffset","feSpecularLighting","feTile","feTurbulence","linearGradient","radialGradient","stop","circle","ellipse","image","line","path","polygon","polyline","rect","text","use","textPath","tspan","foreignObject","clipPath"],a=[...t,...r],s=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"].sort().reverse(),o=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"].sort().reverse(),c=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"].sort().reverse(),d=["accent-color","align-content","align-items","align-self","alignment-baseline","all","anchor-name","animation","animation-composition","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-range","animation-range-end","animation-range-start","animation-timeline","animation-timing-function","appearance","aspect-ratio","backdrop-filter","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-position-x","background-position-y","background-repeat","background-size","baseline-shift","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-end-end-radius","border-end-start-radius","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-start-end-radius","border-start-start-radius","border-style","border-top","border-top
Line truncated
|
||||
]`,N={scope:"string",variants:[p,m,y,x]},S={scope:"number",variants:[{begin:"\\b0[bB][01]+(?:_[01]+)*\\b"},{begin:"\\b0[oO][0-7]+(?:_[0-7]+)*\\b"},{begin:"\\b0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*\\b"},{begin:"(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:[eE][+-]?\\d+)?"}],relevance:0},w=["false","null","true"],k=["__CLASS__","__DIR__","__FILE__","__FUNCTION__","__COMPILER_HALT_OFFSET__","__LINE__","__METHOD__","__NAMESPACE__","__TRAIT__","die","echo","exit","include","include_once","print","require","require_once","array","abstract","and","as","binary","bool","boolean","break","callable","case","catch","class","clone","const","continue","declare","default","do","double","else","elseif","empty","enddeclare","endfor","endforeach","endif","endswitch","endwhile","enum","eval","extends","final","finally","float","for","foreach","from","global","goto","if","implements","instanceof","insteadof","int","integer","interface","isset","iterable","list","match|0","mixed","new","never","object","or","private","protected","public","readonly","real","return","string","switch","throw","trait","try","unset","use","var","void","while","xor","yield"],E=["Error|0","AppendIterator","ArgumentCountError","ArithmeticError","ArrayIterator","ArrayObject","AssertionError","BadFunctionCallException","BadMethodCallException","CachingIterator","CallbackFilterIterator","CompileError","Countable","DirectoryIterator","DivisionByZeroError","DomainException","EmptyIterator","ErrorException","Exception","FilesystemIterator","FilterIterator","GlobIterator","InfiniteIterator","InvalidArgumentException","IteratorIterator","LengthException","LimitIterator","LogicException","MultipleIterator","NoRewindIterator","OutOfBoundsException","OutOfRangeException","OuterIterator","OverflowException","ParentIterator","ParseError","RangeException","RecursiveArrayIterator","RecursiveCachingIterator","RecursiveCallbackFilterIterator","RecursiveDirectoryIterator","RecursiveFilterIterator","RecursiveIterator","RecursiveIteratorIterator","RecursiveRegexIterator","RecursiveTreeIterator","RegexIterator","RuntimeException","SeekableIterator","SplDoublyLinkedList","SplFileInfo","SplFileObject","SplFixedArray","SplHeap","SplMaxHeap","SplMinHeap","SplObjectStorage","SplObserver","SplPriorityQueue","SplQueue","SplStack","SplSubject","SplTempFileObject","TypeError","UnderflowException","UnexpectedValueException","UnhandledMatchError","ArrayAccess","BackedEnum","Closure","Fiber","Generator","Iterator","IteratorAggregate","Serializable","Stringable","Throwable","Traversable","UnitEnum","WeakReference","WeakMap","Directory","__PHP_Incomplete_Class","parent","php_user_filter","self","static","stdClass"],I={keyword:k,literal:($=>{const O=[];return $.forEach(H=>{O.push(H),H.toLowerCase()===H?O.push(H.toUpperCase()):O.push(H.toLowerCase())}),O})(w),built_in:E},R=$=>$.map(O=>O.replace(/\|\d+$/,"")),U={variants:[{match:[/new/,r.concat(_,"+"),r.concat("(?!",R(E).join("\\b|"),"\\b)"),o],scope:{1:"keyword",4:"title.class"}}]},B=r.concat(s,"\\b(?!\\()"),Z={variants:[{match:[r.concat(/::/,r.lookahead(/(?!class\b)/)),B],scope:{2:"variable.constant"}},{match:[/::/,/class/],scope:{2:"variable.language"}},{match:[o,r.concat(/::/,r.lookahead(/(?!class\b)/)),B],scope:{1:"title.class",3:"variable.constant"}},{match:[o,r.concat("::",r.lookahead(/(?!class\b)/))],scope:{1:"title.class"}},{match:[o,/::/,/class/],scope:{1:"title.class",3:"variable.language"}}]},D={scope:"attr",match:r.concat(s,r.lookahead(":"),r.lookahead(/(?!::)/))},z={relevance:0,begin:/\(/,end:/\)/,keywords:I,contains:[D,d,Z,t.C_BLOCK_COMMENT_MODE,N,S,U]},V={relevance:0,match:[/\b/,r.concat("(?!fn\\b|function\\b|",R(k).join("\\b|"),"|",R(E).join("\\b|"),"\\b)"),s,r.concat(_,"*"),r.lookahead(/(?=\()/)],scope:{3:"title.function.invoke"},contains:[z]};z.contains.push(V);const P=[D,Z,t.C_BLOCK_COMMENT_MODE,N,S,U],T={begin:r.concat(/#\[\s*\\?/,r.either(o,c)),beginScope:"meta",end:/]/,endScope:"meta",keywords:{literal:w,keyword:["new","array"]},contains:[{begin:/\[/,end:/]/,keywords:{literal:w,keyword:["new","array"]},contains:["self",...P]},...P,{scope:"meta",variants:[{match:o},{match:c}]}]};return{case_insensitive:!1,keywords:I,contains:[T,t.HASH_COMMENT_MODE,t.COMMENT("//","$"),t.COMMENT("/\\*","\\*/",{contains:[{scope:"doctag",match:"@[A-Za-z]+"}]}),{match:/__halt_compiler\(\);/,keywords:"__halt_compiler",starts:{scope:"comment",end:t.MATCH_NOTHING_RE,contains:[{match:/\?>/,scope:"meta",endsParent:!0}]}},f,{scope:"variable.language",match:/\$this\b/},d,V,Z,{match:[/const/,/\s/,s],scope:{1:"keyword",3:"variable.constant"}},U,{scope:"function",relevance:0,beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[{beginKeywords:"use"},t.UNDERSCORE_TITLE_MODE,{begin:"=>",endsParent:!0},{scope:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:I,contains:["self",T,d,Z,t.C_BLOCK_COMMENT_MODE,N,S]}]},{scope:"class",variants:[{beginKeywords:"enum",illegal:/[($"]/},{beginKeywords:"cl
Line truncated
|
||||
`)||c))return g.jsx("code",{className:`${e||""} bg-white/8 px-1.5 py-0.5 rounded text-[13px]`,children:t});const f=((w=r==null?void 0:r.data)==null?void 0:w.meta)||((k=r==null?void 0:r.properties)==null?void 0:k.metastring)||"",h=/title=["']?([^"'\s}]+)["']?/.exec(f),m=/startLineNumber=(\d+)/.exec(f),p=(h==null?void 0:h[1])||null,y=m?parseInt(m[1],10):1,x=p?m?`${p}:${y}`:p:null,N=iE(o,c==null?void 0:c[1]).split(`
|
||||
`),S=()=>{vp(o),s(!0),setTimeout(()=>s(!1),2e3)};return g.jsxs("div",{className:"group/code relative rounded-md border border-[#2a2a2a] my-4 text-[#ddd] overflow-hidden",children:[x?g.jsxs("div",{className:"flex items-stretch",children:[g.jsxs("span",{className:"relative flex items-center text-[13px] text-[#999] font-mono px-4 py-2 border-r border-[#2a2a2a]",children:[x,g.jsx("span",{className:"absolute top-0 inset-x-0 h-0.5 bg-white/60 rounded-full"})]}),g.jsx("div",{className:"flex-1 border-b border-[#2a2a2a]"}),g.jsx("button",{onClick:S,className:"px-3 py-2 text-[#555] hover:text-white transition-colors border-b border-[#2a2a2a]","aria-label":"Copy code",children:a?g.jsx(Gs,{className:"w-3.5 h-3.5 text-emerald-400"}):g.jsx(mo,{className:"w-3.5 h-3.5"})})]}):g.jsx("button",{onClick:S,className:"absolute top-2 right-2 z-10 p-1 rounded text-[#444] hover:text-white opacity-0 group-hover/code:opacity-100 transition-opacity","aria-label":"Copy code",children:a?g.jsx(Gs,{className:"w-3.5 h-3.5 text-emerald-400"}):g.jsx(mo,{className:"w-3.5 h-3.5"})}),g.jsx("div",{className:"overflow-auto max-h-[400px]",children:g.jsx("table",{className:"w-full border-collapse font-mono text-[12px] leading-[22px] [font-variant-ligatures:none]",children:g.jsx("tbody",{children:N.map((E,M)=>g.jsxs("tr",{children:[g.jsx("td",{className:"select-none w-[1px] whitespace-nowrap px-4 text-right text-[#555] align-top text-[12px] leading-[22px] border-r border-[#2a2a2a]",children:y+M}),g.jsx("td",{className:"pl-4 pr-4 whitespace-pre",dangerouslySetInnerHTML:{__html:E||`
|
||||
`}})]},M))})})})]})}function Pp(){return e=>{const t=r=>{var a;if(r.type==="element"&&r.tagName==="pre"&&r.children){const s=r.children.find(o=>o.type==="element"&&o.tagName==="code");(a=s==null?void 0:s.data)!=null&&a.meta&&(s.properties=s.properties||{},s.properties.metastring=s.data.meta)}r.children&&r.children.forEach(s=>t(s))};t(e)}}const Fp={code:aE,pre:({children:e})=>g.jsx(g.Fragment,{children:e})};function oa({title:e,content:t,action:r}){return g.jsxs("section",{children:[(e||r)&&g.jsxs("div",{className:"flex items-center justify-between gap-3 mb-3",children:[e?g.jsx("h2",{className:"text-xl font-semibold text-white",children:e}):g.jsx("span",{}),r]}),g.jsx("div",{className:"prose-markdown",children:g.jsx(Bp,{remarkPlugins:[qp],rehypePlugins:[Pp],components:Fp,children:t})})]})}class jD{diff(t,r,a={}){let s;typeof a=="function"?(s=a,a={}):"callback"in a&&(s=a.callback);const o=this.castInput(t,a),c=this.castInput(r,a),d=this.removeEmpty(this.tokenize(o,a)),f=this.removeEmpty(this.tokenize(c,a));return this.diffWithOptionsObj(d,f,a,s)}diffWithOptionsObj(t,r,a,s){var o;const c=k=>{if(k=this.postProcess(k,a),s){setTimeout(function(){s(k)},0);return}else return k},d=r.length,f=t.length;let h=1,m=d+f;a.maxEditLength!=null&&(m=Math.min(m,a.maxEditLength));const p=(o=a.timeout)!==null&&o!==void 0?o:1/0,y=Date.now()+p,x=[{oldPos:-1,lastComponent:void 0}];let _=this.extractCommon(x[0],r,t,0,a);if(x[0].oldPos+1>=f&&_+1>=d)return c(this.buildValues(x[0].lastComponent,r,t));let N=-1/0,S=1/0;const w=()=>{for(let k=Math.max(N,-h);k<=Math.min(S,h);k+=2){let E;const M=x[k-1],I=x[k+1];M&&(x[k-1]=void 0);let R=!1;if(I){const B=I.oldPos-k;R=I&&0<=B&&B<d}const U=M&&M.oldPos+1<f;if(!R&&!U){x[k]=void 0;continue}if(!U||R&&M.oldPos<I.oldPos?E=this.addToPath(I,!0,!1,0,a):E=this.addToPath(M,!1,!0,1,a),_=this.extractCommon(E,r,t,k,a),E.oldPos+1>=f&&_+1>=d)return c(this.buildValues(E.lastComponent,r,t))||!0;x[k]=E,E.oldPos+1>=f&&(S=Math.min(S,k-1)),_+1>=d&&(N=Math.max(N,k+1))}h++};if(s)(function k(){setTimeout(function(){if(h>m||Date.now()>y)return s(void 0);w()||k()},0)})();else for(;h<=m&&Date.now()<=y;){const k=w();if(k)return k}}addToPath(t,r,a,s,o){const c=t.lastComponent;return c&&!o.oneChangePerToken&&c.added===r&&c.removed===a?{oldPos:t.oldPos+s,lastComponent:{count:c.count+1,added:r,removed:a,previousComponent:c.previousComponent}}:{oldPos:t.oldPos+s,lastComponent:{count:1,added:r,removed:a,previousComponent:c}}}extractCommon(t,r,a,s,o){const c=r.length,d=a.length;let f=t.oldPos,h=f-s,m=0;for(;h+1<c&&f+1<d&&this.equals(a[f+1],r[h+1],o);)h++,f++,m++,o.oneChangePerToken&&(t.lastComponent={count:1,previousComponent:t.lastComponent,added:!1,removed:!1});return m&&!o.oneChangePerToken&&(t.lastComponent={count:m,previousComponent:t.lastComponent,added:!1,removed:!1}),t.oldPos=f,h}equals(t,r,a){return a.comparator?a.comparator(t,r):t===r||!!a.ignoreCase&&t.toLowerCase()===r.toLowerCase()}removeEmpty(t){const r=[];for(let a=0;a<t.length;a++)t[a]&&r.push(t[a]);return r}castInput(t,r){return t}tokenize(t,r){return Array.from(t)}join(t){return t.join("")}postProcess(t,r){return t}get useLongestToken(){return!1}buildValues(t,r,a){const s=[];let o;for(;t;)s.push(t),o=t.previousComponent,delete t.previousComponent,t=o;s.reverse();const c=s.length;let d=0,f=0,h=0;for(;d<c;d++){const m=s[d];if(m.removed)m.value=this.join(a.slice(h,h+m.count)),h+=m.count;else{if(!m.added&&this.useLongestToken){let p=r.slice(f,f+m.count);p=p.map(function(y,x){const _=a[h+x];return _.length>y.length?_:y}),m.value=this.join(p)}else m.value=this.join(r.slice(f,f+m.count));f+=m.count,m.added||(h+=m.count)}}return s}}class LD extends jD{constructor(){super(...arguments),this.tokenize=BD}equals(t,r,a){return a.ignoreWhitespace?((!a.newlineIsToken||!t.includes(`
|
||||
`))&&(t=t.trim()),(!a.newlineIsToken||!r.includes(`
|
||||
`))&&(r=r.trim())):a.ignoreNewlineAtEof&&!a.newlineIsToken&&(t.endsWith(`
|
||||
`)&&(t=t.slice(0,-1)),r.endsWith(`
|
||||
`)&&(r=r.slice(0,-1))),super.equals(t,r,a)}}const zD=new LD;function ID(e,t,r){return zD.diff(e,t,r)}function BD(e,t){t.stripTrailingCr&&(e=e.replace(/\r\n/g,`
|
||||
`));const r=[],a=e.split(/(\n|\r\n)/);a[a.length-1]||a.pop();for(let s=0;s<a.length;s++){const o=a[s];s%2&&!t.newlineIsToken?r[r.length-1]+=o:r.push(o)}return r}function UD(e,t){try{return Ln.highlight(e,{language:t,ignoreIllegals:!0}).value}catch{return Ln.highlightAuto(e).value}}function HD({file:e,startLine:t,endLine:r,before:a,after:s,onCopy:o}){const[c,d]=ee.useState(!1),f=r&&r!==t?`${t}-${r}`:`${t}`,h=ST(e)||"text",m=ID(a,s);let p=t,y=t;const x=m.flatMap(N=>N.value.replace(/\n$/,"").split(`
|
||||
`).map(S=>{const w=S===""?`
|
||||
`:h!=="text"?UD(S,h):S.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");let k="",E="";return N.removed?k=String(p++):(N.added||(k=String(p++)),E=String(y++)),{highlighted:w,added:!!N.added,removed:!!N.removed,leftNo:k,rightNo:E}})),_=()=>{vp(s),d(!0),setTimeout(()=>d(!1),2e3),o==null||o()};return g.jsxs("div",{className:"rounded-md border border-[#2a2a2a] overflow-hidden",children:[g.jsxs("div",{className:"flex items-stretch",children:[g.jsxs("span",{className:"relative flex items-center text-[13px] text-[#999] font-mono px-4 py-2 border-r border-[#2a2a2a] break-all",children:[e,":",f,g.jsx("span",{className:"absolute top-0 inset-x-0 h-0.5 bg-white/60 rounded-full"})]}),g.jsx("div",{className:"flex-1 border-b border-[#2a2a2a]"}),g.jsx("button",{onClick:_,className:"px-3 py-2 text-[#555] hover:text-white transition-colors flex-shrink-0 border-b border-[#2a2a2a]","aria-label":"Copy fixed code",children:c?g.jsx(Gs,{className:"w-3.5 h-3.5 text-emerald-400"}):g.jsx(mo,{className:"w-3.5 h-3.5"})})]}),g.jsx("div",{className:"overflow-auto max-h-[400px]",children:g.jsx("table",{className:"w-full border-collapse font-mono text-[12px] leading-[22px] [font-variant-ligatures:none]",children:g.jsx("tbody",{children:x.map((N,S)=>g.jsxs("tr",{className:N.added?"bg-blue-500/[0.12]":N.removed?"bg-red-500/[0.12]":"",children:[g.jsx("td",{className:"select-none w-[1px] whitespace-nowrap pl-4 pr-1.5 text-right text-[#555] align-top text-[12px] leading-[22px]",children:N.leftNo}),g.jsx("td",{className:"select-none w-[1px] whitespace-nowrap pl-1.5 pr-4 text-right text-[#555] align-top text-[12px] leading-[22px] border-r border-[#2a2a2a]",children:N.rightNo}),g.jsx("td",{className:"pl-4 pr-4 whitespace-pre",dangerouslySetInnerHTML:{__html:N.highlighted}})]},S))})})})]})}const $D=/^```([^\n`]*)\r?\n([\s\S]*?)\r?\n?```$/;function sE(e){if(!e)return{code:""};const t=$D.exec(e.trim());if(!t)return{code:e};const r=t[1].trim();return{language:(r?r.split(/\s+/)[0]:void 0)||void 0,code:t[2]}}function qD({description:e,scriptCode:t,onCopy:r}){const[a,s]=ee.useState(!1);if(!e&&!t)return null;const{language:o,code:c}=sE(t),d=iE(c,o),f=()=>{c&&(vp(c),s(!0),setTimeout(()=>s(!1),2e3),r==null||r())};return g.jsxs("section",{children:[g.jsx("h2",{className:"text-xl font-semibold text-white mb-3",children:"Proof of Concept"}),g.jsxs("div",{className:"space-y-4",children:[e&&g.jsx("div",{className:"prose-markdown",children:g.jsx(Bp,{remarkPlugins:[qp],rehypePlugins:[Pp],components:Fp,children:e})}),c&&g.jsxs("div",{className:"group/poc relative rounded-md border border-[#2a2a2a] overflow-hidden",children:[g.jsxs("div",{className:"flex items-stretch",children:[g.jsxs("span",{className:"relative flex items-center text-[13px] text-[#999] font-mono px-4 py-2 border-r border-[#2a2a2a]",children:["PoC Script",g.jsx("span",{className:"absolute top-0 inset-x-0 h-0.5 bg-white/60 rounded-full"})]}),g.jsx("div",{className:"flex-1 border-b border-[#2a2a2a]"}),g.jsx("button",{onClick:f,className:"px-3 py-2 text-[#555] hover:text-white transition-colors flex-shrink-0 border-b border-[#2a2a2a]","aria-label":"Copy PoC code",children:a?g.jsx(Gs,{className:"w-3.5 h-3.5 text-emerald-400"}):g.jsx(mo,{className:"w-3.5 h-3.5"})})]}),g.jsx("div",{className:"overflow-auto max-h-[400px] px-4 py-3",children:g.jsx("pre",{className:"font-mono text-[12px] leading-[22px] whitespace-pre [font-variant-ligatures:none]",children:g.jsx("code",{dangerouslySetInnerHTML:{__html:d}})})})]})]})]})}function lE(e){const t=e.match(/(?:https?:\/\/)?(?:www\.)?github\.com\/([^\s/]+\/[^\s/]+)/);if(t){const s=t[1].replace(/\.git$/,"");return{display:s,href:`https://github.com/${s}`,provider:"github"}}const r=e.match(/(?:https?:\/\/)?(?:www\.)?gitlab\.com\/([^\s/]+\/[^\s/]+)/);if(r){const s=r[1].replace(/\.git$/,"");return{display:s,href:`https://gitlab.com/${s}`,provider:"gitlab"}}const a=e.match(/(?:https?:\/\/)?(?:www\.)?bitbucket\.org\/([^\s/]+\/[^\s/]+)/);if(a){const s=a[1].replace(/\.git$/,"");return{display:s,href:`https://bitbucket.org/${s}`,provider:"bitbucket"}}return/^https?:\/\//i.test(e)?{display:e.replace(/^https?:\/\/(www\.)?/,""),href:e,provider:null}:/^[a-zA-Z0-9][\w.-]*\.[a-zA-Z]{2,}/.test(e)?{display:e,href:`https://${e}`,provider:null}:{display:e,href:null,provider:null}}function bo(e,t){return e?lE(e).display.replace(/\/$/,""):t||"Untitled pentest"}function PD({className:e}){return g.jsx("svg",{viewBox:"0 0 24 24",fill:"currentColor",className:e,"aria-hidden":"true",children:g.jsx("path",{d:"M2.65 3a.72.72 0 0 0-.72.83l2.86 17.39a.98.98 0 0 0 .96.82h13.72a.72.72 0 0 0 .72-.6l2.86-17.4A.72.72 0 0 0 22.3 3H2.65Zm12.1 12.53H9.3L8.06 8.9h7.8l-1.11 6.63Z"})})}function FD({provider:e,className:t}){const r=t??"w-4 h-4";return e==="gitlab"?g.jsx(EC,{className:`${r} text-orange-400`}):e==="bitbucket"?g.jsx(PD,{className:`${r} text-blue-400`}):g.jsx(_C,{className:`${r} text-white`})}const GD={attack_vector:{N:"Remotely exploitable",A:"Adjacent network",L:"Local access
Line truncated
|
||||
* @license React
|
||||
* use-sync-external-store-shim.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var g1;function OI(){if(g1)return km;g1=1;var e=To();function t(p,y){return p===y&&(p!==0||1/p===1/y)||p!==p&&y!==y}var r=typeof Object.is=="function"?Object.is:t,a=e.useState,s=e.useEffect,o=e.useLayoutEffect,c=e.useDebugValue;function d(p,y){var x=y(),_=a({inst:{value:x,getSnapshot:y}}),N=_[0].inst,S=_[1];return o(function(){N.value=x,N.getSnapshot=y,f(N)&&S({inst:N})},[p,x,y]),s(function(){return f(N)&&S({inst:N}),p(function(){f(N)&&S({inst:N})})},[p]),c(x),x}function f(p){var y=p.getSnapshot;p=p.value;try{var x=y();return!r(p,x)}catch{return!0}}function h(p,y){return y()}var m=typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"?h:d;return km.useSyncExternalStore=e.useSyncExternalStore!==void 0?e.useSyncExternalStore:m,km}var b1;function RI(){return b1||(b1=1,Sm.exports=OI()),Sm.exports}/**
|
||||
* @license React
|
||||
* use-sync-external-store-shim/with-selector.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var x1;function DI(){if(x1)return Nm;x1=1;var e=To(),t=RI();function r(h,m){return h===m&&(h!==0||1/h===1/m)||h!==h&&m!==m}var a=typeof Object.is=="function"?Object.is:r,s=t.useSyncExternalStore,o=e.useRef,c=e.useEffect,d=e.useMemo,f=e.useDebugValue;return Nm.useSyncExternalStoreWithSelector=function(h,m,p,y,x){var _=o(null);if(_.current===null){var N={hasValue:!1,value:null};_.current=N}else N=_.current;_=d(function(){function w(R){if(!k){if(k=!0,E=R,R=y(R),x!==void 0&&N.hasValue){var U=N.value;if(x(U,R))return M=U}return M=R}if(U=M,a(E,R))return U;var B=y(R);return x!==void 0&&x(U,B)?(E=R,U):(E=R,M=B)}var k=!1,E,M,I=p===void 0?null:p;return[function(){return w(m())},I===null?void 0:function(){return w(I())}]},[m,p,y,x]);var S=s(h,_[0],_[1]);return c(function(){N.hasValue=!0,N.value=S},[S]),f(S),S},Nm}var y1;function jI(){return y1||(y1=1,Em.exports=DI()),Em.exports}var LI=jI();const zI=Co(LI),II={},v1=e=>{let t;const r=new Set,a=(m,p)=>{const y=typeof m=="function"?m(t):m;if(!Object.is(y,t)){const x=t;t=p??(typeof y!="object"||y===null)?y:Object.assign({},t,y),r.forEach(_=>_(t,x))}},s=()=>t,f={setState:a,getState:s,getInitialState:()=>h,subscribe:m=>(r.add(m),()=>r.delete(m)),destroy:()=>{(II?"production":void 0)!=="production"&&console.warn("[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected."),r.clear()}},h=t=e(a,s,f);return f},BI=e=>e?v1(e):v1,{useDebugValue:UI}=ua,{useSyncExternalStoreWithSelector:HI}=zI,$I=e=>e;function mN(e,t=$I,r){const a=HI(e.subscribe,e.getState,e.getServerState||e.getInitialState,t,r);return UI(a),a}const _1=(e,t)=>{const r=BI(e),a=(s,o=t)=>mN(r,s,o);return Object.assign(a,r),a},qI=(e,t)=>e?_1(e,t):_1;function qt(e,t){if(Object.is(e,t))return!0;if(typeof e!="object"||e===null||typeof t!="object"||t===null)return!1;if(e instanceof Map&&t instanceof Map){if(e.size!==t.size)return!1;for(const[a,s]of e)if(!Object.is(s,t.get(a)))return!1;return!0}if(e instanceof Set&&t instanceof Set){if(e.size!==t.size)return!1;for(const a of e)if(!t.has(a))return!1;return!0}const r=Object.keys(e);if(r.length!==Object.keys(t).length)return!1;for(const a of r)if(!Object.prototype.hasOwnProperty.call(t,a)||!Object.is(e[a],t[a]))return!1;return!0}N_();const td=ee.createContext(null),PI=td.Provider,pN=Lr.error001("react");function dt(e,t){const r=ee.useContext(td);if(r===null)throw new Error(pN);return mN(r,e,t)}function Lt(){const e=ee.useContext(td);if(e===null)throw new Error(pN);return ee.useMemo(()=>({getState:e.getState,setState:e.setState,subscribe:e.subscribe}),[e])}const w1={display:"none"},FI={position:"absolute",width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0px, 0px, 0px, 0px)",clipPath:"inset(100%)"},gN="react-flow__node-desc",bN="react-flow__edge-desc",GI="react-flow__aria-live",VI=e=>e.ariaLiveMessage,YI=e=>e.ariaLabelConfig;function XI({rfId:e}){const t=dt(VI);return g.jsx("div",{id:`${GI}-${e}`,"aria-live":"assertive","aria-atomic":"true",style:FI,children:t})}function KI({rfId:e,disableKeyboardA11y:t}){const r=dt(YI);return g.jsxs(g.Fragment,{children:[g.jsx("div",{id:`${gN}-${e}`,style:w1,children:t?r["node.a11yDescription.default"]:r["node.a11yDescription.keyboardDisabled"]}),g.jsx("div",{id:`${bN}-${e}`,style:w1,children:r["edge.a11yDescription.default"]}),!t&&g.jsx(XI,{rfId:e})]})}const nd=ee.forwardRef(({position:e="top-left",children:t,className:r,style:a,...s},o)=>{const c=`${e}`.split("-");return g.jsx("div",{className:ln(["react-flow__panel",r,...c]),style:a,ref:o,...s,children:t})});nd.displayName="Panel";const E1="https://reactflow.dev?utm_source=attribution";function ZI({proOptions:e,position:t="bottom-right"}){return e!=null&&e.hideAttribution?null:g.jsx(nd,{position:t,className:"react-flow__attribution","data-message":`Please only hide this attribution when you are subscribed to React Flow Pro: ${E1}`,children:g.jsx("a",{href:E1,target:"_blank",rel:"noopener noreferrer","aria-label":"React Flow attribution",children:"React Flow"})})}const QI=e=>{const t=[],r=[];for(const[,a]of e.nodeLookup)a.selected&&t.push(a.internals.userNode);for(const[,a]of e.edgeLookup)a.selected&&r.push(a);return{selectedNodes:t,selectedEdges:r}},uu=e=>e.id;function WI(e,t){return qt(e.selectedNodes.map(uu),t.selectedNodes.map(uu))&&qt(e.selectedEdges.map(uu),t.selectedEdges.map(uu))}function JI({onSelectionChange:e}){const t=Lt(),{selectedNodes:r,selectedEdges:a}=dt(QI,WI);return ee.useEffect(()=>{const s={nodes:r,edges:a};e==null||e(s),t.getState().onSelectionChangeHandlers.forEach(o=>o(s))},[r,a,e]),null}const e8=e=>!!e.onSelectionChangeHandlers;function t8({onSelectionChange:e}){const t=dt(e8);return e||t?g.jsx(JI,{onSelectionChange:e}):null}const xN=[0,0],n8={x:0,y:0,zoom:1},r8=["nodes","edges","defaultNodes","defaultEdges","onConnect","onConnectStart","onConnectEnd","onClickConnectStart","onClickConnectEnd","nodesDraLine truncated
|
||||
`).replace(`
|
||||
|
||||
`,`
|
||||
+`).split(`
|
||||
`)),m=h.reduce((p,y)=>p.concat(...y),[]);return[h,m]}return[[],[]]},[e]);return ee.useEffect(()=>{const f=(t==null?void 0:t.target)??C1,h=(t==null?void 0:t.actInsideInputWithModifier)??!0;if(e!==null){const m=x=>{var S,w;if(s.current=x.ctrlKey||x.metaKey||x.shiftKey||x.altKey,(!s.current||s.current&&!h)&&QE(x))return!1;const N=A1(x.code,d);if(o.current.add(x[N]),T1(c,o.current,!1)){const k=((w=(S=x.composedPath)==null?void 0:S.call(x))==null?void 0:w[0])||x.target,E=(k==null?void 0:k.nodeName)==="BUTTON"||(k==null?void 0:k.nodeName)==="A";t.preventDefault!==!1&&(s.current||!E)&&x.preventDefault(),a(!0)}},p=x=>{const _=A1(x.code,d);T1(c,o.current,!0)?(a(!1),o.current.clear()):o.current.delete(x[_]),x.key==="Meta"&&o.current.clear(),s.current=!1},y=()=>{o.current.clear(),a(!1)};return f==null||f.addEventListener("keydown",m),f==null||f.addEventListener("keyup",p),window.addEventListener("blur",y),window.addEventListener("contextmenu",y),()=>{f==null||f.removeEventListener("keydown",m),f==null||f.removeEventListener("keyup",p),window.removeEventListener("blur",y),window.removeEventListener("contextmenu",y)}}},[e,a]),r}function T1(e,t,r){return e.filter(a=>r||a.length===t.size).some(a=>a.every(s=>t.has(s)))}function A1(e,t){return t.includes(e)?"code":"key"}const l8=()=>{const e=Lt();return ee.useMemo(()=>({zoomIn:async t=>{const{panZoom:r}=e.getState();return r?r.scaleBy(1.2,t):!1},zoomOut:async t=>{const{panZoom:r}=e.getState();return r?r.scaleBy(1/1.2,t):!1},zoomTo:async(t,r)=>{const{panZoom:a}=e.getState();return a?a.scaleTo(t,r):!1},getZoom:()=>e.getState().transform[2],setViewport:async(t,r)=>{const{transform:[a,s,o],panZoom:c}=e.getState();return c?(await c.setViewport({x:t.x??a,y:t.y??s,zoom:t.zoom??o},r),!0):!1},getViewport:()=>{const[t,r,a]=e.getState().transform;return{x:t,y:r,zoom:a}},setCenter:async(t,r,a)=>e.getState().setCenter(t,r,a),fitBounds:async(t,r)=>{const{width:a,height:s,minZoom:o,maxZoom:c,panZoom:d}=e.getState(),f=tg(t,a,s,o,c,(r==null?void 0:r.padding)??.1);return d?(await d.setViewport(f,{duration:r==null?void 0:r.duration,ease:r==null?void 0:r.ease,interpolate:r==null?void 0:r.interpolate}),!0):!1},screenToFlowPosition:(t,r={})=>{const{transform:a,snapGrid:s,snapToGrid:o,domNode:c}=e.getState();if(!c)return t;const{x:d,y:f}=c.getBoundingClientRect(),h={x:t.x-d,y:t.y-f},m=r.snapGrid??s,p=r.snapToGrid??o;return Bo(h,a,p,m)},flowToScreenPosition:t=>{const{transform:r,domNode:a}=e.getState();if(!a)return t;const{x:s,y:o}=a.getBoundingClientRect(),c=Ws(t,r);return{x:c.x+s,y:c.y+o}}}),[])};function yN(e,t){const r=[],a=new Map,s=[];for(const o of e)if(o.type==="add"){s.push(o);continue}else if(o.type==="remove"||o.type==="replace")a.set(o.id,[o]);else{const c=a.get(o.id);c?c.push(o):a.set(o.id,[o])}for(const o of t){const c=a.get(o.id);if(!c){r.push(o);continue}if(c[0].type==="remove")continue;if(c[0].type==="replace"){r.push({...c[0].item});continue}const d={...o};for(const f of c)o8(f,d);r.push(d)}return s.length&&s.forEach(o=>{o.index!==void 0?r.splice(o.index,0,{...o.item}):r.push({...o.item})}),r}function o8(e,t){switch(e.type){case"select":{t.selected=e.selected;break}case"position":{typeof e.position<"u"&&(t.position=e.position),typeof e.dragging<"u"&&(t.dragging=e.dragging);break}case"dimensions":{typeof e.dimensions<"u"&&(t.measured={...e.dimensions},e.setAttributes&&((e.setAttributes===!0||e.setAttributes==="width")&&(t.width=e.dimensions.width),(e.setAttributes===!0||e.setAttributes==="height")&&(t.height=e.dimensions.height))),typeof e.resizing=="boolean"&&(t.resizing=e.resizing);break}}}function vN(e,t){return yN(e,t)}function _N(e,t){return yN(e,t)}function Ua(e,t){return{id:e,type:"select",selected:t}}function Hs(e,t=new Set,r=!1){const a=[];for(const[s,o]of e){const c=t.has(s);!(o.selected===void 0&&!c)&&o.selected!==c&&(r&&(o.selected=c),a.push(Ua(o.id,c)))}return a}function M1({items:e=[],lookup:t}){var s;const r=[],a=new Map(e.map(o=>[o.id,o]));for(const[o,c]of e.entries()){const d=t.get(c.id),f=((s=d==null?void 0:d.internals)==null?void 0:s.userNode)??d;f!==void 0&&f!==c&&r.push({id:c.id,item:c,type:"replace"}),f===void 0&&r.push({item:c,type:"add",index:o})}for(const[o]of t)a.get(o)===void 0&&r.push({id:o,type:"remove"});return r}function O1(e){return{id:e.id,type:"remove"}}const c8=YE();function u8(e,t,r={}){return Kz(e,t,{...r,onError:r.onError??c8})}const R1=e=>jz(e),d8=e=>qE(e);function wN(e){return ee.forwardRef(e)}const EN=typeof window<"u"?ee.useLayoutEffect:ee.useEffect;function D1(e){const[t,r]=ee.useState(BigInt(0)),[a]=ee.useState(()=>f8(()=>r(s=>s+BigInt(1))));return EN(()=>{const s=a.get();s.length&&(e(s),a.reset())},[t]),a}function f8(e){let t=[];return{get:()=>t,reset:()=>{t=[]},push:r=>{t.push(r),e()}}}const NN=ee.createContext(null);function h8({children:e}){const t=Lt(),r=ee.useCallback(d=>{const{nodes:f=[],setNodes:h,hasDefaultNodes:m,onNodesChange:p,nodeLookup:y,fitViewQueued:x,onNodesChangeMiddlewareMap:_}=t.getState();let N=f;for(const w of d)N=
Line truncated
|
||||
M${B.x},${B.y}h${B.width}v${B.height}h${-B.width}z`,fillRule:"evenodd",pointerEvents:"none"})]})})}ZN.displayName="MiniMap";const lB=ee.memo(ZN),oB=e=>t=>e?`${Math.max(1/t.transform[2],1)}`:void 0,cB={[Js.Line]:"right",[Js.Handle]:"bottom-right"};function uB({nodeId:e,position:t,variant:r=Js.Handle,className:a,style:s=void 0,children:o,color:c,minWidth:d=10,minHeight:f=10,maxWidth:h=Number.MAX_VALUE,maxHeight:m=Number.MAX_VALUE,keepAspectRatio:p=!1,resizeDirection:y,autoScale:x=!0,shouldResize:_,onResizeStart:N,onResize:S,onResizeEnd:w}){const k=CN(),E=typeof e=="string"?e:k,M=Lt(),I=ee.useRef(null),R=r===Js.Handle,U=dt(ee.useCallback(oB(R&&x),[R,x]),qt),B=ee.useRef(null),Z=t??cB[r];ee.useEffect(()=>{if(!(!I.current||!E))return B.current||(B.current=MI({domNode:I.current,nodeId:E,getStoreItems:()=>{const{nodeLookup:z,transform:V,snapGrid:P,snapToGrid:T,nodeOrigin:$,domNode:O}=M.getState();return{nodeLookup:z,transform:V,snapGrid:P,snapToGrid:T,nodeOrigin:$,paneDomNode:O}},onChange:(z,V)=>{const{triggerNodeChanges:P,nodeLookup:T,parentLookup:$,nodeOrigin:O}=M.getState(),H=[],X={x:z.x,y:z.y},K=T.get(E);if(K&&K.expandParent&&K.parentId){const C=K.origin??O,j=z.width??K.measured.width??0,Y=z.height??K.measured.height??0,L={id:K.id,parentId:K.parentId,rect:{width:j,height:Y,...KE({x:z.x??K.position.x,y:z.y??K.position.y},{width:j,height:Y},K.parentId,T,C)}},G=lg([L],T,$,O);H.push(...G),X.x=z.x?Math.max(C[0]*j,z.x):void 0,X.y=z.y?Math.max(C[1]*Y,z.y):void 0}if(X.x!==void 0&&X.y!==void 0){const C={id:E,type:"position",position:{...X}};H.push(C)}if(z.width!==void 0&&z.height!==void 0){const j={id:E,type:"dimensions",resizing:!0,setAttributes:y?y==="horizontal"?"width":"height":!0,dimensions:{width:z.width,height:z.height}};H.push(j)}for(const C of V){const j={...C,type:"position"};H.push(j)}P(H)},onEnd:({width:z,height:V})=>{const P={id:E,type:"dimensions",resizing:!1,dimensions:{width:z,height:V}};M.getState().triggerNodeChanges([P])}})),B.current.update({controlPosition:Z,boundaries:{minWidth:d,minHeight:f,maxWidth:h,maxHeight:m},keepAspectRatio:p,resizeDirection:y,onResizeStart:N,onResize:S,onResizeEnd:w,shouldResize:_}),()=>{var z;(z=B.current)==null||z.destroy()}},[Z,d,f,h,m,p,N,S,w,_]);const D=Z.split("-");return g.jsx("div",{className:ln(["react-flow__resize-control","nodrag",...D,r,a]),ref:I,style:{...s,scale:U,...c&&{[R?"backgroundColor":"borderColor"]:c}},children:o})}ee.memo(uB);var vt=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),zr=vt((e,t)=>{var r=Object.defineProperty,a=(P,T,$)=>T in P?r(P,T,{enumerable:!0,configurable:!0,writable:!0,value:$}):P[T]=$,s=(P,T)=>()=>(T||P((T={exports:{}}).exports,T),T.exports),o=(P,T,$)=>a(P,typeof T!="symbol"?T+"":T,$),c=s((P,T)=>{var $="\0",O="\0",H="",X=class{constructor(G){o(this,"_isDirected",!0),o(this,"_isMultigraph",!1),o(this,"_isCompound",!1),o(this,"_label"),o(this,"_defaultNodeLabelFn",()=>{}),o(this,"_defaultEdgeLabelFn",()=>{}),o(this,"_nodes",{}),o(this,"_in",{}),o(this,"_preds",{}),o(this,"_out",{}),o(this,"_sucs",{}),o(this,"_edgeObjs",{}),o(this,"_edgeLabels",{}),o(this,"_nodeCount",0),o(this,"_edgeCount",0),o(this,"_parent"),o(this,"_children"),G&&(this._isDirected=Object.hasOwn(G,"directed")?G.directed:!0,this._isMultigraph=Object.hasOwn(G,"multigraph")?G.multigraph:!1,this._isCompound=Object.hasOwn(G,"compound")?G.compound:!1),this._isCompound&&(this._parent={},this._children={},this._children[O]={})}isDirected(){return this._isDirected}isMultigraph(){return this._isMultigraph}isCompound(){return this._isCompound}setGraph(G){return this._label=G,this}graph(){return this._label}setDefaultNodeLabel(G){return this._defaultNodeLabelFn=G,typeof G!="function"&&(this._defaultNodeLabelFn=()=>G),this}nodeCount(){return this._nodeCount}nodes(){return Object.keys(this._nodes)}sources(){var G=this;return this.nodes().filter(q=>Object.keys(G._in[q]).length===0)}sinks(){var G=this;return this.nodes().filter(q=>Object.keys(G._out[q]).length===0)}setNodes(G,q){var Q=arguments,J=this;return G.forEach(function(W){Q.length>1?J.setNode(W,q):J.setNode(W)}),this}setNode(G,q){return Object.hasOwn(this._nodes,G)?(arguments.length>1&&(this._nodes[G]=q),this):(this._nodes[G]=arguments.length>1?q:this._defaultNodeLabelFn(G),this._isCompound&&(this._parent[G]=O,this._children[G]={},this._children[O][G]=!0),this._in[G]={},this._preds[G]={},this._out[G]={},this._sucs[G]={},++this._nodeCount,this)}node(G){return this._nodes[G]}hasNode(G){return Object.hasOwn(this._nodes,G)}removeNode(G){var q=this;if(Object.hasOwn(this._nodes,G)){var Q=J=>q.removeEdge(q._edgeObjs[J]);delete this._nodes[G],this._isCompound&&(this._removeFromParentsChildList(G),delete this._parent[G],this.children(G).forEach(function(J){q.setParent(J)}),delete this._children[G]),Object.keys(this._in[G]).forEach(Q),delete this._in[G],delete this._preds[G],Object.keys(this._out[G]).forEach(Q),delete this._out[G],delete this._sucs[G],--this._nodeCount}return this}setParent(G,q){if(!this._isCompound
Line truncated
|
||||
`).length>t;return g.jsxs("div",{children:[g.jsx("div",{className:r&&o?"max-h-[1200px] overflow-auto":"",style:!r&&o?{display:"-webkit-box",WebkitLineClamp:t,WebkitBoxOrient:"vertical",overflow:"hidden"}:void 0,children:g.jsx($s,{text:e})}),o&&g.jsx("button",{onClick:()=>a(!r),className:"text-xs text-[#555] hover:text-[#888] mt-1",children:r?"Show less":"Show more"})]})}function wi({children:e,className:t=""}){const[r,a]=ee.useState(!1),o=typeof e=="string"?e.trimEnd().split(`
|
||||
`):null,c=o!==null&&o.length>Z1,d=c&&!r?o.slice(0,Z1).join(`
|
||||
`):e;return g.jsxs("div",{children:[g.jsx("pre",{className:`font-mono text-[13px] leading-relaxed whitespace-pre-wrap break-words mt-1 ${r?"overflow-auto max-h-[1200px]":"overflow-hidden"} ${t}`,children:d}),c&&g.jsx("button",{onClick:()=>a(!r),className:"text-xs text-[#555] hover:text-[#888] mt-0.5",children:r?"Show less":"Show more"})]})}function cg({code:e,language:t,className:r="",collapsible:a=!1}){const[s,o]=ee.useState(!1),c=e.trimEnd().split(`
|
||||
`),d=a&&c.length>Q1,f=d&&!s?c.slice(0,Q1).join(`
|
||||
`):e;let h;try{h=t?Ln.highlight(f,{language:t,ignoreIllegals:!0}).value:Ln.highlightAuto(f).value}catch{h=Ln.highlightAuto(f).value}return g.jsxs("div",{children:[g.jsx("pre",{className:`font-mono text-[12px] leading-relaxed px-0 py-1 mt-1 whitespace-pre-wrap break-all ${a?s?"overflow-auto max-h-[1200px]":"overflow-hidden":"overflow-auto max-h-[400px]"} ${r}`,children:g.jsx("code",{dangerouslySetInnerHTML:{__html:h}})}),d&&g.jsx("button",{onClick:()=>o(!s),className:"text-xs text-[#555] hover:text-[#888] mt-0.5",children:s?"Show less":"Show more"})]})}const FB=50,W1=200,J1=25,e_=24,GB=[/\n?\[Command still running after [\d.]+s - showing output so far\.?\s*(?:Use C-c to interrupt if needed\.)?\]/g,/^\[Below is the output of the previous command\.\]\n?/gm,/^No command is currently running\. Cannot send input\.$/gm,/^A command is already running\. Use is_input=true to send input to it, or interrupt it first \(e\.g\., with C-c\)\.$/gm],VB=/^Chunk ID: [0-9a-f]+\s*$/,YB=[/^Wall time: [\d.]+ seconds\s*$/,/^Process exited with code -?\d+\s*$/,/^Process running with session ID \d+\s*$/,/^Original token count: \d+\s*$/];function XB(e){const t=[];for(let r=0;r<e.length;r++){if(VB.test(e[r])){let a=r+1;for(;a<e.length&&YB.some(s=>s.test(e[a]));)a++;a<e.length&&e[a].trim()==="Output:"&&a++,r=a-1;continue}t.push(e[r])}return t}function KB(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Mm(e){return e.length>W1?e.slice(0,W1-3)+"...":e}function ZB(e,t=""){let r=e.replace(/\x1b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07]*\x07)/g,"").replace(/\r/g,"");for(const a of GB)r=r.replace(a,"");if(r.trim()){const a=XB(r.split(`
|
||||
`)),s=[];for(const o of a)s.length===0&&!o.trim()||/^\[STRIX_\d+\]\$\s*/.test(o)||t&&o.trim()===t.trim()||t&&new RegExp(`^[\\$#>]\\s*${KB(t.trim())}\\s*$`).test(o)||s.push(o);for(;s.length>0&&/^\[STRIX_\d+\]\$\s*/.test(s[s.length-1]);)s.pop();r=s.join(`
|
||||
`)}return r.trim()}function QB(e){const t=e.split(`
|
||||
`);if(t.length<=FB)return t.map(Mm).join(`
|
||||
`);const r=t.length-J1-e_;return[...t.slice(0,J1).map(Mm),`... ${r} lines truncated ...`,...t.slice(-e_).map(Mm)].join(`
|
||||
`)}function WB({toolName:e,args:t,result:r}){const a=e==="write_stdin",s=a?t.chars??t.input??"":t.command??t.cmd??"",o=r;let c=null,d=null,f=null;if(o&&typeof o=="object"){c=typeof o.content=="string"?o.content:null,d=typeof o.error=="string"?o.error:null,f=typeof o.exit_code=="number"?o.exit_code:null;const m=typeof o.status=="string"?o.status:"";(m==="running"||m==="command still running")&&(c=null)}else typeof o=="string"&&(c=o);const h=c?QB(ZB(c,s)):null;return g.jsxs("div",{children:[g.jsx("span",{className:"text-emerald-400/80 font-semibold text-sm",children:a?"Terminal input":"Terminal"}),s&&g.jsx(cg,{code:s,language:"bash",collapsible:!0}),d&&g.jsx(wi,{className:"text-red-400/70",children:d}),h&&g.jsx(wi,{className:"text-[#666]",children:h}),f!=null&&f!==0&&g.jsxs("div",{className:"font-mono text-[13px] text-red-400/70 mt-0.5",children:["exit code ",f]})]})}const t_={back:"going back in browser history",forward:"going forward in browser history",scroll_down:"scrolling down",scroll_up:"scrolling up",refresh:"refreshing",close_tab:"closing tab",switch_tab:"switching tab",list_tabs:"listing tabs",view_source:"viewing page source",get_console_logs:"getting console logs",screenshot:"taking screenshot",wait:"waiting...",close:"closing"},n_={click:"clicking",double_click:"double clicking",hover:"hovering"};function Om({prefix:e,url:t,suffix:r}){return g.jsxs("span",{className:"text-[#888] text-[13px]",children:[e,t&&g.jsx("a",{href:t,target:"_blank",rel:"noopener noreferrer",className:"text-cyan-400/80 hover:underline",children:t}),r]})}function JB(e){const t=e.action??"",r=e.url??void 0;if(t in t_)return t_[t];if(t==="launch")return r?g.jsx(Om,{prefix:"launching ",url:r}):"launching";if(t==="goto"||t==="navigate")return g.jsx(Om,{prefix:"navigating to ",url:r});if(t==="new_tab")return g.jsx(Om,{prefix:"opening tab ",url:r});if(t in n_)return n_[t];if(t==="type")return`typing "${(e.text??"").slice(0,40)}"`;if(t==="press_key"||t==="key_press")return`pressing key ${e.key??""}`;if(t==="save_pdf"||t==="save_as_pdf"){const a=e.file_path??"";return`saving PDF${a?` to ${a}`:""}`}return t==="execute_js"?"executing javascript":t||"browser action"}function e7({args:e}){const r=(e.action??"")==="execute_js"?e.js_code??e.code??"":"",a=JB(e);return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-center gap-2 min-w-0",children:[g.jsx("span",{className:"text-blue-400/80 font-semibold text-sm shrink-0",children:"Browser"}),g.jsx("span",{className:"min-w-0 truncate text-[#888] text-[13px]",children:a})]}),r&&g.jsx(cg,{code:r,language:"javascript",collapsible:!0})]})}function ug(e){return e.length>60?"..."+e.slice(-57):e}const fu=30;function t7({toolName:e,args:t}){const r=t.path??t.file_path??"",a=t.command??"",s=t.old_str??"",o=t.new_str??"",c=t.regex??"";let d;e==="list_files"?d="list":e==="search_files"?d="search":a==="view"?d="view":a==="create"?d="create":a==="str_replace"?d="edit":a==="undo_edit"?d="undo":a==="insert"?d="insert":d="file";const f=r?ug(r):"",h=c?` /${c}/`:"",m=s?s.split(`
|
||||
`):[],p=o?o.split(`
|
||||
`):[],y=m.length+p.length,x=y>fu,_=x?Math.round(fu*(m.length/y)):m.length,N=x?fu-_:p.length;return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-baseline gap-2",children:[g.jsx("span",{className:"text-sky-400/80 font-semibold text-sm shrink-0",children:d}),f&&g.jsx("span",{className:"text-[#888] font-mono text-[13px] break-all",children:f})]}),h&&g.jsx("div",{className:"text-purple-400/60 font-mono text-[13px] break-all mt-0.5",children:h}),(s||o)&&g.jsxs("div",{className:"font-mono text-[13px] leading-relaxed mt-1.5",children:[m.slice(0,_).map((S,w)=>g.jsxs("div",{className:"text-red-400/60",children:[g.jsx("span",{className:"select-none text-red-400/30 mr-1",children:"-"}),S]},`o${w}`)),p.slice(0,N).map((S,w)=>g.jsxs("div",{className:"text-emerald-400/60",children:[g.jsx("span",{className:"select-none text-emerald-400/30 mr-1",children:"+"}),S]},`n${w}`)),x&&g.jsxs("div",{className:"text-[#444] mt-0.5",children:["... ",y-fu," more lines"]})]})]})}const hu=30,n7="*** Begin Patch",r7="*** End Patch",r_="*** Add File: ",i_="*** Update File: ",a_="*** Delete File: ",i7={add:"create",update:"edit",delete:"delete"};function a7(e){const t=e.patch;return typeof t=="string"?t:t&&typeof t=="object"&&typeof t.patch=="string"?t.patch:typeof e.input=="string"?e.input:""}function s7(e){const t=[];let r=null;const a=()=>{r&&t.push(r),r=null};for(const s of e.split(`
|
||||
`))if(!(s===n7||s===r7))if(s.startsWith(r_))a(),r={kind:"add",path:s.slice(r_.length).trim(),oldLines:[],newLines:[]};else if(s.startsWith(i_))a(),r={kind:"update",path:s.slice(i_.length).trim(),oldLines:[],newLines:[]};else if(s.startsWith(a_))a(),r={kind:"delete",path:s.slice(a_.length).trim(),oldLines:[],newLines:[]};else if((r==null?void 0:r.kind)==="update"){if(s.startsWith("@@"))continue;s.startsWith("-")&&!s.startsWith("---")?r.oldLines.push(s.slice(1)):s.startsWith("+")&&!s.startsWith("+++")&&r.newLines.push(s.slice(1))}else(r==null?void 0:r.kind)==="add"&&(s.startsWith("+")?r.newLines.push(s.slice(1)):s.trim()&&r.newLines.push(s));return a(),t}function l7({op:e}){const t=i7[e.kind]??"file",r=e.oldLines.length+e.newLines.length,a=r>hu,s=a&&r>0?Math.round(hu*(e.oldLines.length/r)):e.oldLines.length,o=a?hu-s:e.newLines.length;return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-baseline gap-2",children:[g.jsx("span",{className:"text-sky-400/80 font-semibold text-sm shrink-0",children:t}),e.path&&g.jsx("span",{className:"text-[#888] font-mono text-[13px] break-all",children:ug(e.path)})]}),(e.oldLines.length>0||e.newLines.length>0)&&g.jsxs("div",{className:"font-mono text-[13px] leading-relaxed mt-1.5",children:[e.oldLines.slice(0,s).map((c,d)=>g.jsxs("div",{className:"text-red-400/60",children:[g.jsx("span",{className:"select-none text-red-400/30 mr-1",children:"-"}),c]},`o${d}`)),e.newLines.slice(0,o).map((c,d)=>g.jsxs("div",{className:"text-emerald-400/60",children:[g.jsx("span",{className:"select-none text-emerald-400/30 mr-1",children:"+"}),c]},`n${d}`)),a&&g.jsxs("div",{className:"text-[#444] mt-0.5",children:["... ",r-hu," more lines"]})]})]})}function o7({args:e,result:t,status:r}){const a=s7(a7(e));return a.length===0?g.jsxs("div",{children:[g.jsx("span",{className:"text-sky-400/80 font-semibold text-sm",children:"patch"}),r==="failed"&&typeof t=="string"&&t.trim()&&g.jsx("div",{className:"text-red-400/70 text-[13px] mt-1",children:t.trim()})]}):g.jsxs("div",{className:"space-y-2",children:[a.map((s,o)=>g.jsx(l7,{op:s},o)),r==="failed"&&typeof t=="string"&&t.trim()&&g.jsx("div",{className:"text-red-400/70 text-[13px]",children:t.trim()})]})}function c7({args:e,result:t}){const r=(e.path??"").trim(),a=t;let s=null;if(typeof a=="string"){const o=a.trim();o&&!o.toLowerCase().startsWith("data:image/")&&!o.startsWith("{")&&(s=o)}return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-baseline gap-2",children:[g.jsx("span",{className:"text-sky-400/80 font-semibold text-sm shrink-0",children:"view image"}),r&&g.jsx("span",{className:"text-[#888] font-mono text-[13px] break-all",children:ug(r)})]}),s&&g.jsx("div",{className:"text-red-400/70 text-[13px] mt-1",children:s})]})}const u7={critical:"text-red-400",high:"text-orange-400",medium:"text-yellow-400",low:"text-blue-400",info:"text-cyan-400"};function d7({args:e,result:t}){const r=e.title??"",a=e.description??"",s=e.impact??"",o=e.target??"",c=e.endpoint??"",d=e.method??"",f=e.technical_analysis??"",h=e.poc_description??"",{language:m,code:p}=sE(e.poc_script_code??""),y=e.remediation_steps??"",x=e.cve??"",_=e.cwe??"",N=t,S=(N&&typeof N=="object"?N.severity:null)??e.severity??"medium",w=String(S).toLowerCase(),k=(N&&typeof N=="object"?N.cvss_score:null)??e.cvss??null,E=u7[w]??"text-yellow-400";return g.jsxs("div",{className:"space-y-3",children:[g.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[g.jsx("span",{className:`font-semibold text-sm ${E}`,children:w.toUpperCase()}),k!=null&&g.jsxs("span",{className:"text-[#888] text-[13px]",children:["CVSS ",k]}),x&&g.jsx("span",{className:"text-[#888] font-mono text-[13px]",children:x}),_&&g.jsx("span",{className:"text-[#888] font-mono text-[13px]",children:_})]}),r&&g.jsx("div",{className:"text-[15px] text-white/80 font-semibold",children:r}),(o||c)&&g.jsxs("div",{className:"text-[13px] text-[#888] font-mono",children:[o,c?` ${d} ${c}`:""]}),a&&g.jsx(zn,{text:a,maxLines:20}),s&&g.jsxs("div",{children:[g.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Impact"}),g.jsx("div",{className:"mt-1",children:g.jsx(zn,{text:s,maxLines:15})})]}),f&&g.jsxs("div",{children:[g.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Technical Analysis"}),g.jsx("div",{className:"mt-1",children:g.jsx(zn,{text:f,maxLines:20})})]}),(h||p)&&g.jsxs("div",{children:[g.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Proof of Concept"}),h&&g.jsx("div",{className:"mt-1",children:g.jsx($s,{text:h})}),p&&g.jsx(aE,{className:m?`language-${m}`:void 0,children:p})]}),y&&g.jsxs("div",{children:[g.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Remediation"}),g.jsx("div",{className:"mt-1",children:g.jsx(zn,{text:y,maxLines:15})})]})]})}const WN=200,JN={GET:"text-emerald-400/80",POST:"text-blue-400/80",PUT:"text-yellow-400/80",PATCH:"text-orange-400/80",DELETE:"text-red-400/80"};function dg(e
Line truncated
|
||||
`),a=r.slice(0,t).map(s=>Xr(s,WN-5)).join(`
|
||||
`);return r.length>t?a+`
|
||||
...`:a}function f7({args:e,result:t}){const r=e.httpql_filter??"",a=t,s=a?a.requests:null,o=Array.isArray(s)?s:[];return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"listing requests"}),r&&g.jsx("span",{className:"text-[#888] text-[13px]",children:Xr(r,150)})]}),o.length>0&&g.jsxs("div",{className:"mt-1.5 font-mono text-[13px] space-y-0.5",children:[o.slice(0,20).map((c,d)=>{const f=(c.method??"GET").toUpperCase(),h=c.host??"",m=c.path??"",p=c.response,y=(p==null?void 0:p.statusCode)??null;return g.jsxs("div",{className:"flex gap-2",children:[g.jsx("span",{className:`w-10 shrink-0 font-bold ${JN[f]??"text-[#888]"}`,children:f}),g.jsx("span",{className:"text-[#777] truncate",children:Xr(h+m,180)}),y!=null&&g.jsx("span",{className:`ml-auto shrink-0 ${dg(y)}`,children:y})]},d)}),o.length>20&&g.jsxs("div",{className:"text-[#555]",children:["... +",o.length-20," more"]})]})]})}function h7({args:e,result:t}){const r=e.request_id,a=e.part??"request",s=e.search_pattern??"",o=t,c=o?o.matches:null,d=Array.isArray(c)?c:[],f=o?o.content??null:null,h=o?!!o.has_more:!1;return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsxs("span",{className:"text-purple-400/80 font-semibold text-sm",children:[s?"searching":"viewing"," ",a]}),r!=null&&g.jsxs("span",{className:"text-[#888] text-[13px]",children:["#",r]}),s&&g.jsxs("span",{className:"text-[#666] font-mono text-[13px]",children:["/",Xr(s,100),"/"]})]}),d.length>0&&g.jsxs("div",{className:"mt-1.5 font-mono text-[13px] space-y-1",children:[d.slice(0,5).map((m,p)=>{const y=(m.before??"").replace(/\n/g," ").replace(/\r/g,"").slice(-100),x=(m.after??"").replace(/\n/g," ").replace(/\r/g,"").slice(0,100);return g.jsxs("div",{children:[y&&g.jsxs("span",{className:"text-[#555]",children:["...",y]}),g.jsx("span",{className:"text-amber-400/80 font-bold",children:m.match}),x&&g.jsxs("span",{className:"text-[#555]",children:[x,"..."]})]},p)}),d.length>5&&g.jsxs("div",{className:"text-[#555]",children:["... +",d.length-5," more matches"]})]}),f&&!d.length&&(()=>{const m=f.split(`
|
||||
`),p=m.slice(0,15).map(x=>Xr(x,WN)).join(`
|
||||
`),y=h||m.length>15;return g.jsx(wi,{className:"text-[#666]",children:p+(y?`
|
||||
... more content available`:"")})})()]})}function m7({args:e,result:t}){const r=(e.method??"GET").toUpperCase(),a=e.url??"",s=e.headers,o=e.body,c=typeof o=="string"?o:"",d=t,f=d?d.error??null:null,h=d?d.status_code??null:null,m=d?d.response_time_ms??null:null,p=d?d.body:null,y=typeof p=="string"?p:null;return g.jsxs("div",{children:[g.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"request"}),g.jsxs("div",{className:"mt-1.5 font-mono text-[13px] space-y-0.5",children:[g.jsxs("div",{children:[g.jsx("span",{className:"text-[#555] select-none mr-1",children:">>"}),g.jsx("span",{className:`font-bold ${JN[r]??"text-[#888]"}`,children:r}),g.jsx("span",{className:"text-[#888] ml-1 break-all",children:Xr(a,180)})]}),s&&typeof s=="object"&&Object.entries(s).slice(0,5).map(([x,_])=>g.jsxs("div",{className:"text-[#555] pl-5",children:[x,": ",mp(String(_),150)]},x))]}),c&&g.jsx(wi,{className:"text-[#888]",children:pp(c,4)}),f&&g.jsx("div",{className:"text-red-400/70 text-[13px] mt-1.5",children:mp(f,150)}),h!=null&&g.jsxs("div",{className:"font-mono text-[13px] mt-1.5",children:[g.jsx("span",{className:"text-[#555] select-none mr-1",children:"<<"}),g.jsx("span",{className:`font-bold ${dg(h)}`,children:h}),m!=null&&g.jsxs("span",{className:"text-[#555] ml-2",children:[m,"ms"]})]}),y&&g.jsx(wi,{className:"text-[#666]",children:pp(y,6)})]})}function p7({args:e,result:t}){const r=e.request_id,a=e.modifications,s=t,o=s?s.status_code??null:null,c=s?s.response_time_ms??null:null,d=s?s.body:null,f=typeof d=="string"?d:null;return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"repeating request"}),r!=null&&g.jsxs("span",{className:"text-[#888] text-[13px]",children:["#",r]})]}),a&&typeof a=="object"&&Object.keys(a).length>0&&g.jsx("div",{className:"mt-1.5 font-mono text-[13px] space-y-0.5",children:Object.entries(a).slice(0,5).map(([h,m])=>g.jsxs("div",{children:[g.jsxs("span",{className:"text-orange-400/60",children:[h,":"]})," ",g.jsx("span",{className:"text-[#777]",children:mp(typeof m=="string"?m:JSON.stringify(m),150)})]},h))}),o!=null&&g.jsxs("div",{className:"font-mono text-[13px] mt-1.5",children:[g.jsx("span",{className:"text-[#555] select-none mr-1",children:"<<"}),g.jsx("span",{className:`font-bold ${dg(o)}`,children:o}),c!=null&&g.jsxs("span",{className:"text-[#555] ml-2",children:[c,"ms"]})]}),f&&g.jsx(wi,{className:"text-[#666]",children:pp(f,5)})]})}const g7={get:"getting",list:"listing",create:"creating",update:"updating",delete:"deleting"};function b7({args:e}){const t=e.action??"",r=e.scope_name??"",a=g7[t]??(t||"managing");return g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsxs("span",{className:"text-purple-400/80 font-semibold text-sm",children:[a," proxy scope"]}),r&&g.jsx("span",{className:"text-[#888] text-[13px]",children:Xr(r,50)})]})}function x7({args:e}){const t=e.parent_id;return g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"listing sitemap"}),t&&g.jsxs("span",{className:"text-[#888] text-[13px]",children:["under #",Xr(String(t),20)]})]})}function y7({args:e}){const t=e.entry_id;return g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"viewing sitemap entry"}),t&&g.jsxs("span",{className:"text-[#888] text-[13px]",children:["#",Xr(String(t),20)]})]})}function v7(e){switch(e.toolName){case"list_requests":return g.jsx(f7,{...e});case"view_request":return g.jsx(h7,{...e});case"send_request":return g.jsx(m7,{...e});case"repeat_request":return g.jsx(p7,{...e});case"scope_rules":return g.jsx(b7,{...e});case"list_sitemap":return g.jsx(x7,{...e});case"view_sitemap_entry":return g.jsx(y7,{...e});default:return g.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:e.toolName.replace(/_/g," ")})}}function _7({args:e}){const t=e.thought??e.content??"";return t?g.jsxs("div",{children:[g.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"Agent is thinking"}),g.jsx("div",{className:"mt-1.5 italic text-[#888]",children:g.jsx(zn,{text:t,maxLines:20})})]}):null}function w7({toolName:e,args:t}){if(e==="create_agent"){const r=t.name??t.agent_name??"",a=t.task??"";return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx("span",{className:"text-cyan-400/80 font-semibold text-sm",children:"spawning"}),r&&g.jsx("span",{className:"text-cyan-400 font-semibold text-sm",children:r})]}),a&&g.jsx("div",{className:"mt-1.5",children:g.jsx(zn,{text:a,maxLines:15})})]})}if(e==="agent_finish"){const r=t.result_summary??"",a=t.success,s=t.findings,o=Array.isArray(s)?s:void 0;return g.jsxs("div",{children:[g.jsx("span",{className:`font-semibold text-sm ${a===!1?"text-red-400/80":"text-emerald-400/80"}`,children:a===!1?"A
Line truncated
|
||||
`);if(t.length<=N7)return t.map(Rm).join(`
|
||||
`);const r=t.length-l_-o_;return[...t.slice(0,l_).map(Rm),`... ${r} lines truncated ...`,...t.slice(-o_).map(Rm)].join(`
|
||||
`)}function M7({args:e,result:t}){const r=e.action??"",a=e.code??e.script??"",s=t;let o=null;s&&typeof s=="object"?o=typeof s.stdout=="string"?s.stdout:null:typeof s=="string"&&(o=s);const c=r==="new_session"?"new session":r==="close"?"close session":r==="list_sessions"?"list sessions":null,d=o?A7(T7(o)):null;return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx("span",{className:"text-yellow-400/80 font-semibold text-sm",children:"Python"}),c&&g.jsx("span",{className:"text-[#888] text-[13px]",children:c})]}),a&&g.jsx(cg,{code:a,language:"python",collapsible:!0}),d&&g.jsx(wi,{className:"text-[#666]",children:d})]})}function O7({args:e}){const t=e.targets,a=(Array.isArray(t)?t:[]).map(s=>typeof s=="object"&&s?s.original??null:null).filter(Boolean);return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx("span",{className:"text-emerald-400/80 font-semibold text-sm",children:"Starting penetration test"}),a.length===1&&g.jsxs("span",{className:"text-[#888] text-[13px]",children:["on ",a[0]]})]}),a.length>1&&g.jsx("div",{className:"mt-1.5 space-y-0.5",children:a.map((s,o)=>g.jsxs("div",{className:"text-[13px] text-[#888]",children:[g.jsx("span",{className:"text-[#555] mr-1",children:"•"}),s]},o))})]})}function R7({args:e}){const t=e.name??"Unknown Agent",r=e.task??"";return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx("span",{className:"text-[#888] text-[13px]",children:"subagent"}),g.jsx("span",{className:"text-purple-400 font-semibold text-sm",children:t})]}),r&&g.jsx("div",{className:"mt-1.5",children:g.jsx(zn,{text:r,maxLines:15})})]})}function D7(e){return e.toolName==="subagent_start_info"?g.jsx(R7,{...e}):g.jsx(O7,{...e})}function j7({args:e}){const t=e.executive_summary??"",r=e.methodology??"",a=e.technical_analysis??"",s=e.recommendations??"";return g.jsxs("div",{className:"space-y-3",children:[g.jsx("span",{className:"text-emerald-400/80 font-semibold text-sm",children:"Penetration test completed"}),t&&g.jsxs("div",{children:[g.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Executive Summary"}),g.jsx("div",{className:"mt-1",children:g.jsx(zn,{text:t,maxLines:25})})]}),r&&g.jsxs("div",{children:[g.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Methodology"}),g.jsx("div",{className:"mt-1",children:g.jsx(zn,{text:r,maxLines:25})})]}),a&&g.jsxs("div",{children:[g.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Technical Analysis"}),g.jsx("div",{className:"mt-1",children:g.jsx(zn,{text:a,maxLines:25})})]}),s&&g.jsxs("div",{children:[g.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Recommendations"}),g.jsx("div",{className:"mt-1",children:g.jsx(zn,{text:s,maxLines:25})})]}),!t&&!r&&!a&&!s&&g.jsx("div",{className:"text-[#555] text-xs",children:"Generating final report..."})]})}function L7({toolName:e,args:t,result:r}){if(e==="create_note"){const a=t.title??"",s=t.content??"",o=t.category??"general";return g.jsxs("div",{children:[g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx("span",{className:"text-amber-400/80 font-semibold text-sm",children:"note"}),g.jsxs("span",{className:"text-[#555] text-[13px]",children:["(",o,")"]})]}),a&&g.jsx("div",{className:"mt-1.5 text-[#999] text-[13px]",children:a}),s&&g.jsx("div",{className:"mt-1",children:g.jsx($s,{text:s})})]})}if(e==="delete_note")return g.jsx("span",{className:"text-amber-400/80 font-semibold text-sm",children:"note removed"});if(e==="update_note"){const a=t.title??"",s=t.content??"";return g.jsxs("div",{children:[g.jsx("span",{className:"text-amber-400/80 font-semibold text-sm",children:"note updated"}),a&&g.jsx("div",{className:"mt-1.5 text-[#999] text-[13px]",children:a}),s&&g.jsx("div",{className:"mt-1",children:g.jsx($s,{text:s})})]})}if(e==="get_note"){const a=r,s=a&&typeof a=="object"&&a.success?a.note:void 0;return g.jsxs("div",{children:[g.jsx("span",{className:"text-amber-400/80 font-semibold text-sm",children:"note read"}),s&&g.jsxs(g.Fragment,{children:[g.jsxs("div",{className:"mt-1.5 text-[#999] text-[13px]",children:[s.title??"(untitled)",g.jsxs("span",{className:"text-[#555] ml-1",children:["(",s.category??"general",")"]})]}),s.content&&g.jsx("div",{className:"mt-1",children:g.jsx($s,{text:s.content})})]})]})}if(e==="list_notes"){const a=r;let s=[];if(a&&typeof a=="object"&&a.success){const o=a.notes;s=Array.isArray(o)?o:[]}return g.jsxs("div",{children:[g.jsx("span",{className:"text-amber-400/80 font-semibold text-sm",children:"notes"}),s.length>0?g.jsx("div",{className:"mt-1.5 space-y-0.5",children:s.map((o,c)=>g.jsxs("div",{className:"text-[13px]",children:[g.jsx("span",{className:"text-[#555] mr-1",children:"-"}),g.jsx("span",{className:"text-[#999]",children:o.title??"(untitled)"}),g.jsxs("span",{className:"text-[#555] ml-1",children:["(",o.category??"general",")"]}),o.content
Line truncated
|
||||
`)){const s=a.match(/^#{1,6}\s+(.*)$/);if(s){const o=s[1].trim().toLowerCase();if(o===r)continue;r=o}else a.trim()!==""&&(r=null);t.push(a)}return t.join(`
|
||||
`)}function dH({onOpenEmail:e}){return g.jsx("button",{onClick:e,className:"group w-full cursor-pointer rounded-xl border border-emerald-500/25 bg-emerald-500/[0.06] p-4 text-left transition-colors hover:border-emerald-500/40",children:g.jsxs("div",{className:"flex items-center gap-3",children:[g.jsx("div",{className:"flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg",style:{border:"1px solid rgba(16,185,129,0.3)",background:"rgba(16,185,129,0.08)"},children:g.jsx(bp,{className:"h-4 w-4 text-emerald-400","aria-hidden":"true"})}),g.jsxs("div",{className:"min-w-0 flex-1",children:[g.jsx("p",{className:"text-sm font-semibold text-white",children:"Email an encrypted PDF report of this run"}),g.jsx("p",{className:"mt-0.5 text-xs text-[#888]",children:"Encrypted with a key only you can see, email verified with a one-time code before sending."})]}),g.jsx("span",{className:"flex-shrink-0 rounded-lg bg-white px-3 py-1.5 text-xs font-semibold text-black transition-opacity group-hover:opacity-90",children:"Export report to PDF"})]})})}function fH({summary:e,counts:t,total:r,reportMarkdown:a,raw:s,finished:o,onOpenEmail:c}){const d=[["Executive Summary",e.executiveSummary],["Technical Analysis",e.technicalAnalysis],["Methodology",e.methodology],["Recommendations",e.recommendations]].filter(([,f])=>!!f).map(([f,h])=>({title:f,content:cH(h)}));return g.jsxs("div",{className:"space-y-6",children:[g.jsx("div",{className:"animate-card-in",children:g.jsx(ZU,{raw:s,durationSeconds:e.durationSeconds})}),r>0&&g.jsx("div",{className:"animate-card-in rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5",children:g.jsx(ej,{findings:{total:r,...t}})}),o&&g.jsx("div",{className:"animate-card-in",children:g.jsx(dH,{onOpenEmail:c})}),d.length>0?g.jsx("div",{className:"animate-card-in rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5 space-y-8",children:d.map(f=>g.jsx(oa,{title:f.title,content:f.content},f.title))}):a?g.jsx("div",{className:"animate-card-in rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5",children:g.jsx(oa,{content:uH(a)})}):r===0&&g.jsx("p",{className:"text-sm text-[#888]",children:"No summary available for this run yet."})]})}function zm({active:e,onClick:t,children:r}){return g.jsxs("button",{onClick:t,className:`cursor-pointer relative pb-2.5 text-sm font-semibold transition-colors ${e?"text-white":"text-[#666] hover:text-white"}`,children:[r,e&&g.jsx("span",{className:"absolute bottom-0 inset-x-0 h-0.5 bg-white rounded-full"})]})}function hH({run:e,canSteer:t}){const{agents:r,events:a}=e.transcript,s=ee.useMemo(()=>nU(r,a),[r,a]),[o,c]=ee.useState(null),d=o?r.find(h=>h.id===o)??null:null,f=t&&!e.finished;return g.jsxs("div",{className:"space-y-5",children:[g.jsxs("div",{className:"rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5",children:[g.jsxs("div",{className:"flex items-center gap-2",children:[g.jsx(Ao,{className:"w-4 h-4 text-[#888]","aria-hidden":"true"}),g.jsx("h2",{className:"text-sm font-semibold text-white",children:"Agent graph"}),g.jsxs("span",{className:"text-xs text-[#666]",children:[r.length," agent",r.length===1?"":"s"]})]}),g.jsx("p",{className:"mt-1 mb-4 text-xs text-[#666]",children:"Click an agent to open its full transcript."}),g.jsx("div",{className:"h-[480px] rounded-lg border border-[#1a1a1a] overflow-hidden",children:g.jsx(PB,{agents:s,selectedAgentId:o,onSelectAgent:h=>c(h),eventsLoaded:!0,eventsEmpty:s.size===0,scanCompleted:e.finished})})]}),f&&g.jsx(uS,{agents:r}),g.jsxs("div",{className:"rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5",children:[g.jsx("p",{className:"text-sm font-semibold text-white",children:"Run this pentest with more depth"}),g.jsx("p",{className:"mt-0.5 text-xs text-[#666]",children:"Re-run this pentest on managed infra in the cloud."}),g.jsx("div",{className:"mt-3 flex flex-wrap gap-2.5",children:g.jsx(mS,{label:"Re-run in Strix Pro with more depth",desc:"Run this pentest on managed infra with more depth.",slug:"live_scan",surface:"agents",icon:ZC})})]}),g.jsx(EU,{open:d!==null,agent:d,events:a,steerable:f,onClose:()=>c(null)})]})}Sk.createRoot(document.getElementById("root")).render(g.jsx(ee.StrictMode,{children:g.jsx(rH,{})}));
|
||||
@@ -0,0 +1,10 @@
|
||||
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||
Theme: GitHub Dark
|
||||
Description: Dark theme as seen on github.com
|
||||
Author: github.com
|
||||
Maintainer: @Hirse
|
||||
Updated: 2021-05-15
|
||||
|
||||
Outdated base version: https://github.com/primer/github-syntax-dark
|
||||
Current colors taken from GitHub's CSS
|
||||
*/.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id{color:#79c0ff}.hljs-regexp,.hljs-string,.hljs-meta .hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-comment,.hljs-code,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c}.react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #555;--xy-background-pattern-lines-color-default: #333;--xy-background-pattern-cross-color-default: #333;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1;touch-action:none}.react-flow__panLine truncated
|
||||
@@ -6,8 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="color-scheme" content="dark" />
|
||||
<title>Strix Results</title>
|
||||
<script type="module" crossorigin src="./assets/index-BU_tk5L-.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-C0NveaV7.css">
|
||||
<script type="module" crossorigin src="./assets/index-Dd1cyttN.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-vV8wxCG6.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from types import SimpleNamespace
|
||||
from typing import Any, cast
|
||||
|
||||
import pytest
|
||||
from agents.tool import FunctionTool
|
||||
from agents.tool import CustomTool, FunctionTool
|
||||
|
||||
from strix.agents import factory
|
||||
from strix.config import load_settings
|
||||
|
||||
|
||||
def _capturing_exec_tool(captured: dict[str, str]) -> FunctionTool:
|
||||
@@ -32,10 +34,37 @@ async def test_wrap_exec_command_defaults_shell_to_bash() -> None:
|
||||
result = await wrapped.on_invoke_tool(cast("Any", None), json.dumps({"cmd": "source /tmp/env"}))
|
||||
|
||||
assert result == "ok"
|
||||
assert json.loads(captured["raw_input"]) == {
|
||||
"cmd": "source /tmp/env",
|
||||
"shell": "bash",
|
||||
}
|
||||
parsed = json.loads(captured["raw_input"])
|
||||
assert parsed["cmd"] == "source /tmp/env"
|
||||
assert parsed["shell"] == "bash"
|
||||
expected_cap = load_settings().context.tool_output_max_tokens
|
||||
assert parsed["max_output_tokens"] == expected_cap
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wrap_exec_command_preserves_smaller_explicit_output_cap() -> None:
|
||||
captured: dict[str, str] = {}
|
||||
wrapped = factory._wrap_exec_command(_capturing_exec_tool(captured))
|
||||
|
||||
await wrapped.on_invoke_tool(
|
||||
cast("Any", None), json.dumps({"cmd": "echo hi", "max_output_tokens": 42})
|
||||
)
|
||||
|
||||
assert json.loads(captured["raw_input"])["max_output_tokens"] == 42
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wrap_exec_command_clamps_oversized_explicit_output_cap() -> None:
|
||||
captured: dict[str, str] = {}
|
||||
wrapped = factory._wrap_exec_command(_capturing_exec_tool(captured))
|
||||
ceiling = load_settings().context.tool_output_max_tokens
|
||||
|
||||
await wrapped.on_invoke_tool(
|
||||
cast("Any", None),
|
||||
json.dumps({"cmd": "echo hi", "max_output_tokens": ceiling * 100}),
|
||||
)
|
||||
|
||||
assert json.loads(captured["raw_input"])["max_output_tokens"] == ceiling
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -49,3 +78,33 @@ async def test_wrap_exec_command_preserves_explicit_shell(shell: str) -> None:
|
||||
)
|
||||
|
||||
assert json.loads(captured["raw_input"])["shell"] == shell
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_responses_filesystem_custom_tool_output_is_bounded() -> None:
|
||||
async def invoke(_ctx: Any, _inp: str) -> str:
|
||||
return "line\n" * 50_000
|
||||
|
||||
toolset = SimpleNamespace(
|
||||
read_file=CustomTool(name="read_file", description="read", on_invoke_tool=invoke)
|
||||
)
|
||||
factory._configure_filesystem_tools(toolset, chat_completions=False)
|
||||
|
||||
assert isinstance(toolset.read_file, CustomTool)
|
||||
result = await toolset.read_file.on_invoke_tool(cast("Any", None), "{}")
|
||||
|
||||
assert "truncated" in result
|
||||
assert len(result) < len("line\n" * 50_000)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chat_completions_filesystem_custom_tool_becomes_function_tool() -> None:
|
||||
async def invoke(_ctx: Any, _inp: str) -> str:
|
||||
return "ok"
|
||||
|
||||
toolset = SimpleNamespace(
|
||||
read_file=CustomTool(name="read_file", description="read", on_invoke_tool=invoke)
|
||||
)
|
||||
factory._configure_filesystem_tools(toolset, chat_completions=True)
|
||||
|
||||
assert isinstance(toolset.read_file, FunctionTool)
|
||||
@@ -0,0 +1,106 @@
|
||||
"""Tests for the `strix auth` CLI: subcommand routing and provider naming."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import pytest
|
||||
|
||||
from strix.config import codex
|
||||
from strix.interface import auth_cli
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _tmp_store(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(codex, "AUTH_PATH", tmp_path / "home" / ".strix" / "subscription-auth.json")
|
||||
|
||||
|
||||
def test_login_provider_is_chatgpt() -> None:
|
||||
assert auth_cli.LOGIN_PROVIDER == "chatgpt"
|
||||
assert codex.PROVIDER in auth_cli._ACCEPTED_PROVIDERS
|
||||
assert "chatgpt" in auth_cli._ACCEPTED_PROVIDERS
|
||||
|
||||
|
||||
def test_unknown_subcommand_returns_usage_error() -> None:
|
||||
assert auth_cli.run_auth(["bogus"]) == 2
|
||||
|
||||
|
||||
def test_help_returns_zero() -> None:
|
||||
assert auth_cli.run_auth(["--help"]) == 0
|
||||
|
||||
|
||||
def test_status_not_signed_in() -> None:
|
||||
assert auth_cli.run_auth(["status"]) == 1
|
||||
|
||||
|
||||
def test_login_rejects_unsupported_provider(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
def _should_not_run(*_args: Any, **_kwargs: Any) -> dict[str, Any]:
|
||||
msg = "OAuth flow must not start for an unsupported provider"
|
||||
raise AssertionError(msg)
|
||||
|
||||
monkeypatch.setattr(auth_cli, "_run_oauth_flow", _should_not_run)
|
||||
assert auth_cli.run_auth(["login", "gemini"]) == 2
|
||||
|
||||
|
||||
def test_finish_requires_state_on_loopback(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(codex, "exchange_code", lambda *_: {"ok": True})
|
||||
|
||||
# Loopback (require_state=True): missing or mismatched state is rejected.
|
||||
with pytest.raises(codex.CodexAuthError) as missing:
|
||||
auth_cli._finish("code", None, "verifier", "expected", require_state=True)
|
||||
assert missing.value.code == "state_mismatch"
|
||||
with pytest.raises(codex.CodexAuthError) as mismatch:
|
||||
auth_cli._finish("code", "wrong", "verifier", "expected", require_state=True)
|
||||
assert mismatch.value.code == "state_mismatch"
|
||||
|
||||
# Matching state proceeds to the exchange.
|
||||
assert auth_cli._finish("code", "expected", "verifier", "expected", require_state=True) == {
|
||||
"ok": True
|
||||
}
|
||||
|
||||
|
||||
def test_finish_manual_paste_allows_absent_state(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(codex, "exchange_code", lambda *_: {"ok": True})
|
||||
# Manual paste (require_state=False): a bare code with no state is accepted,
|
||||
# but a present-and-wrong state is still rejected.
|
||||
assert auth_cli._finish("code", None, "verifier", "expected", require_state=False) == {
|
||||
"ok": True
|
||||
}
|
||||
with pytest.raises(codex.CodexAuthError):
|
||||
auth_cli._finish("code", "wrong", "verifier", "expected", require_state=False)
|
||||
|
||||
|
||||
def test_finish_rejects_missing_code() -> None:
|
||||
with pytest.raises(codex.CodexAuthError) as exc:
|
||||
auth_cli._finish(None, "expected", "verifier", "expected", require_state=True)
|
||||
assert exc.value.code == "no_code"
|
||||
|
||||
|
||||
def test_model_subcommand_removed() -> None:
|
||||
assert auth_cli.run_auth(["model", "gpt-5.5"]) == 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("provider", ["chatgpt", "codex", "ChatGPT"])
|
||||
def test_login_accepts_provider_aliases(provider: str, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
reached = {"flow": False}
|
||||
|
||||
def _fake_flow(*_args: Any, **_kwargs: Any) -> dict[str, Any]:
|
||||
reached["flow"] = True
|
||||
return {
|
||||
"type": "oauth",
|
||||
"provider": "codex",
|
||||
"access": "a",
|
||||
"refresh": "r",
|
||||
"account_id": "acct",
|
||||
"expires_at": 0,
|
||||
}
|
||||
|
||||
monkeypatch.setattr(auth_cli, "_run_oauth_flow", _fake_flow)
|
||||
monkeypatch.setattr(codex, "save_record", lambda _record: None)
|
||||
|
||||
assert auth_cli.run_auth(["login", provider]) == 0
|
||||
assert reached["flow"] is True
|
||||
@@ -0,0 +1,294 @@
|
||||
"""Tests for ChatGPT (Codex) subscription auth: PKCE, token handling, store."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import pytest
|
||||
|
||||
from strix.config import codex
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _fake_jwt(account_id: str) -> str:
|
||||
def seg(obj: dict[str, Any]) -> str:
|
||||
return base64.urlsafe_b64encode(json.dumps(obj).encode()).rstrip(b"=").decode()
|
||||
|
||||
header = seg({"alg": "none"})
|
||||
payload = seg({"https://api.openai.com/auth": {"chatgpt_account_id": account_id}})
|
||||
return f"{header}.{payload}.sig"
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _tmp_store(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
path = tmp_path / "home" / ".strix" / "subscription-auth.json"
|
||||
monkeypatch.setattr(codex, "AUTH_PATH", path)
|
||||
return path
|
||||
|
||||
|
||||
def test_pkce_challenge_matches_verifier_and_is_unpadded() -> None:
|
||||
verifier, challenge = codex.generate_pkce()
|
||||
expected = (
|
||||
base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode()
|
||||
)
|
||||
assert challenge == expected
|
||||
assert "=" not in verifier
|
||||
assert "=" not in challenge
|
||||
|
||||
|
||||
def test_authorize_url_carries_pkce_and_client() -> None:
|
||||
url = codex.build_authorize_url("chal", "st8")
|
||||
assert codex.AUTHORIZE_URL in url
|
||||
assert "code_challenge=chal" in url
|
||||
assert "code_challenge_method=S256" in url
|
||||
assert f"client_id={codex.CLIENT_ID}" in url
|
||||
assert "state=st8" in url
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("value", "expected"),
|
||||
[
|
||||
("http://localhost:1455/auth/callback?code=AAA&state=BBB", ("AAA", "BBB")),
|
||||
("AAA#BBB", ("AAA", "BBB")),
|
||||
("code=AAA&state=BBB", ("AAA", "BBB")),
|
||||
("AAA", ("AAA", None)),
|
||||
("", (None, None)),
|
||||
],
|
||||
)
|
||||
def test_parse_redirect_input(value: str, expected: tuple[str | None, str | None]) -> None:
|
||||
assert codex.parse_redirect_input(value) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("model", "expected"),
|
||||
[
|
||||
("chatgpt/gpt-5.4", "gpt-5.4"),
|
||||
("ChatGPT/GPT-5.5", "GPT-5.5"),
|
||||
(" chatgpt/gpt-5.4 ", "gpt-5.4"),
|
||||
("openai/gpt-5.4", None), # metered API path
|
||||
("anthropic/claude-opus-4-8", None),
|
||||
("gpt-5.4", None),
|
||||
("chatgpt/", None),
|
||||
("", None),
|
||||
(None, None),
|
||||
],
|
||||
)
|
||||
def test_subscription_model(model: str | None, expected: str | None) -> None:
|
||||
assert codex.subscription_model(model) == expected
|
||||
|
||||
|
||||
def test_auth_mode() -> None:
|
||||
assert codex.auth_mode("chatgpt/gpt-5.4") == "subscription"
|
||||
assert codex.auth_mode("openai/gpt-5.4") == "api_key"
|
||||
assert codex.auth_mode("anthropic/claude-opus-4-8") == "api_key"
|
||||
assert codex.auth_mode(None) == "api_key"
|
||||
|
||||
|
||||
def test_is_content_guardrail_error() -> None:
|
||||
# The backend's real wording (from a live gpt-5.6-sol block).
|
||||
raw = RuntimeError(
|
||||
"This content was flagged for possible cybersecurity risk. If this seems "
|
||||
"wrong, try rephrasing. To get authorized, join the Trusted Access for Cyber program."
|
||||
)
|
||||
assert codex.is_content_guardrail_error(raw) is True
|
||||
# The already-typed error is recognized regardless of its message wording.
|
||||
assert codex.is_content_guardrail_error(codex.CodexContentGuardrailError("gpt-5.6-sol")) is True
|
||||
# Unrelated errors are not misclassified.
|
||||
assert codex.is_content_guardrail_error(RuntimeError("rate limit exceeded")) is False
|
||||
|
||||
|
||||
def test_content_guardrail_error_message() -> None:
|
||||
err = codex.CodexContentGuardrailError("gpt-5.6-sol")
|
||||
assert err.model == "gpt-5.6-sol"
|
||||
assert "gpt-5.6-sol" in str(err)
|
||||
assert "STRIX_LLM" in str(err)
|
||||
|
||||
|
||||
def test_account_id_from_jwt() -> None:
|
||||
assert codex._account_id_from_jwt(_fake_jwt("acct-42")) == "acct-42"
|
||||
assert codex._account_id_from_jwt("not-a-jwt") is None
|
||||
assert codex._account_id_from_jwt("") is None
|
||||
|
||||
|
||||
def test_store_roundtrip_and_logout() -> None:
|
||||
assert codex.read_record() is None
|
||||
assert codex.is_authenticated() is False
|
||||
|
||||
codex.save_record(
|
||||
{
|
||||
"type": "oauth",
|
||||
"provider": "codex",
|
||||
"access": _fake_jwt("acct-42"),
|
||||
"refresh": "r1",
|
||||
"account_id": "acct-42",
|
||||
"expires_at": time.time() + 3600,
|
||||
}
|
||||
)
|
||||
record = codex.read_record()
|
||||
assert record is not None
|
||||
assert record["account_id"] == "acct-42"
|
||||
assert codex.is_authenticated() is True
|
||||
|
||||
codex.logout()
|
||||
assert codex.read_record() is None
|
||||
codex.logout() # no-op when already gone
|
||||
|
||||
|
||||
def test_read_record_rejects_incomplete_records() -> None:
|
||||
codex.save_record({"type": "oauth", "access": "a"}) # missing refresh/account
|
||||
assert codex.read_record() is None
|
||||
assert codex.is_authenticated() is False
|
||||
|
||||
|
||||
def test_get_valid_token_returns_stored_when_fresh(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
def _boom(_payload: dict[str, str]) -> dict[str, Any]:
|
||||
msg = "should not refresh a fresh token"
|
||||
raise AssertionError(msg)
|
||||
|
||||
monkeypatch.setattr(codex, "_post_form", _boom)
|
||||
codex.save_record(
|
||||
{
|
||||
"type": "oauth",
|
||||
"provider": "codex",
|
||||
"access": "access-fresh",
|
||||
"refresh": "r1",
|
||||
"account_id": "acct-42",
|
||||
"expires_at": time.time() + 3600,
|
||||
}
|
||||
)
|
||||
assert codex.get_valid_token() == ("access-fresh", "acct-42")
|
||||
|
||||
|
||||
def test_get_valid_token_refreshes_and_persists_rotation(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
calls = {"n": 0}
|
||||
|
||||
def _fake_post(payload: dict[str, str]) -> dict[str, Any]:
|
||||
calls["n"] += 1
|
||||
assert payload["grant_type"] == "refresh_token"
|
||||
assert payload["refresh_token"] == "r1"
|
||||
return {"access_token": _fake_jwt("acct-42"), "refresh_token": "r2", "expires_in": 3600}
|
||||
|
||||
monkeypatch.setattr(codex, "_post_form", _fake_post)
|
||||
codex.save_record(
|
||||
{
|
||||
"type": "oauth",
|
||||
"provider": "codex",
|
||||
"access": "stale",
|
||||
"refresh": "r1",
|
||||
"account_id": "acct-42",
|
||||
"expires_at": time.time() - 10, # already expired
|
||||
}
|
||||
)
|
||||
_access, account_id = codex.get_valid_token()
|
||||
assert calls["n"] == 1
|
||||
assert account_id == "acct-42"
|
||||
# Rotated refresh token was written back to the store.
|
||||
record = codex.read_record()
|
||||
assert record is not None
|
||||
assert record["refresh"] == "r2"
|
||||
|
||||
|
||||
def test_get_valid_token_uses_token_rotated_by_another_process(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
# Simulate a parallel Strix process rotating the token while we wait for the
|
||||
# refresh guard: the pre-guard read sees the stale token, the in-guard read
|
||||
# sees the winner's fresh one, so we must NOT exchange the now-dead refresh.
|
||||
records = [
|
||||
{
|
||||
"type": "oauth",
|
||||
"provider": "codex",
|
||||
"access": "stale",
|
||||
"refresh": "r1",
|
||||
"account_id": "acct",
|
||||
"expires_at": time.time() - 10,
|
||||
},
|
||||
{
|
||||
"type": "oauth",
|
||||
"provider": "codex",
|
||||
"access": "fresh-from-other-process",
|
||||
"refresh": "r2",
|
||||
"account_id": "acct",
|
||||
"expires_at": time.time() + 3600,
|
||||
},
|
||||
]
|
||||
calls = {"n": 0}
|
||||
|
||||
def _fake_read() -> dict[str, Any]:
|
||||
record = records[min(calls["n"], len(records) - 1)]
|
||||
calls["n"] += 1
|
||||
return record
|
||||
|
||||
def _boom(_payload: dict[str, str]) -> dict[str, Any]:
|
||||
msg = "must not refresh a token another process already rotated"
|
||||
raise AssertionError(msg)
|
||||
|
||||
monkeypatch.setattr(codex, "read_record", _fake_read)
|
||||
monkeypatch.setattr(codex, "_post_form", _boom)
|
||||
|
||||
access, account_id = codex.get_valid_token()
|
||||
assert access == "fresh-from-other-process"
|
||||
assert account_id == "acct"
|
||||
|
||||
|
||||
def _expired_record(refresh: str, access: str) -> dict[str, Any]:
|
||||
return {
|
||||
"type": "oauth",
|
||||
"provider": "codex",
|
||||
"access": access,
|
||||
"refresh": refresh,
|
||||
"account_id": "acct-42",
|
||||
"expires_at": time.time() - 10,
|
||||
}
|
||||
|
||||
|
||||
def test_get_valid_token_recovers_when_refresh_loses_race(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
# Lock failed open: our in-guard read still saw the stale token, so we tried to
|
||||
# refresh and lost the race (invalid_grant). By then a peer has saved a fresh
|
||||
# token — recover from it instead of failing the scan on the dead one.
|
||||
codex.save_record(_expired_record("r1", "stale"))
|
||||
|
||||
def _fake_post(_payload: dict[str, str]) -> dict[str, Any]:
|
||||
codex.save_record(
|
||||
{
|
||||
"type": "oauth",
|
||||
"provider": "codex",
|
||||
"access": "fresh-from-peer",
|
||||
"refresh": "r2",
|
||||
"account_id": "acct-42",
|
||||
"expires_at": time.time() + 3600,
|
||||
}
|
||||
)
|
||||
raise codex.CodexAuthError("token_http_error", "HTTP 400: invalid_grant")
|
||||
|
||||
monkeypatch.setattr(codex, "_post_form", _fake_post)
|
||||
assert codex.get_valid_token() == ("fresh-from-peer", "acct-42")
|
||||
|
||||
|
||||
def test_get_valid_token_reraises_refresh_error_without_rotation(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
# Refresh fails and no peer rotated the token: surface the error, don't mask it.
|
||||
codex.save_record(_expired_record("r1", "stale"))
|
||||
|
||||
def _fake_post(_payload: dict[str, str]) -> dict[str, Any]:
|
||||
raise codex.CodexAuthError("token_http_error", "HTTP 400: invalid_grant")
|
||||
|
||||
monkeypatch.setattr(codex, "_post_form", _fake_post)
|
||||
with pytest.raises(codex.CodexAuthError):
|
||||
codex.get_valid_token()
|
||||
|
||||
|
||||
def test_get_valid_token_raises_when_not_signed_in() -> None:
|
||||
with pytest.raises(codex.CodexAuthError) as exc:
|
||||
codex.get_valid_token()
|
||||
assert exc.value.code == "not_authenticated"
|
||||
@@ -0,0 +1,225 @@
|
||||
"""Regression test for the ChatGPT Codex backend's streaming requirement.
|
||||
|
||||
The backend rejects non-streamed requests with ``{"detail": "Stream must be set
|
||||
to true"}``. ``_CodexResponsesModel`` must therefore issue a streamed request
|
||||
even from the non-streaming ``get_response`` path and aggregate the events into
|
||||
a single response. A local server that mimics that behaviour proves the wrapper
|
||||
works where the stock responses model would fail.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import threading
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import pytest
|
||||
from agents.model_settings import ModelSettings
|
||||
from agents.models.interface import ModelTracing
|
||||
from agents.models.openai_responses import OpenAIResponsesModel
|
||||
from openai import AsyncOpenAI, BadRequestError
|
||||
from openai.types.responses import ResponseOutputMessage, ResponseOutputText
|
||||
|
||||
from strix.config import codex
|
||||
from strix.config.models import _CodexResponsesModel
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import AsyncIterator, Iterator
|
||||
|
||||
|
||||
def _response_payload() -> dict[str, Any]:
|
||||
return {
|
||||
"id": "resp_1",
|
||||
"object": "response",
|
||||
"created_at": 0,
|
||||
"status": "completed",
|
||||
"model": "gpt-5.5",
|
||||
"output": [
|
||||
{
|
||||
"type": "message",
|
||||
"id": "m1",
|
||||
"status": "completed",
|
||||
"role": "assistant",
|
||||
"content": [{"type": "output_text", "text": "OK", "annotations": []}],
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"input_tokens": 1,
|
||||
"output_tokens": 1,
|
||||
"total_tokens": 2,
|
||||
"input_tokens_details": {"cached_tokens": 0},
|
||||
"output_tokens_details": {"reasoning_tokens": 0},
|
||||
},
|
||||
"parallel_tool_calls": False,
|
||||
"tool_choice": "auto",
|
||||
"tools": [],
|
||||
"metadata": {},
|
||||
"temperature": 1.0,
|
||||
"top_p": 1.0,
|
||||
"error": None,
|
||||
"incomplete_details": None,
|
||||
"instructions": None,
|
||||
"max_output_tokens": None,
|
||||
}
|
||||
|
||||
|
||||
_CAPTURED: dict[str, Any] = {}
|
||||
|
||||
|
||||
class _Handler(BaseHTTPRequestHandler):
|
||||
def log_message(self, *args: Any) -> None:
|
||||
pass
|
||||
|
||||
def do_POST(self) -> None:
|
||||
length = int(self.headers.get("Content-Length", 0))
|
||||
body = json.loads(self.rfile.read(length) or b"{}")
|
||||
_CAPTURED.clear()
|
||||
_CAPTURED.update(body)
|
||||
if not body.get("stream"):
|
||||
payload = json.dumps({"detail": "Stream must be set to true"}).encode()
|
||||
self.send_response(400)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(payload)))
|
||||
self.end_headers()
|
||||
self.wfile.write(payload)
|
||||
return
|
||||
event = {
|
||||
"type": "response.completed",
|
||||
"sequence_number": 0,
|
||||
"response": _response_payload(),
|
||||
}
|
||||
frame = f"event: response.completed\ndata: {json.dumps(event)}\n\n".encode()
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/event-stream")
|
||||
self.end_headers()
|
||||
self.wfile.write(frame)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def backend_url() -> Iterator[str]:
|
||||
server = HTTPServer(("127.0.0.1", 0), _Handler)
|
||||
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
||||
thread.start()
|
||||
try:
|
||||
yield f"http://127.0.0.1:{server.server_address[1]}/backend-api/codex"
|
||||
finally:
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
|
||||
|
||||
def _client(base_url: str) -> AsyncOpenAI:
|
||||
return AsyncOpenAI(api_key="tok", base_url=base_url)
|
||||
|
||||
|
||||
def _call_kwargs() -> dict[str, Any]:
|
||||
return {
|
||||
"system_instructions": "s",
|
||||
"input": "hi",
|
||||
"model_settings": ModelSettings(
|
||||
store=False, response_include=["reasoning.encrypted_content"]
|
||||
),
|
||||
"tools": [],
|
||||
"output_schema": None,
|
||||
"handoffs": [],
|
||||
"tracing": ModelTracing.DISABLED,
|
||||
"previous_response_id": None,
|
||||
"conversation_id": None,
|
||||
"prompt": None,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stock_model_fails_on_non_streamed_backend(backend_url: str) -> None:
|
||||
model = OpenAIResponsesModel(model="gpt-5.5", openai_client=_client(backend_url))
|
||||
with pytest.raises(BadRequestError, match="Stream must be set to true"):
|
||||
await model.get_response(**_call_kwargs())
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_codex_model_streams_and_aggregates(backend_url: str) -> None:
|
||||
model = _CodexResponsesModel(model="gpt-5.5", openai_client=_client(backend_url))
|
||||
response = await model.get_response(**_call_kwargs())
|
||||
message = response.output[0]
|
||||
assert isinstance(message, ResponseOutputMessage)
|
||||
text = message.content[0]
|
||||
assert isinstance(text, ResponseOutputText)
|
||||
assert text.text == "OK"
|
||||
assert response.usage.total_tokens == 2
|
||||
|
||||
|
||||
class _TrackingStream:
|
||||
"""An async iterator that yields, then raises, and records if it was closed."""
|
||||
|
||||
def __init__(self, events: list[Any], error: Exception | None) -> None:
|
||||
self._events = iter(events)
|
||||
self._error = error
|
||||
self.closed = False
|
||||
|
||||
def __aiter__(self) -> _TrackingStream:
|
||||
return self
|
||||
|
||||
async def __anext__(self) -> Any:
|
||||
try:
|
||||
return next(self._events)
|
||||
except StopIteration:
|
||||
if self._error is not None:
|
||||
raise self._error from None
|
||||
raise StopAsyncIteration from None
|
||||
|
||||
async def aclose(self) -> None:
|
||||
self.closed = True
|
||||
|
||||
|
||||
async def _drain(gen: AsyncIterator[Any]) -> list[Any]:
|
||||
return [event async for event in gen]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_guarded_converts_guardrail_error() -> None:
|
||||
# A mid-stream backend rejection becomes a typed, model-tagged error.
|
||||
model = _CodexResponsesModel(model="gpt-5.6-sol", openai_client=_client("http://x/backend-api"))
|
||||
guardrail = RuntimeError("This content was flagged for possible cybersecurity risk.")
|
||||
stream = _TrackingStream(["a", "b"], guardrail)
|
||||
with pytest.raises(codex.CodexContentGuardrailError) as exc_info:
|
||||
await _drain(model._guarded(stream))
|
||||
assert exc_info.value.model == "gpt-5.6-sol"
|
||||
assert stream.closed is True # underlying stream is released
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_guarded_passes_through_other_errors() -> None:
|
||||
# A non-guardrail error propagates unchanged (still not swallowed).
|
||||
model = _CodexResponsesModel(model="gpt-5.5", openai_client=_client("http://x/backend-api"))
|
||||
boom = RuntimeError("some unrelated failure")
|
||||
stream = _TrackingStream(["a"], boom)
|
||||
with pytest.raises(RuntimeError, match="some unrelated failure"):
|
||||
await _drain(model._guarded(stream))
|
||||
assert stream.closed is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_guarded_yields_all_events_when_clean() -> None:
|
||||
model = _CodexResponsesModel(model="gpt-5.4", openai_client=_client("http://x/backend-api"))
|
||||
stream = _TrackingStream(["a", "b", "c"], None)
|
||||
assert await _drain(model._guarded(stream)) == ["a", "b", "c"]
|
||||
assert stream.closed is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_codex_model_self_enforces_backend_requirements(backend_url: str) -> None:
|
||||
# The caller passes ordinary settings; the model must impose the backend's
|
||||
# requirements (stream, store=false, encrypted reasoning) and the configured
|
||||
# reasoning effort itself.
|
||||
model = _CodexResponsesModel(
|
||||
model="gpt-5.4", openai_client=_client(backend_url), reasoning_effort="high"
|
||||
)
|
||||
kwargs = _call_kwargs()
|
||||
kwargs["model_settings"] = ModelSettings() # nothing special from the caller
|
||||
await model.get_response(**kwargs)
|
||||
|
||||
assert _CAPTURED["stream"] is True
|
||||
assert _CAPTURED["store"] is False
|
||||
assert _CAPTURED["include"] == ["reasoning.encrypted_content"]
|
||||
assert _CAPTURED["reasoning"] == {"effort": "high"}
|
||||
@@ -6,10 +6,11 @@ import json
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
from pydantic import AliasChoices, Field
|
||||
from pydantic import AliasChoices, Field, ValidationError
|
||||
from pydantic.fields import FieldInfo
|
||||
|
||||
from strix.config import loader
|
||||
from strix.config.settings import ContextSettings
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -120,6 +121,15 @@ def test_read_json_overrides_uses_json_when_no_alias_in_environ(tmp_path: Path)
|
||||
assert loader._read_json_overrides(path) == {"llm": {"api_key": "sk-file"}}
|
||||
|
||||
|
||||
def test_tool_output_max_bytes_rejects_sub_notice_values() -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
ContextSettings(STRIX_TOOL_OUTPUT_MAX_BYTES=64)
|
||||
|
||||
|
||||
def test_tool_output_max_bytes_accepts_floor() -> None:
|
||||
assert ContextSettings(STRIX_TOOL_OUTPUT_MAX_BYTES=1024).tool_output_max_bytes == 1024
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# _aliases_for
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
"""Tests for the dedicated deduplication model configuration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from strix.config import loader
|
||||
from strix.config.settings import DedupeSettings
|
||||
from strix.report.dedupe import _dedupe_model_settings
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_dedupe_key_sent_per_call_not_via_global_env() -> None:
|
||||
dedupe = DedupeSettings(STRIX_DEDUPE_MODEL="deepseek/cheap", DEDUPE_LLM_API_KEY="dedupe-key")
|
||||
settings = _dedupe_model_settings(dedupe, "deepseek/cheap", 300)
|
||||
# The key rides on the request, so a shared-provider main key can't clobber
|
||||
# it (and vice versa) through the global provider env var.
|
||||
assert (settings.extra_args or {})["api_key"] == "dedupe-key"
|
||||
|
||||
|
||||
def test_dedupe_settings_omit_api_key_when_unset() -> None:
|
||||
dedupe = DedupeSettings(STRIX_DEDUPE_MODEL="deepseek/cheap")
|
||||
settings = _dedupe_model_settings(dedupe, "deepseek/cheap", 300)
|
||||
assert "api_key" not in (settings.extra_args or {})
|
||||
assert "api_base" not in (settings.extra_args or {})
|
||||
|
||||
|
||||
def test_dedupe_endpoint_sent_per_call() -> None:
|
||||
dedupe = DedupeSettings(
|
||||
STRIX_DEDUPE_MODEL="openai/cheap",
|
||||
DEDUPE_LLM_API_KEY="dedupe-key",
|
||||
DEDUPE_LLM_API_BASE="https://dedupe.example/v1",
|
||||
)
|
||||
settings = _dedupe_model_settings(dedupe, "openai/cheap", 300)
|
||||
# A distinct dedupe endpoint rides on the request instead of the
|
||||
# process-wide base URL, so it can't clobber the main model's endpoint.
|
||||
assert (settings.extra_args or {})["api_base"] == "https://dedupe.example/v1"
|
||||
assert (settings.extra_args or {})["api_key"] == "dedupe-key"
|
||||
|
||||
|
||||
def test_dedupe_defaults_are_empty() -> None:
|
||||
settings = DedupeSettings()
|
||||
assert settings.model is None
|
||||
assert settings.reasoning_effort is None
|
||||
assert settings.api_key is None
|
||||
|
||||
|
||||
def test_dedupe_model_read_from_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("STRIX_DEDUPE_MODEL", "deepseek/deepseek-v4-flash")
|
||||
monkeypatch.setenv("STRIX_DEDUPE_REASONING_EFFORT", "low")
|
||||
|
||||
settings = DedupeSettings()
|
||||
|
||||
assert settings.model == "deepseek/deepseek-v4-flash"
|
||||
assert settings.reasoning_effort == "low"
|
||||
|
||||
|
||||
def test_config_file_loads_dedupe_model(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
for key in ("STRIX_LLM", "STRIX_DEDUPE_MODEL", "STRIX_DEDUPE_REASONING_EFFORT"):
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
path = tmp_path / "config.json"
|
||||
path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"env": {
|
||||
"STRIX_LLM": "openai/root",
|
||||
"STRIX_DEDUPE_MODEL": "deepseek/cheap",
|
||||
"STRIX_DEDUPE_REASONING_EFFORT": "minimal",
|
||||
}
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
loader._cached = None
|
||||
loader._override = path
|
||||
try:
|
||||
settings = loader.load_settings()
|
||||
finally:
|
||||
loader._cached = None
|
||||
loader._override = None
|
||||
|
||||
assert settings.dedupe.model == "deepseek/cheap"
|
||||
assert settings.dedupe.reasoning_effort == "minimal"
|
||||
# Main model stays independent of the dedupe override.
|
||||
assert settings.llm.model == "openai/root"
|
||||
@@ -0,0 +1,78 @@
|
||||
"""Tests for stripping the markdown code fence off stored code fields."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pygments.lexers import BashLexer, PythonLexer
|
||||
|
||||
from strix.report.writer import (
|
||||
guess_language_name,
|
||||
parse_fenced_code,
|
||||
resolve_lexer,
|
||||
safe_fence,
|
||||
)
|
||||
from strix.viewer.report_pdf import _strip_code_fence
|
||||
|
||||
|
||||
def test_parse_fenced_code_extracts_language_and_body() -> None:
|
||||
language, code = parse_fenced_code("```python\nimport requests\nprint(1)\n```")
|
||||
assert language == "python"
|
||||
assert code == "import requests\nprint(1)"
|
||||
|
||||
|
||||
def test_parse_fenced_code_uses_first_token_of_info_string() -> None:
|
||||
language, code = parse_fenced_code("```python title=app.py\nx = 1\n```")
|
||||
assert language == "python"
|
||||
assert code == "x = 1"
|
||||
|
||||
|
||||
def test_parse_fenced_code_handles_non_python_language() -> None:
|
||||
language, code = parse_fenced_code("```http\nGET / HTTP/1.1\n```")
|
||||
assert language == "http"
|
||||
assert code == "GET / HTTP/1.1"
|
||||
|
||||
|
||||
def test_parse_fenced_code_passes_through_unfenced() -> None:
|
||||
language, code = parse_fenced_code("import requests\nprint(1)")
|
||||
assert language is None
|
||||
assert code == "import requests\nprint(1)"
|
||||
|
||||
|
||||
def test_parse_fenced_code_fence_without_language() -> None:
|
||||
language, code = parse_fenced_code("```\nplain\n```")
|
||||
assert language is None
|
||||
assert code == "plain"
|
||||
|
||||
|
||||
def test_strip_code_fence_removes_fence() -> None:
|
||||
assert _strip_code_fence("```python\nx = 1\n```") == "x = 1"
|
||||
|
||||
|
||||
def test_strip_code_fence_passes_through_non_string_and_unfenced() -> None:
|
||||
assert _strip_code_fence(None) is None
|
||||
assert _strip_code_fence("x = 1") == "x = 1"
|
||||
|
||||
|
||||
def test_resolve_lexer_honors_explicit_language() -> None:
|
||||
assert isinstance(resolve_lexer("bash", "echo hi"), BashLexer)
|
||||
|
||||
|
||||
def test_resolve_lexer_falls_back_to_python_when_unresolvable() -> None:
|
||||
# Unknown language name and empty body -> nothing to auto-detect -> Python.
|
||||
assert isinstance(resolve_lexer("not-a-language", ""), PythonLexer)
|
||||
|
||||
|
||||
def test_guess_language_name_defaults_to_python_when_inconclusive() -> None:
|
||||
assert guess_language_name("") == "python"
|
||||
|
||||
|
||||
def test_parse_fenced_code_handles_crlf() -> None:
|
||||
language, code = parse_fenced_code("```python\r\nx = 1\r\n```")
|
||||
assert language == "python"
|
||||
assert code == "x = 1"
|
||||
|
||||
|
||||
def test_safe_fence_widens_past_embedded_backticks() -> None:
|
||||
# A PoC body containing a ``` run must be wrapped in a longer fence so it
|
||||
# can't terminate the block early.
|
||||
assert safe_fence("plain code") == "```"
|
||||
assert safe_fence("has ```\nfence inside") == "````"
|
||||
@@ -106,3 +106,38 @@ def test_nested_symlinks_inside_linked_dir(tmp_path: Path) -> None:
|
||||
assert (staged / "pkg" / "shared_link" / "conf.json").read_text() == "{}\n"
|
||||
assert not (staged / "pkg" / "shared_link" / "escape").exists()
|
||||
assert not (staged / "shared" / "escape").exists()
|
||||
|
||||
|
||||
def test_staged_path_has_no_symlink_ancestor(tmp_path: Path, monkeypatch) -> None: # noqa: ANN001
|
||||
"""The staging directory itself must never sit behind a symlink.
|
||||
|
||||
``tempfile.mkdtemp()`` honors ``$TMPDIR``, and on macOS the default
|
||||
``$TMPDIR`` resolves through ``/var``, which is itself a symlink to
|
||||
``/private/var``. ``LocalDir`` rejects any symlink component in its
|
||||
source path, so returning the raw ``mkdtemp()`` result breaks every
|
||||
local-dir upload on macOS whenever the source tree contains a symlink.
|
||||
This reproduces that shape without depending on the host OS layout.
|
||||
"""
|
||||
repo = _make_repo(tmp_path)
|
||||
(repo / "link.py").symlink_to(repo / "pkg" / "mod.py")
|
||||
|
||||
real_tmp_root = tmp_path / "real_tmp"
|
||||
real_tmp_root.mkdir()
|
||||
symlinked_tmp_root = tmp_path / "tmp_symlink"
|
||||
symlinked_tmp_root.symlink_to(real_tmp_root)
|
||||
|
||||
def fake_mkdtemp(prefix: str = "") -> str:
|
||||
real_dir = real_tmp_root / f"{prefix}fake"
|
||||
real_dir.mkdir()
|
||||
return str(symlinked_tmp_root / real_dir.name)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"strix.runtime.local_dir_staging.tempfile.mkdtemp", fake_mkdtemp
|
||||
)
|
||||
|
||||
upload_path, staged = stage_symlink_safe_dir(repo)
|
||||
|
||||
assert staged is not None
|
||||
assert upload_path == staged
|
||||
for path in (staged, *staged.parents):
|
||||
assert not path.is_symlink(), f"staged path has a symlink ancestor: {path}"
|
||||
@@ -13,12 +13,15 @@ import asyncio
|
||||
|
||||
from agents.retry import ModelRetryNormalizedError, RetryPolicyContext
|
||||
|
||||
from strix.config import codex
|
||||
from strix.config.models import DEFAULT_MODEL_RETRY, _retry_statusless_provider_errors
|
||||
|
||||
|
||||
def _context(normalized: ModelRetryNormalizedError) -> RetryPolicyContext:
|
||||
def _context(
|
||||
normalized: ModelRetryNormalizedError, error: Exception | None = None
|
||||
) -> RetryPolicyContext:
|
||||
return RetryPolicyContext(
|
||||
error=RuntimeError("boom"),
|
||||
error=error or RuntimeError("boom"),
|
||||
attempt=1,
|
||||
max_retries=5,
|
||||
stream=True,
|
||||
@@ -27,11 +30,11 @@ def _context(normalized: ModelRetryNormalizedError) -> RetryPolicyContext:
|
||||
)
|
||||
|
||||
|
||||
def _retries(normalized: ModelRetryNormalizedError) -> bool:
|
||||
def _retries(normalized: ModelRetryNormalizedError, error: Exception | None = None) -> bool:
|
||||
"""Evaluate the composed DEFAULT_MODEL_RETRY policy for a normalized error."""
|
||||
policy = DEFAULT_MODEL_RETRY.policy
|
||||
assert policy is not None
|
||||
decision = asyncio.run(policy(_context(normalized)))
|
||||
decision = asyncio.run(policy(_context(normalized, error)))
|
||||
return bool(getattr(decision, "retry", decision))
|
||||
|
||||
|
||||
@@ -63,6 +66,16 @@ def test_timeout_error_is_retried() -> None:
|
||||
assert _retries(ModelRetryNormalizedError(is_network_error=True)) is True
|
||||
|
||||
|
||||
def test_content_guardrail_error_is_not_retried() -> None:
|
||||
# A guardrail block is status-less, so it would match the statusless policy;
|
||||
# the guard must keep it from being retried (retrying never clears it).
|
||||
guardrail = codex.CodexContentGuardrailError("gpt-5.6-sol")
|
||||
assert _retries(ModelRetryNormalizedError(status_code=None), guardrail) is False
|
||||
# A raw provider error carrying the backend's wording is excluded too.
|
||||
raw = RuntimeError("This content was flagged for possible cybersecurity risk.")
|
||||
assert _retries(ModelRetryNormalizedError(status_code=None), raw) is False
|
||||
|
||||
|
||||
def test_policy_helper_matches_statusless_only() -> None:
|
||||
assert _retry_statusless_provider_errors(_context(ModelRetryNormalizedError())) is True
|
||||
assert (
|
||||
|
||||
@@ -42,9 +42,11 @@ def test_recommended_models_are_matched_case_insensitively() -> None:
|
||||
"model_name",
|
||||
[
|
||||
"gpt-5.5",
|
||||
"chatgpt/gpt-5.4",
|
||||
"litellm/openai/gpt-5.4-pro",
|
||||
"azure_ai/gpt-5.5-pro",
|
||||
"bedrock_mantle/openai.gpt-5.5",
|
||||
"anthropic/claude-opus-5",
|
||||
"anthropic/claude-opus-4-8",
|
||||
"anthropic.claude-opus-4-8",
|
||||
"anthropic/claude-opus-4-7",
|
||||
@@ -60,8 +62,10 @@ def test_recommended_models_are_matched_case_insensitively() -> None:
|
||||
"deepseek/deepseek-reasoner",
|
||||
"dashscope/qwen3-max-2026-01-23",
|
||||
"qwen3.7-max",
|
||||
"dashscope/qwen3.8-max",
|
||||
"moonshot/kimi-k2.6",
|
||||
"kimi-k2.7-code",
|
||||
"moonshot/kimi-k3",
|
||||
],
|
||||
)
|
||||
def test_frontier_model_families_are_accepted(model_name: str) -> None:
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
"""Tests for per-tool-output bounding."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
from strix.tools.output_store import bound_text
|
||||
|
||||
|
||||
def test_small_output_passes_through_unchanged() -> None:
|
||||
text = "line 1\nline 2\nline 3"
|
||||
assert bound_text(text, max_lines=100, max_bytes=10_000) == text
|
||||
|
||||
|
||||
def test_line_limit_keeps_head_and_tail() -> None:
|
||||
text = "\n".join(str(i) for i in range(1000))
|
||||
bounded = bound_text(text, max_lines=10, max_bytes=1_000_000)
|
||||
|
||||
assert bounded.startswith("0\n1\n2\n3\n4")
|
||||
assert bounded.rstrip().endswith("999")
|
||||
assert "truncated" in bounded
|
||||
assert len(bounded.splitlines()) < 30
|
||||
|
||||
|
||||
def test_byte_limit_enforced_on_single_long_line() -> None:
|
||||
text = "x" * 100_000
|
||||
bounded = bound_text(text, max_lines=2_000, max_bytes=1_000)
|
||||
|
||||
assert "truncated" in bounded
|
||||
assert len(bounded.encode("utf-8")) <= 1_000
|
||||
|
||||
|
||||
def test_multibyte_characters_not_split() -> None:
|
||||
text = "😀" * 50_000
|
||||
bounded = bound_text(text, max_lines=2_000, max_bytes=1_000)
|
||||
|
||||
# Must remain valid UTF-8 (no mid-character cut).
|
||||
assert bounded == bounded.encode("utf-8").decode("utf-8")
|
||||
assert "truncated" in bounded
|
||||
|
||||
|
||||
def test_notice_reports_dropped_counts() -> None:
|
||||
text = "\n".join("y" * 10 for _ in range(500))
|
||||
bounded = bound_text(text, max_lines=10, max_bytes=1_000_000)
|
||||
|
||||
assert "lines" in bounded
|
||||
assert "bytes" in bounded
|
||||
|
||||
|
||||
def test_dropped_line_count_accounts_for_byte_trimming() -> None:
|
||||
# Tight byte budget drops whole lines from head/tail; the notice must count them.
|
||||
text = "\n".join(f"line-{i}" for i in range(200))
|
||||
bounded = bound_text(text, max_lines=20, max_bytes=40)
|
||||
|
||||
match = re.search(r"\[\.\.\. (\d+) lines", bounded)
|
||||
assert match is not None, bounded
|
||||
dropped = int(match.group(1))
|
||||
kept = [ln for ln in bounded.splitlines() if ln and "truncated" not in ln]
|
||||
assert dropped == 200 - len(kept)
|
||||
assert dropped > 200 - 20
|
||||
@@ -119,9 +119,11 @@ def test_render_vulnerability_md_poc_code_cannot_break_out_of_fence() -> None:
|
||||
injected = "curl x\n```\n\n## Injected Heading\n"
|
||||
md = render_vulnerability_md(_sample_report(poc_script_code=injected))
|
||||
lines = md.split("\n")
|
||||
fence = next(ln for ln in lines[lines.index("## Proof of Concept") + 1 :] if ln.strip())
|
||||
assert set(fence) == {"`"}
|
||||
assert len(fence) >= 4 # wider than the payload's 3-backtick run
|
||||
opening = next(ln for ln in lines[lines.index("## Proof of Concept") + 1 :] if ln.strip())
|
||||
ticks = opening[: len(opening) - len(opening.lstrip("`"))]
|
||||
assert len(ticks) >= 4 # wider than the payload's 3-backtick run
|
||||
assert "`" not in opening.removeprefix(ticks) # backtick run + language tag only
|
||||
assert f"\n{ticks}\n" in md # pure-backtick closing fence of the same width
|
||||
assert injected in md # the payload survives verbatim, inside the fence
|
||||
|
||||
|
||||
|
||||
@@ -46,7 +46,8 @@ def _patch_engine_scaffold(
|
||||
reasoning_effort="high",
|
||||
force_required_tool_choice=False,
|
||||
timeout=300,
|
||||
)
|
||||
),
|
||||
runtime=types.SimpleNamespace(max_context_images=3),
|
||||
)
|
||||
monkeypatch.setattr(runner, "load_settings", lambda: settings)
|
||||
monkeypatch.setattr(runner, "configure_sdk_model_defaults", lambda _settings: None)
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import platform
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from rich.console import Console
|
||||
|
||||
from strix.interface import update_check
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _isolated_cache(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(update_check, "_CACHE_PATH", tmp_path / "update-check.json")
|
||||
monkeypatch.setattr(update_check, "_background_thread", None)
|
||||
monkeypatch.delenv("STRIX_NO_UPDATE_CHECK", raising=False)
|
||||
for key in ("CI", "GITHUB_ACTIONS", "GITLAB_CI", "JENKINS_URL", "BUILDKITE", "CIRCLECI"):
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("latest", "current", "expected"),
|
||||
[
|
||||
("1.2.0", "1.1.0", True),
|
||||
("1.1.0", "1.1.0", False),
|
||||
("1.0.9", "1.1.0", False),
|
||||
("2.0.0", "1.99.99", True),
|
||||
("1.10.0", "1.9.0", True),
|
||||
("v1.2.0", "1.1.0", True),
|
||||
("not-a-version", "1.1.0", False),
|
||||
("1.2.0", "unknown", False),
|
||||
],
|
||||
)
|
||||
def test_is_newer(latest: str, current: str, expected: bool) -> None:
|
||||
assert update_check._is_newer(latest, current) is expected
|
||||
|
||||
|
||||
def test_get_available_update_from_cache(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
update_check._CACHE_PATH.write_text(
|
||||
json.dumps({"latest_version": "9.9.9", "checked_at": time.time()})
|
||||
)
|
||||
monkeypatch.setattr(update_check, "get_version", lambda: "1.0.0")
|
||||
assert update_check.get_available_update() == "9.9.9"
|
||||
|
||||
|
||||
def test_get_available_update_up_to_date(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
update_check._CACHE_PATH.write_text(
|
||||
json.dumps({"latest_version": "1.0.0", "checked_at": time.time()})
|
||||
)
|
||||
monkeypatch.setattr(update_check, "get_version", lambda: "1.0.0")
|
||||
assert update_check.get_available_update() is None
|
||||
|
||||
|
||||
def test_get_available_update_disabled_by_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
update_check._CACHE_PATH.write_text(
|
||||
json.dumps({"latest_version": "9.9.9", "checked_at": time.time()})
|
||||
)
|
||||
monkeypatch.setattr(update_check, "get_version", lambda: "1.0.0")
|
||||
monkeypatch.setenv("STRIX_NO_UPDATE_CHECK", "1")
|
||||
assert update_check.get_available_update() is None
|
||||
|
||||
|
||||
def test_get_available_update_disabled_in_ci(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
update_check._CACHE_PATH.write_text(
|
||||
json.dumps({"latest_version": "9.9.9", "checked_at": time.time()})
|
||||
)
|
||||
monkeypatch.setattr(update_check, "get_version", lambda: "1.0.0")
|
||||
monkeypatch.setenv("CI", "true")
|
||||
assert update_check.get_available_update() is None
|
||||
|
||||
|
||||
def test_get_available_update_corrupt_cache(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
update_check._CACHE_PATH.write_text("{not json")
|
||||
monkeypatch.setattr(update_check, "get_version", lambda: "1.0.0")
|
||||
assert update_check.get_available_update() is None
|
||||
|
||||
|
||||
def test_background_check_skipped_when_fresh(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
update_check._CACHE_PATH.write_text(
|
||||
json.dumps({"latest_version": "1.0.0", "checked_at": time.time()})
|
||||
)
|
||||
called = False
|
||||
|
||||
def fake_refresh() -> None:
|
||||
nonlocal called
|
||||
called = True
|
||||
|
||||
monkeypatch.setattr(update_check, "_refresh_cache", fake_refresh)
|
||||
update_check.start_background_check()
|
||||
assert update_check._background_thread is None
|
||||
assert called is False
|
||||
|
||||
|
||||
def test_background_check_runs_when_stale(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
update_check._CACHE_PATH.write_text(
|
||||
json.dumps({"latest_version": "1.0.0", "checked_at": time.time() - 2 * 24 * 60 * 60})
|
||||
)
|
||||
monkeypatch.setattr(update_check, "_fetch_latest_version", lambda: "1.2.3")
|
||||
update_check.start_background_check()
|
||||
assert update_check._background_thread is not None
|
||||
update_check._background_thread.join(timeout=5)
|
||||
cache = json.loads(update_check._CACHE_PATH.read_text())
|
||||
assert cache["latest_version"] == "1.2.3"
|
||||
|
||||
|
||||
def test_skipped_version_suppresses_update(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
update_check._CACHE_PATH.write_text(
|
||||
json.dumps({"latest_version": "9.9.9", "checked_at": time.time()})
|
||||
)
|
||||
monkeypatch.setattr(update_check, "get_version", lambda: "1.0.0")
|
||||
update_check.skip_version("9.9.9")
|
||||
assert update_check.get_available_update() is None
|
||||
assert update_check.get_available_update(respect_skip=False) == "9.9.9"
|
||||
|
||||
|
||||
def test_newer_release_overrides_skipped_version(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
update_check._CACHE_PATH.write_text(
|
||||
json.dumps(
|
||||
{"latest_version": "9.9.10", "checked_at": time.time(), "skipped_version": "9.9.9"}
|
||||
)
|
||||
)
|
||||
monkeypatch.setattr(update_check, "get_version", lambda: "1.0.0")
|
||||
assert update_check.get_available_update() == "9.9.10"
|
||||
|
||||
|
||||
def test_write_cache_preserves_existing_fields() -> None:
|
||||
update_check.skip_version("9.9.9")
|
||||
update_check._write_cache(latest_version="1.2.3", checked_at=123.0)
|
||||
cache = json.loads(update_check._CACHE_PATH.read_text())
|
||||
assert cache == {"latest_version": "1.2.3", "checked_at": 123.0, "skipped_version": "9.9.9"}
|
||||
|
||||
|
||||
def test_get_upgrade_command_all_methods() -> None:
|
||||
assert update_check.get_upgrade_command("binary") == "strix --update"
|
||||
assert update_check.get_upgrade_command("pipx") == "pipx upgrade strix-agent"
|
||||
assert update_check.get_upgrade_command("uv") == "uv tool upgrade strix-agent"
|
||||
assert update_check.get_upgrade_command("pip") == "pip install --upgrade strix-agent"
|
||||
|
||||
|
||||
def test_self_update_non_binary_prints_command(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(update_check, "is_binary_install", lambda: False)
|
||||
buffer = io.StringIO()
|
||||
assert update_check.self_update(Console(file=buffer)) is False
|
||||
assert "upgrade" in buffer.getvalue()
|
||||
|
||||
|
||||
def test_self_update_already_latest(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(update_check, "is_binary_install", lambda: True)
|
||||
monkeypatch.setattr(update_check, "_fetch_latest_version", lambda: "1.0.0")
|
||||
monkeypatch.setattr(update_check, "get_version", lambda: "1.0.0")
|
||||
assert update_check.self_update() is True
|
||||
|
||||
|
||||
def test_sha256_file(tmp_path: Path) -> None:
|
||||
path = tmp_path / "blob"
|
||||
path.write_bytes(b"strix")
|
||||
assert update_check._sha256_file(path) == hashlib.sha256(b"strix").hexdigest()
|
||||
|
||||
|
||||
def test_release_target(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(platform, "system", lambda: "Linux")
|
||||
monkeypatch.setattr(platform, "machine", lambda: "x86_64")
|
||||
assert update_check._release_target() == "linux-x86_64"
|
||||
|
||||
monkeypatch.setattr(platform, "system", lambda: "Darwin")
|
||||
monkeypatch.setattr(platform, "machine", lambda: "arm64")
|
||||
assert update_check._release_target() == "macos-arm64"
|
||||
|
||||
monkeypatch.setattr(platform, "machine", lambda: "riscv64")
|
||||
assert update_check._release_target() is None
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Subscription runs track tokens but report zero cost."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from agents.usage import Usage
|
||||
|
||||
from strix.report.usage import LLMUsageLedger
|
||||
|
||||
|
||||
def _usage() -> Usage:
|
||||
usage = Usage()
|
||||
usage.requests = 1
|
||||
usage.input_tokens = 1000
|
||||
usage.output_tokens = 200
|
||||
usage.total_tokens = 1200
|
||||
return usage
|
||||
|
||||
|
||||
def test_zero_cost_ledger_keeps_tokens_but_reports_no_cost() -> None:
|
||||
ledger = LLMUsageLedger()
|
||||
ledger.zero_cost = True
|
||||
ledger.record(agent_id="a", usage=_usage(), agent_name="strix", model="gpt-5.5")
|
||||
|
||||
record = ledger.to_record()
|
||||
assert record["cost"] == 0.0
|
||||
assert record["total_tokens"] == 1200
|
||||
assert record["input_tokens"] == 1000
|
||||
assert record["output_tokens"] == 200
|
||||
assert ledger.total_cost == 0.0
|
||||
|
||||
|
||||
def test_zero_cost_ledger_ignores_observed_cost() -> None:
|
||||
ledger = LLMUsageLedger()
|
||||
ledger.zero_cost = True
|
||||
ledger.record_observed_cost(4.20)
|
||||
assert ledger.total_cost == 0.0
|
||||
|
||||
|
||||
def test_normal_ledger_still_estimates_cost() -> None:
|
||||
# Sanity check the flag is opt-in: without it, an OpenAI-native model still
|
||||
# accrues an estimated cost (proves zeroing is what suppresses it).
|
||||
ledger = LLMUsageLedger()
|
||||
ledger.record(agent_id="a", usage=_usage(), agent_name="strix", model="gpt-5.5")
|
||||
assert ledger.to_record()["total_tokens"] == 1200
|
||||
# Cost estimation depends on litellm's cost map; it should be >= 0 and not error.
|
||||
assert ledger.total_cost >= 0.0
|
||||
+58
-6
@@ -191,6 +191,62 @@ def test_server_event_endpoint_forwards_email_funnel(
|
||||
httpd.server_close()
|
||||
|
||||
|
||||
def test_server_event_endpoint_forwards_agent_steered(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
run_dir = _make_run(tmp_path, "steerevt", status="running", end_time=None)
|
||||
_bundle(tmp_path, monkeypatch)
|
||||
|
||||
seen: list[bool] = []
|
||||
monkeypatch.setattr("strix.telemetry.posthog.viewer_agent_steered", lambda: seen.append(True))
|
||||
|
||||
httpd, url, _ = serve(run_dir, open_browser=False)
|
||||
try:
|
||||
req = urllib.request.Request( # noqa: S310 - localhost test server
|
||||
f"{url}/api/event",
|
||||
data=json.dumps({"event": "agent_steered"}).encode(),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(req) as resp: # noqa: S310
|
||||
assert resp.status == 204
|
||||
assert seen == [True]
|
||||
finally:
|
||||
httpd.shutdown()
|
||||
httpd.server_close()
|
||||
|
||||
|
||||
def test_feedback_records_telemetry_on_success(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
run_dir = _make_run(tmp_path, "fbtel", status="running", end_time=None)
|
||||
_bundle(tmp_path, monkeypatch)
|
||||
|
||||
sent: list[bool] = []
|
||||
monkeypatch.setattr("strix.viewer.auth.feedback_submit", lambda *_a: None)
|
||||
monkeypatch.setattr(
|
||||
"strix.telemetry.posthog.viewer_feedback_submitted", lambda: sent.append(True)
|
||||
)
|
||||
|
||||
httpd, url, token = serve(run_dir, open_browser=False)
|
||||
try:
|
||||
cookie = _session_cookie(url, token)
|
||||
# A successful, session-holding submission relays and records telemetry.
|
||||
status, _ = _post(
|
||||
url, "/api/feedback", {"email": "[email protected]", "message": "hi"}, cookie=cookie
|
||||
)
|
||||
assert status == 200
|
||||
assert sent == [True]
|
||||
|
||||
# A cookie-less caller is rejected and records nothing.
|
||||
sent.clear()
|
||||
status, _ = _post(url, "/api/feedback", {"email": "[email protected]", "message": "hi"})
|
||||
assert status == 403
|
||||
assert sent == []
|
||||
finally:
|
||||
httpd.shutdown()
|
||||
httpd.server_close()
|
||||
|
||||
|
||||
def _post(
|
||||
url: str, path: str, payload: Mapping[str, object], *, cookie: str | None = None
|
||||
) -> tuple[int, bytes]:
|
||||
@@ -395,9 +451,7 @@ def test_report_send_requires_session_cookie(
|
||||
httpd.server_close()
|
||||
|
||||
|
||||
def test_report_send_rejects_live_run(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
def test_report_send_rejects_live_run(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
# A running scan would only produce a partial report, so the endpoint must
|
||||
# fail closed even for a verified, session-holding caller.
|
||||
run_dir = _make_run(tmp_path, "live", status="running", end_time=None)
|
||||
@@ -406,9 +460,7 @@ def test_report_send_rejects_live_run(
|
||||
|
||||
httpd, url, token = serve(run_dir, open_browser=False)
|
||||
try:
|
||||
status, _ = _post(
|
||||
url, "/api/report/send", {}, cookie=_session_cookie(url, token)
|
||||
)
|
||||
status, _ = _post(url, "/api/report/send", {}, cookie=_session_cookie(url, token))
|
||||
assert status == 409
|
||||
finally:
|
||||
httpd.shutdown()
|
||||
|
||||
@@ -469,55 +469,55 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "46.0.7"
|
||||
version = "48.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/12/45/870e7f4bef50e5f53b9f51d4428aee5290eedf58ba443f16b1ebb7ab8e66/cryptography-48.0.1.tar.gz", hash = "sha256:266f4ee051abb2f725b74ef8072b521ce1feacf685a3364fa6a6b45548db791a", size = 832989, upload-time = "2026-06-09T22:32:31.8Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/5d/4a8f770695d73be252331e60e526291e3df0c9b27556a90a6b47bccca4c2/cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4", size = 7179869, upload-time = "2026-04-08T01:56:17.157Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/bb/a5c213c19ee94b15dfccc48f363738633a493812687f5567addbcbba9f6f/cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457", size = 3026504, upload-time = "2026-04-08T01:56:39.666Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/02/7788f9fefa1d060ca68717c3901ae7fffa21ee087a90b7f23c7a603c32ae/cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b", size = 3488363, upload-time = "2026-04-08T01:56:41.893Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/56/15619b210e689c5403bb0540e4cb7dbf11a6bf42e483b7644e471a2812b3/cryptography-46.0.7-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842", size = 7119671, upload-time = "2026-04-08T01:56:44Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/66/e3ce040721b0b5599e175ba91ab08884c75928fbeb74597dd10ef13505d2/cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c", size = 4268551, upload-time = "2026-04-08T01:56:46.071Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/11/5e395f961d6868269835dee1bafec6a1ac176505a167f68b7d8818431068/cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902", size = 4408887, upload-time = "2026-04-08T01:56:47.718Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/53/8ed1cf4c3b9c8e611e7122fb56f1c32d09e1fff0f1d77e78d9ff7c82653e/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d", size = 4271354, upload-time = "2026-04-08T01:56:49.312Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/46/cf71e26025c2e767c5609162c866a78e8a2915bbcfa408b7ca495c6140c4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022", size = 4905845, upload-time = "2026-04-08T01:56:50.916Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/ea/01276740375bac6249d0a971ebdf6b4dc9ead0ee0a34ef3b5a88c1a9b0d4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce", size = 4444641, upload-time = "2026-04-08T01:56:52.882Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/4c/7d258f169ae71230f25d9f3d06caabcff8c3baf0978e2b7d65e0acac3827/cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f", size = 3967749, upload-time = "2026-04-08T01:56:54.597Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/2a/2ea0767cad19e71b3530e4cad9605d0b5e338b6a1e72c37c9c1ceb86c333/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99", size = 4270942, upload-time = "2026-04-08T01:56:56.416Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/3d/fe14df95a83319af25717677e956567a105bb6ab25641acaa093db79975d/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1", size = 4871079, upload-time = "2026-04-08T01:56:58.31Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/59/4a479e0f36f8f378d397f4eab4c850b4ffb79a2f0d58704b8fa0703ddc11/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2", size = 4443999, upload-time = "2026-04-08T01:57:00.508Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/17/b59a741645822ec6d04732b43c5d35e4ef58be7bfa84a81e5ae6f05a1d33/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e", size = 4399191, upload-time = "2026-04-08T01:57:02.654Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/6a/bb2e166d6d0e0955f1e9ff70f10ec4b2824c9cfcdb4da772c7dd69cc7d80/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee", size = 4655782, upload-time = "2026-04-08T01:57:04.592Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/b6/3da51d48415bcb63b00dc17c2eff3a651b7c4fed484308d0f19b30e8cb2c/cryptography-46.0.7-cp314-cp314t-win32.whl", hash = "sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298", size = 3002227, upload-time = "2026-04-08T01:57:06.91Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/a8/9f0e4ed57ec9cebe506e58db11ae472972ecb0c659e4d52bbaee80ca340a/cryptography-46.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb", size = 3475332, upload-time = "2026-04-08T01:57:08.807Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/7f/cd42fc3614386bc0c12f0cb3c4ae1fc2bbca5c9662dfed031514911d513d/cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4", size = 7165618, upload-time = "2026-04-08T01:57:10.645Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/fa/f0ab06238e899cc3fb332623f337a7364f36f4bb3f2534c2bb95a35b132c/cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246", size = 3013001, upload-time = "2026-04-08T01:57:34.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/bc/ee4137cbbe105652c0ee4252792b78fc8e7afa4b8e61d9d5dc05a7f45731/cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:3e4a1a3232eef2e6c732827d5722db29a0cc8b27af2a4d865b094cf954be9ca1", size = 8008324, upload-time = "2026-06-09T22:31:00.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/85/6379d42181bfc713094f081360fc5784d6c816b599d45e7f082502d173ce/cryptography-48.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:32143b24adb918f078134e1e230f1eb8cc04886b92c28b5f0041aaf3e5699225", size = 4696243, upload-time = "2026-06-09T22:32:33.446Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/87/c85d147b53323c7eb4d850920c8901377323c2a0ff8d79c262d4fee89aa2/cryptography-48.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0d27a5696721ef7a672b8c810f6aded391058e0b9486e63e6d93baf765da691", size = 4713235, upload-time = "2026-06-09T22:31:40.141Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/58/67cbf8cf1ee7c54b439ca07bbecf8362c07afc11a3724fea70f745784add/cryptography-48.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb86ce1af36fe65041b6db9a8bb064ee621a7e5fded0f80d475ec243477cd242", size = 4702323, upload-time = "2026-06-09T22:31:42.191Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/c6/24266ac10c47f6cd2a865f4446062b466da1d1f10b27189eac00e61bf0c9/cryptography-48.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b024e784ad6c077ee0147b35ea9cbfc1e34e1fd4c1dcca214c2794d73a12df08", size = 5300085, upload-time = "2026-06-09T22:31:58.703Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/bb/cc4b78784f97efc8c5874c2a9743708d172be6663024b34a0467885ae0c8/cryptography-48.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3752f2dbc8f07a30aad2932c986cea495b03bb554887828225da104f732852b6", size = 4746137, upload-time = "2026-06-09T22:31:31.01Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/52/0c44de3f5267f8fbe8e835138017522a333436166e406f0db9b9e6e3033f/cryptography-48.0.1-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:bd81490cd5801d755cf97bb68ac191f14b708470b1c7cf4580f669b9c9264cd8", size = 4333867, upload-time = "2026-06-09T22:32:28.096Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/2e/772d7adbfa931537bc401640b7cac9976bff689bda187833e5d63b428e49/cryptography-48.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:66fd0771e7b9c6dcd44cf1120690d2338d16d72795cf40cae2786a39eba65429", size = 4701805, upload-time = "2026-06-09T22:31:38.284Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/a3/b06844f303873493c963caf581c04df31c7035e0c1b0f02c4814d319ec80/cryptography-48.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:3fd2ca57062b241c856670b073487d2e86c4637937ca5601e48f97bf8e11fc8f", size = 5258461, upload-time = "2026-06-09T22:31:04.187Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/13/8b765e2e12b07c74941caadb9d1c8fdc006c4dfbf2b8f2d610519758954d/cryptography-48.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:0ee6ea481db1ab889cba043ec1eda17bb9c1ea79db6722f779c3667f9f70322f", size = 4745488, upload-time = "2026-06-09T22:32:30.07Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/aa/48972bce55049b32a94f4907eda4d75fa385aad8a39506cc2fc72196ecf0/cryptography-48.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f2ceef93cb096aa3c4cc4b5c94ca6131f9196d28c64d6111533402a9b2054d41", size = 4830256, upload-time = "2026-06-09T22:31:43.868Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/a2/e5079a032fb85cf6005046ca92bbd78b0c82dad2b5751ab8c311659da06f/cryptography-48.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9bd3f92d76217892b15df84ca256c2c113d386fdda7a7d8691aeeced976507c6", size = 4979117, upload-time = "2026-06-09T22:31:05.845Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/a0/8f50cae9c74e718ed769d63ed5c74bd0ea830c9550a74629cebd1b9c7bc7/cryptography-48.0.1-cp311-abi3-win32.whl", hash = "sha256:b9a32b876490d66c8bcc9963ef220199569748434ab01a9d6aaeabf88e7f5158", size = 3304154, upload-time = "2026-06-09T22:32:16.845Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/69/0572c77dbace6fef72f33755bd52ea399c71367250d366237f8691826b9e/cryptography-48.0.1-cp311-abi3-win_amd64.whl", hash = "sha256:39489bfca54c7a1f6b297efcd8bc608ab92d16c4ca631b0cad4da46724588b24", size = 3817138, upload-time = "2026-06-09T22:32:00.388Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/06/3e768b4c3bc78201583fa35a0e18f640dd782ff41afba88f8545481a8874/cryptography-48.0.1-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:f817adc181390bd54f2f700107a7419040fb7c1bdf2fc26f36551a06a68c3345", size = 7989830, upload-time = "2026-06-09T22:31:07.8Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/13/6476736484b94041110c8340a3eb63962fea4975baea8cb4a512adb44d4d/cryptography-48.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d5d30989c6917b478b5817902e85fddaea2261efa8648383d965381ccb9e1ac4", size = 4689201, upload-time = "2026-06-09T22:31:09.745Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/62/65a87f34d2a431546e2509b85d55e8c90df86d668f6731da64d538512ac2/cryptography-48.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:df637c05205ea7c1d7fbcbe54bbfea648a52951155f997af13d895d0ecc96991", size = 4702822, upload-time = "2026-06-09T22:32:24.409Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/59/810b5204b0a9b10f4b6bc06bd551a8b609803cd931806bc3b71884b225e5/cryptography-48.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:869c3b8a53bfe27147832df48b32adadf558249d50e76cb3769d40e986b13265", size = 4694875, upload-time = "2026-06-09T22:32:08.737Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/dc/d8ca05ffea724eec6d232ea6f18e74c269eb6bdfdcc9bfba689790d1325f/cryptography-48.0.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:e361afba8918070d376df76f408a4f67fec0ee9cff81a99e48fe9a233ef59e17", size = 5290385, upload-time = "2026-06-09T22:31:15.212Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/8c/3be6cb4da181f5bb6c19cf560c2359d60644a6b5fc5b57854e528f47b296/cryptography-48.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d069066deead00ac7f090be101be875a06855908f7ec004c27b8fefb4acfb411", size = 4737082, upload-time = "2026-06-09T22:32:22.66Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/f6/d5f60a5a1434dbfd949e227fd0065d194c7e6b6ac526b17f5c06152b8231/cryptography-48.0.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:09f73a725d582cef64b91281a322cd798d14a33b2b6f2b7ad9531dc336d84c02", size = 4325328, upload-time = "2026-06-09T22:32:10.777Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/b7/ba75dd947a14b6ad907b01ae8f6b5b348cdd1b48142f0063dee9e20c1d9d/cryptography-48.0.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:15254441469dd6bf027039453288e2072124f8b6603563f5d759e1c9b69273fa", size = 4694530, upload-time = "2026-06-09T22:31:53.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/29/50d6b9e8aff12d8b67afaeb3569335e32dc83a5723e3bbded24fdac9f809/cryptography-48.0.1-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:8ace4507d1e6533c125f4fac754f8bb8b6a74c08e92179dabd7e16571a3efbf3", size = 5245046, upload-time = "2026-06-09T22:31:25.774Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/04/618f4115cfc0add0838c82507aa18a346089428da8653ad38b3ff36f5cb3/cryptography-48.0.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:b4e391975f038e66432328639620a4aff2d307513b004f1ca06d6225bced815c", size = 4736660, upload-time = "2026-06-09T22:32:12.676Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/9c/06e062462a0de28a3b3911322eded4c16deb9f441b1b7575d3dc59488ab5/cryptography-48.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42fcd8e26fe555d9b3577a135f5091fefa0aa4e99129c23fb56787a1bd4ada72", size = 4822229, upload-time = "2026-06-09T22:31:17.062Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/be/0561971eaaee4b8a0e7d5113c536921063ab91aaf23278ac374eaf881e11/cryptography-48.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c1400da5e32a43253392277eac7490a60e497d810a63dd5608d71bbd7af507c9", size = 4966364, upload-time = "2026-06-09T22:31:32.842Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/27/728c77876f12b000820b69ae490f3c4083775e79e07827e9e60be07ad209/cryptography-48.0.1-cp314-cp314t-win32.whl", hash = "sha256:0df56b056bc17c1b7d6821dfa65216e62bd232d8ab05eb3db44e71d235651471", size = 3278498, upload-time = "2026-06-09T22:31:29.154Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/e3/79a612c6d7b1e6ee0edd43633d53035bec2cfb78c82b76f7864f39e36f34/cryptography-48.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:9de21387aa95e2a895823d0745b430bed4f33503ba9ab5e0b5311f33e37d66d2", size = 3798790, upload-time = "2026-06-09T22:31:56.697Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/6c/00fa2a95997164c8b2072ce327c23d4ab20809ccc323ea5fab91e53a4bba/cryptography-48.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:4fdc69f8e4316bcf0c8c8ec1f26f285d12e8142d88d96c876a59a03be3f6ae67", size = 7987408, upload-time = "2026-06-09T22:32:20.777Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/d9/45f309a7e4e5f3f8f121d6d3be9e94024a7726ec598d6e08ae04edb2f04d/cryptography-48.0.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48fe40804d4caa2288f24e70ca8c64c42dd826da0ad7e4f1b41b2128d679e6c8", size = 4690196, upload-time = "2026-06-09T22:31:54.74Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/9f/a1bc8bcc798811b8527eb374bbccf30a3f3e806829d967118222bf1125eb/cryptography-48.0.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:86be3b1b0b6bf09482fb50a979c508d2950ed95f5621ec77f4e385962006b83a", size = 4696782, upload-time = "2026-06-09T22:31:45.615Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/c2/81a4fb4e4373c500bb526bc337ac5719dd31dd15b970b84a238168c6aa08/cryptography-48.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4ab0a343c807bbcd90c971cd1ecf072937cd01847a9e002bef88fb47ac6be577", size = 4696618, upload-time = "2026-06-09T22:31:11.564Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/0b/aa68b221dde92d09cb29a024ede17550ee21e77a404e59fc093c82bb51e1/cryptography-48.0.1-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9621de99d2da096006b629979efd8ae7eb2d8b822488d0c89ee4000c306c59b1", size = 5289970, upload-time = "2026-06-09T22:31:20.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/13/fba657f958d2af66ea959a4ba01212632089249d34af1ae48054136344d7/cryptography-48.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:88c852a0ae366e262e5a1744b685e6a433dc8788dd2a277e418bf4904203609d", size = 4731873, upload-time = "2026-06-09T22:31:22.253Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/4c/9a964756d24a26b3e34dfcb16f961b89838786e6700b635b0d1e3adff4b6/cryptography-48.0.1-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:43c5835e2cb98c8733d86f57d6fc879b613f5c3478607281c3e36daffc6dd8a6", size = 4330804, upload-time = "2026-06-09T22:31:36.56Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/0f/a10f3a6eb12950a10e3a874070283aa2dd5875b2bfd15fad8a3e17b3f13e/cryptography-48.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:fe0180af5bf9236518a087e35bf2d9a347d5f5f51e63c579d683ddff424e3d46", size = 4696217, upload-time = "2026-06-09T22:31:13.351Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/6f/5cd12f951165ea73ef85266775d97e4c763b2474ccfd816dd69d3a18d6f8/cryptography-48.0.1-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:b7a2d1a937a738a881737cec135a38bb61470589b17515b9f73f571d0ae10401", size = 5245252, upload-time = "2026-06-09T22:32:02.193Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/ab/8aaa12e4516ec4464033ab79b6f3b592bd5a92102467c4ace8a0d970203f/cryptography-48.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b74ca3b8e5ecdd833bf6a002ca41b4793bb27fb8f1c06ffaf2643c9e9140e31b", size = 4731388, upload-time = "2026-06-09T22:32:04.019Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/24/50027ea4dca85ec1f40688f3c24fb32ccacd520583c9592c3cc95628e6fb/cryptography-48.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2c37f2461406063b417837f5f3daab668652acd82423efcd7f0a9f04be972de1", size = 4824186, upload-time = "2026-06-09T22:32:18.707Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/41/04cb5eb17085ade6f50cc611fb657df6a0f5885350de8764ece89c050197/cryptography-48.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:86fe77abb1bd87afb251d4d02ada7ecf53a32cee9b67d976abb2e45a13297475", size = 4964539, upload-time = "2026-06-09T22:31:18.793Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/bf/ed70785c496e89d7e73b7cda2d21f2447fd6d4e821714b8d04ff217fed92/cryptography-48.0.1-cp39-abi3-win32.whl", hash = "sha256:6b2c0c3e6ccf3ade7750f836ef3ee36eea250cc467d45c256895573ac08cc6f1", size = 3282307, upload-time = "2026-06-09T22:30:53.162Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/ff/371ea7d252656ee1eb6d83eeeef3d1d0c6baf1d6497687d081ea03814670/cryptography-48.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:9a49ca6c81417f6a5edb50375a60cccdd70fa0a91a5211829dbea74eba94d2ac", size = 3793408, upload-time = "2026-06-09T22:32:15.191Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1685,11 +1685,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pyasn1"
|
||||
version = "0.6.3"
|
||||
version = "0.6.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a4/9a/23310166d960def5897e91fe20e5b724601b02a22e84ba1f94232c0b7f67/pyasn1-0.6.4.tar.gz", hash = "sha256:9c447d8431c947fe4c8febc4ed9e760bc29011a5b01e5c74b67025bd9fb8ce81", size = 151262, upload-time = "2026-07-09T01:12:33.988Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/3b/6163796d69c3977d1e4287bea4a6979161cbbdd170ebb430511e8e1999ce/pyasn1-0.6.4-py3-none-any.whl", hash = "sha256:deda9277cfd454080ec40b207fb6df82206a3a2688735233cdcd8d3d565f088b", size = 84410, upload-time = "2026-07-09T01:12:32.92Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2340,11 +2340,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "82.0.1"
|
||||
version = "83.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2411,7 +2411,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "strix-agent"
|
||||
version = "1.2.0"
|
||||
version = "1.3.1"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "caido-sdk-client" },
|
||||
@@ -2454,7 +2454,7 @@ dev = [
|
||||
requires-dist = [
|
||||
{ name = "boto3", marker = "extra == 'bedrock'", specifier = ">=1.28.0" },
|
||||
{ name = "caido-sdk-client", specifier = ">=0.2.0" },
|
||||
{ name = "cryptography", specifier = ">=42" },
|
||||
{ name = "cryptography", specifier = ">=48.0.1,<49" },
|
||||
{ name = "cvss", specifier = ">=3.2" },
|
||||
{ name = "docker", specifier = ">=7.1.0" },
|
||||
{ name = "google-auth", marker = "extra == 'vertex'", specifier = ">=2.0.0" },
|
||||
|
||||
Reference in new issue
Block a user