commit 498922567109aa14657dae71872e3f7a3ece678b
Author: Hadi <112569860+anotherhadi@users.noreply.github.com>
Date: Mon Apr 6 15:12:34 2026 +0200
init
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 0000000..31babc4
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,46 @@
+# Contributing
+
+Contributions are welcome: new tool integrations especially.
+
+1. Fork the repository
+2. Create a feature branch: `git checkout -b feat/my-tool`
+3. Implement your tool
+4. Open a pull request
+
+Please ensure your tool handles context cancellation, respects rate limits, and declares the correct input types. Document any required API key or external binary dependency.
+
+## Development
+
+### Prerequisites
+
+- [Go 1.25+](https://go.dev/dl/)
+- [Bun](https://bun.sh)
+- [Just](https://github.com/casey/just)
+
+Or you can use the Nix Shell by typing `nix develop`
+
+### Run locally
+
+```bash
+git clone https://github.com/anotherhadi/iknowyou.git
+cd iknowyou
+
+just dev
+```
+
+Open [http://localhost:4321](http://localhost:4321).
+
+The backend listens on `:8080` by default. Configure via environment variables:
+
+| Variable | Default | Description |
+| ------------ | ------------- | ---------------------------- |
+| `IKY_PORT` | `8080` | HTTP port |
+| `IKY_CONFIG` | `config.yaml` | Path to the YAML config file |
+
+## Adding a Tool
+
+1. Create `back/internal/tools/mytool/mytool.go` implementing `tools.ToolRunner`
+2. Optionally implement `tools.Configurable` + `tools.ConfigDescriber` for config UI support
+3. Optionally implement `tools.AvailabilityChecker` if the tool requires an external binary
+4. Register in `back/cmd/server/main.go` and `back/cmd/gendocs/main.go`
+5. Run `just docs` to update the docs
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..b1c5749
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+ko_fi: anotherhadi
diff --git a/.github/assets/banner.png b/.github/assets/banner.png
new file mode 100644
index 0000000..c845da3
Binary files /dev/null and b/.github/assets/banner.png differ
diff --git a/.github/assets/logo.png b/.github/assets/logo.png
new file mode 100644
index 0000000..d3f2c48
Binary files /dev/null and b/.github/assets/logo.png differ
diff --git a/.github/docs/how-it-works.md b/.github/docs/how-it-works.md
new file mode 100644
index 0000000..d29c5f7
--- /dev/null
+++ b/.github/docs/how-it-works.md
@@ -0,0 +1,36 @@
+# How it Works
+
+```
+Browser → POST /api/searches (target, type, profile)
+ ↓
+ Backend filters tools by:
+ · input type compatibility
+ · profile enabled/disabled rules
+ · required config fields (skips if missing)
+ ↓
+ All eligible tools run in parallel goroutines
+ ↓
+ Browser polls GET /api/searches/{id}
+ Results render progressively as tools complete
+```
+
+Each tool is a Go struct implementing a small interface: it declares what input types it accepts, what config it needs, and how to run. The engine handles the rest.
+
+## Architecture
+
+```
+iknowyou/
+├── back/ # Go backend
+│ ├── cmd/
+│ │ ├── server/ # Main HTTP server
+│ │ └── gendocs/ # Doc generator
+│ ├── config/ # YAML config models & builtin profiles
+│ └── internal/
+│ ├── api/ # Chi router + handlers
+│ ├── search/ # Parallel search orchestration
+│ └── tools/ # Tool interface + implementations
+└── front/ # Astro + Svelte frontend
+ └── src/
+ ├── pages/ # / · /tools · /profiles · /search/[id] · /cheatsheets · /help
+ └── components/ # Svelte interactive components
+```
diff --git a/.github/docs/tools.md b/.github/docs/tools.md
new file mode 100644
index 0000000..ada4261
--- /dev/null
+++ b/.github/docs/tools.md
@@ -0,0 +1,18 @@
+# Tools
+
+_12 tools registered._
+
+| Tool | Input types | Description | Link |
+|------|-------------|-------------|------|
+| [`user-scanner`](tools/user-scanner.md) | `email`, `username` | 🕵️♂️ (2-in-1) Email & Username OSINT suite. Analyzes 195+ scan vectors (95+ email / 100+ username) for security research, investigations, and digital footprinting. | [Link](https://github.com/kaifcodec/user-scanner) |
+| [`github-recon`](tools/github-recon.md) | `username`, `email` | GitHub OSINT reconnaissance tool. Gathers profile info, social links, organisations, SSH/GPG keys, commits, and more from a GitHub username or email. | [Link](https://github.com/anotherhadi/nur-osint) |
+| [`whois`](tools/whois.md) | `domain`, `ip` | WHOIS lookup for domain registration and IP ownership information. | [Link](https://en.wikipedia.org/wiki/WHOIS) |
+| [`dig`](tools/dig.md) | `domain`, `ip` | DNS lookup querying A, AAAA, MX, NS, TXT, and SOA records for a domain, or reverse DNS (PTR) for an IP. | [Link](https://linux.die.net/man/1/dig) |
+| [`ipinfo`](tools/ipinfo.md) | `ip` | IP geolocation via ipinfo.io — returns city, region, country, coordinates, ASN/org, timezone, and hostname. | [Link](https://ipinfo.io) |
+| [`gravatar-recon`](tools/gravatar-recon.md) | `email` | Gravatar OSINT tool. Extracts public profile data from a Gravatar account: name, bio, location, employment, social accounts, phone, and more. | [Link](https://github.com/anotherhadi/gravatar-recon) |
+| [`whoisfreaks`](tools/whoisfreaks.md) | `email`, `name`, `domain` | Reverse WHOIS lookup via WhoisFreaks — find all domains registered by an email, owner name, or keyword across 3.6B+ WHOIS records. | [Link](https://whoisfreaks.com) |
+| [`maigret`](tools/maigret.md) | `username` | Username OSINT across 3000+ sites. Searches social networks, forums, and online platforms for an account matching the target username. | [Link](https://github.com/soxoj/maigret) |
+| [`leakcheck`](tools/leakcheck.md) | `email`, `username`, `phone` | Data breach lookup via LeakCheck.io — searches 7B+ leaked records for email addresses, usernames, and phone numbers across hundreds of breaches. | [Link](https://leakcheck.io) |
+| [`crt.sh`](tools/crt.sh.md) | `domain` | SSL/TLS certificate transparency log search via crt.sh — enumerates subdomains and certificates issued for a domain. | [Link](https://crt.sh) |
+| [`breachdirectory`](tools/breachdirectory.md) | `email`, `username` | Data breach search via BreachDirectory — checks if an email, username, or phone appears in known data breaches and returns exposed passwords/hashes. | [Link](https://breachdirectory.org) |
+| [`wappalyzer`](tools/wappalyzer.md) | `domain` | Web technology fingerprinting via wappalyzergo — detects CMS, frameworks, web servers, analytics, CDN, and 1500+ other technologies running on a domain. | [Link](https://github.com/projectdiscovery/wappalyzergo) |
diff --git a/.github/docs/tools/breachdirectory.md b/.github/docs/tools/breachdirectory.md
new file mode 100644
index 0000000..a392261
--- /dev/null
+++ b/.github/docs/tools/breachdirectory.md
@@ -0,0 +1,22 @@
+# `breachdirectory`
+
+Data breach search via BreachDirectory — checks if an email, username, or phone appears in known data breaches and returns exposed passwords/hashes.
+
+**Source / documentation:** [https://breachdirectory.org](https://breachdirectory.org)
+
+## Input types
+
+- `email`
+- `username`
+
+## Configuration
+
+Configure globally via the Tools page or override per profile.
+
+| Field | Type | Required | Default | Description |
+|-------|------|:--------:|---------|-------------|
+| `api_key` | `string` | **yes** | - | RapidAPI key for BreachDirectory (required — get one at rapidapi.com/rohan-patra/api/breachdirectory) |
+
+---
+
+[← Back to tools index](../tools.md)
diff --git a/.github/docs/tools/crt.sh.md b/.github/docs/tools/crt.sh.md
new file mode 100644
index 0000000..e8a0835
--- /dev/null
+++ b/.github/docs/tools/crt.sh.md
@@ -0,0 +1,17 @@
+# `crt.sh`
+
+SSL/TLS certificate transparency log search via crt.sh — enumerates subdomains and certificates issued for a domain.
+
+**Source / documentation:** [https://crt.sh](https://crt.sh)
+
+## Input types
+
+- `domain`
+
+## Configuration
+
+This tool requires no configuration.
+
+---
+
+[← Back to tools index](../tools.md)
diff --git a/.github/docs/tools/dig.md b/.github/docs/tools/dig.md
new file mode 100644
index 0000000..f7ddf32
--- /dev/null
+++ b/.github/docs/tools/dig.md
@@ -0,0 +1,24 @@
+# `dig`
+
+DNS lookup querying A, AAAA, MX, NS, TXT, and SOA records for a domain, or reverse DNS (PTR) for an IP.
+
+**Source / documentation:** [https://linux.die.net/man/1/dig](https://linux.die.net/man/1/dig)
+
+## Input types
+
+- `domain`
+- `ip`
+
+## External dependencies
+
+The following binaries must be installed and available in `$PATH`:
+
+- `dig`
+
+## Configuration
+
+This tool requires no configuration.
+
+---
+
+[← Back to tools index](../tools.md)
diff --git a/.github/docs/tools/github-recon.md b/.github/docs/tools/github-recon.md
new file mode 100644
index 0000000..0c51bfc
--- /dev/null
+++ b/.github/docs/tools/github-recon.md
@@ -0,0 +1,30 @@
+# `github-recon`
+
+GitHub OSINT reconnaissance tool. Gathers profile info, social links, organisations, SSH/GPG keys, commits, and more from a GitHub username or email.
+
+**Source / documentation:** [https://github.com/anotherhadi/nur-osint](https://github.com/anotherhadi/nur-osint)
+
+## Input types
+
+- `username`
+- `email`
+
+## External dependencies
+
+The following binaries must be installed and available in `$PATH`:
+
+- `github-recon`
+
+## Configuration
+
+Configure globally via the Tools page or override per profile.
+
+| Field | Type | Required | Default | Description |
+|-------|------|:--------:|---------|-------------|
+| `token` | `string` | - | - | GitHub personal access token (enables higher rate limits and more data) |
+| `deepscan` | `bool` | - | `false` | Enable deep scan (slower - scans all repositories for authors/emails) |
+| `spoof_email` | `bool` | - | `false` | Include email spoofing check (email mode only, requires token) |
+
+---
+
+[← Back to tools index](../tools.md)
diff --git a/.github/docs/tools/gravatar-recon.md b/.github/docs/tools/gravatar-recon.md
new file mode 100644
index 0000000..e801f55
--- /dev/null
+++ b/.github/docs/tools/gravatar-recon.md
@@ -0,0 +1,23 @@
+# `gravatar-recon`
+
+Gravatar OSINT tool. Extracts public profile data from a Gravatar account: name, bio, location, employment, social accounts, phone, and more.
+
+**Source / documentation:** [https://github.com/anotherhadi/gravatar-recon](https://github.com/anotherhadi/gravatar-recon)
+
+## Input types
+
+- `email`
+
+## External dependencies
+
+The following binaries must be installed and available in `$PATH`:
+
+- `gravatar-recon`
+
+## Configuration
+
+This tool requires no configuration.
+
+---
+
+[← Back to tools index](../tools.md)
diff --git a/.github/docs/tools/ipinfo.md b/.github/docs/tools/ipinfo.md
new file mode 100644
index 0000000..6860152
--- /dev/null
+++ b/.github/docs/tools/ipinfo.md
@@ -0,0 +1,21 @@
+# `ipinfo`
+
+IP geolocation via ipinfo.io — returns city, region, country, coordinates, ASN/org, timezone, and hostname.
+
+**Source / documentation:** [https://ipinfo.io](https://ipinfo.io)
+
+## Input types
+
+- `ip`
+
+## Configuration
+
+Configure globally via the Tools page or override per profile.
+
+| Field | Type | Required | Default | Description |
+|-------|------|:--------:|---------|-------------|
+| `token` | `string` | - | - | ipinfo.io API token (optional — free tier allows 50k req/month without one) |
+
+---
+
+[← Back to tools index](../tools.md)
diff --git a/.github/docs/tools/leakcheck.md b/.github/docs/tools/leakcheck.md
new file mode 100644
index 0000000..f01dbf9
--- /dev/null
+++ b/.github/docs/tools/leakcheck.md
@@ -0,0 +1,23 @@
+# `leakcheck`
+
+Data breach lookup via LeakCheck.io — searches 7B+ leaked records for email addresses, usernames, and phone numbers across hundreds of breaches.
+
+**Source / documentation:** [https://leakcheck.io](https://leakcheck.io)
+
+## Input types
+
+- `email`
+- `username`
+- `phone`
+
+## Configuration
+
+Configure globally via the Tools page or override per profile.
+
+| Field | Type | Required | Default | Description |
+|-------|------|:--------:|---------|-------------|
+| `api_key` | `string` | **yes** | - | LeakCheck API key (required — get one at leakcheck.io) |
+
+---
+
+[← Back to tools index](../tools.md)
diff --git a/.github/docs/tools/maigret.md b/.github/docs/tools/maigret.md
new file mode 100644
index 0000000..dca85bb
--- /dev/null
+++ b/.github/docs/tools/maigret.md
@@ -0,0 +1,27 @@
+# `maigret`
+
+Username OSINT across 3000+ sites. Searches social networks, forums, and online platforms for an account matching the target username.
+
+**Source / documentation:** [https://github.com/soxoj/maigret](https://github.com/soxoj/maigret)
+
+## Input types
+
+- `username`
+
+## External dependencies
+
+The following binaries must be installed and available in `$PATH`:
+
+- `maigret`
+
+## Configuration
+
+Configure globally via the Tools page or override per profile.
+
+| Field | Type | Required | Default | Description |
+|-------|------|:--------:|---------|-------------|
+| `all_sites` | `bool` | - | `false` | Scan all sites in the database instead of just the top 500 (slower) |
+
+---
+
+[← Back to tools index](../tools.md)
diff --git a/.github/docs/tools/user-scanner.md b/.github/docs/tools/user-scanner.md
new file mode 100644
index 0000000..2814ef3
--- /dev/null
+++ b/.github/docs/tools/user-scanner.md
@@ -0,0 +1,29 @@
+# `user-scanner`
+
+🕵️♂️ (2-in-1) Email & Username OSINT suite. Analyzes 195+ scan vectors (95+ email / 100+ username) for security research, investigations, and digital footprinting.
+
+**Source / documentation:** [https://github.com/kaifcodec/user-scanner](https://github.com/kaifcodec/user-scanner)
+
+## Input types
+
+- `email`
+- `username`
+
+## External dependencies
+
+The following binaries must be installed and available in `$PATH`:
+
+- `user-scanner`
+
+## Configuration
+
+Configure globally via the Tools page or override per profile.
+
+| Field | Type | Required | Default | Description |
+|-------|------|:--------:|---------|-------------|
+| `allow_loud` | `bool` | - | `false` | Enable scanning sites that may send emails/notifications (password resets, etc.) |
+| `only_found` | `bool` | - | `true` | Only show sites where the username/email was found |
+
+---
+
+[← Back to tools index](../tools.md)
diff --git a/.github/docs/tools/wappalyzer.md b/.github/docs/tools/wappalyzer.md
new file mode 100644
index 0000000..1b75458
--- /dev/null
+++ b/.github/docs/tools/wappalyzer.md
@@ -0,0 +1,17 @@
+# `wappalyzer`
+
+Web technology fingerprinting via wappalyzergo — detects CMS, frameworks, web servers, analytics, CDN, and 1500+ other technologies running on a domain.
+
+**Source / documentation:** [https://github.com/projectdiscovery/wappalyzergo](https://github.com/projectdiscovery/wappalyzergo)
+
+## Input types
+
+- `domain`
+
+## Configuration
+
+This tool requires no configuration.
+
+---
+
+[← Back to tools index](../tools.md)
diff --git a/.github/docs/tools/whois.md b/.github/docs/tools/whois.md
new file mode 100644
index 0000000..9f37217
--- /dev/null
+++ b/.github/docs/tools/whois.md
@@ -0,0 +1,24 @@
+# `whois`
+
+WHOIS lookup for domain registration and IP ownership information.
+
+**Source / documentation:** [https://en.wikipedia.org/wiki/WHOIS](https://en.wikipedia.org/wiki/WHOIS)
+
+## Input types
+
+- `domain`
+- `ip`
+
+## External dependencies
+
+The following binaries must be installed and available in `$PATH`:
+
+- `whois`
+
+## Configuration
+
+This tool requires no configuration.
+
+---
+
+[← Back to tools index](../tools.md)
diff --git a/.github/docs/tools/whoisfreaks.md b/.github/docs/tools/whoisfreaks.md
new file mode 100644
index 0000000..74c1bea
--- /dev/null
+++ b/.github/docs/tools/whoisfreaks.md
@@ -0,0 +1,23 @@
+# `whoisfreaks`
+
+Reverse WHOIS lookup via WhoisFreaks — find all domains registered by an email, owner name, or keyword across 3.6B+ WHOIS records.
+
+**Source / documentation:** [https://whoisfreaks.com](https://whoisfreaks.com)
+
+## Input types
+
+- `email`
+- `name`
+- `domain`
+
+## Configuration
+
+Configure globally via the Tools page or override per profile.
+
+| Field | Type | Required | Default | Description |
+|-------|------|:--------:|---------|-------------|
+| `api_key` | `string` | **yes** | - | WhoisFreaks API key (required — free account at whoisfreaks.com) |
+
+---
+
+[← Back to tools index](../tools.md)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..90be77b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.claude/
+todolist.md
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7ce7478
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Hadi
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..db28819
--- /dev/null
+++ b/README.md
@@ -0,0 +1,196 @@
+
+
+
+
+# I Know You
+
+**Self-hosted OSINT aggregation platform**
+
+Run dozens of open-source intelligence tools against a single target in parallel; all from one clean web interface.
+
+[](https://go.dev)
+[](https://astro.build)
+[](LICENSE)
+[](https://github.com/anotherhadi/iknowyou)
+
+
+
+---
+
+## What is it?
+
+IKY (iknowyou) is a **self-hosted OSINT (Open-Source Intelligence) platform** that centralises reconnaissance tools into a single reactive web interface. Instead of juggling terminals, browser tabs, and disconnected CLI tools, you type a target once and get results streaming in real time.
+
+Designed for security researchers, penetration testers, and OSINT investigators who need speed and visibility without compromising on control.
+
+**Supported target types:** `email`, `username`, `domain`, `IP`, `phone`, ...
+
+## Features
+
+- **Parallel execution**: all tools run simultaneously; results stream in as they arrive
+- **Profile system**: create named profiles to enable/disable specific tools or override their config per investigation type (quick recon vs. thorough sweep)
+- **Per-tool configuration**: set API keys, rate limits, and options globally or per profile
+- **Tool availability checks**: tools that depend on an external binary report their status; the interface shows which tools are ready, which need config, and which are unavailable
+- **Search history**: completed searches are kept in memory; results can be reviewed without re-running
+- **Extensible architecture**: adding a new tool is a single Go file implementing one interface, registered in one line
+- **Production-ready**: The configuration is YAML-based and read-only mode is supported (for Nix-managed deployments). A NixOS module is available.
+
+## Profiles
+
+Profiles let you tailor which tools run and how, without touching the global config.
+
+| Profile | Type | Description |
+| ---------- | -------- | ---------------------------------------------------------------------------- |
+| `default` | Built-in | All tools active, default settings |
+| `hard` | Built-in | All tools active, including those that may leave traces at the target |
+| _(custom)_ | User | Your own combination of enabled/disabled tools and per-tool config overrides |
+
+Create and manage custom profiles from the **Profiles** page.
+
+## Useful Links
+
+- [See the list of tools](.github/docs/tools.md)
+- [Learn how it works](.github/docs/how-it-works.md)
+- [Learn how to contribute](.github/CONTRIBUTING.md)
+
+## Deployment
+
+
+Deploy on NixOS
+
+1. In the `flake.nix` file, add `iknowyou` in the `inputs` section and import
+ the `iknowyou.nixosModules.default` module:
+
+```nix
+{
+ inputs = {
+ iknowyou.url = "github:anotherhadi/iknowyou";
+ };
+ outputs = {
+ # ...
+ modules = [
+ inputs.iknowyou.nixosModules.default
+ ];
+ # ...
+ }
+}
+```
+
+2. Enable the service:
+
+```nix
+services.iknowyou = {
+ enable = true;
+ port = 8080;
+ openFirewall = true;
+};
+```
+
+All tool dependencies are included automatically.
+
+
+
+## Configuring
+
+
+Via the web interface
+
+No files needed. API keys, tool settings, and profiles can all be managed from the **Settings** page. Changes are written back to the config file automatically.
+
+This only works if the config file is writable. On NixOS with a read-only store path, use one of the methods below instead.
+
+
+
+
+Via a YAML file
+
+Create `/etc/iky/config.yaml` (or any path, then point `IKY_CONFIG` to it):
+
+```yaml
+tools:
+ github-recon:
+ token: ghp_yourtoken
+ whoisfreaks:
+ api_key: yourkey
+ ipinfo:
+ token: yourtoken
+ breachdirectory:
+ api_key: yourkey
+
+profiles:
+ quick:
+ enabled:
+ - whois
+ - dig
+ - crt.sh
+ disabled: []
+```
+
+Only include the tools you want to configure — everything else falls back to defaults.
+
+
+
+
+Via Nix + sops-nix (NixOS)
+
+API keys should never go in the Nix store (world-readable). Use [sops-nix](https://github.com/Mic92/sops-nix) to store the config file as an encrypted secret and have it decrypted at boot with the right permissions.
+
+1. Add your IKY config to your sops-encrypted secrets file (e.g. `secrets/iky.yaml`):
+
+```yaml
+iky-config: |
+ tools:
+ github-recon:
+ token: ghp_yourtoken
+ whoisfreaks:
+ api_key: yourkey
+ ipinfo:
+ token: yourtoken
+ profiles:
+ quick:
+ enabled:
+ - whois
+ - dig
+ - crt.sh
+ disabled: []
+```
+
+2. Declare the secret and point the service to it:
+
+```nix
+sops.secrets."iky-config" = {
+ owner = "iknowyou";
+ mode = "0400";
+ restartUnits = [ "iknowyou.service" ];
+};
+
+services.iknowyou = {
+ enable = true;
+ configFile = config.sops.secrets."iky-config".path;
+};
+```
+
+The module creates the `iknowyou` group automatically and adds it as a supplementary group to the service, so the `DynamicUser` can read the secret without needing a static user.
+
+
+
+## ⚠️ Legal Disclaimer
+
+**IKY is intended for legal, authorized use only.**
+
+This software is designed for:
+
+- Security research on systems you own or have **explicit written permission** to test
+- Penetration testing engagements conducted under a signed scope of work
+- OSINT investigations carried out in compliance with applicable laws and regulations
+- Educational purposes in controlled, isolated environments
+
+**Using IKY against targets without prior authorization may be illegal** under computer fraud and privacy laws. The author(s) of IKY accept **no responsibility** for any misuse, damage, legal consequences, or harm caused by this software. By using IKY you agree that you are solely responsible for ensuring your use is lawful and ethical.
+
+---
+
+
diff --git a/back/.air.toml b/back/.air.toml
new file mode 100644
index 0000000..0d00807
--- /dev/null
+++ b/back/.air.toml
@@ -0,0 +1,24 @@
+root = "."
+testdata_dir = "testdata"
+tmp_dir = "tmp"
+
+[build]
+ bin = "./tmp/main"
+ cmd = "go build -o ./tmp/main ./cmd/server/main.go"
+ include_ext = ["go", "toml"]
+ exclude_dir = ["tmp", "vendor", "node_modules"]
+ delay = 1000
+ stop_on_error = true
+ clean_on_exit = true
+
+[log]
+ time = true
+
+[color]
+ main = "magenta"
+ watcher = "cyan"
+ build = "yellow"
+ runner = "green"
+
+[screen]
+ clear_on_rebuild = true
diff --git a/back/.gitignore b/back/.gitignore
new file mode 100644
index 0000000..c49656d
--- /dev/null
+++ b/back/.gitignore
@@ -0,0 +1,2 @@
+tmp/
+config.yaml
diff --git a/back/cmd/gendocs/main.go b/back/cmd/gendocs/main.go
new file mode 100644
index 0000000..acc042a
--- /dev/null
+++ b/back/cmd/gendocs/main.go
@@ -0,0 +1,155 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/anotherhadi/iknowyou/internal/registry"
+ "github.com/anotherhadi/iknowyou/internal/tools"
+)
+
+func main() {
+ out := flag.String("out", "../.github/docs", "output directory for generated docs")
+ flag.Parse()
+
+ toolsDir := filepath.Join(*out, "tools")
+ if _, err := os.Stat(toolsDir); err == nil {
+ if err := os.RemoveAll(toolsDir); err != nil {
+ fatalf("removing tools dir: %v", err)
+ }
+ }
+
+ if err := os.MkdirAll(toolsDir, 0o755); err != nil {
+ fatalf("mkdir: %v", err)
+ }
+
+ runners := make([]tools.ToolRunner, len(registry.Factories))
+ for i, f := range registry.Factories {
+ runners[i] = f()
+ }
+
+ if err := writeIndex(*out, runners); err != nil {
+ fatalf("index: %v", err)
+ }
+ for _, r := range runners {
+ if err := writeTool(*out, r); err != nil {
+ fatalf("tool %s: %v", r.Name(), err)
+ }
+ }
+
+ fmt.Printf("✓ generated docs for %d tools → %s\n", len(runners), *out)
+}
+
+// writeIndex writes the tools.md index table.
+func writeIndex(outDir string, runners []tools.ToolRunner) error {
+ var b strings.Builder
+
+ b.WriteString("# Tools\n\n")
+ fmt.Fprintf(&b, "_%d tools registered._\n\n", len(runners))
+
+ b.WriteString("| Tool | Input types | Description | Link |\n")
+ b.WriteString("|------|-------------|-------------|------|\n")
+
+ for _, r := range runners {
+ types := make([]string, len(r.InputTypes()))
+ for i, t := range r.InputTypes() {
+ types[i] = fmt.Sprintf("`%s`", t)
+ }
+ link := fmt.Sprintf("[`%s`](tools/%s.md)", r.Name(), r.Name())
+ projectLink := ""
+ if r.Link() != "" {
+ projectLink = fmt.Sprintf("[Link](%s)", r.Link())
+ }
+ fmt.Fprintf(&b, "| %s | %s | %s | %s |\n",
+ link,
+ strings.Join(types, ", "),
+ r.Description(),
+ projectLink,
+ )
+ }
+
+ return writeFile(filepath.Join(outDir, "tools.md"), b.String())
+}
+
+// writeTool writes the per-tool detail page.
+func writeTool(outDir string, r tools.ToolRunner) error {
+ var b strings.Builder
+
+ fmt.Fprintf(&b, "# `%s`\n\n", r.Name())
+ fmt.Fprintf(&b, "%s\n\n", r.Description())
+
+ if r.Link() != "" {
+ fmt.Fprintf(&b, "**Source / documentation:** [%s](%s)\n\n", r.Link(), r.Link())
+ }
+
+ // Input types
+ b.WriteString("## Input types\n\n")
+ for _, t := range r.InputTypes() {
+ fmt.Fprintf(&b, "- `%s`\n", t)
+ }
+ b.WriteString("\n")
+
+ // External binary dependencies
+ if lister, ok := r.(tools.DependencyLister); ok {
+ if deps := lister.Dependencies(); len(deps) > 0 {
+ b.WriteString("## External dependencies\n\n")
+ b.WriteString("The following binaries must be installed and available in `$PATH`:\n\n")
+ for _, dep := range deps {
+ fmt.Fprintf(&b, "- `%s`\n", dep)
+ }
+ b.WriteString("\n")
+ }
+ }
+
+ // Configuration
+ if d, ok := r.(tools.ConfigDescriber); ok {
+ fields := d.ConfigFields()
+ if len(fields) > 0 {
+ b.WriteString("## Configuration\n\n")
+ b.WriteString("Configure globally via the Tools page or override per profile.\n\n")
+ b.WriteString("| Field | Type | Required | Default | Description |\n")
+ b.WriteString("|-------|------|:--------:|---------|-------------|\n")
+ for _, f := range fields {
+ req := "-"
+ if f.Required {
+ req = "**yes**"
+ }
+ def := "-"
+ if f.Default != nil && fmt.Sprintf("%v", f.Default) != "" {
+ def = fmt.Sprintf("`%v`", f.Default)
+ }
+ desc := f.Description
+ if desc == "" {
+ desc = "-"
+ }
+ fmt.Fprintf(&b, "| `%s` | `%s` | %s | %s | %s |\n",
+ f.Name, f.Type, req, def, desc,
+ )
+ }
+ b.WriteString("\n")
+ } else {
+ b.WriteString("## Configuration\n\nThis tool has no configuration fields.\n\n")
+ }
+ } else if _, ok := r.(tools.Configurable); ok {
+ b.WriteString("## Configuration\n\nThis tool is configurable but does not expose field metadata.\n\n")
+ } else {
+ b.WriteString("## Configuration\n\nThis tool requires no configuration.\n\n")
+ }
+
+ b.WriteString("---\n\n")
+ b.WriteString("[← Back to tools index](../tools.md)\n")
+
+ return writeFile(filepath.Join(outDir, "tools", r.Name()+".md"), b.String())
+}
+
+func writeFile(path, content string) error {
+ return os.WriteFile(path, []byte(content), 0o644)
+}
+
+func fatalf(format string, args ...any) {
+ fmt.Fprintf(os.Stderr, "gendocs: "+format+"\n", args...)
+ os.Exit(1)
+}
diff --git a/back/cmd/server/main.go b/back/cmd/server/main.go
new file mode 100644
index 0000000..2876aaf
--- /dev/null
+++ b/back/cmd/server/main.go
@@ -0,0 +1,62 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "net/http"
+ "os/signal"
+ "syscall"
+ "time"
+
+ "github.com/anotherhadi/iknowyou/config/env"
+ internalapi "github.com/anotherhadi/iknowyou/internal/api"
+ "github.com/anotherhadi/iknowyou/internal/registry"
+ "github.com/anotherhadi/iknowyou/internal/search"
+)
+
+func main() {
+ cfg, err := env.Load()
+ if err != nil {
+ log.Fatalf("env: %v", err)
+ }
+
+ manager := search.NewManager(cfg.ConfigPath, registry.Factories, cfg.SearchTTL, cfg.CleanupInterval)
+ defer manager.Stop()
+
+ if cfg.Demo {
+ manager.InjectDemoSearches()
+ log.Println("demo mode enabled")
+ }
+
+ router := internalapi.NewRouter(manager, registry.Factories, cfg.ConfigPath, cfg.FrontDir, cfg.Demo)
+
+ srv := &http.Server{
+ Addr: fmt.Sprintf(":%d", cfg.Port),
+ Handler: router,
+ ReadTimeout: 10 * time.Second,
+ WriteTimeout: 30 * time.Second,
+ IdleTimeout: 60 * time.Second,
+ }
+
+ ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
+ defer stop()
+
+ go func() {
+ log.Printf("listening on :%d (config: %s)", cfg.Port, cfg.ConfigPath)
+ if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ log.Fatalf("server error: %v", err)
+ }
+ }()
+
+ <-ctx.Done()
+ log.Println("shutting down...")
+
+ shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+ if err := srv.Shutdown(shutdownCtx); err != nil {
+ log.Fatalf("graceful shutdown failed: %v", err)
+ }
+
+ log.Println("stopped")
+}
diff --git a/back/config/builtin.go b/back/config/builtin.go
new file mode 100644
index 0000000..80226ac
--- /dev/null
+++ b/back/config/builtin.go
@@ -0,0 +1,62 @@
+package config
+
+import "gopkg.in/yaml.v3"
+
+// BuiltinProfile is a hardcoded, read-only profile with optional tool config overrides.
+type BuiltinProfile struct {
+ Notes string
+ Profile Profile
+ Tools map[string]map[string]any
+}
+
+var BuiltinProfiles = map[string]BuiltinProfile{
+ "default": {
+ Notes: "Standard profile. All tools are active with default settings.",
+ Profile: Profile{},
+ },
+ "hard": {
+ Notes: "Aggressive profile. All tools are active, including those that may send notifications to the target.",
+ Profile: Profile{},
+ Tools: map[string]map[string]any{
+ "user-scanner": {"allow_loud": true},
+ "github-recon": {"deepscan": true},
+ },
+ },
+}
+
+func ApplyBuiltinToolOverride(profileName, toolName string, dst any) error {
+ builtin, ok := BuiltinProfiles[profileName]
+ if !ok || builtin.Tools == nil {
+ return nil
+ }
+ overrides, hasOverride := builtin.Tools[toolName]
+ if !hasOverride {
+ return nil
+ }
+ b, err := yaml.Marshal(overrides)
+ if err != nil {
+ return err
+ }
+ return yaml.Unmarshal(b, dst)
+}
+
+func ActiveToolsForProfile(p Profile, allToolNames []string) []string {
+ active := allToolNames
+ if len(p.Enabled) > 0 {
+ active = p.Enabled
+ }
+ if len(p.Disabled) > 0 {
+ blacklist := make(map[string]struct{}, len(p.Disabled))
+ for _, n := range p.Disabled {
+ blacklist[n] = struct{}{}
+ }
+ var filtered []string
+ for _, n := range active {
+ if _, skip := blacklist[n]; !skip {
+ filtered = append(filtered, n)
+ }
+ }
+ active = filtered
+ }
+ return active
+}
diff --git a/back/config/config.go b/back/config/config.go
new file mode 100644
index 0000000..903d5f3
--- /dev/null
+++ b/back/config/config.go
@@ -0,0 +1,144 @@
+package config
+
+import (
+ "fmt"
+ "log"
+ "os"
+
+ "gopkg.in/yaml.v3"
+)
+
+type Config struct {
+ Tools map[string]yaml.Node `yaml:"tools" json:"tools"`
+ Profiles map[string]Profile `yaml:"profiles" json:"profiles"`
+}
+
+type Profile struct {
+ Notes string `yaml:"notes,omitempty" json:"notes,omitempty"`
+ Tools map[string]yaml.Node `yaml:"tools" json:"tools"`
+ Enabled []string `yaml:"enabled" json:"enabled"`
+ Disabled []string `yaml:"disabled" json:"disabled"`
+}
+
+func (c *Config) DecodeEffective(toolName, profileName string, dst any) error {
+ if node, ok := c.Tools[toolName]; ok {
+ if err := node.Decode(dst); err != nil {
+ return fmt.Errorf("config: decoding global config for tool %q: %w", toolName, err)
+ }
+ }
+
+ if profileName != "" {
+ // Builtin profiles have their overrides defined in Go, not in YAML.
+ if _, isBuiltin := BuiltinProfiles[profileName]; isBuiltin {
+ return ApplyBuiltinToolOverride(profileName, toolName, dst)
+ }
+ p, ok := c.Profiles[profileName]
+ if !ok {
+ return fmt.Errorf("config: unknown profile %q", profileName)
+ }
+ if node, ok := p.Tools[toolName]; ok {
+ if err := node.Decode(dst); err != nil {
+ return fmt.Errorf("config: decoding profile %q override for tool %q: %w", profileName, toolName, err)
+ }
+ }
+ }
+
+ return nil
+}
+
+func (c *Config) ActiveTools(profileName string, allToolNames []string) ([]string, error) {
+ if profileName == "" {
+ return allToolNames, nil
+ }
+ if builtin, ok := BuiltinProfiles[profileName]; ok {
+ return ActiveToolsForProfile(builtin.Profile, allToolNames), nil
+ }
+ p, ok := c.Profiles[profileName]
+ if !ok {
+ return nil, fmt.Errorf("config: unknown profile %q", profileName)
+ }
+ return ActiveToolsForProfile(p, allToolNames), nil
+}
+
+// IsReadonly reports whether the config file at path cannot be written to.
+// Returns false if the file does not exist (it can still be created).
+func IsReadonly(path string) bool {
+ f, err := os.OpenFile(path, os.O_WRONLY, 0)
+ if err != nil {
+ return os.IsPermission(err)
+ }
+ f.Close()
+ return false
+}
+
+func Load(path string) (*Config, error) {
+ f, err := os.Open(path)
+ if os.IsNotExist(err) {
+ log.Printf("config: %q not found, starting with empty config", path)
+ return Default(), nil
+ }
+ if err != nil {
+ return nil, fmt.Errorf("config: open %q: %w", path, err)
+ }
+ defer func() { _ = f.Close() }()
+
+ var cfg Config
+ dec := yaml.NewDecoder(f)
+ dec.KnownFields(true)
+ if err := dec.Decode(&cfg); err != nil {
+ return nil, fmt.Errorf("config: decode: %w", err)
+ }
+ return &cfg, nil
+}
+
+func Default() *Config {
+ return &Config{
+ Tools: make(map[string]yaml.Node),
+ Profiles: make(map[string]Profile),
+ }
+}
+
+func Save(path string, cfg *Config) error {
+ f, err := os.Create(path)
+ if err != nil {
+ return fmt.Errorf("config: create %q: %w", path, err)
+ }
+ defer func() { _ = f.Close() }()
+
+ enc := yaml.NewEncoder(f)
+ enc.SetIndent(2)
+ if err := enc.Encode(cfg); err != nil {
+ return fmt.Errorf("config: encode: %w", err)
+ }
+ return nil
+}
+
+// MergeNodePatch merges patch key-values into an existing yaml.Node (mapping).
+// If existing is a zero value, it starts from an empty mapping.
+func MergeNodePatch(existing yaml.Node, patch map[string]any) (yaml.Node, error) {
+ var m map[string]any
+ if existing.Kind != 0 {
+ if err := existing.Decode(&m); err != nil {
+ return yaml.Node{}, fmt.Errorf("config: decode node: %w", err)
+ }
+ }
+ if m == nil {
+ m = make(map[string]any)
+ }
+ for k, v := range patch {
+ m[k] = v
+ }
+
+ b, err := yaml.Marshal(m)
+ if err != nil {
+ return yaml.Node{}, fmt.Errorf("config: marshal: %w", err)
+ }
+ var doc yaml.Node
+ if err := yaml.Unmarshal(b, &doc); err != nil {
+ return yaml.Node{}, fmt.Errorf("config: unmarshal: %w", err)
+ }
+ if doc.Kind == yaml.DocumentNode && len(doc.Content) == 1 {
+ return *doc.Content[0], nil
+ }
+ return doc, nil
+}
diff --git a/back/config/env/env.go b/back/config/env/env.go
new file mode 100644
index 0000000..e318a56
--- /dev/null
+++ b/back/config/env/env.go
@@ -0,0 +1,64 @@
+package env
+
+import (
+ "fmt"
+ "os"
+ "strconv"
+ "time"
+)
+
+type Config struct {
+ Port int
+ ConfigPath string
+ FrontDir string // when set, serves the Astro static build at "/"
+ SearchTTL time.Duration
+ CleanupInterval time.Duration
+ Demo bool // when true, disables searches and config mutations
+}
+
+func Load() (*Config, error) {
+ cfg := &Config{
+ Port: 8080,
+ ConfigPath: "/etc/iky/config.yaml",
+ SearchTTL: 48 * time.Hour,
+ CleanupInterval: time.Hour,
+ }
+
+ if v := os.Getenv("IKY_PORT"); v != "" {
+ p, err := strconv.Atoi(v)
+ if err != nil || p < 1 || p > 65535 {
+ return nil, fmt.Errorf("env: IKY_PORT %q is not a valid port number", v)
+ }
+ cfg.Port = p
+ }
+
+ if v := os.Getenv("IKY_CONFIG"); v != "" {
+ cfg.ConfigPath = v
+ }
+
+ if v := os.Getenv("IKY_FRONT_DIR"); v != "" {
+ cfg.FrontDir = v
+ }
+
+ if v := os.Getenv("IKY_SEARCH_TTL"); v != "" {
+ d, err := time.ParseDuration(v)
+ if err != nil {
+ return nil, fmt.Errorf("env: IKY_SEARCH_TTL %q is not a valid duration", v)
+ }
+ cfg.SearchTTL = d
+ }
+
+ if v := os.Getenv("IKY_CLEANUP_INTERVAL"); v != "" {
+ d, err := time.ParseDuration(v)
+ if err != nil {
+ return nil, fmt.Errorf("env: IKY_CLEANUP_INTERVAL %q is not a valid duration", v)
+ }
+ cfg.CleanupInterval = d
+ }
+
+ if v := os.Getenv("IKY_DEMO"); v == "true" || v == "1" {
+ cfg.Demo = true
+ }
+
+ return cfg, nil
+}
diff --git a/back/go.mod b/back/go.mod
new file mode 100644
index 0000000..0949e20
--- /dev/null
+++ b/back/go.mod
@@ -0,0 +1,20 @@
+module github.com/anotherhadi/iknowyou
+
+go 1.25.7
+
+require (
+ github.com/go-chi/chi/v5 v5.2.5
+ github.com/google/uuid v1.6.0
+ gopkg.in/yaml.v3 v3.0.1
+)
+
+require github.com/creack/pty v1.1.24
+
+require (
+ github.com/projectdiscovery/wappalyzergo v0.2.75 // indirect
+ github.com/tidwall/gjson v1.18.0 // indirect
+ github.com/tidwall/match v1.1.1 // indirect
+ github.com/tidwall/pretty v1.2.0 // indirect
+ golang.org/x/net v0.52.0 // indirect
+ golang.org/x/time v0.15.0 // indirect
+)
diff --git a/back/go.sum b/back/go.sum
new file mode 100644
index 0000000..1997a84
--- /dev/null
+++ b/back/go.sum
@@ -0,0 +1,22 @@
+github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
+github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
+github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
+github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/projectdiscovery/wappalyzergo v0.2.75 h1:ScmpgoYuIzERh4lJpjWPPY89PUWbhUu6vFbCYAr0kWc=
+github.com/projectdiscovery/wappalyzergo v0.2.75/go.mod h1:hRsnKNleH693FFJsBOD5NMUDbxw/Q94f0Oq2OV04Q6M=
+github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
+github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
+github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
+golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
+golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
+golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/back/internal/api/handler/config.go b/back/internal/api/handler/config.go
new file mode 100644
index 0000000..1a2d850
--- /dev/null
+++ b/back/internal/api/handler/config.go
@@ -0,0 +1,593 @@
+package handler
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "sort"
+ "sync"
+
+ "github.com/go-chi/chi/v5"
+ "gopkg.in/yaml.v3"
+
+ "github.com/anotherhadi/iknowyou/config"
+ "github.com/anotherhadi/iknowyou/internal/respond"
+ "github.com/anotherhadi/iknowyou/internal/tools"
+)
+
+type ConfigHandler struct {
+ configPath string
+ factories []func() tools.ToolRunner
+ demo bool
+ mu sync.Mutex
+}
+
+func NewConfigHandler(configPath string, factories []func() tools.ToolRunner, demo bool) *ConfigHandler {
+ return &ConfigHandler{configPath: configPath, factories: factories, demo: demo}
+}
+
+// GET /api/config
+func (h *ConfigHandler) Get(w http.ResponseWriter, r *http.Request) {
+ cfg, err := config.Load(h.configPath)
+ if err != nil {
+ respond.Error(w, http.StatusInternalServerError, err.Error())
+ return
+ }
+ toolConfigs := make(map[string]any, len(cfg.Tools))
+ for toolName, node := range cfg.Tools {
+ var m map[string]any
+ if err := node.Decode(&m); err == nil {
+ toolConfigs[toolName] = m
+ }
+ }
+ respond.JSON(w, http.StatusOK, map[string]any{
+ "tools": toolConfigs,
+ "profiles": cfg.Profiles,
+ "readonly": h.demo || config.IsReadonly(h.configPath),
+ "demo": h.demo,
+ })
+}
+
+type profileSummary struct {
+ Name string `json:"name"`
+ Notes string `json:"notes,omitempty"`
+ Readonly bool `json:"readonly"`
+}
+
+// GET /api/config/profiles
+func (h *ConfigHandler) ListProfiles(w http.ResponseWriter, r *http.Request) {
+ cfg, err := config.Load(h.configPath)
+ if err != nil {
+ respond.Error(w, http.StatusInternalServerError, err.Error())
+ return
+ }
+
+ builtinNames := make([]string, 0, len(config.BuiltinProfiles))
+ for name := range config.BuiltinProfiles {
+ builtinNames = append(builtinNames, name)
+ }
+ sort.Strings(builtinNames)
+ summaries := make([]profileSummary, 0, len(builtinNames)+len(cfg.Profiles))
+ for _, name := range builtinNames {
+ summaries = append(summaries, profileSummary{Name: name, Notes: config.BuiltinProfiles[name].Notes, Readonly: true})
+ }
+
+ names := make([]string, 0, len(cfg.Profiles))
+ for name := range cfg.Profiles {
+ names = append(names, name)
+ }
+ sort.Strings(names)
+ for _, name := range names {
+ p := cfg.Profiles[name]
+ summaries = append(summaries, profileSummary{Name: name, Notes: p.Notes, Readonly: false})
+ }
+
+ respond.JSON(w, http.StatusOK, summaries)
+}
+
+type profileDetail struct {
+ Name string `json:"name"`
+ Notes string `json:"notes,omitempty"`
+ Readonly bool `json:"readonly"`
+ Enabled []string `json:"enabled"`
+ Disabled []string `json:"disabled"`
+ Tools map[string]any `json:"tools"`
+ ActiveTools []string `json:"active_tools"`
+}
+
+// GET /api/config/profiles/{name}
+func (h *ConfigHandler) GetProfile(w http.ResponseWriter, r *http.Request) {
+ name := chi.URLParam(r, "name")
+
+ allNames := make([]string, 0, len(h.factories))
+ for _, factory := range h.factories {
+ allNames = append(allNames, factory().Name())
+ }
+
+ if builtin, ok := config.BuiltinProfiles[name]; ok {
+ activeTools := config.ActiveToolsForProfile(builtin.Profile, allNames)
+ if activeTools == nil {
+ activeTools = allNames
+ }
+ enabled := builtin.Profile.Enabled
+ if enabled == nil {
+ enabled = []string{}
+ }
+ disabled := builtin.Profile.Disabled
+ if disabled == nil {
+ disabled = []string{}
+ }
+ respond.JSON(w, http.StatusOK, profileDetail{
+ Name: name,
+ Notes: builtin.Notes,
+ Readonly: true,
+ Enabled: enabled,
+ Disabled: disabled,
+ Tools: map[string]any{},
+ ActiveTools: activeTools,
+ })
+ return
+ }
+
+ cfg, err := config.Load(h.configPath)
+ if err != nil {
+ respond.Error(w, http.StatusInternalServerError, err.Error())
+ return
+ }
+ p, ok := cfg.Profiles[name]
+ if !ok {
+ respond.Error(w, http.StatusNotFound, "profile not found")
+ return
+ }
+
+ toolOverrides := make(map[string]any, len(p.Tools))
+ for toolName, node := range p.Tools {
+ var m map[string]any
+ if err := node.Decode(&m); err == nil {
+ toolOverrides[toolName] = m
+ }
+ }
+
+ activeTools, _ := cfg.ActiveTools(name, allNames)
+
+ enabled := p.Enabled
+ if enabled == nil {
+ enabled = []string{}
+ }
+ disabled := p.Disabled
+ if disabled == nil {
+ disabled = []string{}
+ }
+
+ respond.JSON(w, http.StatusOK, profileDetail{
+ Name: name,
+ Notes: p.Notes,
+ Readonly: false,
+ Enabled: enabled,
+ Disabled: disabled,
+ Tools: toolOverrides,
+ ActiveTools: activeTools,
+ })
+}
+
+// POST /api/config/profiles
+func (h *ConfigHandler) CreateProfile(w http.ResponseWriter, r *http.Request) {
+ if h.demo {
+ respond.Error(w, http.StatusForbidden, "demo mode: modifications are disabled")
+ return
+ }
+ if config.IsReadonly(h.configPath) {
+ respond.Error(w, http.StatusForbidden, "config is read-only")
+ return
+ }
+ var req struct {
+ Name string `json:"name"`
+ Notes string `json:"notes"`
+ Enabled []string `json:"enabled"`
+ Disabled []string `json:"disabled"`
+ }
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ respond.Error(w, http.StatusBadRequest, "invalid JSON: "+err.Error())
+ return
+ }
+ if req.Name == "" {
+ respond.Error(w, http.StatusBadRequest, "name is required")
+ return
+ }
+ if err := validateProfileName(req.Name); err != nil {
+ respond.Error(w, http.StatusBadRequest, err.Error())
+ return
+ }
+ if _, isBuiltin := config.BuiltinProfiles[req.Name]; isBuiltin {
+ respond.Error(w, http.StatusForbidden, fmt.Sprintf("profile %q is reserved", req.Name))
+ return
+ }
+
+ h.mu.Lock()
+ defer h.mu.Unlock()
+
+ cfg, err := config.Load(h.configPath)
+ if err != nil {
+ respond.Error(w, http.StatusInternalServerError, err.Error())
+ return
+ }
+ if _, exists := cfg.Profiles[req.Name]; exists {
+ respond.Error(w, http.StatusConflict, "profile already exists")
+ return
+ }
+
+ cfg.Profiles[req.Name] = config.Profile{
+ Notes: req.Notes,
+ Enabled: req.Enabled,
+ Disabled: req.Disabled,
+ }
+ if err := config.Save(h.configPath, cfg); err != nil {
+ respond.Error(w, http.StatusInternalServerError, err.Error())
+ return
+ }
+ respond.JSON(w, http.StatusCreated, cfg.Profiles[req.Name])
+}
+
+// PATCH /api/config/profiles/{name}
+func (h *ConfigHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) {
+ if h.demo {
+ respond.Error(w, http.StatusForbidden, "demo mode: modifications are disabled")
+ return
+ }
+ if config.IsReadonly(h.configPath) {
+ respond.Error(w, http.StatusForbidden, "config is read-only")
+ return
+ }
+ name := chi.URLParam(r, "name")
+ if _, isBuiltin := config.BuiltinProfiles[name]; isBuiltin {
+ respond.Error(w, http.StatusForbidden, fmt.Sprintf("profile %q is read-only", name))
+ return
+ }
+
+ var req struct {
+ Notes *string `json:"notes"`
+ Enabled *[]string `json:"enabled"`
+ Disabled *[]string `json:"disabled"`
+ }
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ respond.Error(w, http.StatusBadRequest, "invalid JSON: "+err.Error())
+ return
+ }
+
+ h.mu.Lock()
+ defer h.mu.Unlock()
+
+ cfg, err := config.Load(h.configPath)
+ if err != nil {
+ respond.Error(w, http.StatusInternalServerError, err.Error())
+ return
+ }
+ p, ok := cfg.Profiles[name]
+ if !ok {
+ respond.Error(w, http.StatusNotFound, "profile not found")
+ return
+ }
+
+ if req.Notes != nil {
+ p.Notes = *req.Notes
+ }
+ if req.Enabled != nil {
+ p.Enabled = *req.Enabled
+ }
+ if req.Disabled != nil {
+ p.Disabled = *req.Disabled
+ }
+ cfg.Profiles[name] = p
+
+ if err := config.Save(h.configPath, cfg); err != nil {
+ respond.Error(w, http.StatusInternalServerError, err.Error())
+ return
+ }
+ respond.JSON(w, http.StatusOK, p)
+}
+
+// DELETE /api/config/profiles/{name}
+func (h *ConfigHandler) DeleteProfile(w http.ResponseWriter, r *http.Request) {
+ if h.demo {
+ respond.Error(w, http.StatusForbidden, "demo mode: modifications are disabled")
+ return
+ }
+ if config.IsReadonly(h.configPath) {
+ respond.Error(w, http.StatusForbidden, "config is read-only")
+ return
+ }
+ name := chi.URLParam(r, "name")
+ if _, isBuiltin := config.BuiltinProfiles[name]; isBuiltin {
+ respond.Error(w, http.StatusForbidden, fmt.Sprintf("profile %q is read-only", name))
+ return
+ }
+
+ h.mu.Lock()
+ defer h.mu.Unlock()
+
+ cfg, err := config.Load(h.configPath)
+ if err != nil {
+ respond.Error(w, http.StatusInternalServerError, err.Error())
+ return
+ }
+ if _, ok := cfg.Profiles[name]; !ok {
+ respond.Error(w, http.StatusNotFound, "profile not found")
+ return
+ }
+ delete(cfg.Profiles, name)
+
+ if err := config.Save(h.configPath, cfg); err != nil {
+ respond.Error(w, http.StatusInternalServerError, err.Error())
+ return
+ }
+ w.WriteHeader(http.StatusNoContent)
+}
+
+// PATCH /api/config/tools/{toolName}
+func (h *ConfigHandler) UpdateToolConfig(w http.ResponseWriter, r *http.Request) {
+ if h.demo {
+ respond.Error(w, http.StatusForbidden, "demo mode: modifications are disabled")
+ return
+ }
+ if config.IsReadonly(h.configPath) {
+ respond.Error(w, http.StatusForbidden, "config is read-only")
+ return
+ }
+ toolName := chi.URLParam(r, "toolName")
+
+ var patch map[string]any
+ if err := json.NewDecoder(r.Body).Decode(&patch); err != nil {
+ respond.Error(w, http.StatusBadRequest, "invalid JSON: "+err.Error())
+ return
+ }
+ if err := h.validatePatch(toolName, patch); err != nil {
+ respond.Error(w, http.StatusBadRequest, err.Error())
+ return
+ }
+
+ h.mu.Lock()
+ defer h.mu.Unlock()
+
+ cfg, err := config.Load(h.configPath)
+ if err != nil {
+ respond.Error(w, http.StatusInternalServerError, err.Error())
+ return
+ }
+
+ merged, err := config.MergeNodePatch(cfg.Tools[toolName], patch)
+ if err != nil {
+ respond.Error(w, http.StatusInternalServerError, err.Error())
+ return
+ }
+ cfg.Tools[toolName] = merged
+
+ if err := config.Save(h.configPath, cfg); err != nil {
+ respond.Error(w, http.StatusInternalServerError, err.Error())
+ return
+ }
+
+ var result map[string]any
+ _ = merged.Decode(&result)
+ respond.JSON(w, http.StatusOK, result)
+}
+
+// DELETE /api/config/tools/{toolName}
+func (h *ConfigHandler) DeleteToolConfig(w http.ResponseWriter, r *http.Request) {
+ if h.demo {
+ respond.Error(w, http.StatusForbidden, "demo mode: modifications are disabled")
+ return
+ }
+ if config.IsReadonly(h.configPath) {
+ respond.Error(w, http.StatusForbidden, "config is read-only")
+ return
+ }
+ toolName := chi.URLParam(r, "toolName")
+
+ h.mu.Lock()
+ defer h.mu.Unlock()
+
+ cfg, err := config.Load(h.configPath)
+ if err != nil {
+ respond.Error(w, http.StatusInternalServerError, err.Error())
+ return
+ }
+ if _, ok := cfg.Tools[toolName]; !ok {
+ respond.Error(w, http.StatusNotFound, "no global config for this tool")
+ return
+ }
+ delete(cfg.Tools, toolName)
+
+ if err := config.Save(h.configPath, cfg); err != nil {
+ respond.Error(w, http.StatusInternalServerError, err.Error())
+ return
+ }
+ w.WriteHeader(http.StatusNoContent)
+}
+
+// PATCH /api/config/profiles/{name}/tools/{toolName}
+func (h *ConfigHandler) UpdateProfileToolConfig(w http.ResponseWriter, r *http.Request) {
+ if h.demo {
+ respond.Error(w, http.StatusForbidden, "demo mode: modifications are disabled")
+ return
+ }
+ if config.IsReadonly(h.configPath) {
+ respond.Error(w, http.StatusForbidden, "config is read-only")
+ return
+ }
+ name := chi.URLParam(r, "name")
+ toolName := chi.URLParam(r, "toolName")
+
+ if _, isBuiltin := config.BuiltinProfiles[name]; isBuiltin {
+ respond.Error(w, http.StatusForbidden, fmt.Sprintf("profile %q is read-only", name))
+ return
+ }
+
+ var patch map[string]any
+ if err := json.NewDecoder(r.Body).Decode(&patch); err != nil {
+ respond.Error(w, http.StatusBadRequest, "invalid JSON: "+err.Error())
+ return
+ }
+ if err := h.validatePatch(toolName, patch); err != nil {
+ respond.Error(w, http.StatusBadRequest, err.Error())
+ return
+ }
+
+ h.mu.Lock()
+ defer h.mu.Unlock()
+
+ cfg, err := config.Load(h.configPath)
+ if err != nil {
+ respond.Error(w, http.StatusInternalServerError, err.Error())
+ return
+ }
+ p, ok := cfg.Profiles[name]
+ if !ok {
+ respond.Error(w, http.StatusNotFound, "profile not found")
+ return
+ }
+ if p.Tools == nil {
+ p.Tools = make(map[string]yaml.Node)
+ }
+
+ merged, err := config.MergeNodePatch(p.Tools[toolName], patch)
+ if err != nil {
+ respond.Error(w, http.StatusInternalServerError, err.Error())
+ return
+ }
+ p.Tools[toolName] = merged
+ cfg.Profiles[name] = p
+
+ if err := config.Save(h.configPath, cfg); err != nil {
+ respond.Error(w, http.StatusInternalServerError, err.Error())
+ return
+ }
+
+ var result map[string]any
+ _ = merged.Decode(&result)
+ respond.JSON(w, http.StatusOK, result)
+}
+
+// DELETE /api/config/profiles/{name}/tools/{toolName}
+func (h *ConfigHandler) DeleteProfileToolConfig(w http.ResponseWriter, r *http.Request) {
+ if h.demo {
+ respond.Error(w, http.StatusForbidden, "demo mode: modifications are disabled")
+ return
+ }
+ if config.IsReadonly(h.configPath) {
+ respond.Error(w, http.StatusForbidden, "config is read-only")
+ return
+ }
+ name := chi.URLParam(r, "name")
+ toolName := chi.URLParam(r, "toolName")
+
+ if _, isBuiltin := config.BuiltinProfiles[name]; isBuiltin {
+ respond.Error(w, http.StatusForbidden, fmt.Sprintf("profile %q is read-only", name))
+ return
+ }
+
+ h.mu.Lock()
+ defer h.mu.Unlock()
+
+ cfg, err := config.Load(h.configPath)
+ if err != nil {
+ respond.Error(w, http.StatusInternalServerError, err.Error())
+ return
+ }
+ p, ok := cfg.Profiles[name]
+ if !ok {
+ respond.Error(w, http.StatusNotFound, "profile not found")
+ return
+ }
+ if _, ok := p.Tools[toolName]; !ok {
+ respond.Error(w, http.StatusNotFound, "no config override for this tool in profile")
+ return
+ }
+ delete(p.Tools, toolName)
+ cfg.Profiles[name] = p
+
+ if err := config.Save(h.configPath, cfg); err != nil {
+ respond.Error(w, http.StatusInternalServerError, err.Error())
+ return
+ }
+ w.WriteHeader(http.StatusNoContent)
+}
+
+func validateProfileName(name string) error {
+ for _, c := range name {
+ if !((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-') {
+ return fmt.Errorf("profile name must contain only lowercase letters (a-z), digits (0-9), and hyphens (-)")
+ }
+ }
+ return nil
+}
+
+func (h *ConfigHandler) validatePatch(toolName string, patch map[string]any) error {
+ var fields []tools.ConfigField
+ for _, factory := range h.factories {
+ t := factory()
+ if t.Name() == toolName {
+ if d, ok := t.(tools.ConfigDescriber); ok {
+ fields = d.ConfigFields()
+ }
+ break
+ }
+ }
+ if len(fields) == 0 {
+ return nil
+ }
+ fieldMap := make(map[string]tools.ConfigField, len(fields))
+ for _, f := range fields {
+ fieldMap[f.Name] = f
+ }
+ for key, val := range patch {
+ f, ok := fieldMap[key]
+ if !ok {
+ continue
+ }
+ if err := validateFieldValue(f, val); err != nil {
+ return fmt.Errorf("field %q: %w", key, err)
+ }
+ }
+ return nil
+}
+
+func validateFieldValue(f tools.ConfigField, val any) error {
+ if val == nil {
+ return nil
+ }
+ switch f.Type {
+ case "string":
+ if _, ok := val.(string); !ok {
+ return fmt.Errorf("expected string, got %T", val)
+ }
+ case "bool":
+ if _, ok := val.(bool); !ok {
+ return fmt.Errorf("expected bool, got %T", val)
+ }
+ case "int":
+ switch v := val.(type) {
+ case float64:
+ if v != float64(int64(v)) {
+ return fmt.Errorf("expected integer, got float")
+ }
+ default:
+ return fmt.Errorf("expected int, got %T", val)
+ }
+ case "float":
+ if _, ok := val.(float64); !ok {
+ return fmt.Errorf("expected number, got %T", val)
+ }
+ case "enum":
+ s, ok := val.(string)
+ if !ok {
+ return fmt.Errorf("expected string, got %T", val)
+ }
+ for _, opt := range f.Options {
+ if s == opt {
+ return nil
+ }
+ }
+ return fmt.Errorf("invalid value %q, must be one of: %v", s, f.Options)
+ }
+ return nil
+}
diff --git a/back/internal/api/handler/search.go b/back/internal/api/handler/search.go
new file mode 100644
index 0000000..dfd2285
--- /dev/null
+++ b/back/internal/api/handler/search.go
@@ -0,0 +1,146 @@
+package handler
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "github.com/go-chi/chi/v5"
+
+ "github.com/anotherhadi/iknowyou/internal/respond"
+ "github.com/anotherhadi/iknowyou/internal/search"
+ "github.com/anotherhadi/iknowyou/internal/tools"
+)
+
+type SearchHandler struct {
+ manager *search.Manager
+ demo bool
+}
+
+func NewSearchHandler(manager *search.Manager, demo bool) *SearchHandler {
+ return &SearchHandler{manager: manager, demo: demo}
+}
+
+type postSearchRequest struct {
+ Target string `json:"target"`
+ InputType tools.InputType `json:"input_type"`
+ Profile string `json:"profile,omitempty"`
+}
+
+type searchSummary struct {
+ ID string `json:"id"`
+ Target string `json:"target"`
+ InputType tools.InputType `json:"input_type"`
+ Profile string `json:"profile,omitempty"`
+ Status search.Status `json:"status"`
+ StartedAt string `json:"started_at"`
+ PlannedTools []search.ToolStatus `json:"planned_tools"`
+}
+
+type searchDetail struct {
+ searchSummary
+ Events []tools.Event `json:"events"`
+}
+
+func toSummary(s *search.Search) searchSummary {
+ planned := s.PlannedTools
+ if planned == nil {
+ planned = []search.ToolStatus{}
+ }
+ return searchSummary{
+ ID: s.ID,
+ Target: s.Target,
+ InputType: s.InputType,
+ Profile: s.Profile,
+ Status: s.Status(),
+ StartedAt: s.StartedAt.UTC().Format("2006-01-02T15:04:05Z"),
+ PlannedTools: planned,
+ }
+}
+
+var validInputTypes = map[tools.InputType]struct{}{
+ tools.InputTypeEmail: {},
+ tools.InputTypeUsername: {},
+ tools.InputTypePhone: {},
+ tools.InputTypeIP: {},
+ tools.InputTypeDomain: {},
+ tools.InputTypePassword: {},
+ tools.InputTypeName: {},
+}
+
+// POST /searches
+func (h *SearchHandler) Create(w http.ResponseWriter, r *http.Request) {
+ if h.demo {
+ respond.Error(w, http.StatusForbidden, "demo mode: searches are disabled")
+ return
+ }
+ var req postSearchRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ respond.Error(w, http.StatusBadRequest, "invalid JSON body")
+ return
+ }
+ if req.Target == "" {
+ respond.Error(w, http.StatusBadRequest, "target is required")
+ return
+ }
+ if len(req.Target) > 500 {
+ respond.Error(w, http.StatusBadRequest, "target is too long (max 500 characters)")
+ return
+ }
+ if req.Target[0] == '-' || req.Target[0] == '@' {
+ respond.Error(w, http.StatusBadRequest, "invalid target")
+ return
+ }
+ if req.InputType == "" {
+ respond.Error(w, http.StatusBadRequest, "input_type is required")
+ return
+ }
+ if _, ok := validInputTypes[req.InputType]; !ok {
+ respond.Error(w, http.StatusBadRequest, "invalid input_type")
+ return
+ }
+
+ s, err := h.manager.Start(context.WithoutCancel(r.Context()), req.Target, req.InputType, req.Profile)
+ if err != nil {
+ respond.Error(w, http.StatusInternalServerError, err.Error())
+ return
+ }
+
+ respond.JSON(w, http.StatusCreated, toSummary(s))
+}
+
+// GET /searches
+func (h *SearchHandler) List(w http.ResponseWriter, r *http.Request) {
+ all := h.manager.All()
+ summaries := make([]searchSummary, len(all))
+ for i, s := range all {
+ summaries[i] = toSummary(s)
+ }
+ respond.JSON(w, http.StatusOK, summaries)
+}
+
+// GET /searches/{id}
+func (h *SearchHandler) Get(w http.ResponseWriter, r *http.Request) {
+ id := chi.URLParam(r, "id")
+ s, err := h.manager.Get(id)
+ if err != nil {
+ respond.Error(w, http.StatusNotFound, err.Error())
+ return
+ }
+
+ detail := searchDetail{
+ searchSummary: toSummary(s),
+ Events: s.Events(),
+ }
+ respond.JSON(w, http.StatusOK, detail)
+}
+
+// DELETE /searches/{id}
+func (h *SearchHandler) Delete(w http.ResponseWriter, r *http.Request) {
+ id := chi.URLParam(r, "id")
+ if err := h.manager.Delete(id); err != nil {
+ respond.Error(w, http.StatusNotFound, err.Error())
+ return
+ }
+ w.WriteHeader(http.StatusNoContent)
+}
diff --git a/back/internal/api/handler/tools.go b/back/internal/api/handler/tools.go
new file mode 100644
index 0000000..744dece
--- /dev/null
+++ b/back/internal/api/handler/tools.go
@@ -0,0 +1,91 @@
+package handler
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/go-chi/chi/v5"
+
+ "github.com/anotherhadi/iknowyou/internal/respond"
+ "github.com/anotherhadi/iknowyou/internal/tools"
+)
+
+type ToolsHandler struct {
+ factories []func() tools.ToolRunner
+}
+
+func NewToolsHandler(factories []func() tools.ToolRunner) *ToolsHandler {
+ return &ToolsHandler{factories: factories}
+}
+
+type toolInfo struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ Link string `json:"link,omitempty"`
+ Icon string `json:"icon,omitempty"`
+ InputTypes []tools.InputType `json:"input_types"`
+ Configurable bool `json:"configurable"`
+ ConfigFields []tools.ConfigField `json:"config_fields,omitempty"`
+ Available *bool `json:"available,omitempty"`
+ UnavailableReason string `json:"unavailable_reason,omitempty"`
+ Dependencies []string `json:"dependencies,omitempty"`
+}
+
+func toToolInfo(t tools.ToolRunner) toolInfo {
+ _, configurable := t.(tools.Configurable)
+
+ var fields []tools.ConfigField
+ if d, ok := t.(tools.ConfigDescriber); ok {
+ fields = d.ConfigFields()
+ }
+
+ var available *bool
+ var unavailableReason string
+ if checker, ok := t.(tools.AvailabilityChecker); ok {
+ avail, reason := checker.Available()
+ available = &avail
+ if !avail {
+ unavailableReason = reason
+ }
+ }
+
+ var dependencies []string
+ if lister, ok := t.(tools.DependencyLister); ok {
+ dependencies = lister.Dependencies()
+ }
+
+ return toolInfo{
+ Name: t.Name(),
+ Description: t.Description(),
+ Link: t.Link(),
+ Icon: t.Icon(),
+ InputTypes: t.InputTypes(),
+ Configurable: configurable,
+ ConfigFields: fields,
+ Available: available,
+ UnavailableReason: unavailableReason,
+ Dependencies: dependencies,
+ }
+}
+
+// GET /api/tools
+func (h *ToolsHandler) List(w http.ResponseWriter, r *http.Request) {
+ infos := make([]toolInfo, 0, len(h.factories))
+ for _, factory := range h.factories {
+ infos = append(infos, toToolInfo(factory()))
+ }
+ respond.JSON(w, http.StatusOK, infos)
+}
+
+// GET /api/tools/{name}
+func (h *ToolsHandler) Get(w http.ResponseWriter, r *http.Request) {
+ name := chi.URLParam(r, "name")
+ for _, factory := range h.factories {
+ t := factory()
+ if t.Name() == name {
+ respond.JSON(w, http.StatusOK, toToolInfo(t))
+ return
+ }
+ }
+ respond.Error(w, http.StatusNotFound, fmt.Sprintf("tool %q not found", name))
+}
diff --git a/back/internal/api/middleware/ratelimit.go b/back/internal/api/middleware/ratelimit.go
new file mode 100644
index 0000000..99c1ca5
--- /dev/null
+++ b/back/internal/api/middleware/ratelimit.go
@@ -0,0 +1,72 @@
+package middleware
+
+import (
+ "net"
+ "net/http"
+ "sync"
+ "time"
+
+ "golang.org/x/time/rate"
+)
+
+type ipLimiter struct {
+ limiter *rate.Limiter
+ lastSeen time.Time
+}
+
+type Limiter struct {
+ mu sync.Mutex
+ visitors map[string]*ipLimiter
+ r rate.Limit
+ burst int
+}
+
+func New(r rate.Limit, burst int) *Limiter {
+ l := &Limiter{
+ visitors: make(map[string]*ipLimiter),
+ r: r,
+ burst: burst,
+ }
+ go l.cleanupLoop()
+ return l
+}
+
+func (l *Limiter) getLimiter(ip string) *rate.Limiter {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ v, exists := l.visitors[ip]
+ if !exists {
+ v = &ipLimiter{limiter: rate.NewLimiter(l.r, l.burst)}
+ l.visitors[ip] = v
+ }
+ v.lastSeen = time.Now()
+ return v.limiter
+}
+
+func (l *Limiter) cleanupLoop() {
+ ticker := time.NewTicker(10 * time.Minute)
+ defer ticker.Stop()
+ for range ticker.C {
+ l.mu.Lock()
+ for ip, v := range l.visitors {
+ if time.Since(v.lastSeen) > 10*time.Minute {
+ delete(l.visitors, ip)
+ }
+ }
+ l.mu.Unlock()
+ }
+}
+
+func (l *Limiter) Handler(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ ip, _, err := net.SplitHostPort(r.RemoteAddr)
+ if err != nil {
+ ip = r.RemoteAddr
+ }
+ if !l.getLimiter(ip).Allow() {
+ http.Error(w, `{"error":"rate limit exceeded, please slow down"}`, http.StatusTooManyRequests)
+ return
+ }
+ next.ServeHTTP(w, r)
+ })
+}
diff --git a/back/internal/api/router.go b/back/internal/api/router.go
new file mode 100644
index 0000000..19bb572
--- /dev/null
+++ b/back/internal/api/router.go
@@ -0,0 +1,73 @@
+package api
+
+import (
+ "time"
+
+ "github.com/go-chi/chi/v5"
+ chimiddleware "github.com/go-chi/chi/v5/middleware"
+ "golang.org/x/time/rate"
+
+ "github.com/anotherhadi/iknowyou/internal/api/handler"
+ ikymiddleware "github.com/anotherhadi/iknowyou/internal/api/middleware"
+ "github.com/anotherhadi/iknowyou/internal/search"
+ "github.com/anotherhadi/iknowyou/internal/tools"
+)
+
+func NewRouter(
+ manager *search.Manager,
+ factories []func() tools.ToolRunner,
+ configPath string,
+ frontDir string,
+ demo bool,
+) *chi.Mux {
+ r := chi.NewRouter()
+
+ r.Use(chimiddleware.Logger)
+ r.Use(chimiddleware.Recoverer)
+ r.Use(chimiddleware.RequestID)
+
+ searchHandler := handler.NewSearchHandler(manager, demo)
+ toolsHandler := handler.NewToolsHandler(factories)
+ configHandler := handler.NewConfigHandler(configPath, factories, demo)
+
+ searchLimiter := ikymiddleware.New(rate.Every(10*time.Second), 3)
+
+ r.Route("/api", func(r chi.Router) {
+ r.Route("/searches", func(r chi.Router) {
+ r.With(searchLimiter.Handler).Post("/", searchHandler.Create)
+ r.Get("/", searchHandler.List)
+ r.Get("/{id}", searchHandler.Get)
+ r.Delete("/{id}", searchHandler.Delete)
+ })
+
+ r.Route("/tools", func(r chi.Router) {
+ r.Get("/", toolsHandler.List)
+ r.Get("/{name}", toolsHandler.Get)
+ })
+
+ r.Route("/config", func(r chi.Router) {
+ r.Get("/", configHandler.Get)
+
+ r.Route("/tools", func(r chi.Router) {
+ r.Patch("/{toolName}", configHandler.UpdateToolConfig)
+ r.Delete("/{toolName}", configHandler.DeleteToolConfig)
+ })
+
+ r.Route("/profiles", func(r chi.Router) {
+ r.Get("/", configHandler.ListProfiles)
+ r.Post("/", configHandler.CreateProfile)
+ r.Get("/{name}", configHandler.GetProfile)
+ r.Patch("/{name}", configHandler.UpdateProfile)
+ r.Delete("/{name}", configHandler.DeleteProfile)
+ r.Patch("/{name}/tools/{toolName}", configHandler.UpdateProfileToolConfig)
+ r.Delete("/{name}/tools/{toolName}", configHandler.DeleteProfileToolConfig)
+ })
+ })
+ })
+
+ if frontDir != "" {
+ r.Handle("/*", newStaticHandler(frontDir))
+ }
+
+ return r
+}
diff --git a/back/internal/api/static.go b/back/internal/api/static.go
new file mode 100644
index 0000000..156753b
--- /dev/null
+++ b/back/internal/api/static.go
@@ -0,0 +1,56 @@
+package api
+
+import (
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+// newStaticHandler serves the Astro static build with SPA fallbacks:
+// /search/ and /tools/ → their respective shell pages.
+func newStaticHandler(dir string) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ urlPath := r.URL.Path
+
+ if strings.HasPrefix(urlPath, "/search/") && len(urlPath) > len("/search/") {
+ http.ServeFile(w, r, filepath.Join(dir, "search", "_", "index.html"))
+ return
+ }
+
+ if strings.HasPrefix(urlPath, "/tools/") {
+ rest := strings.TrimPrefix(urlPath, "/tools/")
+ if rest != "" && !strings.Contains(rest, "/") {
+ http.ServeFile(w, r, filepath.Join(dir, "tools", "_", "index.html"))
+ return
+ }
+ }
+
+ rel := filepath.FromSlash(strings.TrimPrefix(urlPath, "/"))
+ full := filepath.Join(dir, rel)
+
+ info, err := os.Stat(full)
+ if err == nil && info.IsDir() {
+ full = filepath.Join(full, "index.html")
+ if _, err2 := os.Stat(full); err2 != nil {
+ serve404(w, r, dir)
+ return
+ }
+ } else if err != nil {
+ serve404(w, r, dir)
+ return
+ }
+
+ http.ServeFile(w, r, full)
+ })
+}
+
+func serve404(w http.ResponseWriter, r *http.Request, dir string) {
+ p := filepath.Join(dir, "404.html")
+ if _, err := os.Stat(p); err == nil {
+ w.WriteHeader(http.StatusNotFound)
+ http.ServeFile(w, r, p)
+ return
+ }
+ http.NotFound(w, r)
+}
diff --git a/back/internal/registry/registry.go b/back/internal/registry/registry.go
new file mode 100644
index 0000000..27cf3ca
--- /dev/null
+++ b/back/internal/registry/registry.go
@@ -0,0 +1,32 @@
+package registry
+
+import (
+ "github.com/anotherhadi/iknowyou/internal/tools"
+ breachdirectory "github.com/anotherhadi/iknowyou/internal/tools/breachdirectory"
+ crtsh "github.com/anotherhadi/iknowyou/internal/tools/crtsh"
+ digtool "github.com/anotherhadi/iknowyou/internal/tools/dig"
+ githubrecon "github.com/anotherhadi/iknowyou/internal/tools/github-recon"
+ gravatarrecon "github.com/anotherhadi/iknowyou/internal/tools/gravatar-recon"
+ ipinfotool "github.com/anotherhadi/iknowyou/internal/tools/ipinfo"
+ leakcheck "github.com/anotherhadi/iknowyou/internal/tools/leakcheck"
+ maigret "github.com/anotherhadi/iknowyou/internal/tools/maigret"
+ userscanner "github.com/anotherhadi/iknowyou/internal/tools/user-scanner"
+ wappalyzer "github.com/anotherhadi/iknowyou/internal/tools/wappalyzer"
+ whoistool "github.com/anotherhadi/iknowyou/internal/tools/whois"
+ whoisfreaks "github.com/anotherhadi/iknowyou/internal/tools/whoisfreaks"
+)
+
+var Factories = []func() tools.ToolRunner{
+ userscanner.New,
+ githubrecon.New,
+ whoistool.New,
+ digtool.New,
+ ipinfotool.New,
+ gravatarrecon.New,
+ whoisfreaks.New,
+ maigret.New,
+ leakcheck.New,
+ crtsh.New,
+ breachdirectory.New,
+ wappalyzer.New,
+}
diff --git a/back/internal/respond/respond.go b/back/internal/respond/respond.go
new file mode 100644
index 0000000..c1e5b6a
--- /dev/null
+++ b/back/internal/respond/respond.go
@@ -0,0 +1,18 @@
+package respond
+
+import (
+ "encoding/json"
+ "net/http"
+)
+
+// JSON writes a JSON body with the given status code.
+func JSON(w http.ResponseWriter, status int, payload any) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(status)
+ _ = json.NewEncoder(w).Encode(payload)
+}
+
+// Error writes a JSON error body: {"error": "message"}.
+func Error(w http.ResponseWriter, status int, msg string) {
+ JSON(w, status, map[string]string{"error": msg})
+}
diff --git a/back/internal/search/demo.go b/back/internal/search/demo.go
new file mode 100644
index 0000000..65cbde1
--- /dev/null
+++ b/back/internal/search/demo.go
@@ -0,0 +1,114 @@
+package search
+
+import (
+ "context"
+ "time"
+
+ "github.com/google/uuid"
+
+ "github.com/anotherhadi/iknowyou/internal/tools"
+)
+
+func ptr(n int) *int { return &n }
+
+func (m *Manager) InjectDemoSearches() {
+ now := time.Now()
+
+ _, cancel1 := context.WithCancel(context.Background())
+ s1 := &Search{
+ ID: uuid.NewString(),
+ Target: "john.doe@example.com",
+ InputType: tools.InputTypeEmail,
+ Profile: "default",
+ StartedAt: now.Add(-2 * time.Hour),
+ PlannedTools: []ToolStatus{
+ {Name: "user-scanner", ResultCount: ptr(10)},
+ {Name: "github-recon", ResultCount: ptr(3)},
+ },
+ cancelFn: cancel1,
+ status: StatusDone,
+ finishedAt: now.Add(-2*time.Hour + 18*time.Second),
+ }
+ s1.events = []tools.Event{
+ {Tool: "user-scanner", Type: tools.EventTypeOutput, Payload: "\x1b[35m== ADULT SITES ==\x1b[0m\n" +
+ "\x1b[0m \x1b[32m[✔] Xvideos (john.doe@example.com): Registered\x1b[0m\n" +
+ "\x1b[0m \x1b[32m[✔] Pornhub (john.doe@example.com): Registered\x1b[0m\n" +
+ "\x1b[0m\n" +
+ "\x1b[35m== CREATOR SITES ==\x1b[0m\n" +
+ "\x1b[0m \x1b[32m[✔] Adobe (john.doe@example.com): Registered\x1b[0m\n" +
+ "\x1b[0m\n" +
+ "\x1b[35m== MUSIC SITES ==\x1b[0m\n" +
+ "\x1b[0m \x1b[32m[✔] Spotify (john.doe@example.com): Registered\x1b[0m\n" +
+ "\x1b[0m\n" +
+ "\x1b[35m== LEARNING SITES ==\x1b[0m\n" +
+ "\x1b[0m \x1b[32m[✔] Duolingo (john.doe@example.com): Registered\x1b[0m\n" +
+ "\x1b[0m \x1b[32m[✔] Vedantu (john.doe@example.com): Registered\n \x1b[36m└── Phone: +9112****07\x1b[0m\n" +
+ "\x1b[0m\n" +
+ "\x1b[35m== SOCIAL SITES ==\x1b[0m\n" +
+ "\x1b[0m \x1b[32m[✔] Pinterest (john.doe@example.com): Registered\x1b[0m\n" +
+ "\x1b[0m \x1b[32m[✔] Facebook (john.doe@example.com): Registered\x1b[0m\n" +
+ "\x1b[0m\n" +
+ "\x1b[35m== GAMING SITES ==\x1b[0m\n" +
+ "\x1b[0m \x1b[32m[✔] Chess.com (john.doe@example.com): Registered\x1b[0m\n" +
+ "\x1b[0m\n" +
+ "\x1b[35m== SHOPPING SITES ==\x1b[0m\n" +
+ "\x1b[0m \x1b[32m[✔] Amazon (john.doe@example.com): Registered\x1b[0m\n"},
+ {Tool: "user-scanner", Type: tools.EventTypeDone},
+ {Tool: "github-recon", Type: tools.EventTypeOutput, Payload: "\x1b[1;38;2;113;135;253m👤 Commits author\x1b[0m\n\n" +
+ " \x1b[38;2;125;125;125mName:\x1b[0m \x1b[38;2;166;227;161m\"fastHack2025\"\x1b[0m\n" +
+ " \x1b[38;2;125;125;125mEmail:\x1b[0m \x1b[38;2;166;227;161m\"john.doe@example.com\"\x1b[0m\n" +
+ " \x1b[38;2;125;125;125mUsername:\x1b[0m \x1b[38;2;166;227;161m\"Unknown\"\x1b[0m\n" +
+ " \x1b[38;2;125;125;125mOccurrences:\x1b[0m \x1b[38;2;166;227;161m36\x1b[0m\n\n" +
+ " \x1b[38;2;125;125;125mName:\x1b[0m \x1b[38;2;166;227;161m\"Anthony\"\x1b[0m\n" +
+ " \x1b[38;2;125;125;125mEmail:\x1b[0m \x1b[38;2;166;227;161m\"john.doe@example.com\"\x1b[0m\n" +
+ " \x1b[38;2;125;125;125mUsername:\x1b[0m \x1b[38;2;166;227;161m\"Unknown\"\x1b[0m\n" +
+ " \x1b[38;2;125;125;125mOccurrences:\x1b[0m \x1b[38;2;166;227;161m52\x1b[0m\n\n" +
+ " \x1b[38;2;125;125;125mName:\x1b[0m \x1b[38;2;166;227;161m\"Gill\"\x1b[0m\n" +
+ " \x1b[38;2;125;125;125mEmail:\x1b[0m \x1b[38;2;166;227;161m\"john.doe@example.com\"\x1b[0m\n" +
+ " \x1b[38;2;125;125;125mUsername:\x1b[0m \x1b[38;2;166;227;161m\"johndoe\"\x1b[0m\n" +
+ " \x1b[38;2;125;125;125mOccurrences:\x1b[0m \x1b[38;2;166;227;161m60\x1b[0m"},
+ {Tool: "github-recon", Type: tools.EventTypeDone},
+ }
+
+ _, cancel2 := context.WithCancel(context.Background())
+ s2 := &Search{
+ ID: uuid.NewString(),
+ Target: "janedoe",
+ InputType: tools.InputTypeUsername,
+ Profile: "default",
+ StartedAt: now.Add(-30 * time.Minute),
+ PlannedTools: []ToolStatus{
+ {Name: "user-scanner", ResultCount: ptr(10)},
+ {Name: "github-recon", ResultCount: ptr(0)},
+ },
+ cancelFn: cancel2,
+ status: StatusDone,
+ finishedAt: now.Add(-30*time.Minute + 22*time.Second),
+ }
+ s2.events = []tools.Event{
+ {Tool: "user-scanner", Type: tools.EventTypeOutput, Payload: "\x1b[35m== SOCIAL SITES ==\x1b[0m\n" +
+ "\x1b[0m \x1b[32m[✔] Reddit (janedoe): Found\x1b[0m\n" +
+ "\x1b[0m \x1b[32m[✔] Threads (janedoe): Found\x1b[0m\n" +
+ "\x1b[0m \x1b[32m[✔] X (twitter) (janedoe): Found\x1b[0m\n" +
+ "\x1b[0m \x1b[32m[✔] Youtube (janedoe): Found\x1b[0m\n" +
+ "\x1b[0m \x1b[32m[✔] Telegram (janedoe): Found\x1b[0m\n" +
+ "\x1b[0m \x1b[32m[✔] Tiktok (janedoe): Found\x1b[0m\n" +
+ "\x1b[0m \x1b[32m[✔] Instagram (janedoe): Found\x1b[0m\n" +
+ "\x1b[0m\n" +
+ "\x1b[35m== GAMING SITES ==\x1b[0m\n" +
+ "\x1b[0m \x1b[32m[✔] Chess.com (janedoe): Found\x1b[0m\n" +
+ "\x1b[0m \x1b[32m[✔] Roblox (janedoe): Found\x1b[0m\n" +
+ "\x1b[0m\n" +
+ "\x1b[35m== EMAIL SITES ==\x1b[0m\n" +
+ "\x1b[0m \x1b[32m[✔] Protonmail (janedoe): Found\x1b[0m"},
+ {Tool: "user-scanner", Type: tools.EventTypeDone},
+ {Tool: "github-recon", Type: tools.EventTypeOutput, Payload: "\x1b[1;38;2;113;135;253m👤 User informations\x1b[0m\n\n" +
+ " \x1b[38;2;125;125;125mNo data found\x1b[0m"},
+ {Tool: "github-recon", Type: tools.EventTypeDone},
+ }
+
+ m.mu.Lock()
+ m.searches[s1.ID] = s1
+ m.searches[s2.ID] = s2
+ m.mu.Unlock()
+}
diff --git a/back/internal/search/manager.go b/back/internal/search/manager.go
new file mode 100644
index 0000000..d39ded5
--- /dev/null
+++ b/back/internal/search/manager.go
@@ -0,0 +1,274 @@
+package search
+
+import (
+ "context"
+ "fmt"
+ "reflect"
+ "sync"
+ "time"
+
+ "github.com/google/uuid"
+
+ "github.com/anotherhadi/iknowyou/config"
+ "github.com/anotherhadi/iknowyou/internal/tools"
+)
+
+type Manager struct {
+ mu sync.RWMutex
+ searches map[string]*Search
+
+ configPath string
+ factories []func() tools.ToolRunner
+ searchTTL time.Duration
+ cleanupInterval time.Duration
+
+ done chan struct{} // closed by Stop()
+}
+
+func NewManager(configPath string, factories []func() tools.ToolRunner, searchTTL, cleanupInterval time.Duration) *Manager {
+ m := &Manager{
+ searches: make(map[string]*Search),
+ configPath: configPath,
+ factories: factories,
+ searchTTL: searchTTL,
+ cleanupInterval: cleanupInterval,
+ done: make(chan struct{}),
+ }
+ go m.cleanupLoop()
+ return m
+}
+
+func (m *Manager) Stop() {
+ close(m.done)
+}
+
+func (m *Manager) Start(
+ parentCtx context.Context,
+ target string,
+ inputType tools.InputType,
+ profileName string,
+) (*Search, error) {
+
+ // "default" is the canonical UI name for the no-filter profile.
+ if profileName == "default" {
+ profileName = ""
+ }
+
+ cfg, err := config.Load(m.configPath)
+ if err != nil {
+ return nil, fmt.Errorf("manager: loading config: %w", err)
+ }
+
+ activeTools, statuses, err := m.instantiate(cfg, inputType, profileName)
+ if err != nil {
+ return nil, err
+ }
+
+ ctx, cancel := context.WithCancel(parentCtx)
+
+ s := &Search{
+ ID: uuid.NewString(),
+ Target: target,
+ InputType: inputType,
+ Profile: profileName,
+ StartedAt: time.Now(),
+ PlannedTools: statuses,
+ cancelFn: cancel,
+ status: StatusRunning,
+ }
+
+ m.mu.Lock()
+ m.searches[s.ID] = s
+ m.mu.Unlock()
+
+ go m.runAll(ctx, s, activeTools)
+
+ return s, nil
+}
+
+func (m *Manager) Get(id string) (*Search, error) {
+ return m.get(id)
+}
+
+func (m *Manager) All() []*Search {
+ m.mu.RLock()
+ defer m.mu.RUnlock()
+ out := make([]*Search, 0, len(m.searches))
+ for _, s := range m.searches {
+ out = append(out, s)
+ }
+ return out
+}
+
+func (m *Manager) Delete(id string) error {
+ s, err := m.get(id)
+ if err != nil {
+ return err
+ }
+ s.Cancel()
+
+ m.mu.Lock()
+ delete(m.searches, id)
+ m.mu.Unlock()
+ return nil
+}
+
+func (m *Manager) cleanupLoop() {
+ ticker := time.NewTicker(m.cleanupInterval)
+ defer ticker.Stop()
+ for {
+ select {
+ case <-ticker.C:
+ m.purgeExpired()
+ case <-m.done:
+ return
+ }
+ }
+}
+
+func (m *Manager) purgeExpired() {
+ now := time.Now()
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ for id, s := range m.searches {
+ ft := s.FinishedAt()
+ if ft.IsZero() {
+ continue // still running
+ }
+ if now.Sub(ft) > m.searchTTL {
+ delete(m.searches, id)
+ }
+ }
+}
+
+func (m *Manager) instantiate(cfg *config.Config, inputType tools.InputType, profileName string) ([]tools.ToolRunner, []ToolStatus, error) {
+ allNames := make([]string, len(m.factories))
+ allInstances := make([]tools.ToolRunner, len(m.factories))
+ for i, factory := range m.factories {
+ t := factory()
+ allNames[i] = t.Name()
+ allInstances[i] = t
+ }
+
+ activeNames, err := cfg.ActiveTools(profileName, allNames)
+ if err != nil {
+ return nil, nil, err
+ }
+ activeSet := make(map[string]struct{}, len(activeNames))
+ for _, n := range activeNames {
+ activeSet[n] = struct{}{}
+ }
+
+ var runners []tools.ToolRunner
+ var statuses []ToolStatus
+
+ for _, tool := range allInstances {
+ if _, ok := activeSet[tool.Name()]; !ok {
+ continue
+ }
+ if !acceptsInputType(tool, inputType) {
+ continue
+ }
+
+ if a, ok := tool.(tools.AvailabilityChecker); ok {
+ if available, reason := a.Available(); !available {
+ statuses = append(statuses, ToolStatus{
+ Name: tool.Name(),
+ Skipped: true,
+ Reason: reason,
+ })
+ continue
+ }
+ }
+
+ if c, ok := tool.(tools.Configurable); ok {
+ if err := cfg.DecodeEffective(tool.Name(), profileName, c.ConfigPtr()); err != nil {
+ return nil, nil, fmt.Errorf("manager: configuring tool %q: %w", tool.Name(), err)
+ }
+
+ if d, ok := tool.(tools.ConfigDescriber); ok {
+ if missing, fieldName := missingRequiredField(d.ConfigFields()); missing {
+ statuses = append(statuses, ToolStatus{
+ Name: tool.Name(),
+ Skipped: true,
+ Reason: fmt.Sprintf("missing required config field: %s", fieldName),
+ })
+ continue
+ }
+ }
+ }
+
+ statuses = append(statuses, ToolStatus{Name: tool.Name()})
+ runners = append(runners, tool)
+ }
+
+ return runners, statuses, nil
+}
+
+func (m *Manager) runAll(ctx context.Context, s *Search, runners []tools.ToolRunner) {
+ var wg sync.WaitGroup
+ for _, tool := range runners {
+ wg.Add(1)
+ go func(t tools.ToolRunner) {
+ defer wg.Done()
+ m.runOne(ctx, s, t)
+ }(tool)
+ }
+ wg.Wait()
+ s.markDone()
+}
+
+func (m *Manager) runOne(ctx context.Context, s *Search, tool tools.ToolRunner) {
+ out := make(chan tools.Event)
+ go func() {
+ _ = tool.Run(ctx, s.Target, s.InputType, out)
+ }()
+
+ var count int
+ var hasCount bool
+ for e := range out {
+ if e.Type == tools.EventTypeCount {
+ if n, ok := e.Payload.(int); ok {
+ count += n
+ hasCount = true
+ }
+ continue
+ }
+ s.append(e)
+ }
+
+ if hasCount {
+ s.setToolResultCount(tool.Name(), count)
+ }
+}
+
+func (m *Manager) get(id string) (*Search, error) {
+ m.mu.RLock()
+ defer m.mu.RUnlock()
+ s, ok := m.searches[id]
+ if !ok {
+ return nil, fmt.Errorf("search %q not found", id)
+ }
+ return s, nil
+}
+
+func acceptsInputType(tool tools.ToolRunner, inputType tools.InputType) bool {
+ for _, t := range tool.InputTypes() {
+ if t == inputType {
+ return true
+ }
+ }
+ return false
+}
+
+func missingRequiredField(fields []tools.ConfigField) (missing bool, fieldName string) {
+ for _, f := range fields {
+ if !f.Required {
+ continue
+ }
+ if f.Value == nil || reflect.DeepEqual(f.Value, reflect.Zero(reflect.TypeOf(f.Value)).Interface()) {
+ return true, f.Name
+ }
+ }
+ return false, ""
+}
diff --git a/back/internal/search/search.go b/back/internal/search/search.go
new file mode 100644
index 0000000..67679c9
--- /dev/null
+++ b/back/internal/search/search.go
@@ -0,0 +1,97 @@
+package search
+
+import (
+ "context"
+ "sync"
+ "time"
+
+ "github.com/anotherhadi/iknowyou/internal/tools"
+)
+
+type Status string
+
+const (
+ StatusRunning Status = "running"
+ StatusDone Status = "done"
+ StatusCancelled Status = "cancelled"
+)
+
+type ToolStatus struct {
+ Name string `json:"name"`
+ Skipped bool `json:"skipped"`
+ Reason string `json:"reason,omitempty"` // non-empty only when Skipped is true
+ ResultCount *int `json:"result_count,omitempty"` // nil = pending, 0 = no results
+}
+
+type Search struct {
+ ID string
+ Target string
+ InputType tools.InputType
+ Profile string
+ StartedAt time.Time
+ PlannedTools []ToolStatus
+
+ cancelFn context.CancelFunc
+
+ mu sync.RWMutex
+ events []tools.Event
+ status Status
+ finishedAt time.Time
+}
+
+func (s *Search) Events() []tools.Event {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ out := make([]tools.Event, len(s.events))
+ copy(out, s.events)
+ return out
+}
+
+func (s *Search) Status() Status {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ return s.status
+}
+
+func (s *Search) FinishedAt() time.Time {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ return s.finishedAt
+}
+
+func (s *Search) Cancel() {
+ s.mu.Lock()
+ if s.status == StatusRunning {
+ s.status = StatusCancelled
+ s.finishedAt = time.Now()
+ }
+ s.mu.Unlock()
+
+ s.cancelFn()
+}
+
+func (s *Search) setToolResultCount(toolName string, count int) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ for i, t := range s.PlannedTools {
+ if t.Name == toolName {
+ s.PlannedTools[i].ResultCount = &count
+ return
+ }
+ }
+}
+
+func (s *Search) append(e tools.Event) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ s.events = append(s.events, e)
+}
+
+func (s *Search) markDone() {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ if s.status == StatusRunning {
+ s.status = StatusDone
+ s.finishedAt = time.Now()
+ }
+}
diff --git a/back/internal/tools/breachdirectory/tool.go b/back/internal/tools/breachdirectory/tool.go
new file mode 100644
index 0000000..3ed2cc5
--- /dev/null
+++ b/back/internal/tools/breachdirectory/tool.go
@@ -0,0 +1,162 @@
+package breachdirectory
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+
+ "github.com/anotherhadi/iknowyou/internal/tools"
+)
+
+const (
+ name = "breachdirectory"
+ description = "Data breach search via BreachDirectory — checks if an email, username, or phone appears in known data breaches and returns exposed passwords/hashes."
+ link = "https://breachdirectory.org"
+ icon = ""
+)
+
+type Config struct {
+ APIKey string `yaml:"api_key" iky:"desc=RapidAPI key for BreachDirectory (required — get one at rapidapi.com/rohan-patra/api/breachdirectory);required=true"`
+}
+
+type Runner struct {
+ cfg Config
+}
+
+func New() tools.ToolRunner {
+ cfg := Config{}
+ tools.ApplyDefaults(&cfg)
+ return &Runner{cfg: cfg}
+}
+
+func (r *Runner) Name() string { return name }
+func (r *Runner) Description() string { return description }
+func (r *Runner) Link() string { return link }
+func (r *Runner) Icon() string { return icon }
+
+func (r *Runner) InputTypes() []tools.InputType {
+ return []tools.InputType{
+ tools.InputTypeEmail,
+ tools.InputTypeUsername,
+ }
+}
+
+func (r *Runner) ConfigPtr() interface{} { return &r.cfg }
+
+func (r *Runner) ConfigFields() []tools.ConfigField {
+ return tools.ReflectConfigFields(r.cfg)
+}
+
+type bdResponse struct {
+ Success bool `json:"success"`
+ Found int `json:"found"`
+ Result json.RawMessage `json:"result"`
+}
+
+type bdEntry struct {
+ Email string `json:"email"`
+ Password string `json:"password"`
+ Hash string `json:"hash"`
+ SHA1 string `json:"sha1"`
+ Sources string `json:"sources"`
+ HasPassword bool `json:"has_password"`
+}
+
+func (r *Runner) Run(ctx context.Context, target string, _ tools.InputType, out chan<- tools.Event) error {
+ defer close(out)
+
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet,
+ "https://breachdirectory.p.rapidapi.com/?func=auto&term="+target, nil)
+ if err != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: err.Error()}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+ req.Header.Set("X-RapidAPI-Key", r.cfg.APIKey)
+ req.Header.Set("X-RapidAPI-Host", "breachdirectory.p.rapidapi.com")
+ req.Header.Set("Accept", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ if ctx.Err() != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "scan cancelled"}
+ } else {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: err.Error()}
+ }
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+ defer resp.Body.Close()
+
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "failed to read response"}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "invalid or exhausted API key"}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: fmt.Sprintf("API error %d: %s", resp.StatusCode, string(body))}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ var parsed bdResponse
+ if err := json.Unmarshal(body, &parsed); err != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "failed to parse response"}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ if !parsed.Success || parsed.Found == 0 {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ var entries []bdEntry
+ if err := json.Unmarshal(parsed.Result, &entries); err != nil || len(entries) == 0 {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ var sb strings.Builder
+ sb.WriteString(fmt.Sprintf("Found in %d breach record(s)\n\n", parsed.Found))
+
+ for _, entry := range entries {
+ if entry.Sources != "" {
+ sb.WriteString(fmt.Sprintf("Source: %s\n", entry.Sources))
+ }
+ if entry.Password != "" {
+ sb.WriteString(fmt.Sprintf("Password: %s\n", entry.Password))
+ }
+ if entry.Hash != "" {
+ sb.WriteString(fmt.Sprintf("Hash: %s\n", entry.Hash))
+ }
+ if entry.SHA1 != "" {
+ sb.WriteString(fmt.Sprintf("SHA1: %s\n", entry.SHA1))
+ }
+ sb.WriteString("\n")
+ }
+
+ out <- tools.Event{Tool: name, Type: tools.EventTypeOutput, Payload: strings.TrimSpace(sb.String())}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: parsed.Found}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+}
diff --git a/back/internal/tools/config_reflect.go b/back/internal/tools/config_reflect.go
new file mode 100644
index 0000000..2167d5c
--- /dev/null
+++ b/back/internal/tools/config_reflect.go
@@ -0,0 +1,155 @@
+package tools
+
+import (
+ "reflect"
+ "strconv"
+ "strings"
+)
+
+// ReflectConfigFields builds []ConfigField from a struct using yaml/iky tags.
+// iky tag format: iky:"desc=...;default=...;required=true;options=a|b|c"
+func ReflectConfigFields(cfg any) []ConfigField {
+ v := reflect.ValueOf(cfg)
+ if v.Kind() == reflect.Ptr {
+ v = v.Elem()
+ }
+ t := v.Type()
+
+ var fields []ConfigField
+ for i := range t.NumField() {
+ sf := t.Field(i)
+ fv := v.Field(i)
+
+ yamlKey := sf.Tag.Get("yaml")
+ if yamlKey == "" || yamlKey == "-" {
+ continue
+ }
+ yamlKey = strings.SplitN(yamlKey, ",", 2)[0]
+
+ meta := parseIkyTag(sf.Tag.Get("iky"))
+
+ fieldType := goKindToString(sf.Type.Kind())
+ if len(meta.options) > 0 {
+ fieldType = "enum"
+ }
+
+ fields = append(fields, ConfigField{
+ Name: yamlKey,
+ Type: fieldType,
+ Required: meta.required,
+ Description: meta.desc,
+ Default: parseTypedDefault(meta.rawDefault, sf.Type.Kind()),
+ Value: fv.Interface(),
+ Options: meta.options,
+ })
+ }
+ return fields
+}
+
+// ApplyDefaults sets each field to its iky default if the field is zero.
+func ApplyDefaults(cfg any) {
+ v := reflect.ValueOf(cfg)
+ if v.Kind() == reflect.Ptr {
+ v = v.Elem()
+ }
+ t := v.Type()
+
+ for i := range t.NumField() {
+ sf := t.Field(i)
+ fv := v.Field(i)
+
+ if !fv.CanSet() {
+ continue
+ }
+ meta := parseIkyTag(sf.Tag.Get("iky"))
+ if meta.rawDefault == "" || !fv.IsZero() {
+ continue
+ }
+ applyDefault(fv, sf.Type.Kind(), meta.rawDefault)
+ }
+}
+
+type ikyMeta struct {
+ desc string
+ rawDefault string
+ required bool
+ options []string
+}
+
+func parseIkyTag(tag string) ikyMeta {
+ var m ikyMeta
+ for _, part := range strings.Split(tag, ";") {
+ k, v, ok := strings.Cut(strings.TrimSpace(part), "=")
+ if !ok {
+ continue
+ }
+ switch strings.TrimSpace(k) {
+ case "desc":
+ m.desc = strings.TrimSpace(v)
+ case "default":
+ m.rawDefault = strings.TrimSpace(v)
+ case "required":
+ m.required = strings.TrimSpace(v) == "true"
+ case "options":
+ for _, opt := range strings.Split(v, "|") {
+ if o := strings.TrimSpace(opt); o != "" {
+ m.options = append(m.options, o)
+ }
+ }
+ }
+ }
+ return m
+}
+
+func goKindToString(k reflect.Kind) string {
+ switch k {
+ case reflect.String:
+ return "string"
+ case reflect.Bool:
+ return "bool"
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return "int"
+ case reflect.Float32, reflect.Float64:
+ return "float"
+ default:
+ return k.String()
+ }
+}
+
+func parseTypedDefault(raw string, k reflect.Kind) any {
+ if raw == "" {
+ return nil
+ }
+ switch k {
+ case reflect.Bool:
+ b, _ := strconv.ParseBool(raw)
+ return b
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ n, _ := strconv.ParseInt(raw, 10, 64)
+ return int(n)
+ case reflect.Float32, reflect.Float64:
+ f, _ := strconv.ParseFloat(raw, 64)
+ return f
+ default:
+ return raw
+ }
+}
+
+func applyDefault(fv reflect.Value, k reflect.Kind, raw string) {
+ switch k {
+ case reflect.String:
+ fv.SetString(raw)
+ case reflect.Bool:
+ if b, err := strconv.ParseBool(raw); err == nil {
+ fv.SetBool(b)
+ }
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ if n, err := strconv.ParseInt(raw, 10, 64); err == nil {
+ fv.SetInt(n)
+ }
+ case reflect.Float32, reflect.Float64:
+ if f, err := strconv.ParseFloat(raw, 64); err == nil {
+ fv.SetFloat(f)
+ }
+ }
+}
diff --git a/back/internal/tools/crtsh/tool.go b/back/internal/tools/crtsh/tool.go
new file mode 100644
index 0000000..f7a80f0
--- /dev/null
+++ b/back/internal/tools/crtsh/tool.go
@@ -0,0 +1,137 @@
+package crtsh
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "sort"
+ "strings"
+
+ "github.com/anotherhadi/iknowyou/internal/tools"
+)
+
+const (
+ name = "crt.sh"
+ description = "SSL/TLS certificate transparency log search via crt.sh — enumerates subdomains and certificates issued for a domain."
+ link = "https://crt.sh"
+ icon = ""
+)
+
+type Runner struct{}
+
+func New() tools.ToolRunner {
+ return &Runner{}
+}
+
+func (r *Runner) Name() string { return name }
+func (r *Runner) Description() string { return description }
+func (r *Runner) Link() string { return link }
+func (r *Runner) Icon() string { return icon }
+
+func (r *Runner) InputTypes() []tools.InputType {
+ return []tools.InputType{tools.InputTypeDomain}
+}
+
+type crtEntry struct {
+ IssuerName string `json:"issuer_name"`
+ CommonName string `json:"common_name"`
+ NameValue string `json:"name_value"`
+ NotBefore string `json:"not_before"`
+ NotAfter string `json:"not_after"`
+ EntryTimestamp string `json:"entry_timestamp"`
+}
+
+func (r *Runner) Run(ctx context.Context, target string, _ tools.InputType, out chan<- tools.Event) error {
+ defer close(out)
+
+ params := url.Values{}
+ params.Set("q", "%."+target)
+ params.Set("output", "json")
+ apiURL := "https://crt.sh/?" + params.Encode()
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil)
+ if err != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: err.Error()}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+ req.Header.Set("Accept", "application/json")
+ req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; crtsh-scanner/1.0)")
+
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ if ctx.Err() != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "scan cancelled"}
+ } else {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: err.Error()}
+ }
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+ defer resp.Body.Close()
+
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "failed to read response"}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ msg := fmt.Sprintf("API error %d", resp.StatusCode)
+ if resp.StatusCode == http.StatusBadGateway || resp.StatusCode == http.StatusServiceUnavailable || resp.StatusCode == http.StatusGatewayTimeout {
+ msg = fmt.Sprintf("crt.sh is temporarily unavailable (%d), try again later", resp.StatusCode)
+ }
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: msg}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ var entries []crtEntry
+ if err := json.Unmarshal(body, &entries); err != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "failed to parse response"}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ if len(entries) == 0 {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ // Deduplicate subdomains from name_value fields
+ seen := make(map[string]struct{})
+ for _, e := range entries {
+ for _, line := range strings.Split(e.NameValue, "\n") {
+ line = strings.TrimSpace(line)
+ if line != "" && !strings.HasPrefix(line, "*") {
+ seen[line] = struct{}{}
+ }
+ }
+ }
+
+ subdomains := make([]string, 0, len(seen))
+ for s := range seen {
+ subdomains = append(subdomains, s)
+ }
+ sort.Strings(subdomains)
+
+ var sb strings.Builder
+ sb.WriteString(fmt.Sprintf("Found %d unique subdomains across %d certificate entries\n\n", len(subdomains), len(entries)))
+ for _, s := range subdomains {
+ sb.WriteString(s + "\n")
+ }
+
+ out <- tools.Event{Tool: name, Type: tools.EventTypeOutput, Payload: strings.TrimSpace(sb.String())}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: len(subdomains)}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+}
diff --git a/back/internal/tools/dig/tool.go b/back/internal/tools/dig/tool.go
new file mode 100644
index 0000000..0e5f135
--- /dev/null
+++ b/back/internal/tools/dig/tool.go
@@ -0,0 +1,90 @@
+package dig
+
+import (
+ "context"
+ "fmt"
+ "os/exec"
+ "strings"
+
+ "github.com/anotherhadi/iknowyou/internal/tools"
+)
+
+const (
+ name = "dig"
+ description = "DNS lookup querying A, AAAA, MX, NS, TXT, and SOA records for a domain, or reverse DNS (PTR) for an IP."
+ link = "https://linux.die.net/man/1/dig"
+ icon = ""
+)
+
+var recordTypes = []string{"A", "AAAA", "MX", "NS", "TXT", "SOA"}
+
+type Runner struct{}
+
+func New() tools.ToolRunner { return &Runner{} }
+
+func (r *Runner) Name() string { return name }
+func (r *Runner) Description() string { return description }
+func (r *Runner) Link() string { return link }
+func (r *Runner) Icon() string { return icon }
+
+func (r *Runner) InputTypes() []tools.InputType {
+ return []tools.InputType{tools.InputTypeDomain, tools.InputTypeIP}
+}
+
+func (r *Runner) Available() (bool, string) {
+ if _, err := exec.LookPath("dig"); err != nil {
+ return false, "dig binary not found in PATH"
+ }
+ return true, ""
+}
+
+func (r *Runner) Dependencies() []string { return []string{"dig"} }
+
+func (r *Runner) Run(ctx context.Context, target string, inputType tools.InputType, out chan<- tools.Event) error {
+ defer close(out)
+
+ var sb strings.Builder
+ totalCount := 0
+
+ if inputType == tools.InputTypeIP {
+ cmd := exec.CommandContext(ctx, "dig", "-x", target, "+noall", "+answer")
+ output, err := cmd.Output()
+ if err != nil && ctx.Err() != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "scan cancelled"}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+ result := strings.TrimSpace(string(output))
+ if result != "" {
+ sb.WriteString("=== Reverse DNS (PTR) ===\n")
+ sb.WriteString(result)
+ totalCount += strings.Count(result, "\n") + 1
+ }
+ } else {
+ for _, rtype := range recordTypes {
+ if ctx.Err() != nil {
+ break
+ }
+ cmd := exec.CommandContext(ctx, "dig", target, rtype, "+noall", "+answer")
+ output, _ := cmd.Output()
+ result := strings.TrimSpace(string(output))
+ if result == "" {
+ continue
+ }
+ sb.WriteString(fmt.Sprintf("=== %s ===\n", rtype))
+ sb.WriteString(result)
+ sb.WriteString("\n\n")
+ totalCount += strings.Count(result, "\n") + 1
+ }
+ }
+
+ if ctx.Err() != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "scan cancelled"}
+ } else if sb.Len() > 0 {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeOutput, Payload: strings.TrimSpace(sb.String())}
+ }
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: totalCount}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+}
diff --git a/back/internal/tools/github-recon/tool.go b/back/internal/tools/github-recon/tool.go
new file mode 100644
index 0000000..707954b
--- /dev/null
+++ b/back/internal/tools/github-recon/tool.go
@@ -0,0 +1,91 @@
+package githubrecon
+
+import (
+ "context"
+ "os/exec"
+ "strings"
+
+ "github.com/anotherhadi/iknowyou/internal/tools"
+)
+
+const (
+ name = "github-recon"
+ description = "GitHub OSINT reconnaissance tool. Gathers profile info, social links, organisations, SSH/GPG keys, commits, and more from a GitHub username or email."
+ link = "https://github.com/anotherhadi/nur-osint"
+ icon = "github"
+)
+
+type Config struct {
+ Token string `yaml:"token" iky:"desc=GitHub personal access token (enables higher rate limits and more data);required=false"`
+ Deepscan bool `yaml:"deepscan" iky:"desc=Enable deep scan (slower - scans all repositories for authors/emails);default=false"`
+ SpoofEmail bool `yaml:"spoof_email" iky:"desc=Include email spoofing check (email mode only, requires token);default=false"`
+}
+
+type Runner struct {
+ cfg Config
+}
+
+func New() tools.ToolRunner {
+ cfg := Config{}
+ tools.ApplyDefaults(&cfg)
+ return &Runner{cfg: cfg}
+}
+
+func (r *Runner) Name() string { return name }
+func (r *Runner) Description() string { return description }
+func (r *Runner) Link() string { return link }
+func (r *Runner) Icon() string { return icon }
+
+func (r *Runner) InputTypes() []tools.InputType {
+ return []tools.InputType{
+ tools.InputTypeUsername,
+ tools.InputTypeEmail,
+ }
+}
+
+func (r *Runner) ConfigPtr() interface{} { return &r.cfg }
+
+func (r *Runner) ConfigFields() []tools.ConfigField {
+ return tools.ReflectConfigFields(r.cfg)
+}
+
+func (r *Runner) Available() (bool, string) {
+ if _, err := exec.LookPath("github-recon"); err != nil {
+ return false, "github-recon binary not found in PATH"
+ }
+ return true, ""
+}
+
+func (r *Runner) Dependencies() []string { return []string{"github-recon"} }
+
+func (r *Runner) Run(ctx context.Context, target string, inputType tools.InputType, out chan<- tools.Event) error {
+ defer close(out)
+
+ args := []string{target}
+ if r.cfg.Token != "" {
+ args = append(args, "--token", r.cfg.Token)
+ }
+ if r.cfg.Deepscan {
+ args = append(args, "--deepscan")
+ }
+ if r.cfg.SpoofEmail && inputType == tools.InputTypeEmail {
+ args = append(args, "--spoof-email")
+ }
+
+ cmd := exec.CommandContext(ctx, "github-recon", args...)
+ output, err := tools.RunWithPTY(ctx, cmd)
+
+ // Remove banner
+ output = tools.RemoveFirstLines(output, 10)
+
+ count := 0
+ if err != nil && ctx.Err() != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "scan cancelled"}
+ } else if output != "" {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeOutput, Payload: output}
+ count = strings.Count(output, "Username:")
+ }
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: count}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+}
diff --git a/back/internal/tools/gravatar-recon/tool.go b/back/internal/tools/gravatar-recon/tool.go
new file mode 100644
index 0000000..e1445f6
--- /dev/null
+++ b/back/internal/tools/gravatar-recon/tool.go
@@ -0,0 +1,55 @@
+package gravatarrecon
+
+import (
+ "context"
+ "os/exec"
+
+ "github.com/anotherhadi/iknowyou/internal/tools"
+)
+
+const (
+ name = "gravatar-recon"
+ description = "Gravatar OSINT tool. Extracts public profile data from a Gravatar account: name, bio, location, employment, social accounts, phone, and more."
+ link = "https://github.com/anotherhadi/gravatar-recon"
+ icon = ""
+)
+
+type Runner struct{}
+
+func New() tools.ToolRunner { return &Runner{} }
+
+func (r *Runner) Name() string { return name }
+func (r *Runner) Description() string { return description }
+func (r *Runner) Link() string { return link }
+func (r *Runner) Icon() string { return icon }
+
+func (r *Runner) InputTypes() []tools.InputType {
+ return []tools.InputType{tools.InputTypeEmail}
+}
+
+func (r *Runner) Available() (bool, string) {
+ if _, err := exec.LookPath("gravatar-recon"); err != nil {
+ return false, "gravatar-recon binary not found in PATH"
+ }
+ return true, ""
+}
+
+func (r *Runner) Dependencies() []string { return []string{"gravatar-recon"} }
+
+func (r *Runner) Run(ctx context.Context, target string, _ tools.InputType, out chan<- tools.Event) error {
+ defer close(out)
+
+ cmd := exec.CommandContext(ctx, "gravatar-recon", "--silent", target)
+ output, err := tools.RunWithPTY(ctx, cmd)
+
+ count := 0
+ if err != nil && ctx.Err() != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "scan cancelled"}
+ } else if output != "" {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeOutput, Payload: output}
+ count = 1
+ }
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: count}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+}
diff --git a/back/internal/tools/ipinfo/tool.go b/back/internal/tools/ipinfo/tool.go
new file mode 100644
index 0000000..ea8ad1f
--- /dev/null
+++ b/back/internal/tools/ipinfo/tool.go
@@ -0,0 +1,133 @@
+package ipinfo
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/anotherhadi/iknowyou/internal/tools"
+)
+
+const (
+ name = "ipinfo"
+ description = "IP geolocation via ipinfo.io — returns city, region, country, coordinates, ASN/org, timezone, and hostname."
+ link = "https://ipinfo.io"
+ icon = ""
+)
+
+type Config struct {
+ Token string `yaml:"token" iky:"desc=ipinfo.io API token (optional — free tier allows 50k req/month without one);required=false"`
+}
+
+type Runner struct {
+ cfg Config
+}
+
+func New() tools.ToolRunner {
+ cfg := Config{}
+ tools.ApplyDefaults(&cfg)
+ return &Runner{cfg: cfg}
+}
+
+func (r *Runner) Name() string { return name }
+func (r *Runner) Description() string { return description }
+func (r *Runner) Link() string { return link }
+func (r *Runner) Icon() string { return icon }
+
+func (r *Runner) InputTypes() []tools.InputType {
+ return []tools.InputType{tools.InputTypeIP}
+}
+
+func (r *Runner) ConfigPtr() interface{} { return &r.cfg }
+
+func (r *Runner) ConfigFields() []tools.ConfigField {
+ return tools.ReflectConfigFields(r.cfg)
+}
+
+type ipinfoResponse struct {
+ IP string `json:"ip"`
+ Hostname string `json:"hostname"`
+ City string `json:"city"`
+ Region string `json:"region"`
+ Country string `json:"country"`
+ Loc string `json:"loc"`
+ Org string `json:"org"`
+ Postal string `json:"postal"`
+ Timezone string `json:"timezone"`
+ Bogon bool `json:"bogon"`
+}
+
+func (r *Runner) Run(ctx context.Context, target string, _ tools.InputType, out chan<- tools.Event) error {
+ defer close(out)
+
+ url := fmt.Sprintf("https://ipinfo.io/%s/json", target)
+ if r.cfg.Token != "" {
+ url += "?token=" + r.cfg.Token
+ }
+
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
+ if err != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: err.Error()}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+ req.Header.Set("Accept", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ if ctx.Err() != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "scan cancelled"}
+ } else {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: err.Error()}
+ }
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+ defer resp.Body.Close()
+
+ var info ipinfoResponse
+ if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "failed to parse response"}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ if info.Bogon {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeOutput, Payload: fmt.Sprintf("IP: %s\nType: Bogon/Private address", info.IP)}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 1}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ var sb strings.Builder
+ field := func(label, value string) {
+ if value != "" {
+ sb.WriteString(fmt.Sprintf("%-12s %s\n", label+":", value))
+ }
+ }
+
+ field("IP", info.IP)
+ field("Hostname", info.Hostname)
+ field("City", info.City)
+ field("Region", info.Region)
+ field("Country", info.Country)
+ field("Coordinates", info.Loc)
+ field("Postal", info.Postal)
+ field("Timezone", info.Timezone)
+ field("Org/ASN", info.Org)
+
+ result := strings.TrimSpace(sb.String())
+ count := 0
+ if result != "" {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeOutput, Payload: result}
+ count = 1
+ }
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: count}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+}
diff --git a/back/internal/tools/leakcheck/tool.go b/back/internal/tools/leakcheck/tool.go
new file mode 100644
index 0000000..09aefcc
--- /dev/null
+++ b/back/internal/tools/leakcheck/tool.go
@@ -0,0 +1,178 @@
+package leakcheck
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+
+ "github.com/anotherhadi/iknowyou/internal/tools"
+)
+
+const (
+ name = "leakcheck"
+ description = "Data breach lookup via LeakCheck.io — searches 7B+ leaked records for email addresses, usernames, and phone numbers across hundreds of breaches."
+ link = "https://leakcheck.io"
+ icon = ""
+)
+
+type Config struct {
+ APIKey string `yaml:"api_key" iky:"desc=LeakCheck API key (required — get one at leakcheck.io);required=true"`
+}
+
+type Runner struct {
+ cfg Config
+}
+
+func New() tools.ToolRunner {
+ cfg := Config{}
+ tools.ApplyDefaults(&cfg)
+ return &Runner{cfg: cfg}
+}
+
+func (r *Runner) Name() string { return name }
+func (r *Runner) Description() string { return description }
+func (r *Runner) Link() string { return link }
+func (r *Runner) Icon() string { return icon }
+
+func (r *Runner) InputTypes() []tools.InputType {
+ return []tools.InputType{
+ tools.InputTypeEmail,
+ tools.InputTypeUsername,
+ tools.InputTypePhone,
+ }
+}
+
+func (r *Runner) ConfigPtr() interface{} { return &r.cfg }
+
+func (r *Runner) ConfigFields() []tools.ConfigField {
+ return tools.ReflectConfigFields(r.cfg)
+}
+
+type leakCheckResponse struct {
+ Success bool `json:"success"`
+ Found int `json:"found"`
+ Result []struct {
+ Email string `json:"email"`
+ Username string `json:"username"`
+ Phone string `json:"phone"`
+ Password string `json:"password"`
+ Hash string `json:"hash"`
+ Sources []string `json:"sources"`
+ Fields []string `json:"fields"`
+ } `json:"result"`
+ Error string `json:"error"`
+}
+
+func (r *Runner) Run(ctx context.Context, target string, inputType tools.InputType, out chan<- tools.Event) error {
+ defer close(out)
+
+ queryType := "auto"
+ switch inputType {
+ case tools.InputTypeEmail:
+ queryType = "email"
+ case tools.InputTypeUsername:
+ queryType = "login"
+ case tools.InputTypePhone:
+ queryType = "phone"
+ }
+
+ url := fmt.Sprintf("https://leakcheck.io/api/v2/query/%s?type=%s", target, queryType)
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
+ if err != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: err.Error()}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+ req.Header.Set("X-API-Key", r.cfg.APIKey)
+ req.Header.Set("Accept", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ if ctx.Err() != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "scan cancelled"}
+ } else {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: err.Error()}
+ }
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+ defer resp.Body.Close()
+
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "failed to read response"}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "invalid or exhausted API key"}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ var result leakCheckResponse
+ if err := json.Unmarshal(body, &result); err != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "failed to parse response"}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ if !result.Success {
+ msg := result.Error
+ if msg == "" {
+ msg = "API returned failure"
+ }
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: msg}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ if result.Found == 0 || len(result.Result) == 0 {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ var sb strings.Builder
+ sb.WriteString(fmt.Sprintf("Found in %d breach(es)\n\n", result.Found))
+
+ for _, entry := range result.Result {
+ if len(entry.Sources) > 0 {
+ sb.WriteString(fmt.Sprintf("Sources: %s\n", strings.Join(entry.Sources, ", ")))
+ }
+ if entry.Email != "" {
+ sb.WriteString(fmt.Sprintf(" Email: %s\n", entry.Email))
+ }
+ if entry.Username != "" {
+ sb.WriteString(fmt.Sprintf(" Username: %s\n", entry.Username))
+ }
+ if entry.Phone != "" {
+ sb.WriteString(fmt.Sprintf(" Phone: %s\n", entry.Phone))
+ }
+ if entry.Password != "" {
+ sb.WriteString(fmt.Sprintf(" Password: %s\n", entry.Password))
+ }
+ if entry.Hash != "" {
+ sb.WriteString(fmt.Sprintf(" Hash: %s\n", entry.Hash))
+ }
+ if len(entry.Fields) > 0 {
+ sb.WriteString(fmt.Sprintf(" Fields: %s\n", strings.Join(entry.Fields, ", ")))
+ }
+ sb.WriteString("\n")
+ }
+
+ out <- tools.Event{Tool: name, Type: tools.EventTypeOutput, Payload: strings.TrimSpace(sb.String())}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: result.Found}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+}
diff --git a/back/internal/tools/maigret/tool.go b/back/internal/tools/maigret/tool.go
new file mode 100644
index 0000000..20fe4f3
--- /dev/null
+++ b/back/internal/tools/maigret/tool.go
@@ -0,0 +1,89 @@
+package maigret
+
+import (
+ "context"
+ "os/exec"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "github.com/anotherhadi/iknowyou/internal/tools"
+)
+
+const (
+ name = "maigret"
+ description = "Username OSINT across 3000+ sites. Searches social networks, forums, and online platforms for an account matching the target username."
+ link = "https://github.com/soxoj/maigret"
+ icon = ""
+)
+
+var accountsRe = regexp.MustCompile(`returned (\d+) accounts`)
+
+type Config struct {
+ AllSites bool `yaml:"all_sites" iky:"desc=Scan all sites in the database instead of just the top 500 (slower);default=false"`
+}
+
+type Runner struct {
+ cfg Config
+}
+
+func New() tools.ToolRunner {
+ cfg := Config{}
+ tools.ApplyDefaults(&cfg)
+ return &Runner{cfg: cfg}
+}
+
+func (r *Runner) Name() string { return name }
+func (r *Runner) Description() string { return description }
+func (r *Runner) Link() string { return link }
+func (r *Runner) Icon() string { return icon }
+
+func (r *Runner) InputTypes() []tools.InputType {
+ return []tools.InputType{tools.InputTypeUsername}
+}
+
+func (r *Runner) ConfigPtr() interface{} { return &r.cfg }
+
+func (r *Runner) ConfigFields() []tools.ConfigField {
+ return tools.ReflectConfigFields(r.cfg)
+}
+
+func (r *Runner) Available() (bool, string) {
+ if _, err := exec.LookPath("maigret"); err != nil {
+ return false, "maigret binary not found in PATH"
+ }
+ return true, ""
+}
+
+func (r *Runner) Dependencies() []string { return []string{"maigret"} }
+
+func (r *Runner) Run(ctx context.Context, target string, _ tools.InputType, out chan<- tools.Event) error {
+ defer close(out)
+
+ args := []string{"--no-progressbar", target}
+ if r.cfg.AllSites {
+ args = append(args, "-a")
+ }
+
+ cmd := exec.CommandContext(ctx, "maigret", args...)
+ output, err := tools.RunWithPTY(ctx, cmd)
+
+ // Crop at Python traceback (NixOS read-only store error — results are unaffected)
+ if idx := strings.Index(output, "Traceback (most recent call last)"); idx != -1 {
+ output = strings.TrimSpace(output[:idx])
+ }
+
+ count := 0
+ if err != nil && ctx.Err() != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "scan cancelled"}
+ } else if output != "" {
+ // Parse count from summary line: "returned N accounts"
+ if m := accountsRe.FindStringSubmatch(output); len(m) == 2 {
+ count, _ = strconv.Atoi(m[1])
+ }
+ out <- tools.Event{Tool: name, Type: tools.EventTypeOutput, Payload: output}
+ }
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: count}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+}
diff --git a/back/internal/tools/ptyrun.go b/back/internal/tools/ptyrun.go
new file mode 100644
index 0000000..563fc60
--- /dev/null
+++ b/back/internal/tools/ptyrun.go
@@ -0,0 +1,28 @@
+package tools
+
+import (
+ "context"
+ "io"
+ "os/exec"
+ "regexp"
+
+ "github.com/creack/pty"
+)
+
+// oscRe strips OSC terminal sequences emitted by the PTY (e.g. colour queries).
+var oscRe = regexp.MustCompile(`\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)`)
+
+// RunWithPTY runs cmd under a pseudo-terminal (preserving ANSI colours) and
+// returns the full output once the process exits.
+func RunWithPTY(ctx context.Context, cmd *exec.Cmd) (string, error) {
+ ptmx, err := pty.StartWithSize(cmd, &pty.Winsize{Rows: 50, Cols: 220})
+ if err != nil {
+ return "", err
+ }
+ defer func() { _ = ptmx.Close() }()
+
+ output, _ := io.ReadAll(ptmx)
+ _ = cmd.Wait()
+
+ return oscRe.ReplaceAllString(string(output), ""), ctx.Err()
+}
diff --git a/back/internal/tools/tools.go b/back/internal/tools/tools.go
new file mode 100644
index 0000000..53a0cb8
--- /dev/null
+++ b/back/internal/tools/tools.go
@@ -0,0 +1,72 @@
+package tools
+
+import "context"
+
+type EventType string
+
+const (
+ EventTypeOutput EventType = "output" // raw ANSI text, payload is a plain string
+ EventTypeError EventType = "error"
+ EventTypeCount EventType = "count" // payload is int, additive — emit once or multiple times from Run
+ EventTypeDone EventType = "done"
+)
+
+type InputType string
+
+const (
+ InputTypeEmail InputType = "email"
+ InputTypeUsername InputType = "username"
+ InputTypePhone InputType = "phone"
+ InputTypeIP InputType = "ip"
+ InputTypeDomain InputType = "domain"
+ InputTypePassword InputType = "password"
+ InputTypeName InputType = "name"
+)
+
+type Event struct {
+ Tool string `json:"tool"`
+ Type EventType `json:"type"`
+ Payload interface{} `json:"payload,omitempty"`
+}
+
+// ToolRunner is the core interface every tool must implement.
+type ToolRunner interface {
+ Name() string
+ Description() string
+ Link() string // URL to source or documentation
+ Icon() string // Simple Icons slug, empty if none
+
+ InputTypes() []InputType
+
+ // Run executes the tool and sends Events to out. Must close out when done.
+ // inputType indicates what kind of value target is (email, username, ...).
+ Run(ctx context.Context, target string, inputType InputType, out chan<- Event) error
+}
+
+type Configurable interface {
+ ConfigPtr() interface{}
+}
+
+type ConfigField struct {
+ Name string `json:"name"`
+ Type string `json:"type"` // "string", "bool", "int", "float", "enum"
+ Required bool `json:"required"`
+ Default any `json:"default"`
+ Description string `json:"description"`
+ Value any `json:"value"`
+ Options []string `json:"options,omitempty"` // non-empty when Type == "enum"
+}
+
+type ConfigDescriber interface {
+ ConfigFields() []ConfigField
+}
+
+// AvailabilityChecker is implemented by tools that require an external binary.
+type AvailabilityChecker interface {
+ Available() (ok bool, reason string)
+}
+
+type DependencyLister interface {
+ Dependencies() []string
+}
+
diff --git a/back/internal/tools/user-scanner/tool.go b/back/internal/tools/user-scanner/tool.go
new file mode 100644
index 0000000..3be8766
--- /dev/null
+++ b/back/internal/tools/user-scanner/tool.go
@@ -0,0 +1,95 @@
+package userscanner
+
+import (
+ "context"
+ "os/exec"
+ "strings"
+
+ "github.com/anotherhadi/iknowyou/internal/tools"
+)
+
+const (
+ name = "user-scanner"
+ description = "🕵️♂️ (2-in-1) Email & Username OSINT suite. Analyzes 195+ scan vectors (95+ email / 100+ username) for security research, investigations, and digital footprinting."
+ link = "https://github.com/kaifcodec/user-scanner"
+ icon = ""
+)
+
+type Config struct {
+ AllowLoud bool `yaml:"allow_loud" iky:"desc=Enable scanning sites that may send emails/notifications (password resets, etc.);default=false"`
+ OnlyFound bool `yaml:"only_found" iky:"desc=Only show sites where the username/email was found;default=true"`
+}
+
+type Runner struct {
+ cfg Config
+}
+
+func New() tools.ToolRunner {
+ cfg := Config{}
+ tools.ApplyDefaults(&cfg)
+ return &Runner{cfg: cfg}
+}
+
+func (r *Runner) Name() string { return name }
+func (r *Runner) Description() string { return description }
+func (r *Runner) Link() string { return link }
+func (r *Runner) Icon() string { return icon }
+
+func (r *Runner) InputTypes() []tools.InputType {
+ return []tools.InputType{
+ tools.InputTypeEmail,
+ tools.InputTypeUsername,
+ }
+}
+
+func (r *Runner) ConfigPtr() interface{} { return &r.cfg }
+
+func (r *Runner) ConfigFields() []tools.ConfigField {
+ return tools.ReflectConfigFields(r.cfg)
+}
+
+func (r *Runner) Available() (bool, string) {
+ if _, err := exec.LookPath("user-scanner"); err != nil {
+ return false, "user-scanner binary not found in PATH"
+ }
+ return true, ""
+}
+
+func (r *Runner) Dependencies() []string { return []string{"user-scanner"} }
+
+func (r *Runner) Run(ctx context.Context, target string, inputType tools.InputType, out chan<- tools.Event) error {
+ defer close(out)
+
+ args := make([]string, 0, 6)
+ switch inputType {
+ case tools.InputTypeEmail:
+ args = append(args, "-e", target)
+ default:
+ args = append(args, "-u", target)
+ }
+ if r.cfg.AllowLoud {
+ args = append(args, "--allow-loud")
+ }
+ if r.cfg.OnlyFound {
+ args = append(args, "--only-found")
+ }
+
+ cmd := exec.CommandContext(ctx, "user-scanner", args...)
+ output, err := tools.RunWithPTY(ctx, cmd)
+
+ // Removing banner
+ output = tools.RemoveFirstLines(output, 8)
+ // count =
+ output = tools.CropAfterExclude(output, "[i] Scan complete.")
+
+ count := 0
+ if err != nil && ctx.Err() != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "scan cancelled"}
+ } else if output != "" {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeOutput, Payload: output}
+ count = strings.Count(output, "[✔]")
+ }
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: count}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+}
diff --git a/back/internal/tools/utils.go b/back/internal/tools/utils.go
new file mode 100644
index 0000000..6bdc0bd
--- /dev/null
+++ b/back/internal/tools/utils.go
@@ -0,0 +1,61 @@
+package tools
+
+import "strings"
+
+// RemoveFirstLines removes the first n lines. Returns "" if n >= total lines.
+func RemoveFirstLines(input string, n int) string {
+ lines := strings.Split(input, "\n")
+ if n >= len(lines) {
+ return ""
+ }
+ return strings.Join(lines[n:], "\n")
+}
+
+// RemoveLastLines removes the last n lines. Returns "" if n >= total lines.
+func RemoveLastLines(input string, n int) string {
+ lines := strings.Split(input, "\n")
+ if n >= len(lines) {
+ return ""
+ }
+ return strings.Join(lines[:len(lines)-n], "\n")
+}
+
+// CropBefore removes everything before the first occurrence of y (inclusive of y).
+// Returns input unchanged if y is not found.
+func CropBefore(input string, y string) string {
+ idx := strings.Index(input, y)
+ if idx == -1 {
+ return input
+ }
+ return input[idx:]
+}
+
+// CropAfter removes everything after the last occurrence of y (inclusive of y).
+// Returns input unchanged if y is not found.
+func CropAfter(input string, y string) string {
+ idx := strings.LastIndex(input, y)
+ if idx == -1 {
+ return input
+ }
+ return input[:idx+len(y)]
+}
+
+// CropBeforeExclude removes everything before and including the first occurrence of y.
+// Returns input unchanged if y is not found.
+func CropBeforeExclude(input string, y string) string {
+ idx := strings.Index(input, y)
+ if idx == -1 {
+ return input
+ }
+ return input[idx+len(y):]
+}
+
+// CropAfterExclude removes everything from the last occurrence of y onwards.
+// Returns input unchanged if y is not found.
+func CropAfterExclude(input string, y string) string {
+ idx := strings.LastIndex(input, y)
+ if idx == -1 {
+ return input
+ }
+ return input[:idx]
+}
diff --git a/back/internal/tools/wappalyzer/tool.go b/back/internal/tools/wappalyzer/tool.go
new file mode 100644
index 0000000..966a3fa
--- /dev/null
+++ b/back/internal/tools/wappalyzer/tool.go
@@ -0,0 +1,126 @@
+package wappalyzer
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "net/http"
+ "sort"
+ "strings"
+
+ wappalyzergo "github.com/projectdiscovery/wappalyzergo"
+
+ "github.com/anotherhadi/iknowyou/internal/tools"
+)
+
+const (
+ name = "wappalyzer"
+ description = "Web technology fingerprinting via wappalyzergo — detects CMS, frameworks, web servers, analytics, CDN, and 1500+ other technologies running on a domain."
+ link = "https://github.com/projectdiscovery/wappalyzergo"
+ icon = "wappalyzer"
+)
+
+type Runner struct {
+ wappalyze *wappalyzergo.Wappalyze
+}
+
+func New() tools.ToolRunner {
+ w, _ := wappalyzergo.New()
+ return &Runner{wappalyze: w}
+}
+
+func (r *Runner) Name() string { return name }
+func (r *Runner) Description() string { return description }
+func (r *Runner) Link() string { return link }
+func (r *Runner) Icon() string { return icon }
+
+func (r *Runner) InputTypes() []tools.InputType {
+ return []tools.InputType{tools.InputTypeDomain}
+}
+
+func (r *Runner) Run(ctx context.Context, target string, _ tools.InputType, out chan<- tools.Event) error {
+ defer close(out)
+
+ // Try HTTPS first, fall back to HTTP
+ var (
+ resp *http.Response
+ body []byte
+ err error
+ )
+ for _, scheme := range []string{"https", "http"} {
+ targetURL := fmt.Sprintf("%s://%s", scheme, target)
+ req, reqErr := http.NewRequestWithContext(ctx, http.MethodGet, targetURL, nil)
+ if reqErr != nil {
+ continue
+ }
+ req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)")
+
+ resp, err = http.DefaultClient.Do(req)
+ if err == nil {
+ defer resp.Body.Close()
+ body, err = io.ReadAll(resp.Body)
+ if err == nil {
+ break
+ }
+ }
+ if ctx.Err() != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "scan cancelled"}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+ }
+
+ if err != nil || resp == nil {
+ msg := "failed to connect to target"
+ if err != nil {
+ msg = err.Error()
+ }
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: msg}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ fingerprints := r.wappalyze.FingerprintWithInfo(resp.Header, body)
+ if len(fingerprints) == 0 {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ // Group by category
+ byCategory := make(map[string][]string)
+ for tech, info := range fingerprints {
+ cats := info.Categories
+ if len(cats) == 0 {
+ cats = []string{"Other"}
+ }
+ for _, cat := range cats {
+ byCategory[cat] = append(byCategory[cat], tech)
+ }
+ }
+
+ cats := make([]string, 0, len(byCategory))
+ for c := range byCategory {
+ cats = append(cats, c)
+ }
+ sort.Strings(cats)
+
+ var sb strings.Builder
+ sb.WriteString(fmt.Sprintf("Detected %d technologies\n\n", len(fingerprints)))
+ for _, cat := range cats {
+ techs := byCategory[cat]
+ sort.Strings(techs)
+ sb.WriteString(fmt.Sprintf("%s:\n", cat))
+ for _, t := range techs {
+ sb.WriteString(fmt.Sprintf(" - %s\n", t))
+ }
+ sb.WriteString("\n")
+ }
+
+ out <- tools.Event{Tool: name, Type: tools.EventTypeOutput, Payload: strings.TrimSpace(sb.String())}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: len(fingerprints)}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+}
diff --git a/back/internal/tools/whois/tool.go b/back/internal/tools/whois/tool.go
new file mode 100644
index 0000000..3131aaa
--- /dev/null
+++ b/back/internal/tools/whois/tool.go
@@ -0,0 +1,62 @@
+package whois
+
+import (
+ "context"
+ "os/exec"
+ "strings"
+
+ "github.com/anotherhadi/iknowyou/internal/tools"
+)
+
+const (
+ name = "whois"
+ description = "WHOIS lookup for domain registration and IP ownership information."
+ link = "https://en.wikipedia.org/wiki/WHOIS"
+ icon = ""
+)
+
+type Runner struct{}
+
+func New() tools.ToolRunner { return &Runner{} }
+
+func (r *Runner) Name() string { return name }
+func (r *Runner) Description() string { return description }
+func (r *Runner) Link() string { return link }
+func (r *Runner) Icon() string { return icon }
+
+func (r *Runner) InputTypes() []tools.InputType {
+ return []tools.InputType{tools.InputTypeDomain, tools.InputTypeIP}
+}
+
+func (r *Runner) Available() (bool, string) {
+ if _, err := exec.LookPath("whois"); err != nil {
+ return false, "whois binary not found in PATH"
+ }
+ return true, ""
+}
+
+func (r *Runner) Dependencies() []string { return []string{"whois"} }
+
+func (r *Runner) Run(ctx context.Context, target string, _ tools.InputType, out chan<- tools.Event) error {
+ defer close(out)
+
+ cmd := exec.CommandContext(ctx, "whois", target)
+ output, err := cmd.Output()
+
+ if err != nil && ctx.Err() != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "scan cancelled"}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ result := strings.TrimSpace(string(output))
+ count := 0
+ if result != "" {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeOutput, Payload: result}
+ count = 1
+ }
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: count}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+}
diff --git a/back/internal/tools/whoisfreaks/tool.go b/back/internal/tools/whoisfreaks/tool.go
new file mode 100644
index 0000000..29dfe84
--- /dev/null
+++ b/back/internal/tools/whoisfreaks/tool.go
@@ -0,0 +1,232 @@
+package whoisfreaks
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+
+ "github.com/anotherhadi/iknowyou/internal/tools"
+ "github.com/tidwall/gjson"
+)
+
+const (
+ name = "whoisfreaks"
+ description = "Reverse WHOIS lookup via WhoisFreaks — find all domains registered by an email, owner name, or keyword across 3.6B+ WHOIS records."
+ link = "https://whoisfreaks.com"
+ icon = ""
+)
+
+type Config struct {
+ APIKey string `yaml:"api_key" iky:"desc=WhoisFreaks API key (required — free account at whoisfreaks.com);required=true"`
+}
+
+type Runner struct {
+ cfg Config
+}
+
+func New() tools.ToolRunner {
+ cfg := Config{}
+ tools.ApplyDefaults(&cfg)
+ return &Runner{cfg: cfg}
+}
+
+func (r *Runner) Name() string { return name }
+func (r *Runner) Description() string { return description }
+func (r *Runner) Link() string { return link }
+func (r *Runner) Icon() string { return icon }
+
+func (r *Runner) InputTypes() []tools.InputType {
+ return []tools.InputType{
+ tools.InputTypeEmail,
+ tools.InputTypeName,
+ tools.InputTypeDomain,
+ }
+}
+
+func (r *Runner) ConfigPtr() interface{} { return &r.cfg }
+
+func (r *Runner) ConfigFields() []tools.ConfigField {
+ return tools.ReflectConfigFields(r.cfg)
+}
+
+
+var skipKeys = map[string]bool{
+ "num": true, "status": true, "query_time": true, "update_date": true,
+ "iana_id": true, "whois_server": true, "handle": true,
+ "zip_code": true, "country_code": true, "mailing_address": true,
+ "phone_number": true, "administrative_contact": true, "technical_contact": true,
+}
+
+func prettyResult(r gjson.Result, depth int) string {
+ indent := strings.Repeat(" ", depth)
+ var sb strings.Builder
+ r.ForEach(func(key, val gjson.Result) bool {
+ k := key.String()
+ if skipKeys[k] {
+ return true
+ }
+ switch val.Type {
+ case gjson.JSON:
+ if val.IsArray() {
+ arr := val.Array()
+ if len(arr) == 0 {
+ return true
+ }
+ sb.WriteString(fmt.Sprintf("%s%s:\n", indent, k))
+ for _, item := range arr {
+ if item.Type == gjson.JSON {
+ sb.WriteString(fmt.Sprintf("%s -\n", indent))
+ sb.WriteString(prettyResult(item, depth+2))
+ } else {
+ sb.WriteString(fmt.Sprintf("%s - %s\n", indent, item.String()))
+ }
+ }
+ } else {
+ sb.WriteString(fmt.Sprintf("%s%s:\n", indent, k))
+ sb.WriteString(prettyResult(val, depth+1))
+ }
+ default:
+ v := val.String()
+ if v == "" {
+ return true
+ }
+ sb.WriteString(fmt.Sprintf("%s%s: %s\n", indent, k, v))
+ }
+ return true
+ })
+ return sb.String()
+}
+
+func doRequest(ctx context.Context, req *http.Request) ([]byte, *http.Response, error) {
+ for {
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ return nil, nil, err
+ }
+ body, err := io.ReadAll(resp.Body)
+ resp.Body.Close()
+ if err != nil {
+ return nil, resp, err
+ }
+ if resp.StatusCode != http.StatusTooManyRequests {
+ return body, resp, nil
+ }
+ select {
+ case <-ctx.Done():
+ return nil, resp, ctx.Err()
+ case <-time.After(60 * time.Second):
+ }
+ // Rebuild the request since the body was consumed
+ req2, err := http.NewRequestWithContext(ctx, req.Method, req.URL.String(), nil)
+ if err != nil {
+ return nil, resp, err
+ }
+ req2.Header = req.Header
+ req = req2
+ }
+}
+
+func (r *Runner) Run(ctx context.Context, target string, inputType tools.InputType, out chan<- tools.Event) error {
+ defer close(out)
+
+ params := url.Values{}
+ params.Set("whois", "reverse")
+ params.Set("apiKey", r.cfg.APIKey)
+
+ switch inputType {
+ case tools.InputTypeEmail:
+ params.Set("email", target)
+ case tools.InputTypeName:
+ params.Set("owner", target)
+ case tools.InputTypeDomain:
+ params.Set("keyword", target)
+ default:
+ params.Set("keyword", target)
+ }
+
+ req, err := http.NewRequestWithContext(ctx,
+ http.MethodGet,
+ "https://api.whoisfreaks.com/v1.0/whois?"+params.Encode(),
+ nil,
+ )
+ if err != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: err.Error()}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+ req.Header.Set("Accept", "application/json")
+
+ body, resp, err := doRequest(ctx, req)
+ if err != nil {
+ if ctx.Err() != nil {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "scan cancelled"}
+ } else {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: err.Error()}
+ }
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "invalid or exhausted API key"}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ if resp.StatusCode == http.StatusNotFound {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: fmt.Sprintf("API error %d: %s", resp.StatusCode, string(body))}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ j := gjson.ParseBytes(body)
+
+ if !j.Get("whois_domains_historical").Exists() {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "unexpected response format"}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ domains := j.Get("whois_domains_historical").Array()
+ if len(domains) == 0 {
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+ }
+
+ total := j.Get("total_Result").Int()
+ totalPages := j.Get("total_Pages").Int()
+ currentPage := j.Get("current_Page").Int()
+
+ var sb strings.Builder
+ sb.WriteString(fmt.Sprintf("Found %d domain(s)", total))
+ if totalPages > 1 {
+ sb.WriteString(fmt.Sprintf(" across %d pages (showing page %d)", totalPages, currentPage))
+ }
+ sb.WriteString("\n\n")
+
+ for _, d := range domains {
+ sb.WriteString(prettyResult(d, 0))
+ sb.WriteString("\n")
+ }
+
+ out <- tools.Event{Tool: name, Type: tools.EventTypeOutput, Payload: strings.TrimSpace(sb.String())}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: int(total)}
+ out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
+ return nil
+}
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..17e2472
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,157 @@
+{
+ "nodes": {
+ "bun2nix": {
+ "inputs": {
+ "flake-parts": "flake-parts",
+ "import-tree": "import-tree",
+ "nixpkgs": [
+ "nixpkgs"
+ ],
+ "systems": "systems",
+ "treefmt-nix": "treefmt-nix"
+ },
+ "locked": {
+ "lastModified": 1770895533,
+ "narHash": "sha256-v3QaK9ugy9bN9RXDnjw0i2OifKmz2NnKM82agtqm/UY=",
+ "owner": "nix-community",
+ "repo": "bun2nix",
+ "rev": "c843f477b15f51151f8c6bcc886954699440a6e1",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "bun2nix",
+ "type": "github"
+ }
+ },
+ "flake-parts": {
+ "inputs": {
+ "nixpkgs-lib": "nixpkgs-lib"
+ },
+ "locked": {
+ "lastModified": 1769996383,
+ "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "type": "github"
+ }
+ },
+ "import-tree": {
+ "locked": {
+ "lastModified": 1763762820,
+ "narHash": "sha256-ZvYKbFib3AEwiNMLsejb/CWs/OL/srFQ8AogkebEPF0=",
+ "owner": "vic",
+ "repo": "import-tree",
+ "rev": "3c23749d8013ec6daa1d7255057590e9ca726646",
+ "type": "github"
+ },
+ "original": {
+ "owner": "vic",
+ "repo": "import-tree",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1775036866,
+ "narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "6201e203d09599479a3b3450ed24fa81537ebc4e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs-lib": {
+ "locked": {
+ "lastModified": 1769909678,
+ "narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=",
+ "owner": "nix-community",
+ "repo": "nixpkgs.lib",
+ "rev": "72716169fe93074c333e8d0173151350670b824c",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "nixpkgs.lib",
+ "type": "github"
+ }
+ },
+ "nur-osint": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1774035694,
+ "narHash": "sha256-PtORnAJ/SKeOwrPAjZ0LR00Pu8aDIzXO8H8v9CoM7zk=",
+ "owner": "anotherhadi",
+ "repo": "nur-osint",
+ "rev": "813351d47721d411441bb6221faf2c6163846946",
+ "type": "github"
+ },
+ "original": {
+ "owner": "anotherhadi",
+ "repo": "nur-osint",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "bun2nix": "bun2nix",
+ "nixpkgs": "nixpkgs",
+ "nur-osint": "nur-osint"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ },
+ "treefmt-nix": {
+ "inputs": {
+ "nixpkgs": [
+ "bun2nix",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1770228511,
+ "narHash": "sha256-wQ6NJSuFqAEmIg2VMnLdCnUc0b7vslUohqqGGD+Fyxk=",
+ "owner": "numtide",
+ "repo": "treefmt-nix",
+ "rev": "337a4fe074be1042a35086f15481d763b8ddc0e7",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "treefmt-nix",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..72da7bd
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,74 @@
+{
+ description = "iknowyou: self-hosted OSINT aggregation platform";
+
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+
+ bun2nix = {
+ url = "github:nix-community/bun2nix";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+
+ nur-osint = {
+ url = "github:anotherhadi/nur-osint";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ };
+
+ outputs = {
+ self,
+ nixpkgs,
+ bun2nix,
+ nur-osint,
+ }: let
+ system = "x86_64-linux";
+ pkgs = import nixpkgs {
+ inherit system;
+ config.permittedInsecurePackages = ["python3.13-pypdf2-3.0.1"];
+ };
+
+ backendPkg = import ./nix/backend.nix {inherit pkgs;};
+ frontendPkg = import ./nix/frontend.nix {inherit pkgs bun2nix system;};
+
+ osintTools = with pkgs; [
+ whois
+ dnsutils
+ maigret
+ nur-osint.packages.${system}.user-scanner
+ nur-osint.packages.${system}.github-recon
+ ];
+
+ ikyPkg = pkgs.symlinkJoin {
+ name = "iky";
+ paths = [backendPkg] ++ osintTools;
+ nativeBuildInputs = [pkgs.makeWrapper];
+ postBuild = ''
+ mkdir -p $out/share/iky
+ cp -r ${frontendPkg} $out/share/iky/frontend
+ wrapProgram $out/bin/server \
+ --set-default IKY_FRONT_DIR $out/share/iky/frontend
+ '';
+ };
+ in {
+ nixosModules.default = import ./nix/module.nix;
+
+ packages.${system} = {
+ backend = backendPkg;
+ frontend = frontendPkg;
+ default = ikyPkg;
+ };
+
+ devShells.${system}.default = pkgs.mkShell {
+ packages = with pkgs;
+ [
+ bun2nix.packages.${system}.default
+ bun
+ go
+ air
+ just
+ ]
+ ++ osintTools;
+ IKY_CONFIG = "./config.yaml";
+ };
+ };
+}
diff --git a/front/.gitignore b/front/.gitignore
new file mode 100644
index 0000000..a91db0d
--- /dev/null
+++ b/front/.gitignore
@@ -0,0 +1,27 @@
+# build output
+dist/
+# generated types
+.astro/
+
+# dependencies
+node_modules/
+
+# logs
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+
+# environment variables
+.env
+.env.production
+
+# macOS-specific files
+.DS_Store
+
+result/
+
+# code editors
+.idea/
+.vscode/
diff --git a/front/astro.config.mjs b/front/astro.config.mjs
new file mode 100644
index 0000000..223fec2
--- /dev/null
+++ b/front/astro.config.mjs
@@ -0,0 +1,52 @@
+// @ts-check
+import { defineConfig } from "astro/config";
+import tailwindcss from "@tailwindcss/vite";
+import svelte from "@astrojs/svelte";
+import remarkGithubBlockquoteAlert from "remark-github-blockquote-alert";
+import { EventEmitter } from "events";
+
+EventEmitter.defaultMaxListeners = 25;
+
+// https://astro.build/config
+export default defineConfig({
+ site: "https://iky.hadi.icu",
+ output: "static",
+ vite: {
+ resolve: {
+ noExternal: ["@lucide/svelte"],
+ },
+ plugins: [
+ tailwindcss(),
+ {
+ name: "shell-rewrite",
+ configureServer(server) {
+ server.middlewares.use((req, _res, next) => {
+ if (/^\/tools\/[^/]+\/?(\?.*)?$/.test(req.url)) req.url = "/tools/_";
+ if (/^\/search\/[^/]+\/?(\?.*)?$/.test(req.url)) req.url = "/search/_";
+ next();
+ });
+ },
+ },
+ ],
+ server: {
+ proxy: {
+ "/api": "http://localhost:8080",
+ },
+ },
+ },
+ integrations: [svelte()],
+ markdown: {
+ remarkPlugins: [remarkGithubBlockquoteAlert],
+ shikiConfig: {
+ theme: "github-dark",
+ transformers: [
+ {
+ name: "code-block-meta",
+ pre(node) {
+ node.properties["data-lang"] = this.options.lang || "text";
+ },
+ },
+ ],
+ },
+ },
+});
diff --git a/front/bun.lock b/front/bun.lock
new file mode 100644
index 0000000..1882992
--- /dev/null
+++ b/front/bun.lock
@@ -0,0 +1,877 @@
+{
+ "lockfileVersion": 1,
+ "configVersion": 1,
+ "workspaces": {
+ "": {
+ "name": "iknowyou",
+ "dependencies": {
+ "@astrojs/svelte": "8.0.4",
+ "@lucide/svelte": "^1.7.0",
+ "@tailwindcss/vite": "^4.2.1",
+ "ansi_up": "^6.0.6",
+ "astro": "6.1.2",
+ "dompurify": "^3.3.3",
+ "js-yaml": "^4.1.1",
+ "remark-github-blockquote-alert": "^2.1.0",
+ "svelte": "^5.53.12",
+ "tailwindcss": "^4.2.1",
+ "typescript": "^5.9.3",
+ },
+ "devDependencies": {
+ "@tailwindcss/typography": "^0.5.19",
+ "@types/dompurify": "^3.2.0",
+ "@types/js-yaml": "^4.0.9",
+ "@types/node": "^25.5.0",
+ "concurrently": "^9.2.1",
+ "daisyui": "^5.5.19",
+ },
+ },
+ },
+ "packages": {
+ "@astrojs/compiler": ["@astrojs/compiler@3.0.1", "", {}, "sha512-z97oYbdebO5aoWzuJ/8q5hLK232+17KcLZ7cJ8BCWk6+qNzVxn/gftC0KzMBUTD8WAaBkPpNSQK6PXLnNrZ0CA=="],
+
+ "@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.8.0", "", { "dependencies": { "picomatch": "^4.0.3" } }, "sha512-J56GrhEiV+4dmrGLPNOl2pZjpHXAndWVyiVDYGDuw6MWKpBSEMLdFxHzeM/6sqaknw9M+HFfHZAcvi3OfT3D/w=="],
+
+ "@astrojs/markdown-remark": ["@astrojs/markdown-remark@7.1.0", "", { "dependencies": { "@astrojs/internal-helpers": "0.8.0", "@astrojs/prism": "4.0.1", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "retext-smartypants": "^6.2.0", "shiki": "^4.0.0", "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.1.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-P+HnCsu2js3BoTc8kFmu+E9gOcFeMdPris75g+Zl4sY8+bBRbSQV6xzcBDbZ27eE7yBGEGQoqjpChx+KJYIPYQ=="],
+
+ "@astrojs/prism": ["@astrojs/prism@4.0.1", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-nksZQVjlferuWzhPsBpQ1JE5XuKAf1id1/9Hj4a9KG4+ofrlzxUUwX4YGQF/SuDiuiGKEnzopGOt38F3AnVWsQ=="],
+
+ "@astrojs/svelte": ["@astrojs/svelte@8.0.4", "", { "dependencies": { "@sveltejs/vite-plugin-svelte": "^6.2.4", "svelte2tsx": "^0.7.52", "vite": "^7.3.1" }, "peerDependencies": { "astro": "^6.0.0", "svelte": "^5.43.6", "typescript": "^5.3.3" } }, "sha512-c5m3chjtgxBE3BzsE/bZbCFBkLPhq041rm2WJFaTIKGwt/3xNm/5efYCj23reuAcBsl4iYS8n2UwkAHQJzhkZA=="],
+
+ "@astrojs/telemetry": ["@astrojs/telemetry@3.3.0", "", { "dependencies": { "ci-info": "^4.2.0", "debug": "^4.4.0", "dlv": "^1.1.3", "dset": "^3.1.4", "is-docker": "^3.0.0", "is-wsl": "^3.1.0", "which-pm-runs": "^1.1.0" } }, "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ=="],
+
+ "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
+
+ "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
+
+ "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
+
+ "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
+
+ "@capsizecss/unpack": ["@capsizecss/unpack@4.0.0", "", { "dependencies": { "fontkitten": "^1.0.0" } }, "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA=="],
+
+ "@clack/core": ["@clack/core@1.2.0", "", { "dependencies": { "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-qfxof/3T3t9DPU/Rj3OmcFyZInceqj/NVtO9rwIuJqCUgh32gwPjpFQQp/ben07qKlhpwq7GzfWpST4qdJ5Drg=="],
+
+ "@clack/prompts": ["@clack/prompts@1.2.0", "", { "dependencies": { "@clack/core": "1.2.0", "fast-string-width": "^1.1.0", "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w=="],
+
+ "@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
+
+ "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="],
+
+ "@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="],
+
+ "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="],
+
+ "@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="],
+
+ "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="],
+
+ "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="],
+
+ "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="],
+
+ "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="],
+
+ "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="],
+
+ "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="],
+
+ "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="],
+
+ "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="],
+
+ "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="],
+
+ "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="],
+
+ "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="],
+
+ "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="],
+
+ "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="],
+
+ "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q=="],
+
+ "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="],
+
+ "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow=="],
+
+ "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="],
+
+ "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg=="],
+
+ "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="],
+
+ "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="],
+
+ "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="],
+
+ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="],
+
+ "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="],
+
+ "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
+
+ "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="],
+
+ "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="],
+
+ "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="],
+
+ "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="],
+
+ "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="],
+
+ "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="],
+
+ "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="],
+
+ "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="],
+
+ "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="],
+
+ "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="],
+
+ "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="],
+
+ "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="],
+
+ "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="],
+
+ "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="],
+
+ "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="],
+
+ "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="],
+
+ "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="],
+
+ "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="],
+
+ "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="],
+
+ "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="],
+
+ "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="],
+
+ "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="],
+
+ "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
+
+ "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
+
+ "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
+
+ "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
+
+ "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
+
+ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
+
+ "@lucide/svelte": ["@lucide/svelte@1.7.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-YytBKOUBGox7yWcykZnYxOkn5WpR5G1qYXLYXV/j1B79SOTTEKzB+s5yF5Rq9l9OkweDStNH2b4yTqfvhEhV8g=="],
+
+ "@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="],
+
+ "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
+
+ "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.1", "", { "os": "android", "cpu": "arm" }, "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA=="],
+
+ "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.1", "", { "os": "android", "cpu": "arm64" }, "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA=="],
+
+ "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw=="],
+
+ "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew=="],
+
+ "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w=="],
+
+ "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g=="],
+
+ "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g=="],
+
+ "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg=="],
+
+ "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ=="],
+
+ "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA=="],
+
+ "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ=="],
+
+ "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw=="],
+
+ "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw=="],
+
+ "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg=="],
+
+ "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg=="],
+
+ "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg=="],
+
+ "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ=="],
+
+ "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg=="],
+
+ "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w=="],
+
+ "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw=="],
+
+ "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA=="],
+
+ "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g=="],
+
+ "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg=="],
+
+ "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg=="],
+
+ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ=="],
+
+ "@shikijs/core": ["@shikijs/core@4.0.2", "", { "dependencies": { "@shikijs/primitive": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw=="],
+
+ "@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag=="],
+
+ "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg=="],
+
+ "@shikijs/langs": ["@shikijs/langs@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2" } }, "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg=="],
+
+ "@shikijs/primitive": ["@shikijs/primitive@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw=="],
+
+ "@shikijs/themes": ["@shikijs/themes@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2" } }, "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA=="],
+
+ "@shikijs/types": ["@shikijs/types@4.0.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg=="],
+
+ "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
+
+ "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.9", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA=="],
+
+ "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.2.4", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "deepmerge": "^4.3.1", "magic-string": "^0.30.21", "obug": "^2.1.0", "vitefu": "^1.1.1" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA=="],
+
+ "@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@5.0.2", "", { "dependencies": { "obug": "^2.1.0" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig=="],
+
+ "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="],
+
+ "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="],
+
+ "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="],
+
+ "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="],
+
+ "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="],
+
+ "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="],
+
+ "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="],
+
+ "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="],
+
+ "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="],
+
+ "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="],
+
+ "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="],
+
+ "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="],
+
+ "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="],
+
+ "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="],
+
+ "@tailwindcss/typography": ["@tailwindcss/typography@0.5.19", "", { "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg=="],
+
+ "@tailwindcss/vite": ["@tailwindcss/vite@4.2.2", "", { "dependencies": { "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "tailwindcss": "4.2.2" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w=="],
+
+ "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="],
+
+ "@types/dompurify": ["@types/dompurify@3.2.0", "", { "dependencies": { "dompurify": "*" } }, "sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg=="],
+
+ "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
+
+ "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
+
+ "@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="],
+
+ "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
+
+ "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
+
+ "@types/nlcst": ["@types/nlcst@2.0.3", "", { "dependencies": { "@types/unist": "*" } }, "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA=="],
+
+ "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
+
+ "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
+
+ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
+
+ "@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="],
+
+ "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
+
+ "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
+
+ "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+
+ "ansi_up": ["ansi_up@6.0.6", "", {}, "sha512-yIa1x3Ecf8jWP4UWEunNjqNX6gzE4vg2gGz+xqRGY+TBSucnYp6RRdPV4brmtg6bQ1ljD48mZ5iGSEj7QEpRKA=="],
+
+ "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
+
+ "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
+
+ "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
+
+ "array-iterate": ["array-iterate@2.0.1", "", {}, "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg=="],
+
+ "astro": ["astro@6.1.2", "", { "dependencies": { "@astrojs/compiler": "^3.0.1", "@astrojs/internal-helpers": "0.8.0", "@astrojs/markdown-remark": "7.1.0", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^4.0.0", "@clack/prompts": "^1.1.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "ci-info": "^4.4.0", "clsx": "^2.1.1", "common-ancestor-path": "^2.0.0", "cookie": "^1.1.1", "devalue": "^5.6.3", "diff": "^8.0.3", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^2.0.0", "esbuild": "^0.27.3", "flattie": "^1.1.1", "fontace": "~0.4.1", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "js-yaml": "^4.1.1", "magic-string": "^0.30.21", "magicast": "^0.5.2", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "obug": "^2.1.1", "p-limit": "^7.3.0", "p-queue": "^9.1.0", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.3", "rehype": "^13.0.2", "semver": "^7.7.4", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "svgo": "^4.0.1", "tinyclip": "^0.1.12", "tinyexec": "^1.0.4", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", "unifont": "~0.7.4", "unist-util-visit": "^5.1.0", "unstorage": "^1.17.4", "vfile": "^6.0.3", "vite": "^7.3.1", "vitefu": "^1.1.2", "xxhash-wasm": "^1.1.0", "yargs-parser": "^22.0.0", "zod": "^4.3.6" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "bin/astro.mjs" } }, "sha512-r3iIvmB6JvQxsdJLvapybKKq7Bojd1iQK6CCx5P55eRnXJIyUpHx/1UB/GdMm+em/lwaCUasxHCmIO0lCLV2uA=="],
+
+ "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
+
+ "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
+
+ "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
+
+ "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
+
+ "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
+ "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
+
+ "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
+
+ "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
+
+ "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
+
+ "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="],
+
+ "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
+
+ "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
+
+ "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
+
+ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
+
+ "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
+
+ "commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
+
+ "common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="],
+
+ "concurrently": ["concurrently@9.2.1", "", { "dependencies": { "chalk": "4.1.2", "rxjs": "7.8.2", "shell-quote": "1.8.3", "supports-color": "8.1.1", "tree-kill": "1.2.2", "yargs": "17.7.2" }, "bin": { "conc": "dist/bin/concurrently.js", "concurrently": "dist/bin/concurrently.js" } }, "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng=="],
+
+ "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
+
+ "cookie-es": ["cookie-es@1.2.3", "", {}, "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw=="],
+
+ "crossws": ["crossws@0.3.5", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA=="],
+
+ "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
+
+ "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="],
+
+ "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
+
+ "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
+
+ "csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="],
+
+ "daisyui": ["daisyui@5.5.19", "", {}, "sha512-pbFAkl1VCEh/MPCeclKL61I/MqRIFFhNU7yiXoDDRapXN4/qNCoMxeCCswyxEEhqL5eiTTfwHvucFtOE71C9sA=="],
+
+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+
+ "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="],
+
+ "dedent-js": ["dedent-js@1.0.1", "", {}, "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ=="],
+
+ "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
+
+ "defu": ["defu@6.1.6", "", {}, "sha512-f8mefEW4WIVg4LckePx3mALjQSPQgFlg9U8yaPdlsbdYcHQyj9n2zL2LJEA52smeYxOvmd/nB7TpMtHGMTHcug=="],
+
+ "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
+
+ "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
+
+ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
+
+ "devalue": ["devalue@5.6.4", "", {}, "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA=="],
+
+ "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
+
+ "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="],
+
+ "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
+
+ "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
+
+ "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
+
+ "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
+
+ "dompurify": ["dompurify@3.3.3", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA=="],
+
+ "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
+
+ "dset": ["dset@3.1.4", "", {}, "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA=="],
+
+ "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+ "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="],
+
+ "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
+
+ "es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="],
+
+ "esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="],
+
+ "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
+
+ "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
+
+ "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
+
+ "esrap": ["esrap@2.2.4", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15", "@typescript-eslint/types": "^8.2.0" } }, "sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg=="],
+
+ "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
+
+ "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
+
+ "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
+
+ "fast-string-truncated-width": ["fast-string-truncated-width@1.2.1", "", {}, "sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow=="],
+
+ "fast-string-width": ["fast-string-width@1.1.0", "", { "dependencies": { "fast-string-truncated-width": "^1.2.0" } }, "sha512-O3fwIVIH5gKB38QNbdg+3760ZmGz0SZMgvwJbA1b2TGXceKE6A2cOlfogh1iw8lr049zPyd7YADHy+B7U4W9bQ=="],
+
+ "fast-wrap-ansi": ["fast-wrap-ansi@0.1.6", "", { "dependencies": { "fast-string-width": "^1.1.0" } }, "sha512-HlUwET7a5gqjURj70D5jl7aC3Zmy4weA1SHUfM0JFI0Ptq987NH2TwbBFLoERhfwk+E+eaq4EK3jXoT+R3yp3w=="],
+
+ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
+
+ "flattie": ["flattie@1.1.1", "", {}, "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ=="],
+
+ "fontace": ["fontace@0.4.1", "", { "dependencies": { "fontkitten": "^1.0.2" } }, "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw=="],
+
+ "fontkitten": ["fontkitten@1.0.3", "", { "dependencies": { "tiny-inflate": "^1.0.3" } }, "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw=="],
+
+ "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
+
+ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
+
+ "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
+
+ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
+
+ "h3": ["h3@1.15.11", "", { "dependencies": { "cookie-es": "^1.2.3", "crossws": "^0.3.5", "defu": "^6.1.6", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg=="],
+
+ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
+
+ "hast-util-from-html": ["hast-util-from-html@2.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.1.0", "hast-util-from-parse5": "^8.0.0", "parse5": "^7.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0" } }, "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw=="],
+
+ "hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="],
+
+ "hast-util-is-element": ["hast-util-is-element@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g=="],
+
+ "hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="],
+
+ "hast-util-raw": ["hast-util-raw@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="],
+
+ "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
+
+ "hast-util-to-parse5": ["hast-util-to-parse5@8.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA=="],
+
+ "hast-util-to-text": ["hast-util-to-text@4.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "hast-util-is-element": "^3.0.0", "unist-util-find-after": "^5.0.0" } }, "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A=="],
+
+ "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],
+
+ "hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="],
+
+ "html-escaper": ["html-escaper@3.0.3", "", {}, "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="],
+
+ "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="],
+
+ "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="],
+
+ "iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="],
+
+ "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
+
+ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
+
+ "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
+
+ "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
+
+ "is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
+
+ "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="],
+
+ "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
+
+ "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
+
+ "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
+
+ "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
+
+ "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
+
+ "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
+
+ "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
+
+ "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
+
+ "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
+
+ "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
+
+ "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
+
+ "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
+
+ "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
+
+ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
+
+ "locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
+
+ "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
+
+ "lru-cache": ["lru-cache@11.2.7", "", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="],
+
+ "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
+
+ "magicast": ["magicast@0.5.2", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ=="],
+
+ "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
+
+ "mdast-util-definitions": ["mdast-util-definitions@6.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ=="],
+
+ "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="],
+
+ "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="],
+
+ "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="],
+
+ "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="],
+
+ "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="],
+
+ "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="],
+
+ "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="],
+
+ "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="],
+
+ "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="],
+
+ "mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="],
+
+ "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="],
+
+ "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
+
+ "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
+
+ "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="],
+
+ "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="],
+
+ "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="],
+
+ "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="],
+
+ "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="],
+
+ "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="],
+
+ "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="],
+
+ "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="],
+
+ "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="],
+
+ "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="],
+
+ "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="],
+
+ "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="],
+
+ "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="],
+
+ "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="],
+
+ "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
+
+ "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="],
+
+ "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="],
+
+ "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="],
+
+ "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="],
+
+ "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="],
+
+ "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
+
+ "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="],
+
+ "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="],
+
+ "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="],
+
+ "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="],
+
+ "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="],
+
+ "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="],
+
+ "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="],
+
+ "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
+
+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+
+ "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
+
+ "neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="],
+
+ "nlcst-to-string": ["nlcst-to-string@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0" } }, "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA=="],
+
+ "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
+
+ "node-mock-http": ["node-mock-http@1.0.4", "", {}, "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ=="],
+
+ "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
+
+ "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
+
+ "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
+
+ "ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
+
+ "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
+
+ "oniguruma-parser": ["oniguruma-parser@0.12.1", "", {}, "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w=="],
+
+ "oniguruma-to-es": ["oniguruma-to-es@4.3.5", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ=="],
+
+ "p-limit": ["p-limit@7.3.0", "", { "dependencies": { "yocto-queue": "^1.2.1" } }, "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw=="],
+
+ "p-queue": ["p-queue@9.1.1", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^7.0.0" } }, "sha512-yQS1vV2V7Q14MQrgD8jMNY5owPuGgVHVdSK8NqmKpOVajnjbaeMa6uLOzTALPtvJ7Vo4bw0BGsw7qfUT8z24Ig=="],
+
+ "p-timeout": ["p-timeout@7.0.1", "", {}, "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg=="],
+
+ "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="],
+
+ "parse-latin": ["parse-latin@7.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "@types/unist": "^3.0.0", "nlcst-to-string": "^4.0.0", "unist-util-modify-children": "^4.0.0", "unist-util-visit-children": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ=="],
+
+ "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
+
+ "piccolore": ["piccolore@0.1.3", "", {}, "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw=="],
+
+ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
+
+ "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
+
+ "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
+
+ "postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="],
+
+ "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
+
+ "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
+
+ "radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="],
+
+ "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
+
+ "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="],
+
+ "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="],
+
+ "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="],
+
+ "rehype": ["rehype@13.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "rehype-parse": "^9.0.0", "rehype-stringify": "^10.0.0", "unified": "^11.0.0" } }, "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A=="],
+
+ "rehype-parse": ["rehype-parse@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-from-html": "^2.0.0", "unified": "^11.0.0" } }, "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag=="],
+
+ "rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="],
+
+ "rehype-stringify": ["rehype-stringify@10.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-to-html": "^9.0.0", "unified": "^11.0.0" } }, "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA=="],
+
+ "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="],
+
+ "remark-github-blockquote-alert": ["remark-github-blockquote-alert@2.1.0", "", { "dependencies": { "unist-util-visit": "^5.0.0" } }, "sha512-J392jmIP684d7iGsENN0uguL10IGbRdc8bTUSrd/jOLzdWkwg721Fj3JPQGN8tF6fTIrE5HHOIA3nBuwuaeuPQ=="],
+
+ "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="],
+
+ "remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="],
+
+ "remark-smartypants": ["remark-smartypants@3.0.2", "", { "dependencies": { "retext": "^9.0.0", "retext-smartypants": "^6.0.0", "unified": "^11.0.4", "unist-util-visit": "^5.0.0" } }, "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA=="],
+
+ "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="],
+
+ "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
+
+ "retext": ["retext@9.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "retext-latin": "^4.0.0", "retext-stringify": "^4.0.0", "unified": "^11.0.0" } }, "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA=="],
+
+ "retext-latin": ["retext-latin@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "parse-latin": "^7.0.0", "unified": "^11.0.0" } }, "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA=="],
+
+ "retext-smartypants": ["retext-smartypants@6.2.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ=="],
+
+ "retext-stringify": ["retext-stringify@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unified": "^11.0.0" } }, "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA=="],
+
+ "rollup": ["rollup@4.60.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="],
+
+ "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
+
+ "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
+
+ "scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="],
+
+ "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
+
+ "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
+
+ "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
+
+ "shiki": ["shiki@4.0.2", "", { "dependencies": { "@shikijs/core": "4.0.2", "@shikijs/engine-javascript": "4.0.2", "@shikijs/engine-oniguruma": "4.0.2", "@shikijs/langs": "4.0.2", "@shikijs/themes": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ=="],
+
+ "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
+
+ "smol-toml": ["smol-toml@1.6.1", "", {}, "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg=="],
+
+ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
+
+ "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
+
+ "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+ "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
+
+ "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
+
+ "svelte": ["svelte@5.55.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.6.4", "esm-env": "^1.2.1", "esrap": "^2.2.4", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-QjvU7EFemf6mRzdMGlAFttMWtAAVXrax61SZYHdkD6yoVGQ89VeyKfZD4H1JrV1WLmJBxWhFch9H6ig/87VGjw=="],
+
+ "svelte2tsx": ["svelte2tsx@0.7.53", "", { "dependencies": { "dedent-js": "^1.0.1", "scule": "^1.3.0" }, "peerDependencies": { "svelte": "^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0", "typescript": "^4.9.4 || ^5.0.0" } }, "sha512-ljVSwmnYRDHRm8+7ICP6QoAN7U7vgOFfPBLN6T745YWNYqRRSzHxlrzUVqMjYls2Un8MzJissfziy/38e6Deeg=="],
+
+ "svgo": ["svgo@4.0.1", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w=="],
+
+ "tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="],
+
+ "tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="],
+
+ "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
+
+ "tinyclip": ["tinyclip@0.1.12", "", {}, "sha512-Ae3OVUqifDw0wBriIBS7yVaW44Dp6eSHQcyq4Igc7eN2TJH/2YsicswaW+J/OuMvhpDPOKEgpAZCjkb4hpoyeA=="],
+
+ "tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="],
+
+ "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
+
+ "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
+
+ "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
+
+ "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
+
+ "tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
+
+ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
+
+ "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="],
+
+ "ultrahtml": ["ultrahtml@1.6.0", "", {}, "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw=="],
+
+ "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
+
+ "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
+
+ "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
+
+ "unifont": ["unifont@0.7.4", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg=="],
+
+ "unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="],
+
+ "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="],
+
+ "unist-util-modify-children": ["unist-util-modify-children@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "array-iterate": "^2.0.0" } }, "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw=="],
+
+ "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="],
+
+ "unist-util-remove-position": ["unist-util-remove-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q=="],
+
+ "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
+
+ "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="],
+
+ "unist-util-visit-children": ["unist-util-visit-children@3.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA=="],
+
+ "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="],
+
+ "unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="],
+
+ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
+
+ "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
+
+ "vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="],
+
+ "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
+
+ "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
+
+ "vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="],
+
+ "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
+
+ "which-pm-runs": ["which-pm-runs@1.1.0", "", {}, "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="],
+
+ "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
+
+ "xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="],
+
+ "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
+
+ "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
+
+ "yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
+
+ "yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="],
+
+ "zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
+
+ "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
+
+ "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
+
+ "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
+
+ "csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="],
+
+ "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
+
+ "svelte/aria-query": ["aria-query@5.3.1", "", {}, "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g=="],
+
+ "yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
+
+ "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="],
+ }
+}
diff --git a/front/bun.nix b/front/bun.nix
new file mode 100644
index 0000000..5fe15b7
--- /dev/null
+++ b/front/bun.nix
@@ -0,0 +1,1700 @@
+# Autogenerated by `bun2nix`, editing manually is not recommended
+#
+# Set of Bun packages to install
+#
+# Consume this with `fetchBunDeps` (recommended)
+# or `pkgs.callPackage` if you wish to handle
+# it manually.
+{
+ copyPathToStore,
+ fetchFromGitHub,
+ fetchgit,
+ fetchurl,
+ ...
+}:
+{
+ "@astrojs/compiler@3.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/@astrojs/compiler/-/compiler-3.0.1.tgz";
+ hash = "sha512-z97oYbdebO5aoWzuJ/8q5hLK232+17KcLZ7cJ8BCWk6+qNzVxn/gftC0KzMBUTD8WAaBkPpNSQK6PXLnNrZ0CA==";
+ };
+ "@astrojs/internal-helpers@0.8.0" = fetchurl {
+ url = "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.8.0.tgz";
+ hash = "sha512-J56GrhEiV+4dmrGLPNOl2pZjpHXAndWVyiVDYGDuw6MWKpBSEMLdFxHzeM/6sqaknw9M+HFfHZAcvi3OfT3D/w==";
+ };
+ "@astrojs/markdown-remark@7.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-7.1.0.tgz";
+ hash = "sha512-P+HnCsu2js3BoTc8kFmu+E9gOcFeMdPris75g+Zl4sY8+bBRbSQV6xzcBDbZ27eE7yBGEGQoqjpChx+KJYIPYQ==";
+ };
+ "@astrojs/prism@4.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/@astrojs/prism/-/prism-4.0.1.tgz";
+ hash = "sha512-nksZQVjlferuWzhPsBpQ1JE5XuKAf1id1/9Hj4a9KG4+ofrlzxUUwX4YGQF/SuDiuiGKEnzopGOt38F3AnVWsQ==";
+ };
+ "@astrojs/svelte@8.0.4" = fetchurl {
+ url = "https://registry.npmjs.org/@astrojs/svelte/-/svelte-8.0.4.tgz";
+ hash = "sha512-c5m3chjtgxBE3BzsE/bZbCFBkLPhq041rm2WJFaTIKGwt/3xNm/5efYCj23reuAcBsl4iYS8n2UwkAHQJzhkZA==";
+ };
+ "@astrojs/telemetry@3.3.0" = fetchurl {
+ url = "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.0.tgz";
+ hash = "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==";
+ };
+ "@babel/helper-string-parser@7.27.1" = fetchurl {
+ url = "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz";
+ hash = "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==";
+ };
+ "@babel/helper-validator-identifier@7.28.5" = fetchurl {
+ url = "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz";
+ hash = "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==";
+ };
+ "@babel/parser@7.29.2" = fetchurl {
+ url = "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz";
+ hash = "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==";
+ };
+ "@babel/types@7.29.0" = fetchurl {
+ url = "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz";
+ hash = "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==";
+ };
+ "@capsizecss/unpack@4.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.0.tgz";
+ hash = "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==";
+ };
+ "@clack/core@1.2.0" = fetchurl {
+ url = "https://registry.npmjs.org/@clack/core/-/core-1.2.0.tgz";
+ hash = "sha512-qfxof/3T3t9DPU/Rj3OmcFyZInceqj/NVtO9rwIuJqCUgh32gwPjpFQQp/ben07qKlhpwq7GzfWpST4qdJ5Drg==";
+ };
+ "@clack/prompts@1.2.0" = fetchurl {
+ url = "https://registry.npmjs.org/@clack/prompts/-/prompts-1.2.0.tgz";
+ hash = "sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w==";
+ };
+ "@emnapi/core@1.9.1" = fetchurl {
+ url = "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz";
+ hash = "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==";
+ };
+ "@emnapi/runtime@1.9.1" = fetchurl {
+ url = "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz";
+ hash = "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==";
+ };
+ "@emnapi/wasi-threads@1.2.0" = fetchurl {
+ url = "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz";
+ hash = "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==";
+ };
+ "@esbuild/aix-ppc64@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz";
+ hash = "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==";
+ };
+ "@esbuild/android-arm64@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz";
+ hash = "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==";
+ };
+ "@esbuild/android-arm@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz";
+ hash = "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==";
+ };
+ "@esbuild/android-x64@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz";
+ hash = "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==";
+ };
+ "@esbuild/darwin-arm64@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz";
+ hash = "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==";
+ };
+ "@esbuild/darwin-x64@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz";
+ hash = "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==";
+ };
+ "@esbuild/freebsd-arm64@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz";
+ hash = "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==";
+ };
+ "@esbuild/freebsd-x64@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz";
+ hash = "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==";
+ };
+ "@esbuild/linux-arm64@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz";
+ hash = "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==";
+ };
+ "@esbuild/linux-arm@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz";
+ hash = "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==";
+ };
+ "@esbuild/linux-ia32@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz";
+ hash = "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==";
+ };
+ "@esbuild/linux-loong64@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz";
+ hash = "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==";
+ };
+ "@esbuild/linux-mips64el@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz";
+ hash = "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==";
+ };
+ "@esbuild/linux-ppc64@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz";
+ hash = "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==";
+ };
+ "@esbuild/linux-riscv64@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz";
+ hash = "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==";
+ };
+ "@esbuild/linux-s390x@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz";
+ hash = "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==";
+ };
+ "@esbuild/linux-x64@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz";
+ hash = "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==";
+ };
+ "@esbuild/netbsd-arm64@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz";
+ hash = "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==";
+ };
+ "@esbuild/netbsd-x64@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz";
+ hash = "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==";
+ };
+ "@esbuild/openbsd-arm64@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz";
+ hash = "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==";
+ };
+ "@esbuild/openbsd-x64@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz";
+ hash = "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==";
+ };
+ "@esbuild/openharmony-arm64@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz";
+ hash = "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==";
+ };
+ "@esbuild/sunos-x64@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz";
+ hash = "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==";
+ };
+ "@esbuild/win32-arm64@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz";
+ hash = "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==";
+ };
+ "@esbuild/win32-ia32@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz";
+ hash = "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==";
+ };
+ "@esbuild/win32-x64@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz";
+ hash = "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==";
+ };
+ "@img/colour@1.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz";
+ hash = "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==";
+ };
+ "@img/sharp-darwin-arm64@0.34.5" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz";
+ hash = "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==";
+ };
+ "@img/sharp-darwin-x64@0.34.5" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz";
+ hash = "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==";
+ };
+ "@img/sharp-libvips-darwin-arm64@1.2.4" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz";
+ hash = "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==";
+ };
+ "@img/sharp-libvips-darwin-x64@1.2.4" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz";
+ hash = "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==";
+ };
+ "@img/sharp-libvips-linux-arm64@1.2.4" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz";
+ hash = "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==";
+ };
+ "@img/sharp-libvips-linux-arm@1.2.4" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz";
+ hash = "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==";
+ };
+ "@img/sharp-libvips-linux-ppc64@1.2.4" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz";
+ hash = "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==";
+ };
+ "@img/sharp-libvips-linux-riscv64@1.2.4" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz";
+ hash = "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==";
+ };
+ "@img/sharp-libvips-linux-s390x@1.2.4" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz";
+ hash = "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==";
+ };
+ "@img/sharp-libvips-linux-x64@1.2.4" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz";
+ hash = "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==";
+ };
+ "@img/sharp-libvips-linuxmusl-arm64@1.2.4" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz";
+ hash = "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==";
+ };
+ "@img/sharp-libvips-linuxmusl-x64@1.2.4" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz";
+ hash = "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==";
+ };
+ "@img/sharp-linux-arm64@0.34.5" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz";
+ hash = "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==";
+ };
+ "@img/sharp-linux-arm@0.34.5" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz";
+ hash = "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==";
+ };
+ "@img/sharp-linux-ppc64@0.34.5" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz";
+ hash = "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==";
+ };
+ "@img/sharp-linux-riscv64@0.34.5" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz";
+ hash = "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==";
+ };
+ "@img/sharp-linux-s390x@0.34.5" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz";
+ hash = "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==";
+ };
+ "@img/sharp-linux-x64@0.34.5" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz";
+ hash = "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==";
+ };
+ "@img/sharp-linuxmusl-arm64@0.34.5" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz";
+ hash = "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==";
+ };
+ "@img/sharp-linuxmusl-x64@0.34.5" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz";
+ hash = "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==";
+ };
+ "@img/sharp-wasm32@0.34.5" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz";
+ hash = "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==";
+ };
+ "@img/sharp-win32-arm64@0.34.5" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz";
+ hash = "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==";
+ };
+ "@img/sharp-win32-ia32@0.34.5" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz";
+ hash = "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==";
+ };
+ "@img/sharp-win32-x64@0.34.5" = fetchurl {
+ url = "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz";
+ hash = "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==";
+ };
+ "@jridgewell/gen-mapping@0.3.13" = fetchurl {
+ url = "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz";
+ hash = "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==";
+ };
+ "@jridgewell/remapping@2.3.5" = fetchurl {
+ url = "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz";
+ hash = "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==";
+ };
+ "@jridgewell/resolve-uri@3.1.2" = fetchurl {
+ url = "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz";
+ hash = "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==";
+ };
+ "@jridgewell/sourcemap-codec@1.5.5" = fetchurl {
+ url = "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz";
+ hash = "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==";
+ };
+ "@jridgewell/trace-mapping@0.3.31" = fetchurl {
+ url = "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz";
+ hash = "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==";
+ };
+ "@lucide/svelte@1.7.0" = fetchurl {
+ url = "https://registry.npmjs.org/@lucide/svelte/-/svelte-1.7.0.tgz";
+ hash = "sha512-YytBKOUBGox7yWcykZnYxOkn5WpR5G1qYXLYXV/j1B79SOTTEKzB+s5yF5Rq9l9OkweDStNH2b4yTqfvhEhV8g==";
+ };
+ "@napi-rs/wasm-runtime@1.1.2" = fetchurl {
+ url = "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz";
+ hash = "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==";
+ };
+ "@oslojs/encoding@1.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz";
+ hash = "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==";
+ };
+ "@rollup/pluginutils@5.3.0" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz";
+ hash = "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==";
+ };
+ "@rollup/rollup-android-arm-eabi@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz";
+ hash = "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==";
+ };
+ "@rollup/rollup-android-arm64@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz";
+ hash = "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==";
+ };
+ "@rollup/rollup-darwin-arm64@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz";
+ hash = "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==";
+ };
+ "@rollup/rollup-darwin-x64@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz";
+ hash = "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==";
+ };
+ "@rollup/rollup-freebsd-arm64@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz";
+ hash = "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==";
+ };
+ "@rollup/rollup-freebsd-x64@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz";
+ hash = "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==";
+ };
+ "@rollup/rollup-linux-arm-gnueabihf@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz";
+ hash = "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==";
+ };
+ "@rollup/rollup-linux-arm-musleabihf@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz";
+ hash = "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==";
+ };
+ "@rollup/rollup-linux-arm64-gnu@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz";
+ hash = "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==";
+ };
+ "@rollup/rollup-linux-arm64-musl@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz";
+ hash = "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==";
+ };
+ "@rollup/rollup-linux-loong64-gnu@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz";
+ hash = "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==";
+ };
+ "@rollup/rollup-linux-loong64-musl@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz";
+ hash = "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==";
+ };
+ "@rollup/rollup-linux-ppc64-gnu@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz";
+ hash = "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==";
+ };
+ "@rollup/rollup-linux-ppc64-musl@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz";
+ hash = "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==";
+ };
+ "@rollup/rollup-linux-riscv64-gnu@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz";
+ hash = "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==";
+ };
+ "@rollup/rollup-linux-riscv64-musl@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz";
+ hash = "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==";
+ };
+ "@rollup/rollup-linux-s390x-gnu@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz";
+ hash = "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==";
+ };
+ "@rollup/rollup-linux-x64-gnu@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz";
+ hash = "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==";
+ };
+ "@rollup/rollup-linux-x64-musl@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz";
+ hash = "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==";
+ };
+ "@rollup/rollup-openbsd-x64@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz";
+ hash = "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==";
+ };
+ "@rollup/rollup-openharmony-arm64@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz";
+ hash = "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==";
+ };
+ "@rollup/rollup-win32-arm64-msvc@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz";
+ hash = "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==";
+ };
+ "@rollup/rollup-win32-ia32-msvc@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz";
+ hash = "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==";
+ };
+ "@rollup/rollup-win32-x64-gnu@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz";
+ hash = "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==";
+ };
+ "@rollup/rollup-win32-x64-msvc@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz";
+ hash = "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==";
+ };
+ "@shikijs/core@4.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/@shikijs/core/-/core-4.0.2.tgz";
+ hash = "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==";
+ };
+ "@shikijs/engine-javascript@4.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.0.2.tgz";
+ hash = "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==";
+ };
+ "@shikijs/engine-oniguruma@4.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.0.2.tgz";
+ hash = "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==";
+ };
+ "@shikijs/langs@4.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/@shikijs/langs/-/langs-4.0.2.tgz";
+ hash = "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==";
+ };
+ "@shikijs/primitive@4.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.0.2.tgz";
+ hash = "sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==";
+ };
+ "@shikijs/themes@4.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/@shikijs/themes/-/themes-4.0.2.tgz";
+ hash = "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==";
+ };
+ "@shikijs/types@4.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/@shikijs/types/-/types-4.0.2.tgz";
+ hash = "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==";
+ };
+ "@shikijs/vscode-textmate@10.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz";
+ hash = "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==";
+ };
+ "@sveltejs/acorn-typescript@1.0.9" = fetchurl {
+ url = "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz";
+ hash = "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==";
+ };
+ "@sveltejs/vite-plugin-svelte-inspector@5.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.2.tgz";
+ hash = "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==";
+ };
+ "@sveltejs/vite-plugin-svelte@6.2.4" = fetchurl {
+ url = "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz";
+ hash = "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==";
+ };
+ "@tailwindcss/node@4.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz";
+ hash = "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==";
+ };
+ "@tailwindcss/oxide-android-arm64@4.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz";
+ hash = "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==";
+ };
+ "@tailwindcss/oxide-darwin-arm64@4.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz";
+ hash = "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==";
+ };
+ "@tailwindcss/oxide-darwin-x64@4.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz";
+ hash = "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==";
+ };
+ "@tailwindcss/oxide-freebsd-x64@4.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz";
+ hash = "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==";
+ };
+ "@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz";
+ hash = "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==";
+ };
+ "@tailwindcss/oxide-linux-arm64-gnu@4.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz";
+ hash = "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==";
+ };
+ "@tailwindcss/oxide-linux-arm64-musl@4.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz";
+ hash = "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==";
+ };
+ "@tailwindcss/oxide-linux-x64-gnu@4.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz";
+ hash = "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==";
+ };
+ "@tailwindcss/oxide-linux-x64-musl@4.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz";
+ hash = "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==";
+ };
+ "@tailwindcss/oxide-wasm32-wasi@4.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz";
+ hash = "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==";
+ };
+ "@tailwindcss/oxide-win32-arm64-msvc@4.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz";
+ hash = "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==";
+ };
+ "@tailwindcss/oxide-win32-x64-msvc@4.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz";
+ hash = "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==";
+ };
+ "@tailwindcss/oxide@4.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz";
+ hash = "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==";
+ };
+ "@tailwindcss/typography@0.5.19" = fetchurl {
+ url = "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz";
+ hash = "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==";
+ };
+ "@tailwindcss/vite@4.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.2.tgz";
+ hash = "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==";
+ };
+ "@tybys/wasm-util@0.10.1" = fetchurl {
+ url = "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz";
+ hash = "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==";
+ };
+ "@types/debug@4.1.13" = fetchurl {
+ url = "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz";
+ hash = "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==";
+ };
+ "@types/dompurify@3.2.0" = fetchurl {
+ url = "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.2.0.tgz";
+ hash = "sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg==";
+ };
+ "@types/estree@1.0.8" = fetchurl {
+ url = "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz";
+ hash = "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==";
+ };
+ "@types/hast@3.0.4" = fetchurl {
+ url = "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz";
+ hash = "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==";
+ };
+ "@types/js-yaml@4.0.9" = fetchurl {
+ url = "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz";
+ hash = "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==";
+ };
+ "@types/mdast@4.0.4" = fetchurl {
+ url = "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz";
+ hash = "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==";
+ };
+ "@types/ms@2.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz";
+ hash = "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==";
+ };
+ "@types/nlcst@2.0.3" = fetchurl {
+ url = "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz";
+ hash = "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==";
+ };
+ "@types/node@25.5.0" = fetchurl {
+ url = "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz";
+ hash = "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==";
+ };
+ "@types/trusted-types@2.0.7" = fetchurl {
+ url = "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz";
+ hash = "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==";
+ };
+ "@types/unist@3.0.3" = fetchurl {
+ url = "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz";
+ hash = "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==";
+ };
+ "@typescript-eslint/types@8.58.0" = fetchurl {
+ url = "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz";
+ hash = "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==";
+ };
+ "@ungap/structured-clone@1.3.0" = fetchurl {
+ url = "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz";
+ hash = "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==";
+ };
+ "acorn@8.16.0" = fetchurl {
+ url = "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz";
+ hash = "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==";
+ };
+ "ansi-regex@5.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz";
+ hash = "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==";
+ };
+ "ansi-styles@4.3.0" = fetchurl {
+ url = "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz";
+ hash = "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==";
+ };
+ "ansi_up@6.0.6" = fetchurl {
+ url = "https://registry.npmjs.org/ansi_up/-/ansi_up-6.0.6.tgz";
+ hash = "sha512-yIa1x3Ecf8jWP4UWEunNjqNX6gzE4vg2gGz+xqRGY+TBSucnYp6RRdPV4brmtg6bQ1ljD48mZ5iGSEj7QEpRKA==";
+ };
+ "anymatch@3.1.3" = fetchurl {
+ url = "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz";
+ hash = "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==";
+ };
+ "argparse@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz";
+ hash = "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==";
+ };
+ "aria-query@5.3.1" = fetchurl {
+ url = "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz";
+ hash = "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==";
+ };
+ "aria-query@5.3.2" = fetchurl {
+ url = "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz";
+ hash = "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==";
+ };
+ "array-iterate@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz";
+ hash = "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==";
+ };
+ "astro@6.1.2" = fetchurl {
+ url = "https://registry.npmjs.org/astro/-/astro-6.1.2.tgz";
+ hash = "sha512-r3iIvmB6JvQxsdJLvapybKKq7Bojd1iQK6CCx5P55eRnXJIyUpHx/1UB/GdMm+em/lwaCUasxHCmIO0lCLV2uA==";
+ };
+ "axobject-query@4.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz";
+ hash = "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==";
+ };
+ "bail@2.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz";
+ hash = "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==";
+ };
+ "boolbase@1.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz";
+ hash = "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==";
+ };
+ "ccount@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz";
+ hash = "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==";
+ };
+ "chalk@4.1.2" = fetchurl {
+ url = "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz";
+ hash = "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==";
+ };
+ "character-entities-html4@2.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz";
+ hash = "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==";
+ };
+ "character-entities-legacy@3.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz";
+ hash = "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==";
+ };
+ "character-entities@2.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz";
+ hash = "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==";
+ };
+ "chokidar@5.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz";
+ hash = "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==";
+ };
+ "ci-info@4.4.0" = fetchurl {
+ url = "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz";
+ hash = "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==";
+ };
+ "cliui@8.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz";
+ hash = "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==";
+ };
+ "clsx@2.1.1" = fetchurl {
+ url = "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz";
+ hash = "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==";
+ };
+ "color-convert@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz";
+ hash = "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==";
+ };
+ "color-name@1.1.4" = fetchurl {
+ url = "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz";
+ hash = "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==";
+ };
+ "comma-separated-tokens@2.0.3" = fetchurl {
+ url = "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz";
+ hash = "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==";
+ };
+ "commander@11.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz";
+ hash = "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==";
+ };
+ "common-ancestor-path@2.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz";
+ hash = "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==";
+ };
+ "concurrently@9.2.1" = fetchurl {
+ url = "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz";
+ hash = "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==";
+ };
+ "cookie-es@1.2.3" = fetchurl {
+ url = "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.3.tgz";
+ hash = "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==";
+ };
+ "cookie@1.1.1" = fetchurl {
+ url = "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz";
+ hash = "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==";
+ };
+ "crossws@0.3.5" = fetchurl {
+ url = "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz";
+ hash = "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==";
+ };
+ "css-select@5.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz";
+ hash = "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==";
+ };
+ "css-tree@2.2.1" = fetchurl {
+ url = "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz";
+ hash = "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==";
+ };
+ "css-tree@3.2.1" = fetchurl {
+ url = "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz";
+ hash = "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==";
+ };
+ "css-what@6.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz";
+ hash = "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==";
+ };
+ "cssesc@3.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz";
+ hash = "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==";
+ };
+ "csso@5.0.5" = fetchurl {
+ url = "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz";
+ hash = "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==";
+ };
+ "daisyui@5.5.19" = fetchurl {
+ url = "https://registry.npmjs.org/daisyui/-/daisyui-5.5.19.tgz";
+ hash = "sha512-pbFAkl1VCEh/MPCeclKL61I/MqRIFFhNU7yiXoDDRapXN4/qNCoMxeCCswyxEEhqL5eiTTfwHvucFtOE71C9sA==";
+ };
+ "debug@4.4.3" = fetchurl {
+ url = "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz";
+ hash = "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==";
+ };
+ "decode-named-character-reference@1.3.0" = fetchurl {
+ url = "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz";
+ hash = "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==";
+ };
+ "dedent-js@1.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz";
+ hash = "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==";
+ };
+ "deepmerge@4.3.1" = fetchurl {
+ url = "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz";
+ hash = "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==";
+ };
+ "defu@6.1.6" = fetchurl {
+ url = "https://registry.npmjs.org/defu/-/defu-6.1.6.tgz";
+ hash = "sha512-f8mefEW4WIVg4LckePx3mALjQSPQgFlg9U8yaPdlsbdYcHQyj9n2zL2LJEA52smeYxOvmd/nB7TpMtHGMTHcug==";
+ };
+ "dequal@2.0.3" = fetchurl {
+ url = "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz";
+ hash = "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==";
+ };
+ "destr@2.0.5" = fetchurl {
+ url = "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz";
+ hash = "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==";
+ };
+ "detect-libc@2.1.2" = fetchurl {
+ url = "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz";
+ hash = "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==";
+ };
+ "devalue@5.6.4" = fetchurl {
+ url = "https://registry.npmjs.org/devalue/-/devalue-5.6.4.tgz";
+ hash = "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==";
+ };
+ "devlop@1.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz";
+ hash = "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==";
+ };
+ "diff@8.0.4" = fetchurl {
+ url = "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz";
+ hash = "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==";
+ };
+ "dlv@1.1.3" = fetchurl {
+ url = "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz";
+ hash = "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==";
+ };
+ "dom-serializer@2.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz";
+ hash = "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==";
+ };
+ "domelementtype@2.3.0" = fetchurl {
+ url = "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz";
+ hash = "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==";
+ };
+ "domhandler@5.0.3" = fetchurl {
+ url = "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz";
+ hash = "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==";
+ };
+ "dompurify@3.3.3" = fetchurl {
+ url = "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz";
+ hash = "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==";
+ };
+ "domutils@3.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz";
+ hash = "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==";
+ };
+ "dset@3.1.4" = fetchurl {
+ url = "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz";
+ hash = "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==";
+ };
+ "emoji-regex@8.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz";
+ hash = "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==";
+ };
+ "enhanced-resolve@5.20.1" = fetchurl {
+ url = "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz";
+ hash = "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==";
+ };
+ "entities@4.5.0" = fetchurl {
+ url = "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz";
+ hash = "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==";
+ };
+ "entities@6.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz";
+ hash = "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==";
+ };
+ "es-module-lexer@2.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz";
+ hash = "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==";
+ };
+ "esbuild@0.27.4" = fetchurl {
+ url = "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz";
+ hash = "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==";
+ };
+ "escalade@3.2.0" = fetchurl {
+ url = "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz";
+ hash = "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==";
+ };
+ "escape-string-regexp@5.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz";
+ hash = "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==";
+ };
+ "esm-env@1.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz";
+ hash = "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==";
+ };
+ "esrap@2.2.4" = fetchurl {
+ url = "https://registry.npmjs.org/esrap/-/esrap-2.2.4.tgz";
+ hash = "sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg==";
+ };
+ "estree-walker@2.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz";
+ hash = "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==";
+ };
+ "eventemitter3@5.0.4" = fetchurl {
+ url = "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz";
+ hash = "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==";
+ };
+ "extend@3.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz";
+ hash = "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==";
+ };
+ "fast-string-truncated-width@1.2.1" = fetchurl {
+ url = "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-1.2.1.tgz";
+ hash = "sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow==";
+ };
+ "fast-string-width@1.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/fast-string-width/-/fast-string-width-1.1.0.tgz";
+ hash = "sha512-O3fwIVIH5gKB38QNbdg+3760ZmGz0SZMgvwJbA1b2TGXceKE6A2cOlfogh1iw8lr049zPyd7YADHy+B7U4W9bQ==";
+ };
+ "fast-wrap-ansi@0.1.6" = fetchurl {
+ url = "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.1.6.tgz";
+ hash = "sha512-HlUwET7a5gqjURj70D5jl7aC3Zmy4weA1SHUfM0JFI0Ptq987NH2TwbBFLoERhfwk+E+eaq4EK3jXoT+R3yp3w==";
+ };
+ "fdir@6.5.0" = fetchurl {
+ url = "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz";
+ hash = "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==";
+ };
+ "flattie@1.1.1" = fetchurl {
+ url = "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz";
+ hash = "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==";
+ };
+ "fontace@0.4.1" = fetchurl {
+ url = "https://registry.npmjs.org/fontace/-/fontace-0.4.1.tgz";
+ hash = "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==";
+ };
+ "fontkitten@1.0.3" = fetchurl {
+ url = "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.3.tgz";
+ hash = "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==";
+ };
+ "fsevents@2.3.3" = fetchurl {
+ url = "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz";
+ hash = "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==";
+ };
+ "get-caller-file@2.0.5" = fetchurl {
+ url = "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz";
+ hash = "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==";
+ };
+ "github-slugger@2.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz";
+ hash = "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==";
+ };
+ "graceful-fs@4.2.11" = fetchurl {
+ url = "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz";
+ hash = "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==";
+ };
+ "h3@1.15.11" = fetchurl {
+ url = "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz";
+ hash = "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==";
+ };
+ "has-flag@4.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz";
+ hash = "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==";
+ };
+ "hast-util-from-html@2.0.3" = fetchurl {
+ url = "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz";
+ hash = "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==";
+ };
+ "hast-util-from-parse5@8.0.3" = fetchurl {
+ url = "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz";
+ hash = "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==";
+ };
+ "hast-util-is-element@3.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz";
+ hash = "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==";
+ };
+ "hast-util-parse-selector@4.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz";
+ hash = "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==";
+ };
+ "hast-util-raw@9.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz";
+ hash = "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==";
+ };
+ "hast-util-to-html@9.0.5" = fetchurl {
+ url = "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz";
+ hash = "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==";
+ };
+ "hast-util-to-parse5@8.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz";
+ hash = "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==";
+ };
+ "hast-util-to-text@4.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz";
+ hash = "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==";
+ };
+ "hast-util-whitespace@3.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz";
+ hash = "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==";
+ };
+ "hastscript@9.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz";
+ hash = "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==";
+ };
+ "html-escaper@3.0.3" = fetchurl {
+ url = "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz";
+ hash = "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==";
+ };
+ "html-void-elements@3.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz";
+ hash = "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==";
+ };
+ "http-cache-semantics@4.2.0" = fetchurl {
+ url = "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz";
+ hash = "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==";
+ };
+ "iron-webcrypto@1.2.1" = fetchurl {
+ url = "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz";
+ hash = "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==";
+ };
+ "is-docker@3.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz";
+ hash = "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==";
+ };
+ "is-fullwidth-code-point@3.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz";
+ hash = "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==";
+ };
+ "is-inside-container@1.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz";
+ hash = "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==";
+ };
+ "is-plain-obj@4.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz";
+ hash = "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==";
+ };
+ "is-reference@3.0.3" = fetchurl {
+ url = "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz";
+ hash = "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==";
+ };
+ "is-wsl@3.1.1" = fetchurl {
+ url = "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz";
+ hash = "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==";
+ };
+ "jiti@2.6.1" = fetchurl {
+ url = "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz";
+ hash = "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==";
+ };
+ "js-yaml@4.1.1" = fetchurl {
+ url = "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz";
+ hash = "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==";
+ };
+ "lightningcss-android-arm64@1.32.0" = fetchurl {
+ url = "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz";
+ hash = "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==";
+ };
+ "lightningcss-darwin-arm64@1.32.0" = fetchurl {
+ url = "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz";
+ hash = "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==";
+ };
+ "lightningcss-darwin-x64@1.32.0" = fetchurl {
+ url = "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz";
+ hash = "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==";
+ };
+ "lightningcss-freebsd-x64@1.32.0" = fetchurl {
+ url = "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz";
+ hash = "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==";
+ };
+ "lightningcss-linux-arm-gnueabihf@1.32.0" = fetchurl {
+ url = "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz";
+ hash = "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==";
+ };
+ "lightningcss-linux-arm64-gnu@1.32.0" = fetchurl {
+ url = "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz";
+ hash = "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==";
+ };
+ "lightningcss-linux-arm64-musl@1.32.0" = fetchurl {
+ url = "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz";
+ hash = "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==";
+ };
+ "lightningcss-linux-x64-gnu@1.32.0" = fetchurl {
+ url = "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz";
+ hash = "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==";
+ };
+ "lightningcss-linux-x64-musl@1.32.0" = fetchurl {
+ url = "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz";
+ hash = "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==";
+ };
+ "lightningcss-win32-arm64-msvc@1.32.0" = fetchurl {
+ url = "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz";
+ hash = "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==";
+ };
+ "lightningcss-win32-x64-msvc@1.32.0" = fetchurl {
+ url = "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz";
+ hash = "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==";
+ };
+ "lightningcss@1.32.0" = fetchurl {
+ url = "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz";
+ hash = "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==";
+ };
+ "locate-character@3.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz";
+ hash = "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==";
+ };
+ "longest-streak@3.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz";
+ hash = "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==";
+ };
+ "lru-cache@11.2.7" = fetchurl {
+ url = "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz";
+ hash = "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==";
+ };
+ "magic-string@0.30.21" = fetchurl {
+ url = "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz";
+ hash = "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==";
+ };
+ "magicast@0.5.2" = fetchurl {
+ url = "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz";
+ hash = "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==";
+ };
+ "markdown-table@3.0.4" = fetchurl {
+ url = "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz";
+ hash = "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==";
+ };
+ "mdast-util-definitions@6.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz";
+ hash = "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==";
+ };
+ "mdast-util-find-and-replace@3.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz";
+ hash = "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==";
+ };
+ "mdast-util-from-markdown@2.0.3" = fetchurl {
+ url = "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz";
+ hash = "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==";
+ };
+ "mdast-util-gfm-autolink-literal@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz";
+ hash = "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==";
+ };
+ "mdast-util-gfm-footnote@2.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz";
+ hash = "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==";
+ };
+ "mdast-util-gfm-strikethrough@2.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz";
+ hash = "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==";
+ };
+ "mdast-util-gfm-table@2.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz";
+ hash = "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==";
+ };
+ "mdast-util-gfm-task-list-item@2.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz";
+ hash = "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==";
+ };
+ "mdast-util-gfm@3.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz";
+ hash = "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==";
+ };
+ "mdast-util-phrasing@4.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz";
+ hash = "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==";
+ };
+ "mdast-util-to-hast@13.2.1" = fetchurl {
+ url = "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz";
+ hash = "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==";
+ };
+ "mdast-util-to-markdown@2.1.2" = fetchurl {
+ url = "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz";
+ hash = "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==";
+ };
+ "mdast-util-to-string@4.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz";
+ hash = "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==";
+ };
+ "mdn-data@2.0.28" = fetchurl {
+ url = "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz";
+ hash = "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==";
+ };
+ "mdn-data@2.27.1" = fetchurl {
+ url = "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz";
+ hash = "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==";
+ };
+ "micromark-core-commonmark@2.0.3" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz";
+ hash = "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==";
+ };
+ "micromark-extension-gfm-autolink-literal@2.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz";
+ hash = "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==";
+ };
+ "micromark-extension-gfm-footnote@2.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz";
+ hash = "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==";
+ };
+ "micromark-extension-gfm-strikethrough@2.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz";
+ hash = "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==";
+ };
+ "micromark-extension-gfm-table@2.1.1" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz";
+ hash = "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==";
+ };
+ "micromark-extension-gfm-tagfilter@2.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz";
+ hash = "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==";
+ };
+ "micromark-extension-gfm-task-list-item@2.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz";
+ hash = "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==";
+ };
+ "micromark-extension-gfm@3.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz";
+ hash = "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==";
+ };
+ "micromark-factory-destination@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz";
+ hash = "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==";
+ };
+ "micromark-factory-label@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz";
+ hash = "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==";
+ };
+ "micromark-factory-space@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz";
+ hash = "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==";
+ };
+ "micromark-factory-title@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz";
+ hash = "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==";
+ };
+ "micromark-factory-whitespace@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz";
+ hash = "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==";
+ };
+ "micromark-util-character@2.1.1" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz";
+ hash = "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==";
+ };
+ "micromark-util-chunked@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz";
+ hash = "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==";
+ };
+ "micromark-util-classify-character@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz";
+ hash = "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==";
+ };
+ "micromark-util-combine-extensions@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz";
+ hash = "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==";
+ };
+ "micromark-util-decode-numeric-character-reference@2.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz";
+ hash = "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==";
+ };
+ "micromark-util-decode-string@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz";
+ hash = "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==";
+ };
+ "micromark-util-encode@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz";
+ hash = "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==";
+ };
+ "micromark-util-html-tag-name@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz";
+ hash = "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==";
+ };
+ "micromark-util-normalize-identifier@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz";
+ hash = "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==";
+ };
+ "micromark-util-resolve-all@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz";
+ hash = "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==";
+ };
+ "micromark-util-sanitize-uri@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz";
+ hash = "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==";
+ };
+ "micromark-util-subtokenize@2.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz";
+ hash = "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==";
+ };
+ "micromark-util-symbol@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz";
+ hash = "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==";
+ };
+ "micromark-util-types@2.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz";
+ hash = "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==";
+ };
+ "micromark@4.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz";
+ hash = "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==";
+ };
+ "mrmime@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz";
+ hash = "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==";
+ };
+ "ms@2.1.3" = fetchurl {
+ url = "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz";
+ hash = "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==";
+ };
+ "nanoid@3.3.11" = fetchurl {
+ url = "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz";
+ hash = "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==";
+ };
+ "neotraverse@0.6.18" = fetchurl {
+ url = "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz";
+ hash = "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==";
+ };
+ "nlcst-to-string@4.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz";
+ hash = "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==";
+ };
+ "node-fetch-native@1.6.7" = fetchurl {
+ url = "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz";
+ hash = "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==";
+ };
+ "node-mock-http@1.0.4" = fetchurl {
+ url = "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz";
+ hash = "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==";
+ };
+ "normalize-path@3.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz";
+ hash = "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==";
+ };
+ "nth-check@2.1.1" = fetchurl {
+ url = "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz";
+ hash = "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==";
+ };
+ "obug@2.1.1" = fetchurl {
+ url = "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz";
+ hash = "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==";
+ };
+ "ofetch@1.5.1" = fetchurl {
+ url = "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz";
+ hash = "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==";
+ };
+ "ohash@2.0.11" = fetchurl {
+ url = "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz";
+ hash = "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==";
+ };
+ "oniguruma-parser@0.12.1" = fetchurl {
+ url = "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz";
+ hash = "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==";
+ };
+ "oniguruma-to-es@4.3.5" = fetchurl {
+ url = "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.5.tgz";
+ hash = "sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ==";
+ };
+ "p-limit@7.3.0" = fetchurl {
+ url = "https://registry.npmjs.org/p-limit/-/p-limit-7.3.0.tgz";
+ hash = "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==";
+ };
+ "p-queue@9.1.1" = fetchurl {
+ url = "https://registry.npmjs.org/p-queue/-/p-queue-9.1.1.tgz";
+ hash = "sha512-yQS1vV2V7Q14MQrgD8jMNY5owPuGgVHVdSK8NqmKpOVajnjbaeMa6uLOzTALPtvJ7Vo4bw0BGsw7qfUT8z24Ig==";
+ };
+ "p-timeout@7.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz";
+ hash = "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==";
+ };
+ "package-manager-detector@1.6.0" = fetchurl {
+ url = "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz";
+ hash = "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==";
+ };
+ "parse-latin@7.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz";
+ hash = "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==";
+ };
+ "parse5@7.3.0" = fetchurl {
+ url = "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz";
+ hash = "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==";
+ };
+ "piccolore@0.1.3" = fetchurl {
+ url = "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz";
+ hash = "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==";
+ };
+ "picocolors@1.1.1" = fetchurl {
+ url = "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz";
+ hash = "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==";
+ };
+ "picomatch@2.3.2" = fetchurl {
+ url = "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz";
+ hash = "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==";
+ };
+ "picomatch@4.0.4" = fetchurl {
+ url = "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz";
+ hash = "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==";
+ };
+ "postcss-selector-parser@6.0.10" = fetchurl {
+ url = "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz";
+ hash = "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==";
+ };
+ "postcss@8.5.8" = fetchurl {
+ url = "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz";
+ hash = "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==";
+ };
+ "prismjs@1.30.0" = fetchurl {
+ url = "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz";
+ hash = "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==";
+ };
+ "property-information@7.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz";
+ hash = "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==";
+ };
+ "radix3@1.1.2" = fetchurl {
+ url = "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz";
+ hash = "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==";
+ };
+ "readdirp@5.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz";
+ hash = "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==";
+ };
+ "regex-recursion@6.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz";
+ hash = "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==";
+ };
+ "regex-utilities@2.3.0" = fetchurl {
+ url = "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz";
+ hash = "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==";
+ };
+ "regex@6.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz";
+ hash = "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==";
+ };
+ "rehype-parse@9.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz";
+ hash = "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==";
+ };
+ "rehype-raw@7.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz";
+ hash = "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==";
+ };
+ "rehype-stringify@10.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz";
+ hash = "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==";
+ };
+ "rehype@13.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz";
+ hash = "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==";
+ };
+ "remark-gfm@4.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz";
+ hash = "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==";
+ };
+ "remark-github-blockquote-alert@2.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/remark-github-blockquote-alert/-/remark-github-blockquote-alert-2.1.0.tgz";
+ hash = "sha512-J392jmIP684d7iGsENN0uguL10IGbRdc8bTUSrd/jOLzdWkwg721Fj3JPQGN8tF6fTIrE5HHOIA3nBuwuaeuPQ==";
+ };
+ "remark-parse@11.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz";
+ hash = "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==";
+ };
+ "remark-rehype@11.1.2" = fetchurl {
+ url = "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz";
+ hash = "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==";
+ };
+ "remark-smartypants@3.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz";
+ hash = "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==";
+ };
+ "remark-stringify@11.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz";
+ hash = "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==";
+ };
+ "require-directory@2.1.1" = fetchurl {
+ url = "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz";
+ hash = "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==";
+ };
+ "retext-latin@4.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz";
+ hash = "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==";
+ };
+ "retext-smartypants@6.2.0" = fetchurl {
+ url = "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz";
+ hash = "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==";
+ };
+ "retext-stringify@4.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz";
+ hash = "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==";
+ };
+ "retext@9.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz";
+ hash = "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==";
+ };
+ "rollup@4.60.1" = fetchurl {
+ url = "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz";
+ hash = "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==";
+ };
+ "rxjs@7.8.2" = fetchurl {
+ url = "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz";
+ hash = "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==";
+ };
+ "sax@1.6.0" = fetchurl {
+ url = "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz";
+ hash = "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==";
+ };
+ "scule@1.3.0" = fetchurl {
+ url = "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz";
+ hash = "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==";
+ };
+ "semver@7.7.4" = fetchurl {
+ url = "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz";
+ hash = "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==";
+ };
+ "sharp@0.34.5" = fetchurl {
+ url = "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz";
+ hash = "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==";
+ };
+ "shell-quote@1.8.3" = fetchurl {
+ url = "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz";
+ hash = "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==";
+ };
+ "shiki@4.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/shiki/-/shiki-4.0.2.tgz";
+ hash = "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==";
+ };
+ "sisteransi@1.0.5" = fetchurl {
+ url = "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz";
+ hash = "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==";
+ };
+ "smol-toml@1.6.1" = fetchurl {
+ url = "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz";
+ hash = "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==";
+ };
+ "source-map-js@1.2.1" = fetchurl {
+ url = "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz";
+ hash = "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==";
+ };
+ "space-separated-tokens@2.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz";
+ hash = "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==";
+ };
+ "string-width@4.2.3" = fetchurl {
+ url = "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz";
+ hash = "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==";
+ };
+ "stringify-entities@4.0.4" = fetchurl {
+ url = "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz";
+ hash = "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==";
+ };
+ "strip-ansi@6.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz";
+ hash = "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==";
+ };
+ "supports-color@7.2.0" = fetchurl {
+ url = "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz";
+ hash = "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==";
+ };
+ "supports-color@8.1.1" = fetchurl {
+ url = "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz";
+ hash = "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==";
+ };
+ "svelte2tsx@0.7.53" = fetchurl {
+ url = "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.7.53.tgz";
+ hash = "sha512-ljVSwmnYRDHRm8+7ICP6QoAN7U7vgOFfPBLN6T745YWNYqRRSzHxlrzUVqMjYls2Un8MzJissfziy/38e6Deeg==";
+ };
+ "svelte@5.55.1" = fetchurl {
+ url = "https://registry.npmjs.org/svelte/-/svelte-5.55.1.tgz";
+ hash = "sha512-QjvU7EFemf6mRzdMGlAFttMWtAAVXrax61SZYHdkD6yoVGQ89VeyKfZD4H1JrV1WLmJBxWhFch9H6ig/87VGjw==";
+ };
+ "svgo@4.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz";
+ hash = "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==";
+ };
+ "tailwindcss@4.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz";
+ hash = "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==";
+ };
+ "tapable@2.3.2" = fetchurl {
+ url = "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz";
+ hash = "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==";
+ };
+ "tiny-inflate@1.0.3" = fetchurl {
+ url = "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz";
+ hash = "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==";
+ };
+ "tinyclip@0.1.12" = fetchurl {
+ url = "https://registry.npmjs.org/tinyclip/-/tinyclip-0.1.12.tgz";
+ hash = "sha512-Ae3OVUqifDw0wBriIBS7yVaW44Dp6eSHQcyq4Igc7eN2TJH/2YsicswaW+J/OuMvhpDPOKEgpAZCjkb4hpoyeA==";
+ };
+ "tinyexec@1.0.4" = fetchurl {
+ url = "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz";
+ hash = "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==";
+ };
+ "tinyglobby@0.2.15" = fetchurl {
+ url = "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz";
+ hash = "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==";
+ };
+ "tree-kill@1.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz";
+ hash = "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==";
+ };
+ "trim-lines@3.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz";
+ hash = "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==";
+ };
+ "trough@2.2.0" = fetchurl {
+ url = "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz";
+ hash = "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==";
+ };
+ "tsconfck@3.1.6" = fetchurl {
+ url = "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz";
+ hash = "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==";
+ };
+ "tslib@2.8.1" = fetchurl {
+ url = "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz";
+ hash = "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==";
+ };
+ "typescript@5.9.3" = fetchurl {
+ url = "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz";
+ hash = "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==";
+ };
+ "ufo@1.6.3" = fetchurl {
+ url = "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz";
+ hash = "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==";
+ };
+ "ultrahtml@1.6.0" = fetchurl {
+ url = "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.6.0.tgz";
+ hash = "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==";
+ };
+ "uncrypto@0.1.3" = fetchurl {
+ url = "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz";
+ hash = "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==";
+ };
+ "undici-types@7.18.2" = fetchurl {
+ url = "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz";
+ hash = "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==";
+ };
+ "unified@11.0.5" = fetchurl {
+ url = "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz";
+ hash = "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==";
+ };
+ "unifont@0.7.4" = fetchurl {
+ url = "https://registry.npmjs.org/unifont/-/unifont-0.7.4.tgz";
+ hash = "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==";
+ };
+ "unist-util-find-after@5.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz";
+ hash = "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==";
+ };
+ "unist-util-is@6.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz";
+ hash = "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==";
+ };
+ "unist-util-modify-children@4.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz";
+ hash = "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==";
+ };
+ "unist-util-position@5.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz";
+ hash = "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==";
+ };
+ "unist-util-remove-position@5.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz";
+ hash = "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==";
+ };
+ "unist-util-stringify-position@4.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz";
+ hash = "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==";
+ };
+ "unist-util-visit-children@3.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz";
+ hash = "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==";
+ };
+ "unist-util-visit-parents@6.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz";
+ hash = "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==";
+ };
+ "unist-util-visit@5.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz";
+ hash = "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==";
+ };
+ "unstorage@1.17.5" = fetchurl {
+ url = "https://registry.npmjs.org/unstorage/-/unstorage-1.17.5.tgz";
+ hash = "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==";
+ };
+ "util-deprecate@1.0.2" = fetchurl {
+ url = "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz";
+ hash = "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==";
+ };
+ "vfile-location@5.0.3" = fetchurl {
+ url = "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz";
+ hash = "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==";
+ };
+ "vfile-message@4.0.3" = fetchurl {
+ url = "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz";
+ hash = "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==";
+ };
+ "vfile@6.0.3" = fetchurl {
+ url = "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz";
+ hash = "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==";
+ };
+ "vite@7.3.1" = fetchurl {
+ url = "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz";
+ hash = "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==";
+ };
+ "vitefu@1.1.3" = fetchurl {
+ url = "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz";
+ hash = "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==";
+ };
+ "web-namespaces@2.0.1" = fetchurl {
+ url = "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz";
+ hash = "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==";
+ };
+ "which-pm-runs@1.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz";
+ hash = "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==";
+ };
+ "wrap-ansi@7.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz";
+ hash = "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==";
+ };
+ "xxhash-wasm@1.1.0" = fetchurl {
+ url = "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz";
+ hash = "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==";
+ };
+ "y18n@5.0.8" = fetchurl {
+ url = "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz";
+ hash = "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==";
+ };
+ "yargs-parser@21.1.1" = fetchurl {
+ url = "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz";
+ hash = "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==";
+ };
+ "yargs-parser@22.0.0" = fetchurl {
+ url = "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz";
+ hash = "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==";
+ };
+ "yargs@17.7.2" = fetchurl {
+ url = "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz";
+ hash = "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==";
+ };
+ "yocto-queue@1.2.2" = fetchurl {
+ url = "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz";
+ hash = "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==";
+ };
+ "zimmerframe@1.1.4" = fetchurl {
+ url = "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz";
+ hash = "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==";
+ };
+ "zod@4.3.6" = fetchurl {
+ url = "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz";
+ hash = "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==";
+ };
+ "zwitch@2.0.4" = fetchurl {
+ url = "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz";
+ hash = "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==";
+ };
+}
diff --git a/front/package.json b/front/package.json
new file mode 100644
index 0000000..d01d83b
--- /dev/null
+++ b/front/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "iknowyou",
+ "type": "module",
+ "version": "1.0.0",
+ "scripts": {
+ "dev:frontend": "astro dev",
+ "dev:backend": "cd ../back/ && air",
+ "dev": "concurrently \"bun dev:frontend\" \"bun dev:backend\"",
+ "build": "astro build",
+ "preview": "astro preview",
+ "astro": "astro"
+ },
+ "dependencies": {
+ "@astrojs/svelte": "8.0.4",
+ "@lucide/svelte": "^1.7.0",
+ "@tailwindcss/vite": "^4.2.1",
+ "ansi_up": "^6.0.6",
+ "astro": "6.1.2",
+ "dompurify": "^3.3.3",
+ "js-yaml": "^4.1.1",
+ "remark-github-blockquote-alert": "^2.1.0",
+ "svelte": "^5.53.12",
+ "tailwindcss": "^4.2.1",
+ "typescript": "^5.9.3"
+ },
+ "devDependencies": {
+ "@tailwindcss/typography": "^0.5.19",
+ "@types/dompurify": "^3.2.0",
+ "@types/js-yaml": "^4.0.9",
+ "@types/node": "^25.5.0",
+ "concurrently": "^9.2.1",
+ "daisyui": "^5.5.19"
+ }
+}
diff --git a/front/public/.well-known/security.txt b/front/public/.well-known/security.txt
new file mode 100644
index 0000000..99a2956
--- /dev/null
+++ b/front/public/.well-known/security.txt
@@ -0,0 +1,5 @@
+Contact: mailto:anotherhadi.clapped234[at]passmail.net
+Expires: 2028-12-31T23:00:00.000Z
+Encryption: /anotherhadi.asc
+Preferred-Languages: en, fr
+Canonical: /.well-known/security.txt
diff --git a/front/public/Wrench.svg b/front/public/Wrench.svg
new file mode 100644
index 0000000..980159b
--- /dev/null
+++ b/front/public/Wrench.svg
@@ -0,0 +1 @@
+
diff --git a/front/public/anotherhadi.asc b/front/public/anotherhadi.asc
new file mode 100644
index 0000000..174ea26
--- /dev/null
+++ b/front/public/anotherhadi.asc
@@ -0,0 +1,52 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBGlUVOcBEADC8BaIMD71bTsFTOEI5pSJTiKrMQdgYkkDiK8yBdstSLInBeTV
+Xsxlgd9s9Nw9sNkbIytUB3rSbVwlYbH+o6A5qaQQkOBq/3/RR+zdPB5lonpvBPYs
+agcjRLc2Z0W/83ERMuiOfrJHsOjwopL72PaG1KzuXEDI2o5vUFIvt4iER+ZGXAPU
+GKh7YvTWy1qPkHYeHWN3khE4hKffx+ozxQWFQEr90DrJUwDPSMwkCxkzk3R68qSG
+x6dEp21XChSJmvN+SQjhMRRCcE6PIQgWBzitEHZhWdpBfbfYlqP2Fc1d56kqSYXP
+5bwUXjbJ/1RgmULWjgyAlbkYkfkw2fqvgpwz4GPFZV16Qa2yh6iuswoSp4uHP1Pp
+9v8TTI2xXX8FuhhdVbSu1xAbgcD7GAaTZJz8qCqU7oZieHMLQVAdEio0dDq7pO9s
+bUND1Syd4tOxpiKedwOs4YFOU5tc59Ik7amb7PUZr/OR6JmFWBHuhnw763zl046G
+3NoLCb3lsKq6wxBNGPoEhlDGSe7ayIY2KdawzH62ymK53FHI2d+kpuNIGLas6+JL
+RqzpG5DVK4JaozoYTnckS0dK/y28gANUaZ8dFB/gcEnHHP8rHq/I4tAcPiGc+I87
+o25cBwJctOA4ucD9/G8rBjlt7wVkZjFZNKDmXhHIe9Tw5+Jv5HPA7OpUYwARAQAB
+tCpIYWRpIDxhbm90aGVyaGFkaS5jbGFwcGVkMjM0QHBhc3NtYWlsLm5ldD6JAk4E
+EwEKADgWIQScdA7pGcNUcalhd2qk3jJylKG9VQUCaVRU5wIbAwULCQgHAgYVCgkI
+CwIEFgIDAQIeAQIXgAAKCRCk3jJylKG9VTYSD/9jZxiU9SBIpxNbKtEQo2M8/QBk
+4xE5m/6gYE8fx1DndEsRK3eONIXNiRAq5fSadfF0EG8tL4RoIZksSx2usu84VH3y
+y3Tn/mJnK25v6DNQWZmREhvgOw0gP4py7of0fBi+T0FYFGqxdPEYwTkqZ4Hb/phz
+QQHdwa8nYd0+KHMujQwwMC9m+v5qCRv4F4sMAPfs9jZpxMD/gpsV0Cnrg1Vq+JDm
+l2upEioKLbq8cPPCJyT85wACu6zb/OiObu2Fs4IKvC97kcaKUyfjGHGN15384bgM
+HOihrKVbNc6SK44kOp1fdUz4zz3gXT7Qq2EJHrt33VxLT/QnhihD7MZaDqlpdrjh
+w0y3tMc4dhqmDSSoDx3GANn23F5NmFtXsx1Ndhqhv5pPDLbYVAKfKCzJHrBWPUkP
+n8ZS4bs9XN+Nv7lfXKxSkPg9CQARYFmrHQyL2IHTeBIOUcznji04p7AG8YtGOJ7G
+ZZS3KZ4kxSKTi/QleTa9ZldRecvT4cYk1juwmoIO5pIwPOv+KsUPSn3oeLLYup/A
+y8lg3zxc9jFk3tyrHgIVM7sM9VqGxOctCORNzRma8O7TbbkiPGXLZc8KaYLVo0QT
+9gmwuaDpJ6hT26EVvVCj1dlOJBXqsrsaTe4ev/jllJjOMOwtc3EcCMhWMD5fTEkE
+oZFDZOY3YBnbHEJaXLkCDQRpVFTnARAAn9YLGQkFiVeovLCbyHt62aVOFOp7j2AS
+Jj/vg+P96D71Wn8NomNLjKokEkYnMsgKb9D+jSWRFiUmgHdQYDQuouz4w8bH6Bwz
+owJL489QyQLml9uFL4I7ZwLzQSnxxVMGYe+1XKJNXPTlhwroKNCMe5wfuzMRZvGc
+CzfLmkvNDXY36GVfHIRkh31R/6ILIa+s5HAmjz1560RcaJ9qxQDFdJGUdbbaP2Ag
+lCYZXHTFwK8X8cFi+qpW31vY3nzSzcNc+DZdPa7Mkhsa3dHt7gLu1GLjeoyUThmu
+Q/+orpqagj04i8T1pjucAmhbnw8N9UwpLAUwUhhMTsPik1EvQiKJq2CgHddtuPDx
+ekJzBU6/fZQu8czaIezjqJ7yD0PYqSnOqtZ+MNoWE8HVUyXZPdVC5Fpnm5Lix/Tr
+ie9ylu3V5sSVQ5FIs+cZ1vZnOTShjw26rE2FfatVJeHpC5ioV3z6lzaCi4/zkSWi
+fCKhi9cOROeV+oslV7esnFGXDWLtd22H54NW8Kc140TpPq8457wbvS5r1BMMHite
+OGXcMSsKiivB+nO4acAPHpl093/qYcFMpP+2GP24ogJuL58UkfiboZz7b3gLPGZJ
+rgMmdlG5p0EMKOki1pruopVWW6fca8eLx5FuX5AzHZg7lsTXIi7iTfvGAo99Udbj
+RNQGHofu1qkAEQEAAYkCNgQYAQoAIBYhBJx0DukZw1RxqWF3aqTeMnKUob1VBQJp
+VFTnAhsMAAoJEKTeMnKUob1V8IQP/iQZAB1dtlsTkMZyUUP1ZqY0RwEkDyYgRMMV
+Xg845CoNnHss+ioHf6ObneKNwogow/r9OTXSDA4gBWvk1WsJ8j6tGwMSKY+mH/rh
+lER/lBeMCuvMc2/KE8VoOJkpXBktSdwEzLAxToTkyHuevxY43/g59xbBCImrIcVa
+kPJBtboxLkm0BE3nwhD6m/Uo1oECq8F2cDI5luYzOsIMjKyvavTUDsNX1RE7Ula8
+m8ra3QkgM8f4f1cmJP6y927RLEgXLxsbFlUjvAXRPe2sMIGV+32RTcXbR8zizuZl
+eyORq45t6EO0a8x1Bh0Jkimk0wBQKA00iiHb3vJYySJltfaWmJMPVXs1zZpK3n1l
+WRX4aO8MSBvqE+ZLrWJW3M8Yb6CeFh5yQv6B+1Nqyfk4pO5Q95/aRtG6qijQsxfb
+c+RqhL4yT+2KLBUjj0gg98MEM9+MoFxbWpJtnyA7LmgfatG8gHj1HxLBmEhkc5d5
+moM81nG/pWfUonOaVqcJji4UyDPNTOyAqHtp7iZUB80zu0IoyFRorsE9BkN4q/oY
+6lyyKcOLalvL2sJ3GtzBd1nmCjE2BMLD3jn8l7ig/FNVF2AZPWYmmCBiMuAMBRcA
+NXzOwAMmrgN41olFG9nAsoLLlxH69mUJTtw0sqAqtVzEnTwynbSm6H6dIL4yW7dA
+/jkWmwL9
+=4VM7
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/front/public/favicon.svg b/front/public/favicon.svg
new file mode 100644
index 0000000..0882396
--- /dev/null
+++ b/front/public/favicon.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/front/public/fonts/unbounded-black.ttf b/front/public/fonts/unbounded-black.ttf
new file mode 100644
index 0000000..8e28e35
Binary files /dev/null and b/front/public/fonts/unbounded-black.ttf differ
diff --git a/front/public/logo-large.svg b/front/public/logo-large.svg
new file mode 100644
index 0000000..5285a7f
--- /dev/null
+++ b/front/public/logo-large.svg
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/front/public/logo.svg b/front/public/logo.svg
new file mode 100644
index 0000000..90a3dff
--- /dev/null
+++ b/front/public/logo.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/front/public/op.png b/front/public/op.png
new file mode 100644
index 0000000..c845da3
Binary files /dev/null and b/front/public/op.png differ
diff --git a/front/public/security.txt b/front/public/security.txt
new file mode 100644
index 0000000..99a2956
--- /dev/null
+++ b/front/public/security.txt
@@ -0,0 +1,5 @@
+Contact: mailto:anotherhadi.clapped234[at]passmail.net
+Expires: 2028-12-31T23:00:00.000Z
+Encryption: /anotherhadi.asc
+Preferred-Languages: en, fr
+Canonical: /.well-known/security.txt
diff --git a/front/src/components/CheatsheetList.svelte b/front/src/components/CheatsheetList.svelte
new file mode 100644
index 0000000..a9cccc9
--- /dev/null
+++ b/front/src/components/CheatsheetList.svelte
@@ -0,0 +1,83 @@
+
+
+
+
+
+ {#if allTags.length > 0}
+
+ {#each allTags as tag}
+ (activeTag = activeTag === tag ? null : tag)}
+ >
+ {tag}
+
+ {/each}
+
+ {/if}
+
+
+
diff --git a/front/src/components/DemoBanner.svelte b/front/src/components/DemoBanner.svelte
new file mode 100644
index 0000000..40e3080
--- /dev/null
+++ b/front/src/components/DemoBanner.svelte
@@ -0,0 +1,24 @@
+
+
+{#if demo}
+
+
+ Demo mode — searches and configuration changes are disabled
+
+{/if}
diff --git a/front/src/components/HomePage.svelte b/front/src/components/HomePage.svelte
new file mode 100644
index 0000000..4fd77ae
--- /dev/null
+++ b/front/src/components/HomePage.svelte
@@ -0,0 +1,98 @@
+
+
+
+
+
+ {#if redirecting}
+
+
+
+ Searching {redirectTarget} ...
+
+
+ {:else}
+
+ {/if}
+
+
+
+
+
Recent searches
+ refresh
+
+
+ {#if loadError}
+
+ {:else}
+
+ {/if}
+
+
+
diff --git a/front/src/components/Nav.svelte b/front/src/components/Nav.svelte
new file mode 100644
index 0000000..ff59528
--- /dev/null
+++ b/front/src/components/Nav.svelte
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+ {@render action?.()}
+
+
+
diff --git a/front/src/components/ProfileSettings.svelte b/front/src/components/ProfileSettings.svelte
new file mode 100644
index 0000000..a012756
--- /dev/null
+++ b/front/src/components/ProfileSettings.svelte
@@ -0,0 +1,571 @@
+
+
+{#if loading}
+
+
+
+{:else if error}
+
+{:else}
+
+
+
+
+
Profiles
+ {#if !configReadonly}
+
{ showNewProfile = !showNewProfile; newName = ""; newProfileError = ""; }}
+ >
+
+
+ {/if}
+
+
+ {#if showNewProfile && !configReadonly}
+
+
e.key === "Enter" && createProfile()}
+ />
+ {#if newProfileError}
+
{newProfileError}
+ {/if}
+
+ {#if newProfileSaving}
+
+ {:else}
+ Create
+ {/if}
+
+
+ {/if}
+
+ {#each profiles as p}
+
+ selectProfile(p.name)}
+ >
+ {#if selectedProfile === p.name}
+
+ {/if}
+ {#if p.readonly}
+
+ {/if}
+ {p.name}
+
+ {#if !p.readonly}
+ deleteProfile(p.name)}
+ >
+
+
+ {/if}
+
+ {/each}
+
+ {#if profiles.length === 0}
+
No profiles yet.
+ {/if}
+
+
+
+ {#if configReadonly}
+
+
+ Config is managed externally and is read-only.
+
+ {/if}
+ {#if !selectedProfile}
+
Select a profile to view it.
+ {:else if profileLoading}
+
+
+
+ {:else if profileDetail}
+
+
+
+
+ {#if isReadonly} {/if}
+ {selectedProfile}
+
+ {#if isReadonly}
+
+ {/if}
+ {#if profileDetail.active_tools?.length > 0}
+
+ {profileDetail.active_tools.length} active tool{profileDetail.active_tools.length !== 1 ? "s" : ""}
+
+ {/if}
+
+
+ {#if isReadonly}
+ {#if profileDetail.notes}
+
{profileDetail.notes}
+ {/if}
+ {:else}
+
+ Notes
+
+
+ {/if}
+
+
+
+
+
Rules
+ {#if !isReadonly && rulesMsg}
+ {rulesMsg.text}
+ {/if}
+
+
+
+
+ Enabled
+
+
+
+ {#if isReadonly}
+ {#each (profileDetail.enabled ?? []) as toolName}
+ {toolName}
+ {/each}
+ {#if (profileDetail.enabled ?? []).length === 0}
+ All tools
+ {/if}
+ {:else}
+ {#each enabledEdit as toolName}
+
+ {toolName}
+ (enabledEdit = enabledEdit.filter((x) => x !== toolName))}>
+
+
+
+ {/each}
+ !enabledEdit.includes(n))}
+ placeholder="add tool"
+ size="xs"
+ onselect={(val) => (enabledEdit = [...enabledEdit, val])}
+ />
+ {/if}
+
+
+
+
+
+ Disabled
+
+
+
+ {#if isReadonly}
+ {#each (profileDetail.disabled ?? []) as toolName}
+ {toolName}
+ {/each}
+ {#if (profileDetail.disabled ?? []).length === 0}
+ None
+ {/if}
+ {:else}
+ {#each disabledEdit as toolName}
+
+ {toolName}
+ (disabledEdit = disabledEdit.filter((x) => x !== toolName))}>
+
+
+
+ {/each}
+ !disabledEdit.includes(n))}
+ placeholder="add tool"
+ size="xs"
+ onselect={(val) => (disabledEdit = [...disabledEdit, val])}
+ />
+ {/if}
+
+
+
+ {#if !isReadonly}
+
+ {#if rulesSaving}
+
+ {:else}
+
+ {/if}
+ Save
+
+ {/if}
+
+
+
+ {#if !isReadonly}
+
+
+
+
Tool overrides
+ {#if availableForOverride.length > 0}
+ t.name)}
+ placeholder="add override"
+ size="xs"
+ onselect={(val) => addOverrideFor(val)}
+ />
+ {/if}
+
+
+ {#if overrideToolNames.length === 0}
+
No overrides configured.
+ {:else}
+
+ {#each overrideToolNames as toolName}
+ {@const tool = tools.find((t) => t.name === toolName)}
+
+
+
{toolName}
+
+ {#if overrideMsg[toolName]}
+
+ {overrideMsg[toolName].text}
+
+ {/if}
+ deleteOverride(toolName)}
+ >
+
+
+
+
+
+ {#if tool?.config_fields?.length && overrideEdits[toolName]}
+
+ {#each tool.config_fields as field}
+
+ {/each}
+
+
saveOverride(toolName)}
+ disabled={overrideSaving[toolName]}
+ >
+ {#if overrideSaving[toolName]}
+
+ {:else}
+
+ {/if}
+ Save
+
+ {:else}
+
This tool has no configurable fields.
+ {/if}
+
+ {/each}
+
+ {/if}
+
+
+ {/if}
+
+
+ {/if}
+
+
+{/if}
diff --git a/front/src/components/SearchBar.svelte b/front/src/components/SearchBar.svelte
new file mode 100644
index 0000000..3e0e665
--- /dev/null
+++ b/front/src/components/SearchBar.svelte
@@ -0,0 +1,232 @@
+
+
+
+ {#if error}
+
+ {/if}
+
+ {#if showRevert}
+
+ Switched to {inputType}
+
+ ← {prevType}
+
+
+ {/if}
+
+
+
+
+ e.key === "Enter" && submit()}
+ disabled={demo}
+ />
+
+
+
+ type
+
+
+
+ profile
+ { profile = v; }}
+ />
+
+
+ {#if loading}
+
+ {:else}
+
+ {/if}
+ Search
+
+
+
+
+
+
+
+ type
+
+
+
+
e.key === "Enter" && submit()}
+ disabled={demo}
+ />
+
+
+ profile
+ { profile = v; }}
+ />
+
+
+
+ {#if loading}
+
+ {:else}
+
+ {/if}
+ Search
+
+
+
+ {#if validationError}
+
{validationError}
+ {/if}
+
diff --git a/front/src/components/SearchDetail.svelte b/front/src/components/SearchDetail.svelte
new file mode 100644
index 0000000..d9c058b
--- /dev/null
+++ b/front/src/components/SearchDetail.svelte
@@ -0,0 +1,349 @@
+
+
+{#if error}
+
+
+{:else if !search}
+
+
+
+
+{:else}
+
+
+
+
{search.target}
+
+ {#if search.status === "running"}
+
+ {/if}
+ {search.status}
+
+
+
+ {search.input_type}
+ {#if search.profile}
+ {search.profile}
+ {/if}
+ · started {fmtDate(search.started_at)}
+ {#if search.status !== "running" && totalResults > 0}
+ · {totalResults} result{totalResults !== 1 ? "s" : ""}
+ {/if}
+
+
+
+
+ Refresh
+
+ {#if search.status === "running"}
+
+ Cancel
+
+ {/if}
+
+
+
+ {#if search.planned_tools?.length > 0}
+
+
+
+
+
Tools
+
+
+ {toolProgress.done.length}/{toolProgress.active.length} done
+
+ {#if toolProgress.skippedTotal > 0}
+
+ {toolProgress.skippedTotal} skipped
+
+ {/if}
+
+
+
+
+
+ {#if search.status === "running"}
+
+ {/if}
+
+
+
+
+
+ Show tools
+
+
+ {#each search.planned_tools as t}
+ {@const d = grouped[t.name]}
+ {@const isErrored = d?.done && d.errors.length > 0 && d.output.length === 0}
+ {#if t.skipped}
+
+ {t.name}
+
+ {:else if isErrored}
+
+ {t.name}
+
+ {:else if d?.done}
+
+ {t.name}
+
+ {:else if search.status === "running"}
+
+ {t.name}
+
+ {:else}
+
+ {t.name}
+
+ {/if}
+ {/each}
+
+
+
+ {#if demo}
+
Results shown are not exhaustive — demo mode only displays a subset of what the tools can find.
+ {/if}
+
+
+
+ {/if}
+
+ {#if Object.keys(grouped).length === 0 && search.status === "running"}
+ Waiting for results...
+ {:else if Object.keys(grouped).length === 0}
+ No results.
+ {:else}
+
+
+ {#each sortedEntries.withResults as [toolName, data]}
+ {@render toolCard(toolName, data)}
+ {/each}
+
+ {#each sortedEntries.running as [toolName, data]}
+ {@render toolCard(toolName, data)}
+ {/each}
+
+ {#if sortedEntries.noResults.length > 0}
+
+
+
+ No results
+ ({sortedEntries.noResults.length})
+
+
+ {#each sortedEntries.noResults as [toolName, data]}
+ {@render toolCard(toolName, data)}
+ {/each}
+
+
+ {/if}
+
+
+ {/if}
+{/if}
+
+{#snippet toolCard(toolName, data)}
+ {@const toolStatus = search.planned_tools?.find((t) => t.name === toolName)}
+ {@const resultCount = data.output.length > 0 ? (toolStatus?.result_count ?? null) : null}
+
+
+
+
+
+
e.stopPropagation()}>
+ {toolName}
+
+ {#if !data.done}
+
+ running
+
+ {:else if data.errors.length > 0 && data.output.length === 0}
+
error
+ {:else}
+
done
+ {/if}
+
+ {#if resultCount === null}
+ output
+ {:else}
+ {resultCount} result{resultCount !== 1 ? "s" : ""}
+ {/if}
+ {#if data.errors.length > 0}
+ · {data.errors.length} error{data.errors.length !== 1 ? "s" : ""}
+ {/if}
+
+
+
+
+
+ {#each data.errors as err}
+
+ {/each}
+
+ {#if data.output.length > 0}
+
+ {:else if data.done && data.errors.length === 0}
+
No results.
+ {/if}
+
+
+
+
+{/snippet}
+
+{#if toast}
+
+{/if}
+
+
diff --git a/front/src/components/SearchList.svelte b/front/src/components/SearchList.svelte
new file mode 100644
index 0000000..78a918d
--- /dev/null
+++ b/front/src/components/SearchList.svelte
@@ -0,0 +1,71 @@
+
+
+{#if searches.length === 0}
+ No searches yet. Run one above.
+{:else}
+
+{/if}
diff --git a/front/src/components/ToolDetail.svelte b/front/src/components/ToolDetail.svelte
new file mode 100644
index 0000000..a027b5d
--- /dev/null
+++ b/front/src/components/ToolDetail.svelte
@@ -0,0 +1,265 @@
+
+
+{#if error}
+
+
+{:else if !tool}
+
+
+
+
+{:else}
+
+
+ {#if tool.available === false}
+
+
+
+
Tool unavailable
+ {#if tool.unavailable_reason}
+
{tool.unavailable_reason}
+ {/if}
+
+
+ {/if}
+
+
+
+
+
+
{tool.name}
+ {#if tool.description}
+
{tool.description}
+ {/if}
+
+
+ {#if tool.link}
+
+ ↗ source
+
+ {/if}
+
+
+
+
+
Accepted input types
+
+ {#each tool.input_types as t}
+ {t}
+ {/each}
+
+
+
+
+ {#if tool.dependencies?.length > 0}
+
+
+
+ External dependencies
+
+
+ {#each tool.dependencies as dep}
+
+ {dep}
+ must be in $PATH
+
+ {/each}
+
+
+
+ {/if}
+
+ {#if tool.config_fields?.length > 0}
+
+
+
+
+
Global config
+ {#if hasGlobalConfig}
+ configured
+ {/if}
+ {#if configReadonly}
+ read-only
+ {/if}
+
+ {#if msg}
+
{msg.text}
+ {/if}
+
+
+ {#each tool.config_fields as field}
+
+
+ {field.name}
+ {field.type}
+ {#if field.required}
+ required
+ {/if}
+
+ {#if field.description}
+
{field.description}
+ {/if}
+
+ default: {field.default ?? "-"}
+
+
+ {#if field.type === "bool"}
+
+
+
+ {edits[field.name] ? "enabled" : "disabled"}
+
+
+ {:else if field.type === "int"}
+
+ {:else if field.type === "float"}
+
+ {:else if field.type === "enum"}
+
+ {#each field.options as opt}
+ {opt}
+ {/each}
+
+ {:else}
+
+ {/if}
+
+ {/each}
+
+ {#if !configReadonly}
+
+
+ {#if saving}
+
+ {:else}
+
+ {/if}
+ Save
+
+ {#if hasGlobalConfig}
+
+ Reset to defaults
+
+ {/if}
+
+ {/if}
+
+
+ {/if}
+
+
+{/if}
diff --git a/front/src/components/ToolList.svelte b/front/src/components/ToolList.svelte
new file mode 100644
index 0000000..1167152
--- /dev/null
+++ b/front/src/components/ToolList.svelte
@@ -0,0 +1,346 @@
+
+
+{#snippet toolCard(tool, missing)}
+
+{/snippet}
+
+{#if loading}
+
+
+
+{:else if error}
+
+{:else}
+
+
+ Profile
+
+ {#if profileLoading}
+
+ {/if}
+
+
+
+ Input
+ (selectedInputType = val)}
+ />
+
+
+
+ {#if tools.length === 0}
+
+ No tools registered.
+
+ {:else}
+
+ {#if active.length > 0}
+
+
+
+ Active
+ {active.length}
+
+
+
+ {/if}
+
+ {#if activeMissing.length > 0}
+
+
+
+ Active - required config missing
+ {activeMissing.length}
+
+
+
+ {/if}
+
+ {#if activeUnavail.length > 0}
+
+
+
+ Active - unavailable
+ {activeUnavail.length}
+
+
+
+ {/if}
+
+ {#if active.length + activeMissing.length + activeUnavail.length > 0 && inactive.length + inactiveMissing.length + inactiveUnavail.length > 0}
+
+ {/if}
+
+ {#if inactive.length > 0}
+
+
+
+ Disabled
+ {inactive.length}
+
+
+
+ {/if}
+
+ {#if inactiveMissing.length > 0}
+
+
+
+ Disabled - required config missing
+ {inactiveMissing.length}
+
+
+
+ {/if}
+
+ {#if inactiveUnavail.length > 0}
+
+
+
+ Disabled - unavailable
+ {inactiveUnavail.length}
+
+
+
+ {/if}
+
+ {/if}
+{/if}
diff --git a/front/src/components/comps/Badge.svelte b/front/src/components/comps/Badge.svelte
new file mode 100644
index 0000000..15c46e7
--- /dev/null
+++ b/front/src/components/comps/Badge.svelte
@@ -0,0 +1,29 @@
+
+
+
+ {#if loading}
+
+ {:else if IconComponent}
+
+ {/if}
+ {text}
+
diff --git a/front/src/components/comps/InfoTip.svelte b/front/src/components/comps/InfoTip.svelte
new file mode 100644
index 0000000..ac6ef40
--- /dev/null
+++ b/front/src/components/comps/InfoTip.svelte
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/front/src/components/comps/Select.svelte b/front/src/components/comps/Select.svelte
new file mode 100644
index 0000000..ccf5112
--- /dev/null
+++ b/front/src/components/comps/Select.svelte
@@ -0,0 +1,84 @@
+
+
+ {
+ if (container && !container.contains(e.target)) {
+ open = false;
+ query = "";
+ }
+}} />
+
+
+
+
+ {selected ?? placeholder}
+
+
+ {#if open}
+
+
+ { if (e.key === "Escape") { open = false; query = ""; } }}
+ />
+
+
+ {#if filtered.length === 0}
+ No results
+ {:else}
+ {#each filtered as option}
+
+ select(option)}
+ >
+ {option}
+
+
+ {/each}
+ {/if}
+
+
+ {/if}
+
diff --git a/front/src/components/comps/ToolIcon.svelte b/front/src/components/comps/ToolIcon.svelte
new file mode 100644
index 0000000..2ecdc6f
--- /dev/null
+++ b/front/src/components/comps/ToolIcon.svelte
@@ -0,0 +1,29 @@
+
+
+{#if iconName}
+ {
+ const target = e.currentTarget as HTMLImageElement;
+ target.src = genericFallbackUrl;
+ }}
+ />
+{:else}
+
+{/if}
diff --git a/front/src/components/comps/TtyOutput.svelte b/front/src/components/comps/TtyOutput.svelte
new file mode 100644
index 0000000..de8e136
--- /dev/null
+++ b/front/src/components/comps/TtyOutput.svelte
@@ -0,0 +1,63 @@
+
+
+{#if html}
+ {@html html}
+{/if}
+
+
diff --git a/front/src/content.config.ts b/front/src/content.config.ts
new file mode 100644
index 0000000..984afa4
--- /dev/null
+++ b/front/src/content.config.ts
@@ -0,0 +1,14 @@
+import { defineCollection, z } from "astro:content";
+import { glob } from "astro/loaders";
+
+const cheatsheets = defineCollection({
+ loader: glob({ pattern: "**/*.md", base: "./src/content/cheatsheets" }),
+ schema: z.object({
+ title: z.string(),
+ description: z.string().optional(),
+ order: z.number().optional(),
+ tags: z.array(z.string()).optional(),
+ }),
+});
+
+export const collections = { cheatsheets };
diff --git a/front/src/content/cheatsheets/github-osint.md b/front/src/content/cheatsheets/github-osint.md
new file mode 100644
index 0000000..35d968e
--- /dev/null
+++ b/front/src/content/cheatsheets/github-osint.md
@@ -0,0 +1,134 @@
+---
+title: "Unmasking Github Users: How to Identify the Person Behind Any Github Profile"
+description: "Ever wondered who is behind a specific Github username? This guide covers advanced OSINT techniques to deanonymize users, find hidden email addresses, and link Github accounts to real-world identities."
+tags: [github, social]
+---
+
+In the world of Open-Source Intelligence (OSINT), we often focus on social media platforms like Twitter or LinkedIn. However, developers frequently leave behind much more detailed personal information on **Github**.
+
+Whether you are a recruiter, a security researcher, or a digital investigator, Github is a goldmine. Why? Because while a user might choose a cryptic handle like `anotherhadi`, their Git configuration often reveals their real name and email address.
+
+## Level 1: The Low-Hanging Fruit
+
+Before diving into technical exploits, start with the obvious. Many users forget how much they have shared in their profile settings.
+
+- **The Bio & Location**: Even a vague location like "Montpellier, France," combined with a niche tech stack (e.g., "COBOL expert"), significantly narrows down the search.
+- **External Links**: Check the personal website or blog link. Run a WHOIS lookup on that domain to find registration details. Use other OSINT tools and techniques on those websites to pivot further.
+- **The Profile Picture**: Right-click the avatar and use Google Reverse Image Search, Yandex, or other reverse image engines. Developers often use the same professional headshot on Github as they do on LinkedIn.
+
+## Level 2: Digging into Commits
+
+This is the **most effective OSINT** method. While Github masks author names and emails in the web view, this information is permanently embedded in the commit metadata.
+
+### The `.patch` Method
+
+Find a repository where the target has contributed. Open any commit they made, and simply add `.patch` to the end of the URL.
+
+- **URL**: `https://github.com/{username}/{repo}/commit/{commit_hash}.patch`
+- Look at the `From:` line. It should look like this: `From: John Doe `
+
+For example, check: [github.com/anotherhadi/nixy/commit/e6873e8caae491073d8ab7daad9d2e50a04490ce.patch](https://github.com/anotherhadi/nixy/commit/e6873e8caae491073d8ab7daad9d2e50a04490ce.patch)
+
+### The API Events Method
+
+If you cannot find a recent commit, check their **public activity** stream via the Github API.
+
+- **Go to**: `https://api.github.com/users/{target_username}/events/public`
+- Search (Ctrl+F) for the word `email`. You will often find the **email address** associated with their `PushEvent` headers, even if they have "Keep my email addresses private" enabled in their current settings.
+
+## The Verification Loop: Linking Email to Account
+
+If you have found an email address and want to be 100% sure it belongs to a specific Github profile, you can use Github’s own attribution engine against itself.
+
+### The Email Spoofing Method
+
+While the previous methods help you find an email _from_ a profile, this technique does the opposite: it identifies which Github account is linked to a specific email address.
+
+**How it works:**
+Github attributes commits based on the email address found in the Git metadata. If you push a commit using a specific email, Github will automatically link that commit to the account associated with that address as its **primary email**.
+
+**The Process:**
+
+1. **Initialize a local repo:** `git init investigation`
+2. **Configure the target email:** `git config user.email "target@example.com"` and `git config user.name "A Username"`
+3. **Create a dummy commit:** `echo "test" > probe.txt && git add . && git commit -m "Probe"`
+4. **Push to a repo you own:** Create a new empty repository on your Github account and push the code there.
+5. **Observe the result:** Go to the commit history on the Github web interface. The avatar and username of the account linked to that email will appear as the author of the commit.
+
+> **Note:** This method only works if the target email is set as the **Primary Email** on the user's account. It is a foolproof way to confirm if an email address you found elsewhere belongs to a specific Github user.
+
+### The Search Index: Finding Hidden Contributions
+
+Even if an email address is not listed on a user's profile, it may still be indexed within Github's global search.
+Github allows you to filter search results by the metadata fields of a commit.
+This is particularly useful if the target has **contributed to public repositories** using their real email.
+
+You can use these specific qualifiers in the **Github search bar** (select the "Commits" tab):
+
+- `author-email:target@example.com`: Finds commits where the target is the original author.
+- `committer-email:target@example.com`: Finds commits where the target was the one who committed the code (sometimes different from the author).
+
+## Level 3: Technical Metadata
+
+If the email is masked or missing, we can look at the **cryptographic keys** the user uses to communicate with Github.
+
+### SSH Keys
+
+Every user’s public **SSH keys are public**.
+
+- **URL**: `https://github.com/{username}.keys`
+- **The Pivot**: You can take the key string and search for it on platforms like **Censys** or **Shodan**. If that same key is authorized on a specific server IP, you have successfully located the user’s infrastructure.
+
+### GPG Keys
+
+If a user signs their commits, their **GPG key** is available at:
+
+- **URL**: `https://github.com/{username}.gpg`
+- **The Reveal**: Import this key into your local GPG tool (`gpg --import`). It will often reveal the **Verified Identity** and the primary email address linked to the encryption key.
+
+## Level 4: Connecting the Dots
+
+Once you have a **name**, an **email**, or a **unique username**, it’s time to _pivot_.
+
+- **Username Pivoting**: Use tools like [Sherlock](https://github.com/sherlock-project/sherlock) or [Maigret](https://github.com/soxoj/maigret/) to search for the same username across hundreds of other platforms. Developers are creatures of habit; they likely use the same handle on Stack Overflow, Reddit, or even old gaming forums.
+- **Email Pivoting**: Use tools like [holehe](https://github.com/megadose/holehe) to find other accounts registered with the email addresses you just uncovered.
+
+## Automating the Hunt: Github-Recon
+
+If you want to move from manual investigation to automated intelligence, check out [Github-Recon](https://github.com/anotherhadi/github-recon).
+Written in Go, this powerful CLI tool aggregates public OSINT data by automating the techniques mentioned above and more. Whether you start with a username or a single email address, it can retrieve SSH/GPG keys, enumerate social accounts, and find "close friends" based on interactions.
+Its standout features include a **Deep Scan** mode-which clones repositories to perform regex searches and TruffleHog secret detection—and an automated **Email Spoofing** engine that instantly identifies the account linked to any primary email address.
+
+
+
+ anotherhadi/github-recon
+ GitHub OSINT reconnaissance tool. Gathers profile info, social links, organisations, SSH/GPG keys, commits, and more from a GitHub username or email.
+
+
+
+## Conclusion and Protection: How to Stay Anonymous
+
+If you are a developer reading this, you might be feeling exposed.
+Understanding what information about you is publicly visible is the **first step to managing your online presence**. This guide and tools like [github-recon](https://github.com/anotherhadi/github-recon) can help you identify your own publicly available data on Github. Here’s how you can take steps to protect your privacy and security:
+
+- **Review your public profile**: Regularly check your Github profile and
+ repositories to ensure that you are not unintentionally exposing sensitive
+ information.
+- **Manage email exposure**: Use Github's settings to control which email
+ addresses are visible on your profile and in commit history. You can also **use
+ a no-reply email** address for commits, and an
+ [alias email](https://proton.me/support/addresses-and-aliases) for your
+ account. Delete/modify any sensitive information in your commit history.
+- **Be Mindful of Repository Content**: **Avoid including sensitive information** in
+ your repositories, such as API keys, passwords, emails or personal data. Use
+ `.gitignore` to exclude files that contain sensitive information.
+
+You can also use a tool like [TruffleHog](github.com/trufflesecurity/trufflehog)
+to scan your repositories specifically for exposed secrets and tokens.
+
+**Useful links:**
+
+- [Blocking command line pushes that expose your personal email address](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-email-preferences/blocking-command-line-pushes-that-expose-your-personal-email-address)
+- [No-reply email address](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-email-preferences/setting-your-commit-email-address)
+
+In OSINT, the best hidden secrets are the ones we forget we ever shared. Happy hunting!
diff --git a/front/src/content/cheatsheets/google-dorks.md b/front/src/content/cheatsheets/google-dorks.md
new file mode 100644
index 0000000..08ef7f8
--- /dev/null
+++ b/front/src/content/cheatsheets/google-dorks.md
@@ -0,0 +1,100 @@
+---
+title: "Google Dorks"
+description: "Essential cheatsheet for Google Dorking, using advanced search operators to perform Open Source Intelligence (OSINT) and identify publicly exposed information or misconfigurations on target websites."
+tags: [google, dorks]
+---
+
+[Google](https://google.com) hacking, also named Google dorking, is a hacker technique that uses Google Search and other Google applications to find security holes in the configuration and computer code that websites are using.
+Dorks also works on [Startpage](https://startpage.com) or [Duckduckgo](https://duckduckgo.com).
+
+## Basics
+
+- `-` excludes a term
+- `OR` searches for either term
+- `""` searches for an exact phrase
+- `*` acts as a wildcard
+- `site:` restricts the search to a specific domain
+- `inurl:` restricts the search to a specific URL
+- `intitle:` restricts the search to a specific title
+- `intext:` restricts the search to a specific text
+- `allintext:` restricts the search to all text
+- `filetype:` restricts the search to a specific file type
+
+## Information gathering
+
+Replace "{target}" with a name or other identifiers used online. Always remember
+to use these queries solely for legal and ethical purposes on information you
+own or have permission to check.
+
+- **File Types:**
+ - `"{target}" filetype:pdf`
+ - `"{target}" filetype:doc OR filetype:docx OR filetype:xls OR filetype:ppt`
+ - Config files:
+ `site:{target}+filetype:xml+|+filetype:conf+|+filetype:cnf+|+filetype:reg+|+filetype:inf+|+filetype:rdp+|+filetype:cfg+|+filetype:txt+|+filetype:ora+|+filetype:ini`
+ - Database files: `site:{target}+filetype:sql+|+filetype:dbf+|+filetype:mdb`
+ - Data files: `site:{target} ext:csv OR ext:xls OR ext:log` or `site:{target} "@gmail.com" ext:csv`
+ - Log files: `site:{target}+filetype:log+|filetype:txt` - Backup files:
+ `site:{target}+filetype:bkf+|+filetype:bkp+|+filetype:bak+|+filetype:old+|+filetype:backup`
+ - Setup files:
+ `site:{target}+inurl:readme+|+inurl:license+|+inurl:install+|+inurl:setup+|+inurl:config`
+ - Private files:
+ `site:{target} "internal use only" ( you can replace with "classified", "private", "unauthorised" )`
+ - Sensitive docs:
+ `ext:txt | ext:pdf | ext:xml | ext:xls | ext:xlsx | ext:ppt | ext:pptx | ext:doc | ext:docx intext:“confidential” | intext:“Not for Public Release” | intext:”internal use only” | intext:“do not distribute” site:{target}`
+ - Code leaks: Check for code snippets, secrets, configs
+ ```txt
+ site:pastebin.com "{target}"
+ site:jsfiddle.net "{target}"
+ site:codebeautify.org "{target}"
+ site:codepen.io "{target}"`
+ ```
+ - Cloud File Shares: Find exposed files linked to your target
+ ```txt
+ site:http://drive.google.com "{target}"
+ site:http://docs.google.com inurl:"/d/" "{target}"
+ site:http://dropbox.com/s "{target}"
+ ```
+ - Other: `site:{target}+filetype:pdf+|+filetype:xlsx+|+filetype:docx`
+
+- **Social Media & Professional Networks:**
+ - `site:linkedin.com/in "{target}"`
+ - `site:facebook.com "{target}"`
+ - `site:twitter.com "{target}"`
+ - `site:instagram.com "{target}"`
+
+- **Profile & Resume Searches:**
+ - `inurl:"profile" "{target}"`
+ - `intitle:"{target}" "profile"`
+ - `"{target}" intext:"resume"`
+ - `intitle:"Curriculum Vitae" OR intitle:"CV" "{target}"`
+
+- **Email and Contact Information:**
+ - `"{target}" intext:"@gmail.com"`
+ - `"{target}" intext:"email"`
+ - `"{target}" AND "contact"`
+
+- **Forums and Public Repositories:**
+ - `site:pastebin.com "{target}"`
+ - `site:github.com "{target}"`
+ - `site:forums "{target}"`
+
+- **Directory Listings and Miscellaneous:**
+ - `site:{target}+intitle:index.of`,
+
+- **Exclusion Searches:**
+ - `"{target}" -site:facebook.com`
+ - `"{target}" -site:twitter.com`
+
+## Advanced Google Operators
+
+- `related:site` finds websites similar to the specified URL
+- `define:term` shows a word or phrase definition directly in the results
+- `inanchor:word` filters pages where the anchor text includes the specified
+ word
+- `around(n)` restricts results to pages where two words appear within _n_ words
+ of each other
+
+## Ressources
+
+- [TakSec's google dorks](https://github.com/TakSec/google-dorks-bug-bounty/)
+- [Exploit-db Google hacking database](https://www.exploit-db.com/google-hacking-database)
diff --git a/front/src/content/cheatsheets/sock-puppets.md b/front/src/content/cheatsheets/sock-puppets.md
new file mode 100644
index 0000000..1638923
--- /dev/null
+++ b/front/src/content/cheatsheets/sock-puppets.md
@@ -0,0 +1,61 @@
+---
+title: "Sock Puppets"
+description: "Essential cheatsheet on creating and managing Sock Puppets (fake identities) for ethical security research and Open Source Intelligence (OSINT), focusing on maintaining separation from personal data and bypassing common verification."
+tags: [sock-puppets]
+---
+
+Sock puppets are fake identities use to gather information from a target.
+The sock puppet should have no link between your personal information and the fakes ones. (No ip address, mail, follow, etc..)
+
+## Information generation
+
+
+
+ Faker
+ Generate massive amounts of fake data
+
+
+
+
+
+ Fake Name
+ Personal informations
+
+
+
+
+
+ This Person Does Not Exist
+ Generate fake image
+
+
+
+## Bypass phone verification
+
+
+
+ SMSPool
+ Cheapest and Fastest Online SMS verification
+
+
+
+
+
+ Online Sim
+ SMS verification with free tier
+
+
+
+
+
+ Sms 4 Sats
+ Paid SMS verification
+
+
+
+
+
+ Sms 4 Sats (Onion)
+ Paid SMS verification. Tor version
+
+
diff --git a/front/src/content/cheatsheets/tips.md b/front/src/content/cheatsheets/tips.md
new file mode 100644
index 0000000..c9ec7ab
--- /dev/null
+++ b/front/src/content/cheatsheets/tips.md
@@ -0,0 +1,23 @@
+---
+title: "Tips"
+description: "A cheatsheet of practical tips and unconventional methods for Open Source Intelligence (OSINT), focusing on advanced data visualization, information leakage detection, and utilizing web archives for historical data."
+---
+
+## Visualisation
+
+Use [OSINTracker](https://app.osintracker.com/) to visualise your findings.
+It allows you to create a graph of your findings, which can help you see connections and relationships between different pieces of information.
+
+## Forgotten passwords
+
+To find email addresses and phone numbers associated with an account, you can click on "Forgot password?" on the login page of a website. Be careful, though, this creates notifications and can be detected by the target, and often gives your information away.
+
+## Archive Search
+
+- [Wayback Machine](https://web.archive.org) stores over 618 billion web captures
+- [Archive.today](https://archive.ph) creates on-demand snapshots, including for JS-heavy sites, with both a functional page and screenshot version
+
+## Bookmarklets
+
+- [K2SOsint/Bookmarklets](https://github.com/K2SOsint/Bookmarklets)
+- [MyOsint.training](https://tools.myosint.training/)
diff --git a/front/src/content/cheatsheets/x-twitter-osint.md b/front/src/content/cheatsheets/x-twitter-osint.md
new file mode 100644
index 0000000..f3766ea
--- /dev/null
+++ b/front/src/content/cheatsheets/x-twitter-osint.md
@@ -0,0 +1,88 @@
+---
+title: "Twitter/X OSINT"
+description: "Essential cheatsheet for Open Source Intelligence (OSINT) on Twitter/X, detailing advanced search operators, engagement filters, and temporal/geographic capabilities for effective data collection."
+tags: [social]
+---
+
+## Banner last update time
+
+The banner URL includes a Unix timestamp indicating when the banner was last
+updated.
+
+For example:
+`https://pbs.twimg.com/profile_banners/1564326938851921921/1750897704/600x200`
+
+In this case, `1750897704` is the timestamp. You can convert it using
+[unixtimestamp.com](https://www.unixtimestamp.com/) or any other Unix time converter.
+
+## Basic Search Operators
+
+Twitter's advanced search functionality provides powerful filtering capabilities
+for OSINT investigations:
+
+- **Keywords**: `word1 word2` (tweets containing both words)
+- **Exact phrases**: `"exact phrase"` (tweets with this exact sequence)
+- **Exclusion**: `-word` (excludes tweets containing this word)
+- **Either/or**: `word1 OR word2` (tweets containing either term)
+- **Hashtags**: `#hashtag` (tweets with specific hashtag)
+- **Accounts**: `from:username` (tweets sent by specific account)
+- **Mentions**: `to:username` (tweets in reply to an account)
+- **Mentions in any context**: `@username` (tweets mentioning an account)
+
+## Advanced Filters
+
+
+
+ Twitter/X Search advanced GUI
+ Graphical User Interface (GUI) for the twitter search advanced functionality
+
+
+
+### Engagement Filters
+
+- **Minimum retweets**: `min_retweets:number`
+- **Minimum likes**: `min_faves:number`
+- **Minimum replies**: `min_replies:number`
+- **Filter for links**: `filter:links`
+- **Filter for media**: `filter:media`
+- **Filter for images**: `filter:images`
+- **Filter for videos**: `filter:videos`
+
+### Temporal and Geographic Filters
+
+- **Date range**: `since:YYYY-MM-DD until:YYYY-MM-DD`
+- **Geolocation**: `geocode:latitude,longitude,radius` (e.g.,
+ `geocode:40.7128,-74.0060,5km`)
+- **Language**: `lang:code` (e.g., `lang:en` for English)
+
+### Tweet Characteristics
+
+- **Positive attitude**: `🙂 OR :) OR filter:positive`
+- **Negative attitude**: `🙁 OR :( OR filter:negative`
+- **Questions**: `?` or `filter:questions`
+- **Retweets only**: `filter:retweets`
+- **Native retweets only**: `filter:nativeretweets`
+- **Twitter Blue subscribers**: `filter:verified` (note: since 2023, "verified" means Twitter Blue subscriber, not a traditionally verified account)
+- **Safe content**: `filter:safe`
+
+## Practical Search Combinations
+
+- **Content from a user within a date range**:
+ `from:username since:2023-01-01 until:2023-12-31`
+
+- **High-engagement tweets about a topic**:
+ `"artificial intelligence" min_retweets:100 lang:en -filter:retweets`
+
+- **Media shared by a specific user**:
+ `from:username filter:media -filter:retweets`
+
+- **Conversations between specific users**:
+ `from:username1 to:username2 OR from:username2 to:username1`
+
+- **Link sharing on a topic by verified users**:
+ `"climate change" filter:links filter:verified since:2023-01-01`
+
+## Disclaimer
+
+Remember that all Twitter searches should comply with Twitter's Terms of Service
+and appropriate legal frameworks for your jurisdiction.
diff --git a/front/src/layouts/Layout.astro b/front/src/layouts/Layout.astro
new file mode 100644
index 0000000..a2554e4
--- /dev/null
+++ b/front/src/layouts/Layout.astro
@@ -0,0 +1,92 @@
+---
+import "@src/styles/global.css";
+import "@src/styles/gfm.css";
+import "@src/styles/markdown.css";
+import Navbar from "@src/components/Nav.svelte";
+import DemoBanner from "@src/components/DemoBanner.svelte";
+import { Coffee } from "@lucide/svelte";
+
+interface Props {
+ title?: string;
+ description?: string;
+}
+
+const {
+ title = "iknowyou",
+ description = "Self-hosted OSINT aggregation. Run multiple recon tools against a target in parallel and get results in one place.",
+} = Astro.props;
+
+const pageTitle = title === "iknowyou" ? title : `${title} — iky`;
+const canonicalURL = new URL(Astro.url.pathname, Astro.site ?? Astro.url.origin);
+---
+
+
+
+
+
+
+
+ {pageTitle}
+
+
+
+
+
+
+
+
+
+
+ Support me
+
+
+
+
+
+
+
diff --git a/front/src/lib/utils.ts b/front/src/lib/utils.ts
new file mode 100644
index 0000000..2a385e2
--- /dev/null
+++ b/front/src/lib/utils.ts
@@ -0,0 +1,17 @@
+export function cleanUserInput(query: string | undefined | null): string {
+ if (!query) return "";
+ return query.replace(/[^a-zA-Z0-9\s.\-_/]/g, "").trim();
+}
+
+export function getRandomEmoji(): string {
+ const emojis = [
+ "(·.·)",
+ "(>_<)",
+ "¯\\_(ツ)_/¯",
+ "(╯_╰)",
+ "(-_-)",
+ "┐(‘~`;)┌",
+ "(X_X)",
+ ];
+ return emojis[Math.floor(Math.random() * emojis.length)];
+}
diff --git a/front/src/lib/vars.ts b/front/src/lib/vars.ts
new file mode 100644
index 0000000..664aacb
--- /dev/null
+++ b/front/src/lib/vars.ts
@@ -0,0 +1,16 @@
+import { Mail, User, Phone, Globe, Server, KeyRound, Contact } from "@lucide/svelte";
+
+export const INPUT_TYPES = [
+ "email", "username", "name", "phone", "ip",
+ "domain", "password",
+];
+
+export const INPUT_TYPE_ICON = {
+ email: Mail,
+ username: User,
+ name: Contact,
+ phone: Phone,
+ domain: Globe,
+ ip: Server,
+ password: KeyRound,
+};
diff --git a/front/src/pages/403.astro b/front/src/pages/403.astro
new file mode 100644
index 0000000..d2aa066
--- /dev/null
+++ b/front/src/pages/403.astro
@@ -0,0 +1,30 @@
+---
+import Layout from "@src/layouts/Layout.astro";
+import { ShieldAlert, Home } from "@lucide/svelte";
+---
+
+
+
+
+
+
403
+
+
+
+
Access Denied
+
+ You don't have the necessary clearance to access this sector of the app.
+
+
+
+
+ ERROR_CODE: INSUFFICIENT_PERMISSIONS
+
+
+
+ Return to Surface
+
+
+
diff --git a/front/src/pages/404.astro b/front/src/pages/404.astro
new file mode 100644
index 0000000..9e5e361
--- /dev/null
+++ b/front/src/pages/404.astro
@@ -0,0 +1,26 @@
+---
+import Layout from "@src/layouts/Layout.astro";
+import { Ghost, Home } from "@lucide/svelte";
+---
+
+
+
+
+
+
404
+
+
+
+
Lost?
+
+ The page you are looking for doesn't exist or has been moved.
+
+
+
+
+ Back to Home
+
+
+
diff --git a/front/src/pages/500.astro b/front/src/pages/500.astro
new file mode 100644
index 0000000..48925a6
--- /dev/null
+++ b/front/src/pages/500.astro
@@ -0,0 +1,32 @@
+---
+import Layout from "@src/layouts/Layout.astro";
+import { AlertTriangle, RefreshCw } from "@lucide/svelte";
+---
+
+
+
+
+
+
+
System Failure
+
+ The server encountered an unexpected error.
+
+
+
+
+
+
diff --git a/front/src/pages/cheatsheets/[slug].astro b/front/src/pages/cheatsheets/[slug].astro
new file mode 100644
index 0000000..26de50b
--- /dev/null
+++ b/front/src/pages/cheatsheets/[slug].astro
@@ -0,0 +1,126 @@
+---
+import Layout from "@src/layouts/Layout.astro";
+import { getCollection, render } from "astro:content";
+
+export async function getStaticPaths() {
+ const sheets = await getCollection("cheatsheets");
+ return sheets.map((sheet) => ({
+ params: { slug: sheet.id },
+ props: { sheet },
+ }));
+}
+
+const { sheet } = Astro.props;
+const { Content, headings } = await render(sheet);
+
+const toc = headings.filter((h) => h.depth === 2 || h.depth === 3);
+---
+
+
+
+
+
+
+
+
{sheet.data.title}
+ {sheet.data.description && (
+
{sheet.data.description}
+ )}
+ {sheet.data.tags && sheet.data.tags.length > 0 && (
+
+ {sheet.data.tags.map((tag) => (
+
{tag}
+ ))}
+
+ )}
+
+
+ {toc.length > 0 && (
+
+
+ On this page
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+ {toc.length > 0 && (
+
+
+
+ On this page
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
diff --git a/front/src/pages/cheatsheets/index.astro b/front/src/pages/cheatsheets/index.astro
new file mode 100644
index 0000000..50e60de
--- /dev/null
+++ b/front/src/pages/cheatsheets/index.astro
@@ -0,0 +1,33 @@
+---
+import Layout from "@src/layouts/Layout.astro";
+import { getCollection } from "astro:content";
+import CheatsheetList from "@src/components/CheatsheetList.svelte";
+
+const sheets = (await getCollection("cheatsheets"))
+ .sort((a, b) => (a.data.order ?? 99) - (b.data.order ?? 99))
+ .map((s) => ({
+ id: s.id,
+ title: s.data.title,
+ description: s.data.description,
+ tags: s.data.tags,
+ }));
+---
+
+
+
+
+
+
+
+
OSINT Cheatsheets
+
+ Quick reference cards for common OSINT techniques.
+
+
+
+
+
+
+
diff --git a/front/src/pages/help.astro b/front/src/pages/help.astro
new file mode 100644
index 0000000..2fb1403
--- /dev/null
+++ b/front/src/pages/help.astro
@@ -0,0 +1,172 @@
+---
+import Layout from "@src/layouts/Layout.astro";
+---
+
+
+
+
+
+
+
+
How it works
+
+ A guide to iknowyou: concepts, tools, profiles, and configuration.
+
+
+
+
+
+
+
+
+ What is it?
+
+
+ Iknowyou (IKY) is an OSINT aggregation platform. It runs multiple
+ open-source intelligence tools against a target in parallel and presents the results
+ in a unified interface. Targets can be email addresses, usernames, phone numbers, IP
+ addresses, domains, and more.
+
+
+ Instead of running each tool manually, IKY handles orchestration, config management,
+ and result rendering so you can focus on analysis.
+
+
+
+
+
+
+
+
+ Tools
+
+
+ Each tool is a Go module that knows how to query one data source
+ (a website, an API, a local binary...). Tools declare:
+
+
+ Which input types they accept (email, username, IP...)
+ Optional configuration fields (API keys, options)
+ Whether they require an external binary to be installed
+
+
+ The Tools page shows all registered tools
+ grouped by status:
+
+
+
+
+ Active - ready to run
+
+
+
+ Active: config missing - needs an API key or required field
+
+
+
+ Active: unavailable - required binary not found on the system
+
+
+
+ Disabled - excluded by the selected profile
+
+
+
+
+
+
+
+
+
+ Profiles
+
+
+ A profile is a named search configuration. When you start a search,
+ you pick which profile to use. Profiles control:
+
+
+ Enabled list (whitelist): if set, only these tools run
+ Disabled list (blacklist): these tools are always skipped
+ Tool overrides : per-profile config that overrides global settings for specific tools
+ Notes : a description of what the profile is for
+
+
+ Two profiles are built-in and cannot be modified:
+
+
+
+
default
+
+ All tools active with default settings. No restrictions.
+
+
+
+
hard
+
+ Aggressive mode. All tools active, including those that may send
+ notifications or leave traces at the target.
+
+
+
+
+ You can create custom profiles on the Profiles page .
+
+
+
+
+
+
+
+
+ Configuration & Overrides
+
+
+ Tool configuration works in two layers:
+
+
+
+ Global config : set on the
+ Tools page . Applied to every
+ search regardless of profile.
+
+
+ Profile override : set inside a profile. Takes precedence over
+ global config when that profile is used. Useful when you want a tool to behave
+ differently in specific contexts (e.g. slower rate-limiting in a "quiet" profile).
+
+
+
+ Config is stored in config.yaml.
+ Built-in profiles are hardcoded in Go and are never written to disk.
+
+
+
+
+
+
+
+
+ How a search runs
+
+
+ You enter a target, select its type (email, username...) and pick a profile.
+
+ The backend filters tools by input type and the profile's enabled/disabled rules,
+ then skips any tool with a missing required config field.
+
+ All eligible tools run in parallel against the target.
+
+ The frontend polls for results and renders them progressively as each tool finishes.
+
+
+
+ A search can be cancelled at any time from the results page.
+ Completed searches are kept in memory.
+
+
+
+
+
+
diff --git a/front/src/pages/index.astro b/front/src/pages/index.astro
new file mode 100644
index 0000000..51c9654
--- /dev/null
+++ b/front/src/pages/index.astro
@@ -0,0 +1,19 @@
+---
+import Layout from "@src/layouts/Layout.astro";
+import HomePage from "@src/components/HomePage.svelte";
+---
+
+
+
+
+ i know you
+
+ Centralizing your OSINT tools in one place.
+ Iknowyou is a self-hosted OSINT (Open-Source Intelligence) platform that centralises reconnaissance tools
+ into a single reactive web interface. Instead of juggling terminals, browser tabs, and disconnected CLI tools, you type a target once and get results in real time.
+
+
+
+
+
+
diff --git a/front/src/pages/profiles.astro b/front/src/pages/profiles.astro
new file mode 100644
index 0000000..d95bf4e
--- /dev/null
+++ b/front/src/pages/profiles.astro
@@ -0,0 +1,21 @@
+---
+import Layout from "@src/layouts/Layout.astro";
+import ProfileSettings from "@src/components/ProfileSettings.svelte";
+---
+
+
+
+
+
+
+
Profiles
+
+ Manage search profiles: allowed/blocked tools and per-tool config overrides.
+
+
+
+
+
+
diff --git a/front/src/pages/search/[id].astro b/front/src/pages/search/[id].astro
new file mode 100644
index 0000000..f34bc8b
--- /dev/null
+++ b/front/src/pages/search/[id].astro
@@ -0,0 +1,22 @@
+---
+import Layout from "@src/layouts/Layout.astro";
+import SearchDetail from "@src/components/SearchDetail.svelte";
+
+export async function getStaticPaths() {
+ return [{ params: { id: "_" } }];
+}
+
+// Shell page — SearchDetail reads the real ID from window.location.
+const id = null;
+---
+
+
+
diff --git a/front/src/pages/tools/[name].astro b/front/src/pages/tools/[name].astro
new file mode 100644
index 0000000..0305072
--- /dev/null
+++ b/front/src/pages/tools/[name].astro
@@ -0,0 +1,22 @@
+---
+import Layout from "@src/layouts/Layout.astro";
+import ToolDetail from "@src/components/ToolDetail.svelte";
+
+export async function getStaticPaths() {
+ return [{ params: { name: "_" } }];
+}
+
+// Shell page — ToolDetail reads the real name from window.location.
+const name = null;
+---
+
+
+
diff --git a/front/src/pages/tools/index.astro b/front/src/pages/tools/index.astro
new file mode 100644
index 0000000..9c88481
--- /dev/null
+++ b/front/src/pages/tools/index.astro
@@ -0,0 +1,20 @@
+---
+import Layout from "@src/layouts/Layout.astro";
+import ToolList from "@src/components/ToolList.svelte";
+---
+
+
+
+
+
+
+
Tools
+
All registered OSINT tools.
+
+
+
+
+
+
diff --git a/front/src/styles/gfm.css b/front/src/styles/gfm.css
new file mode 100644
index 0000000..3c8ca05
--- /dev/null
+++ b/front/src/styles/gfm.css
@@ -0,0 +1,144 @@
+/* ANSI Terminal Output */
+
+.ansi-output {
+ font-family: monospace;
+ font-size: 0.8rem;
+ line-height: 1.5;
+ white-space: pre-wrap;
+ word-break: break-all;
+ background-color: var(--color-base-100);
+ border: 1px solid color-mix(in srgb, var(--color-base-content) 10%, transparent);
+ border-radius: 0.5rem;
+ padding: 0.75rem 1rem;
+ overflow-x: auto;
+ max-height: 600px;
+ overflow-y: auto;
+}
+
+/* GitHub Flavored Markdown - Code Blocks */
+
+.code-block {
+ border: 1px solid color-mix(in srgb, var(--color-base-content) 12%, transparent);
+ border-radius: 0.5rem;
+ overflow: hidden;
+ margin: 1.25rem 0;
+ font-size: 0.875rem;
+}
+
+.code-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0.4rem 0.75rem;
+ background-color: color-mix(in srgb, var(--color-base-content) 6%, transparent);
+ border-bottom: 1px solid color-mix(in srgb, var(--color-base-content) 10%, transparent);
+}
+
+.code-lang {
+ font-family: monospace;
+ font-size: 0.75rem;
+ color: var(--color-base-content);
+ opacity: 0.5;
+ text-transform: lowercase;
+}
+
+.copy-btn {
+ display: flex;
+ align-items: center;
+ gap: 0.3rem;
+ font-size: 0.7rem;
+ padding: 0.2rem 0.5rem;
+ border-radius: 0.3rem;
+ border: 1px solid color-mix(in srgb, var(--color-base-content) 15%, transparent);
+ background: transparent;
+ color: var(--color-base-content);
+ opacity: 0.5;
+ cursor: pointer;
+ transition: opacity 0.15s;
+}
+.copy-btn:hover { opacity: 1; }
+.copy-btn.copied { color: var(--color-success); opacity: 1; }
+
+.code-block pre {
+ margin: 0 !important;
+ border-radius: 0 !important;
+ border: none !important;
+ padding: 1rem 0 !important;
+}
+
+.code-block pre code {
+ counter-reset: line;
+}
+.code-block pre code .line::before {
+ counter-increment: line;
+ content: counter(line);
+ display: inline-block;
+ width: 2rem;
+ text-align: right;
+ margin-right: 1.25rem;
+ color: var(--color-base-content);
+ opacity: 0.25;
+ user-select: none;
+}
+
+/* GitHub Flavored Markdown - Alerts */
+
+.markdown-alert {
+ border-left: 4px solid;
+ padding: 0.75rem 1rem;
+ border-radius: 0 0.5rem 0.5rem 0;
+ margin: 1.25rem 0;
+}
+
+.markdown-alert p {
+ margin: 0;
+}
+
+.markdown-alert-title {
+ display: flex;
+ align-items: center;
+ gap: 0.4rem;
+ font-weight: 600;
+ font-size: 0.875rem;
+ margin-bottom: 0.4rem;
+}
+
+.markdown-alert-note {
+ border-color: var(--color-info);
+ background-color: color-mix(in srgb, var(--color-info) 10%, transparent);
+}
+.markdown-alert-note .markdown-alert-title {
+ color: var(--color-info);
+}
+
+.markdown-alert-tip {
+ border-color: var(--color-success);
+ background-color: color-mix(in srgb, var(--color-success) 10%, transparent);
+}
+.markdown-alert-tip .markdown-alert-title {
+ color: var(--color-success);
+}
+
+.markdown-alert-important {
+ border-color: var(--color-secondary);
+ background-color: color-mix(in srgb, var(--color-secondary) 10%, transparent);
+}
+.markdown-alert-important .markdown-alert-title {
+ color: var(--color-secondary);
+}
+
+.markdown-alert-warning {
+ border-color: var(--color-warning);
+ background-color: color-mix(in srgb, var(--color-warning) 10%, transparent);
+}
+.markdown-alert-warning .markdown-alert-title {
+ color: var(--color-warning);
+}
+
+.markdown-alert-caution {
+ border-color: var(--color-error);
+ background-color: color-mix(in srgb, var(--color-error) 10%, transparent);
+}
+.markdown-alert-caution .markdown-alert-title {
+ color: var(--color-error);
+}
diff --git a/front/src/styles/global.css b/front/src/styles/global.css
new file mode 100644
index 0000000..e3e44a4
--- /dev/null
+++ b/front/src/styles/global.css
@@ -0,0 +1,84 @@
+@import "tailwindcss";
+@plugin "@tailwindcss/typography";
+
+@plugin "daisyui" {
+ themes: dark --prefersdark --default;
+}
+
+@plugin "daisyui/theme" {
+ name: "dark";
+ default: true;
+ prefersdark: true;
+ color-scheme: dark;
+
+ --color-primary: #E9F7A7;
+ --color-primary-content: #11111b;
+
+ --color-secondary: #f5c2e7;
+ --color-secondary-content: #11111b;
+
+ --color-accent: #94e2d5;
+ --color-accent-content: #11111b;
+
+ --color-neutral: #313244;
+ --color-neutral-content: #cdd6f4;
+
+ --color-base-300: #1a1a2a;
+ --color-base-200: #12121F;
+ --color-base-100: #0C0C16;
+ --color-base-content: #cdd6f4;
+
+ --color-info: #89b4fa;
+ --color-info-content: #11111b;
+
+ --color-success: #a6e3a1;
+ --color-success-content: #11111b;
+
+ --color-warning: #f9e2af;
+ --color-warning-content: #11111b;
+
+ --color-error: #f38ba8;
+ --color-error-content: #11111b;
+
+ --radius-selector: 1rem;
+ --radius-field: 0.5rem;
+ --radius-box: 1rem;
+ --size-selector: 0.25rem;
+ --size-field: 0.25rem;
+ --border: 1px;
+ --depth: 0;
+ --noise: 0;
+}
+
+@theme {
+ --font-unbounded: "Unbounded", sans-serif;
+}
+
+@utility logo-gradient {
+ @apply bg-gradient-to-b from-[#E9F7A7] via-[#E9BED9] to-[#BBA6EB] bg-clip-text text-transparent;
+}
+
+.animate-fade-in {
+ animation: fadeIn 0.6s ease-out;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(10px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@font-face {
+ font-family: 'Unbounded';
+ src: url('/fonts/unbounded-black.ttf') format('truetype');
+ font-weight: 900;
+ font-style: normal;
+ font-display: swap;
+}
+
diff --git a/front/src/styles/markdown.css b/front/src/styles/markdown.css
new file mode 100644
index 0000000..a7f414d
--- /dev/null
+++ b/front/src/styles/markdown.css
@@ -0,0 +1,20 @@
+@reference "./global.css";
+
+.link-card {
+ @apply flex items-center justify-between p-4 bg-base-200 rounded-xl border border-base-300 no-underline hover:border-primary transition-all mb-4;
+}
+
+.link-card h4 {
+ @apply font-bold m-0 text-base;
+}
+
+.link-card p {
+ @apply text-sm opacity-70 m-0;
+}
+
+.link-card::after {
+ content: "";
+ @apply w-5 h-5 bg-current shrink-0 ml-4;
+ mask: url('data:image/svg+xml;utf8, ') no-repeat center;
+ mask-size: contain;
+}
diff --git a/front/svelte.config.js b/front/svelte.config.js
new file mode 100644
index 0000000..522c1ef
--- /dev/null
+++ b/front/svelte.config.js
@@ -0,0 +1,5 @@
+import { vitePreprocess } from '@astrojs/svelte';
+
+export default {
+ preprocess: vitePreprocess(),
+}
diff --git a/front/tsconfig.json b/front/tsconfig.json
new file mode 100644
index 0000000..de0d88b
--- /dev/null
+++ b/front/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "astro/tsconfigs/strict",
+ "include": [".astro/types.d.ts", "**/*"],
+ "exclude": ["dist"],
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "@src/*": ["src/*"],
+ "@lib/*": ["src/lib/*"]
+ }
+ }
+}
diff --git a/justfile b/justfile
new file mode 100644
index 0000000..1402230
--- /dev/null
+++ b/justfile
@@ -0,0 +1,31 @@
+set shell := ["nix", "develop", "--command", "bash", "-c"]
+
+# Run dev mode for both front & backend. Live reload enabled.
+dev: install-deps
+ cd front && bun run dev
+
+# Install dependencies if not already installed. This is a no-op if node_modules already exists.
+install-deps:
+ cd front && ([ -d node_modules ] || bun install)
+
+# Regenerate .github/docs/ from registered tools
+docs:
+ cd back && go run ./cmd/gendocs/
+
+# Build the default Nix package (backend + frontend)
+nix-build:
+ nix build ".#packages.x86_64-linux.default"
+
+# Run the app directly via Nix
+nix-run:
+ nix run ".#"
+
+update: update-flake update-bun-nix
+
+# Regenerate front/bun.nix from the current bun.lock
+update-bun-nix:
+ cd front && bun2nix > bun.nix
+
+# Update the flake.lock file
+update-flake:
+ nix flake update
diff --git a/nix/backend.nix b/nix/backend.nix
new file mode 100644
index 0000000..b41faab
--- /dev/null
+++ b/nix/backend.nix
@@ -0,0 +1,17 @@
+{pkgs, ...}:
+pkgs.buildGoModule {
+ pname = "iky";
+ version = "0.1.0";
+
+ src = ../back;
+
+ vendorHash = "sha256-gR7P7Wcd7wojNkUz71vb2vvbbbQJF2QNnSld7WZ6moc=";
+
+ env.CGO_ENABLED = "0";
+ ldflags = ["-s" "-w"];
+
+ meta = {
+ description = "Iknowyou OSINT platform: backend API server";
+ mainProgram = "server";
+ };
+}
diff --git a/nix/frontend.nix b/nix/frontend.nix
new file mode 100644
index 0000000..38ffc46
--- /dev/null
+++ b/nix/frontend.nix
@@ -0,0 +1,38 @@
+{
+ pkgs,
+ bun2nix,
+ system,
+ ...
+}: let
+ bun2nixPkg = bun2nix.packages.${system}.default;
+
+ bunDeps = bun2nixPkg.fetchBunDeps {
+ bunNix = ../front/bun.nix;
+ };
+in
+ pkgs.stdenv.mkDerivation {
+ pname = "iky-frontend";
+ version = "0.1.0";
+
+ src = ../front;
+
+ nativeBuildInputs = [
+ bun2nixPkg.hook
+ ];
+
+ inherit bunDeps;
+
+ buildPhase = ''
+ runHook preBuild
+ bun run build
+ runHook postBuild
+ '';
+
+ installPhase = ''
+ runHook preInstall
+ cp -r dist $out
+ runHook postInstall
+ '';
+
+ meta.description = "Iknowyou OSINT platform: static Astro frontend";
+ }
diff --git a/nix/module.nix b/nix/module.nix
new file mode 100644
index 0000000..f981602
--- /dev/null
+++ b/nix/module.nix
@@ -0,0 +1,88 @@
+{
+ config,
+ lib,
+ ...
+}: let
+ cfg = config.services.iknowyou;
+in {
+ options.services.iknowyou = {
+ enable = lib.mkEnableOption "Iknowyou OSINT aggregation platform";
+
+ port = lib.mkOption {
+ type = lib.types.port;
+ default = 8080;
+ description = "TCP port the IKY server listens on.";
+ };
+
+ configFile = lib.mkOption {
+ type = lib.types.path;
+ default = "/etc/iky/config.yaml";
+ description = "Path to the IKY YAML configuration file (optional, server starts with empty config if absent).";
+ };
+
+ package = lib.mkOption {
+ type = lib.types.package;
+ description = "The IKY package (must expose bin/server and share/iky/frontend/).";
+ };
+
+ openFirewall = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = "Open the firewall for the IKY port.";
+ };
+
+ searchTTL = lib.mkOption {
+ type = lib.types.str;
+ default = "48h";
+ description = "How long a completed or cancelled search is kept in memory (Go duration string, e.g. \"24h\", \"168h\").";
+ };
+
+ cleanupInterval = lib.mkOption {
+ type = lib.types.str;
+ default = "1h";
+ description = "How often the search cleanup goroutine runs (Go duration string, e.g. \"30m\", \"2h\").";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ networking.firewall.allowedTCPPorts =
+ lib.mkIf cfg.openFirewall [cfg.port];
+
+ users.users.iknowyou = {
+ isSystemUser = true;
+ group = "iknowyou";
+ description = "Iknowyou service user";
+ };
+ users.groups.iknowyou = {};
+
+ systemd.tmpfiles.rules = [
+ "d /etc/iky 0700 iknowyou iknowyou -"
+ ];
+
+ systemd.services.iknowyou = {
+ description = "Iknowyou OSINT platform";
+ wantedBy = ["multi-user.target"];
+ after = ["network.target"];
+
+ path = [cfg.package];
+
+ environment = {
+ IKY_PORT = toString cfg.port;
+ IKY_CONFIG = cfg.configFile;
+ IKY_FRONT_DIR = "${cfg.package}/share/iky/frontend";
+ IKY_SEARCH_TTL = cfg.searchTTL;
+ IKY_CLEANUP_INTERVAL = cfg.cleanupInterval;
+ };
+
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/server";
+ Restart = "on-failure";
+ RestartSec = "5s";
+ User = "iknowyou";
+ Group = "iknowyou";
+ StateDirectory = "iky";
+ WorkingDirectory = "%S/iky";
+ };
+ };
+ };
+}