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 @@ +
+ +logo + +# 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. + +[![Go](https://img.shields.io/badge/Go-1.25+-00ADD8?style=flat&logo=go)](https://go.dev) +[![Astro](https://img.shields.io/badge/Astro-Svelte-FF5D01?style=flat&logo=astro)](https://astro.build) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) +[![Self-hosted](https://img.shields.io/badge/deployment-self--hosted-blueviolet)](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. + +--- + +
+ github | + gitlab (mirror) | + gitea (mirror) +
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} + + {/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

+ +
+ + {#if loadError} +
{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 @@ + + +
+ +
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} +
{error}
+{:else} +
+ +
+
+ Profiles + {#if !configReadonly} + + {/if} +
+ + {#if showNewProfile && !configReadonly} +
+ e.key === "Enter" && createProfile()} + /> + {#if newProfileError} +

{newProfileError}

+ {/if} + +
+ {/if} + + {#each profiles as p} +
+ + {#if !p.readonly} + + {/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} + + + {/each} + !disabledEdit.includes(n))} + placeholder="add tool" + size="xs" + onselect={(val) => (disabledEdit = [...disabledEdit, val])} + /> + {/if} +
+
+ + {#if !isReadonly} + + {/if} +
+
+ + {#if !isReadonly} +
+
+
+

Tool overrides

+ {#if availableForOverride.length > 0} + + + {overrideEdits[toolName][field.name] ? "enabled" : "disabled"} + + + {:else if field.type === "int"} + + {:else if field.type === "float"} + + {:else if field.type === "enum"} + + {:else} + + {/if} +
+ {/each} +
+ + {: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} +
{error}
+ {/if} + + {#if showRevert} +
+ Switched to {inputType} + +
+ {/if} + + +
+
+ e.key === "Enter" && submit()} + disabled={demo} + /> +
+
+
+ type + { profile = v; }} + /> +
+ +
+
+ + + + {/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)} +
+
+
+ +
+ +
+
+ {tool.name} + {#if tool.unavailable} + + unavailable + + {:else if missing} + + config required + + {/if} +
+ {#if tool.unavailable && tool.unavailable_reason} +

+ {tool.unavailable_reason} +

+ {:else if tool.description} +

+ {tool.description} +

+ {/if} +
+ {#each tool.input_types as t} + {t} + {/each} +
+
+
+
+{/snippet} + +{#if loading} +
+ +
+{:else if error} +
{error}
+{:else} +
+
+ Profile + (selectedInputType = val)} + /> +
+
+ + {#if tools.length === 0} +

+ No tools registered. +

+ {:else} +
+ {#if active.length > 0} +
+
+ + Active + {active.length} +
+
+ {#each active as tool} + + {@render toolCard(tool, false)} + + {/each} +
+
+ {/if} + + {#if activeMissing.length > 0} +
+
+ + Active - required config missing + {activeMissing.length} +
+
+ {#each activeMissing as tool} + + {@render toolCard(tool, true)} + + {/each} +
+
+ {/if} + + {#if activeUnavail.length > 0} +
+
+ + Active - unavailable + {activeUnavail.length} +
+
+ {#each activeUnavail as tool} + + {@render toolCard(tool, false)} + + {/each} +
+
+ {/if} + + {#if active.length + activeMissing.length + activeUnavail.length > 0 && inactive.length + inactiveMissing.length + inactiveUnavail.length > 0} +
+ {/if} + + {#if inactive.length > 0} +
+
+ + Disabled + {inactive.length} +
+
+ {#each inactive as tool} + + {@render toolCard(tool, false)} + + {/each} +
+
+ {/if} + + {#if inactiveMissing.length > 0} +
+
+ + Disabled - required config missing + {inactiveMissing.length} +
+
+ {#each inactiveMissing as tool} + + {@render toolCard(tool, true)} + + {/each} +
+
+ {/if} + + {#if inactiveUnavail.length > 0} +
+
+ + Disabled - unavailable + {inactiveUnavail.length} +
+
+ {#each inactiveUnavail as tool} + + {@render toolCard(tool, false)} + + {/each} +
+
+ {/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 = ""; + } +}} /> + +
+ + + {#if open} +
+
+ { if (e.key === "Escape") { open = false; query = ""; } }} + /> +
+
    + {#if filtered.length === 0} +
  • No results
  • + {:else} + {#each filtered as 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} + {iconName { + const target = e.currentTarget as HTMLImageElement; + target.src = genericFallbackUrl; + }} + /> +{:else} + {"Tool +{/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"; +--- + + +
+
+ +

500

+
+ +
+

System Failure

+

+ The server encountered an unexpected error. +

+
+ +
+ + Go Home +
+
+
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 && ( + + )} +
+ +
+
+ + + + 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, + })); +--- + + +
+ +
+ ← Back +
+ +
+

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"; +--- + + +
+ +
+ ← Back +
+ +
+

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: +

+
    +
  1. + Global config: set on the + Tools page. Applied to every + search regardless of profile. +
  2. +
  3. + 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). +
  4. +
+

+ Config is stored in config.yaml. + Built-in profiles are hardcoded in Go and are never written to disk. +

+
+ +
+ +
+

+ + How a search runs +

+
    +
  1. You enter a target, select its type (email, username...) and pick a profile.
  2. +
  3. + The backend filters tools by input type and the profile's enabled/disabled rules, + then skips any tool with a missing required config field. +
  4. +
  5. All eligible tools run in parallel against the target.
  6. +
  7. + The frontend polls for results and renders them progressively as each tool finishes. +
  8. +
+

+ 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"; +--- + +
+
+ iknowyou +

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"; +--- + + +
+
+ ← Back +
+ +
+

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; +--- + +
+ +
+ ← Back +
+ + + +
+
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; +--- + +
+ +
+ ← Tools +
+ + + +
+
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"; +--- + +
+ +
+ ← Back +
+ +
+

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"; + }; + }; + }; +}