Commit e0b7ced
CLI.md
@@ -0,0 +1,457 @@
+# JA4 CLI Tool
+
+A TypeScript-based command-line interface for analyzing JA4+ network fingerprints with comprehensive threat intelligence integration.
+
+## Features
+
+- **Multi-format Support**: Analyze JA4, JA4S, JA4H, JA4X, and JA4T fingerprints
+- **Database Integration**: Automatic lookup against the JA4+ database
+- **Comparison Tools**: Compare fingerprints to identify similarities and differences
+- **Batch Processing**: Process multiple fingerprints from files
+- **Search Capabilities**: Search by application name or operating system
+- **Pretty Output**: Beautiful colored terminal output with progress indicators
+- **JSON Export**: Machine-readable output for automation
+
+## Installation
+
+### Prerequisites
+
+- Node.js >= 20.0.0
+- npm or yarn
+
+### Installation
+
+To install the CLI globally, run:
+
+```bash
+npm install -g
+```
+
+Alternatively, you can use the CLI directly without installation:
+
+```bash
+npx ja4-mcp-server cli [command]
+```
+
+### Install Dependencies
+
+```bash
+npm install
+# or
+yarn install
+```
+
+### Make CLI Executable
+
+```bash
+chmod +x cli.ts
+```
+
+## Usage
+
+### Basic Analysis
+
+Analyze a single JA4 fingerprint:
+
+```bash
+npm run cli analyze "t13d1516h2_8daaf6152771_b0da82dd1658"
+```
+
+Analyze different fingerprint types:
+
+```bash
+# JA4S (Server)
+npm run cli analyze -t ja4s "t13d15h2_002f_0035"
+
+# JA4H (HTTP)
+npm run cli analyze -t ja4h "ge11nn05_7a9c_b4a9_ad9e"
+
+# JA4T (TCP)
+npm run cli analyze -t ja4t "1460_020405b4_1460_8"
+```
+
+### Verbose Analysis
+
+Get detailed breakdown of all components:
+
+```bash
+npm run cli analyze -v "t13d1516h2_8daaf6152771_b0da82dd1658"
+```
+
+### Skip Database Lookup
+
+For faster analysis without database queries:
+
+```bash
+ja4 analyze --no-db "t13d1516h2_8daaf6152771_b0da82dd1658"
+```
+
+### JSON Output
+
+Get machine-readable output:
+
+```bash
+ja4 analyze -j "t13d1516h2_8daaf6152771_b0da82dd1658"
+```
+
+### Compare Fingerprints
+
+Compare two fingerprints to identify differences:
+
+```bash
+ja4 compare "t13d1516h2_8daaf6152771_b0da82dd1658" "t13d1517h2_8daaf6152771_7128f82b508a"
+```
+
+```bash
+ja4 compare "t13d1516h2_8daaf6152771_b0da82dd1658" "t13d1516h2_8daaf6152772_b0da82dd1659"
+```
+
+JSON comparison output:
+
+```bash
+ja4 compare -j "fp1" "fp2"
+```
+
+### Search Database
+
+Search by application name:
+
+```bash
+ja4 search "Chrome"
+ja4 search "Firefox"
+ja4 search -l 100 "Safari" # Limit to 100 results
+```
+
+Search by operating system:
+
+```bash
+ja4 search -t os "Windows"
+ja4 search -t os "Linux"
+```
+
+### Database Statistics
+
+View database statistics:
+
+```bash
+ja4 stats
+```
+
+JSON output:
+
+```bash
+npm run cli stats -j
+```
+
+### Batch Processing
+
+Process multiple fingerprints from a file:
+
+Create a file `fingerprints.txt`:
+```
+t13d1516h2_8daaf6152771_b0da82dd1658
+t13d1715h2_9daaf6152771_c1da82dd1659
+t12d1314h1_5daaf6152771_a2da82dd1660
+```
+
+Process the batch:
+
+```bash
+ja4 batch fingerprints.txt
+```
+
+
+Process with options:
+
+```bash
+# Different fingerprint type
+ja4 batch -t ja4s server_fingerprints.txt
+
+# Save to JSON file
+ja4 batch -o results.json fingerprints.txt
+
+# Skip database lookups for speed
+ja4 batch --no-db fingerprints.txt
+```
+
+## Command Reference
+
+### `analyze`
+
+Analyze a single JA4+ fingerprint.
+
+**Usage:** `analyze <fingerprint> [options]`
+
+**Options:**
+- `-t, --type <type>` - Fingerprint type (ja4, ja4s, ja4h, ja4x, ja4t) [default: ja4]
+- `--no-db` - Skip database lookup
+- `-j, --json` - Output as JSON
+- `-v, --verbose` - Verbose output with detailed breakdown
+
+**Examples:**
+```bash
+npm run cli analyze "t13d1516h2_8daaf6152771_b0da82dd1658"
+npm run cli analyze -t ja4s -v "t13d15h2_002f_0035"
+npm run cli analyze -j --no-db "ge11nn05_7a9c_b4a9_ad9e"
+```
+
+### `compare`
+
+Compare two JA4+ fingerprints.
+
+**Usage:** `compare <fingerprint1> <fingerprint2> [options]`
+
+**Options:**
+- `-j, --json` - Output as JSON
+
+**Examples:**
+```bash
+npm run cli compare "fp1" "fp2"
+npm run cli compare -j "fp1" "fp2"
+```
+
+### `search`
+
+Search the JA4+ database.
+
+**Usage:** `search <query> [options]`
+
+**Options:**
+- `-t, --type <type>` - Search type (app or os) [default: app]
+- `-l, --limit <number>` - Limit results [default: 50]
+- `-j, --json` - Output as JSON
+
+**Examples:**
+```bash
+npm run cli search "Chrome"
+npm run cli search -t os "Windows"
+npm run cli search -l 100 -j "Firefox"
+```
+
+### `stats`
+
+Show database statistics.
+
+**Usage:** `stats [options]`
+
+**Options:**
+- `-j, --json` - Output as JSON
+
+**Examples:**
+```bash
+npm run cli stats
+npm run cli stats -j
+```
+
+### `batch`
+
+Process multiple fingerprints from a file.
+
+**Usage:** `batch <file> [options]`
+
+**Options:**
+- `-t, --type <type>` - Fingerprint type [default: ja4]
+- `-o, --output <file>` - Output file (JSON format)
+- `--no-db` - Skip database lookups
+
+**Examples:**
+```bash
+npm run cli batch fingerprints.txt
+npm run cli batch -t ja4s -o results.json server_fps.txt
+npm run cli batch --no-db large_dataset.txt
+```
+
+## Output Examples
+
+### Analysis Output (Pretty)
+
+```
+๐ JA4 Analysis
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+Fingerprint: t13d1516h2_8daaf6152771_b0da82dd1658
+Format: JA4
+Summary: TCP TLS 1.3 connection with SNI present, using HTTP/2
+
+๐ Breakdown:
+ PART_A: t13d1516h2
+ PART_B: 8daaf6152771
+ PART_C: b0da82dd1658
+
+๐ก Use Cases:
+ โข TLS client fingerprinting
+ โข Bot detection
+ โข Security analysis
+
+๐๏ธ Database Results (3 found):
+ 1. Google Chrome
+ OS: Windows 10
+ โ Verified
+ 2. Chromium Browser
+ OS: Linux
+ 3. Edge Browser
+ OS: Windows 11
+ โ Verified
+```
+
+### Comparison Output (Pretty)
+
+```
+๐ Fingerprint Comparison
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+โ ๏ธ Fingerprints differ
+
+Analysis: Fingerprints are similar (67% match) - likely same client type with minor differences
+
+โ
Similarities:
+ Part A: t13d1516h2
+ Part B: 8daaf6152771
+
+โ Differences:
+ Part C:
+ FP1: b0da82dd1658
+ FP2: b0da82dd1659
+ Note: Different cryptographic hash values indicating different cipher/extension sets
+```
+
+### JSON Output
+
+```json
+{
+ "fingerprint": "t13d1516h2_8daaf6152771_b0da82dd1658",
+ "type": "JA4",
+ "analysis": {
+ "format": "JA4",
+ "breakdown": {
+ "part_a": {
+ "raw": "t13d1516h2",
+ "protocol": "TCP",
+ "tls_version": "TLS 1.3",
+ "sni_presence": "SNI Present",
+ "cipher_count": 21,
+ "extension_count": 22,
+ "alpn": "HTTP/2"
+ }
+ },
+ "human_readable": "TCP TLS 1.3 connection with SNI present, using HTTP/2"
+ },
+ "database": {
+ "count": 3,
+ "results": [
+ {
+ "ja4": "t13d1516h2_8daaf6152771_b0da82dd1658",
+ "application": "Google Chrome",
+ "os": "Windows 10",
+ "verified": true
+ }
+ ]
+ }
+}
+```
+
+## Development
+
+### Build the CLI
+
+```bash
+npm run build:cli
+```
+
+### Run in Development Mode
+
+```bash
+npm run cli -- analyze "fingerprint"
+```
+
+### Add New Commands
+
+1. Import the `Command` class from commander
+2. Add your command definition
+3. Implement the action handler
+4. Update this README
+
+### Error Handling
+
+The CLI includes comprehensive error handling:
+
+- Invalid fingerprint formats
+- Network connectivity issues
+- File I/O problems
+- Malformed data
+
+All errors are displayed with colored output and appropriate exit codes.
+
+## Integration
+
+### Use with Scripts
+
+```bash
+# Check exit code
+npm run cli analyze "fingerprint" && echo "Success" || echo "Failed"
+
+# Pipe JSON output
+npm run cli analyze -j "fingerprint" | jq '.analysis.human_readable'
+
+# Batch processing with custom logic
+while read -r fp; do
+ npm run cli analyze -j "$fp" >> results.jsonl
+done < fingerprints.txt
+```
+
+### CI/CD Integration
+
+```yaml
+# GitHub Actions example
+- name: Analyze fingerprints
+ run: |
+ npm install
+ npm run cli batch --no-db -o analysis.json fingerprints.txt
+
+- name: Upload results
+ uses: actions/upload-artifact@v2
+ with:
+ name: ja4-analysis
+ path: analysis.json
+```
+
+## Performance Tips
+
+1. Use `--no-db` flag for faster analysis when database lookup isn't needed
+2. Process large batches in chunks to avoid memory issues
+3. Use JSON output for programmatic processing
+4. Cache database locally (happens automatically)
+
+## Troubleshooting
+
+### Database Download Issues
+
+If database download fails:
+
+```bash
+# Check network connectivity
+curl -I https://ja4db.com/api/download/ja4plus_db.json
+
+# Clear cache and retry
+rm -rf ~/.cache/ja4-cli
+npm run cli stats
+```
+
+### TypeScript Errors
+
+Ensure you have the correct Node.js version:
+
+```bash
+node --version # Should be >= 20.0.0
+npm install # Reinstall dependencies
+```
+
+### Performance Issues
+
+For large datasets:
+
+```bash
+# Use smaller batch sizes
+split -l 1000 large_dataset.txt batch_
+for file in batch_*; do
+ npm run cli batch "$file" -o "results_$file.json"
+done
+```
cli.ts
@@ -0,0 +1,1016 @@
+#!/usr/bin/env tsx
+import { Command } from "commander";
+import chalk from "chalk";
+import ora from "ora";
+import { readFileSync, existsSync, createWriteStream, statSync } from "fs";
+import { promises as fs } from "fs";
+import path from "path";
+import os from "os";
+import https from "https";
+
+// Types
+interface JA4Analysis {
+ format: string;
+ breakdown: {
+ part_a: {
+ raw: string;
+ protocol?: string;
+ tls_version?: string;
+ sni_presence?: string;
+ cipher_count?: number;
+ extension_count?: number;
+ alpn?: string;
+ method?: string;
+ version?: string;
+ has_cookies?: boolean;
+ has_referer?: boolean;
+ header_count?: number;
+ language?: string;
+ description?: string;
+ note?: string;
+ value?: string | number;
+ };
+ part_b: {
+ raw: string;
+ description: string;
+ note: string;
+ };
+ part_c: {
+ raw: string;
+ description: string;
+ note: string;
+ };
+ part_d?: {
+ raw: string;
+ description: string;
+ note: string;
+ };
+ };
+ human_readable?: string;
+ use_cases?: string[];
+ analysis?: {
+ actual_window_size?: number;
+ network_overhead?: string;
+ os_indicators?: string[];
+ tunnel_vpn_indicators?: string[];
+ };
+}
+
+interface DatabaseResult {
+ ja4?: string;
+ ja4s?: string;
+ ja4h?: string;
+ ja4x?: string;
+ ja4t?: string;
+ application?: string;
+ os?: string;
+ verified?: boolean;
+ observation_count?: number;
+}
+
+interface SearchResult {
+ count: number;
+ results: DatabaseResult[];
+}
+
+interface ComparisonResult {
+ fingerprint_1: string;
+ fingerprint_2: string;
+ identical: boolean;
+ similarities: Array<{ part: string; value: string; note: string }>;
+ differences: Array<{ part: string; fp1: string; fp2: string; note: string }>;
+ analysis: string;
+}
+
+interface DatabaseStats {
+ status: string;
+ total_records: number;
+ ja4_count: number;
+ ja4s_count: number;
+ ja4h_count: number;
+ ja4x_count: number;
+ ja4t_count: number;
+ verified_count: number;
+ applications: number;
+ operating_systems: number;
+ last_update: string;
+}
+
+// Database URL
+const DB_URL = "https://ja4db.com/api/download/";
+
+// Get XDG cache directory
+function getXDGCacheDir(): string {
+ if (process.platform === "win32") {
+ return process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
+ }
+
+ const homeDir = os.homedir();
+ return process.env.XDG_CACHE_HOME || path.join(homeDir, ".cache");
+}
+
+const CACHE_DIR = path.join(getXDGCacheDir(), "ja4-cli");
+const DB_FILE = path.join(CACHE_DIR, "ja4plus_db.json");
+const CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours
+
+class JA4Database {
+ private database: any = null;
+
+ async ensureCacheDir(): Promise<void> {
+ try {
+ await fs.mkdir(CACHE_DIR, { recursive: true });
+ } catch (error) {
+ // Directory might already exist
+ }
+ }
+
+ async downloadDatabase(): Promise<void> {
+ await this.ensureCacheDir();
+
+ return new Promise((resolve, reject) => {
+ const file = createWriteStream(DB_FILE);
+
+ https
+ .get(DB_URL, (response) => {
+ if (response.statusCode !== 200) {
+ reject(new Error(`Failed to download database: ${response.statusCode}`));
+ return;
+ }
+
+ response.pipe(file);
+ file.on("finish", () => {
+ file.close();
+ resolve();
+ });
+ })
+ .on("error", reject);
+ });
+ }
+
+ async loadDatabase(): Promise<void> {
+ try {
+ const stats = await fs.stat(DB_FILE);
+ const age = Date.now() - stats.mtime.getTime();
+
+ if (age > CACHE_DURATION) {
+ await this.downloadDatabase();
+ }
+
+ const content = await fs.readFile(DB_FILE, "utf8");
+ this.database = JSON.parse(content);
+ } catch (error) {
+ await this.downloadDatabase();
+ const content = await fs.readFile(DB_FILE, "utf8");
+ this.database = JSON.parse(content);
+ }
+ }
+
+ lookupJA4(fingerprint: string): DatabaseResult[] {
+ if (!this.database) return [];
+ return this.database.filter((entry: any) => entry.ja4 === fingerprint);
+ }
+
+ lookupJA4S(fingerprint: string): DatabaseResult[] {
+ if (!this.database) return [];
+ return this.database.filter((entry: any) => entry.ja4s === fingerprint);
+ }
+
+ lookupJA4H(fingerprint: string): DatabaseResult[] {
+ if (!this.database) return [];
+ return this.database.filter((entry: any) => entry.ja4h === fingerprint);
+ }
+
+ lookupJA4X(fingerprint: string): DatabaseResult[] {
+ if (!this.database) return [];
+ return this.database.filter((entry: any) => entry.ja4x === fingerprint);
+ }
+
+ lookupJA4T(fingerprint: string): DatabaseResult[] {
+ if (!this.database) return [];
+ return this.database.filter((entry: any) => entry.ja4t === fingerprint);
+ }
+
+ searchByApplication(query: string, limit: number = 50): SearchResult {
+ if (!this.database) return { count: 0, results: [] };
+
+ const searchTerm = query.toLowerCase();
+ const results = this.database
+ .filter((entry: any) => entry.application && entry.application.toLowerCase().includes(searchTerm))
+ .slice(0, limit);
+
+ return {
+ count: results.length,
+ results,
+ };
+ }
+
+ searchByOS(query: string, limit: number = 50): SearchResult {
+ if (!this.database) return { count: 0, results: [] };
+
+ const searchTerm = query.toLowerCase();
+ const results = this.database
+ .filter((entry: any) => entry.os && entry.os.toLowerCase().includes(searchTerm))
+ .slice(0, limit);
+
+ return {
+ count: results.length,
+ results,
+ };
+ }
+
+ getStatistics(): DatabaseStats {
+ if (!this.database) {
+ return {
+ status: "No database loaded",
+ total_records: 0,
+ ja4_count: 0,
+ ja4s_count: 0,
+ ja4h_count: 0,
+ ja4x_count: 0,
+ ja4t_count: 0,
+ verified_count: 0,
+ applications: 0,
+ operating_systems: 0,
+ last_update: "Unknown",
+ };
+ }
+
+ const total_records = this.database.length;
+ const ja4_count = this.database.filter((entry: any) => entry.ja4).length;
+ const ja4s_count = this.database.filter((entry: any) => entry.ja4s).length;
+ const ja4h_count = this.database.filter((entry: any) => entry.ja4h).length;
+ const ja4x_count = this.database.filter((entry: any) => entry.ja4x).length;
+ const ja4t_count = this.database.filter((entry: any) => entry.ja4t).length;
+ const verified_count = this.database.filter((entry: any) => entry.verified).length;
+ const applications = new Set(this.database.map((entry: any) => entry.application).filter(Boolean)).size;
+ const operating_systems = new Set(this.database.map((entry: any) => entry.os).filter(Boolean)).size;
+
+ let last_update = "Unknown";
+ try {
+ const stats = statSync(DB_FILE);
+ last_update = stats.mtime.toISOString();
+ } catch (error) {
+ // Ignore error
+ }
+
+ return {
+ status: "Database loaded successfully",
+ total_records,
+ ja4_count,
+ ja4s_count,
+ ja4h_count,
+ ja4x_count,
+ ja4t_count,
+ verified_count,
+ applications,
+ operating_systems,
+ last_update,
+ };
+ }
+}
+
+class JA4Analyzer {
+ static parseJA4(fingerprint: string): JA4Analysis {
+ const parts = fingerprint.split("_");
+ if (parts.length !== 3) {
+ throw new Error("Invalid JA4 fingerprint format");
+ }
+
+ const [partA, partB, partC] = parts;
+
+ const protocol = partA.charAt(0);
+ const version = partA.substring(1, 3);
+ const sni = partA.charAt(3);
+ const cipherCount = parseInt(partA.substring(4, 6));
+ const extensionCount = parseInt(partA.substring(6, 8));
+ const alpn = partA.substring(8);
+
+ return {
+ format: "JA4",
+ breakdown: {
+ part_a: {
+ raw: partA,
+ protocol: this.explainProtocol(protocol).description,
+ tls_version: this.explainTLSVersion(version).description,
+ sni_presence: this.explainSNI(sni).description,
+ cipher_count: cipherCount,
+ extension_count: extensionCount,
+ alpn: this.explainALPN(alpn).description,
+ },
+ part_b: {
+ raw: partB,
+ description: "SHA256 hash of cipher suites in order",
+ note: "First 12 characters of hash",
+ },
+ part_c: {
+ raw: partC,
+ description: "SHA256 hash of extensions in order",
+ note: "First 12 characters of hash",
+ },
+ },
+ human_readable: this.generateHumanReadable(protocol, version, sni, alpn),
+ };
+ }
+
+ static parseJA4S(fingerprint: string): JA4Analysis {
+ const parts = fingerprint.split("_");
+ if (parts.length !== 3) {
+ throw new Error("Invalid JA4S fingerprint format");
+ }
+
+ const [partA, partB, partC] = parts;
+
+ const protocol = partA.charAt(0);
+ const version = partA.substring(1, 3);
+ const cipherCount = parseInt(partA.substring(3, 5));
+ const extensionCount = parseInt(partA.substring(5, 7));
+ const alpn = partA.substring(7);
+
+ return {
+ format: "JA4S",
+ breakdown: {
+ part_a: {
+ raw: partA,
+ protocol: this.explainProtocol(protocol).description,
+ tls_version: this.explainTLSVersion(version).description,
+ cipher_count: cipherCount,
+ extension_count: extensionCount,
+ alpn: this.explainALPN(alpn).description,
+ },
+ part_b: {
+ raw: partB,
+ description: "SHA256 hash of cipher suite",
+ note: "First 12 characters of hash",
+ },
+ part_c: {
+ raw: partC,
+ description: "SHA256 hash of extensions",
+ note: "First 12 characters of hash",
+ },
+ },
+ human_readable: `JA4S Server: ${this.explainProtocol(protocol).description} ${this.explainTLSVersion(version).description}`,
+ };
+ }
+
+ static parseJA4H(fingerprint: string): JA4Analysis {
+ const parts = fingerprint.split("_");
+ if (parts.length !== 4) {
+ throw new Error("Invalid JA4H fingerprint format");
+ }
+
+ const [partA, partB, partC, partD] = parts;
+
+ const method = partA.substring(0, 2);
+ const version = partA.substring(2, 4);
+ const cookiePresence = partA.charAt(4);
+ const refererPresence = partA.charAt(5);
+ const headerCount = parseInt(partA.substring(6, 8));
+ const languageHeader = partA.substring(8);
+
+ return {
+ format: "JA4H",
+ breakdown: {
+ part_a: {
+ raw: partA,
+ method: this.explainHTTPMethod(method).method,
+ version: this.explainHTTPVersion(version).description,
+ has_cookies: cookiePresence === "c",
+ has_referer: refererPresence === "r",
+ header_count: headerCount,
+ language: languageHeader,
+ },
+ part_b: {
+ raw: partB,
+ description: "SHA256 hash of header names",
+ note: "First 12 characters of hash",
+ },
+ part_c: {
+ raw: partC,
+ description: "SHA256 hash of header values",
+ note: "First 12 characters of hash",
+ },
+ part_d: {
+ raw: partD,
+ description: "SHA256 hash of cookies",
+ note: "First 12 characters of hash",
+ },
+ },
+ use_cases: ["HTTP client fingerprinting", "Bot detection", "User-Agent validation", "Header analysis"],
+ };
+ }
+
+ static parseJA4X(fingerprint: string): JA4Analysis {
+ const parts = fingerprint.split("_");
+ if (parts.length !== 3) {
+ throw new Error("Invalid JA4X fingerprint format");
+ }
+
+ return {
+ format: "JA4X",
+ breakdown: {
+ part_a: {
+ raw: parts[0],
+ description: "X.509 certificate details",
+ note: "Certificate chain information",
+ },
+ part_b: {
+ raw: parts[1],
+ description: "Certificate algorithms",
+ note: "Signature and key algorithms",
+ },
+ part_c: {
+ raw: parts[2],
+ description: "Certificate extensions",
+ note: "Extension fields and values",
+ },
+ },
+ use_cases: ["Certificate fingerprinting", "TLS server identification", "Certificate chain analysis"],
+ };
+ }
+
+ static parseJA4T(fingerprint: string): JA4Analysis {
+ const parts = fingerprint.split("_");
+ if (parts.length !== 4) {
+ throw new Error("Invalid JA4T fingerprint format");
+ }
+
+ const [windowSize, tcpOptions, mss, windowScale] = parts;
+
+ return {
+ format: "JA4T",
+ breakdown: {
+ part_a: {
+ raw: windowSize,
+ description: "TCP window size",
+ note: "Initial window size from SYN packet",
+ },
+ part_b: {
+ raw: tcpOptions,
+ description: "TCP options",
+ note: "Options from TCP header",
+ },
+ part_c: {
+ raw: mss,
+ description: "Maximum Segment Size",
+ note: "MSS value from TCP options",
+ },
+ part_d: {
+ raw: windowScale,
+ description: "Window scale factor",
+ note: "Window scaling option value",
+ },
+ },
+ analysis: {
+ actual_window_size: parseInt(windowSize) * Math.pow(2, parseInt(windowScale) || 0),
+ network_overhead: this.analyzeNetworkOverhead(parseInt(mss)),
+ os_indicators: this.analyzeTCPOSIndicators(tcpOptions),
+ tunnel_vpn_indicators: this.analyzeTunnelIndicators(parseInt(mss)),
+ },
+ use_cases: [
+ "Operating system fingerprinting",
+ "Network stack identification",
+ "VPN/Tunnel detection",
+ "Network performance analysis",
+ ],
+ };
+ }
+
+ static compareFingerprints(fp1: string, fp2: string): ComparisonResult {
+ const parts1 = fp1.split("_");
+ const parts2 = fp2.split("_");
+
+ if (parts1.length !== parts2.length) {
+ throw new Error("Fingerprints must be of the same type");
+ }
+
+ const similarities: Array<{ part: string; value: string; note: string }> = [];
+ const differences: Array<{ part: string; fp1: string; fp2: string; note: string }> = [];
+
+ const partNames = ["A", "B", "C", "D"];
+
+ for (let i = 0; i < parts1.length; i++) {
+ const letter = partNames[i];
+
+ if (parts1[i] === parts2[i]) {
+ similarities.push({
+ part: `Part ${letter}`,
+ value: parts1[i],
+ note: "Identical values",
+ });
+ } else {
+ differences.push({
+ part: `Part ${letter}`,
+ fp1: parts1[i],
+ fp2: parts2[i],
+ note: this.getDifferenceNote(letter, parts1[i], parts2[i]),
+ });
+ }
+ }
+
+ const identical = differences.length === 0;
+ const analysis = this.generateComparisonAnalysis(similarities, differences);
+
+ return {
+ fingerprint_1: fp1,
+ fingerprint_2: fp2,
+ identical,
+ similarities,
+ differences,
+ analysis,
+ };
+ }
+
+ private static explainProtocol(code: string): { description: string; full_name: string } {
+ const protocols: Record<string, { description: string; full_name: string }> = {
+ t: { description: "TCP", full_name: "Transmission Control Protocol" },
+ q: { description: "QUIC", full_name: "Quick UDP Internet Connections" },
+ d: { description: "DTLS", full_name: "Datagram Transport Layer Security" },
+ };
+
+ return protocols[code] || { description: "Unknown", full_name: "Unknown Protocol" };
+ }
+
+ private static explainTLSVersion(version: string): { description: string; security: string } {
+ const versions: Record<string, { description: string; security: string }> = {
+ "10": { description: "TLS 1.0", security: "Deprecated - Not Secure" },
+ "11": { description: "TLS 1.1", security: "Deprecated - Not Secure" },
+ "12": { description: "TLS 1.2", security: "Secure" },
+ "13": { description: "TLS 1.3", security: "Most Secure" },
+ };
+
+ return versions[version] || { description: "Unknown", security: "Unknown" };
+ }
+
+ private static explainSNI(code: string): { description: string; note: string } {
+ const sni: Record<string, { description: string; note: string }> = {
+ d: { description: "SNI Present", note: "Server Name Indication extension present" },
+ i: {
+ description: "SNI Not Present",
+ note: "Server Name Indication extension not present or IP address used",
+ },
+ };
+
+ return sni[code] || { description: "Unknown", note: "Unknown SNI status" };
+ }
+
+ private static explainALPN(code: string): { description: string; protocol: string } {
+ const alpns: Record<string, { description: string; protocol: string }> = {
+ h1: { description: "HTTP/1.1", protocol: "HTTP/1.1" },
+ h2: { description: "HTTP/2", protocol: "HTTP/2" },
+ h3: { description: "HTTP/3", protocol: "HTTP/3" },
+ dt: { description: "DoT", protocol: "DNS over TLS" },
+ dq: { description: "DoQ", protocol: "DNS over QUIC" },
+ };
+
+ return alpns[code] || { description: "None or Other", protocol: "Unknown" };
+ }
+
+ private static explainHTTPMethod(code: string): { method: string; description: string } {
+ const methods: Record<string, { method: string; description: string }> = {
+ ge: { method: "GET", description: "Retrieve data from server" },
+ po: { method: "POST", description: "Send data to server" },
+ he: { method: "HEAD", description: "Get headers only" },
+ pu: { method: "PUT", description: "Update/replace resource" },
+ de: { method: "DELETE", description: "Remove resource" },
+ op: { method: "OPTIONS", description: "Get allowed methods" },
+ pa: { method: "PATCH", description: "Partial update" },
+ };
+
+ return methods[code] || { method: "Unknown", description: "Unknown HTTP method" };
+ }
+
+ private static explainHTTPVersion(version: string): { description: string } {
+ const versions: Record<string, { description: string }> = {
+ "10": { description: "HTTP/1.0" },
+ "11": { description: "HTTP/1.1" },
+ "20": { description: "HTTP/2" },
+ "30": { description: "HTTP/3" },
+ };
+
+ return versions[version] || { description: "Unknown HTTP version" };
+ }
+
+ private static generateHumanReadable(protocol: string, version: string, sni: string, alpn: string): string {
+ const proto = this.explainProtocol(protocol);
+ const ver = this.explainTLSVersion(version);
+ const sniInfo = this.explainSNI(sni);
+ const alpnInfo = this.explainALPN(alpn);
+
+ return `${proto.description} ${ver.description} connection with ${sniInfo.description.toLowerCase()}, using ${alpnInfo.description}`;
+ }
+
+ private static getDifferenceNote(part: string, value1: string, value2: string): string {
+ if (part === "A") {
+ return "Different TLS configuration parameters";
+ } else {
+ return "Different cryptographic hash values indicating different cipher/extension sets";
+ }
+ }
+
+ private static generateComparisonAnalysis(similarities: any[], differences: any[]): string {
+ const simCount = similarities.length;
+ const diffCount = differences.length;
+ const total = simCount + diffCount;
+
+ if (diffCount === 0) {
+ return "Fingerprints are identical - same client configuration";
+ } else if (simCount > diffCount) {
+ return `Fingerprints are similar (${Math.round((simCount / total) * 100)}% match) - likely same client type with minor differences`;
+ } else {
+ return `Fingerprints are different (${Math.round((simCount / total) * 100)}% match) - likely different client types or major configuration differences`;
+ }
+ }
+
+ private static analyzeNetworkOverhead(mss: number): string {
+ if (mss <= 536) return "High overhead (dialup/satellite)";
+ if (mss <= 1460) return "Standard Ethernet";
+ if (mss <= 8960) return "Jumbo frames";
+ return "Custom MTU";
+ }
+
+ private static analyzeTCPOSIndicators(options: string): string[] {
+ // Simplified OS detection based on TCP options
+ const indicators = [];
+ if (options.includes("020405b4")) indicators.push("Linux");
+ if (options.includes("020405dc")) indicators.push("Windows");
+ if (options.includes("02040578")) indicators.push("macOS");
+ return indicators.length ? indicators : ["Unknown"];
+ }
+
+ private static analyzeTunnelIndicators(mss: number): string[] {
+ const indicators = [];
+ if (mss < 1460) indicators.push("Possible VPN/Tunnel");
+ if (mss === 1436) indicators.push("Common VPN MSS");
+ return indicators;
+ }
+}
+
+// CLI Implementation
+const program = new Command();
+const db = new JA4Database();
+
+program.name("ja4-cli").description("JA4 fingerprint analysis CLI tool").version("1.0.0");
+
+program
+ .command("analyze")
+ .description("Analyze a JA4 fingerprint")
+ .argument("<fingerprint>", "Fingerprint to analyze")
+ .option("-t, --type <type>", "Fingerprint type", "ja4")
+ .option("--no-db", "Skip database lookup")
+ .option("-j, --json", "Output as JSON")
+ .option("-v, --verbose", "Verbose output")
+ .action(async (fingerprint: string, options) => {
+ const spinner = ora("Analyzing fingerprint...").start();
+
+ try {
+ let analysis: JA4Analysis;
+ let dbResults: DatabaseResult[] = [];
+
+ // Load database if needed
+ if (options.db) {
+ spinner.text = "Loading database...";
+ await db.loadDatabase();
+ }
+
+ // Analyze fingerprint
+ spinner.text = "Parsing fingerprint...";
+ const type = options.type.toLowerCase();
+
+ switch (type) {
+ case "ja4":
+ analysis = JA4Analyzer.parseJA4(fingerprint);
+ if (options.db) dbResults = db.lookupJA4(fingerprint);
+ break;
+ case "ja4s":
+ analysis = JA4Analyzer.parseJA4S(fingerprint);
+ if (options.db) dbResults = db.lookupJA4S(fingerprint);
+ break;
+ case "ja4h":
+ analysis = JA4Analyzer.parseJA4H(fingerprint);
+ if (options.db) dbResults = db.lookupJA4H(fingerprint);
+ break;
+ case "ja4x":
+ analysis = JA4Analyzer.parseJA4X(fingerprint);
+ if (options.db) dbResults = db.lookupJA4X(fingerprint);
+ break;
+ case "ja4t":
+ analysis = JA4Analyzer.parseJA4T(fingerprint);
+ if (options.db) dbResults = db.lookupJA4T(fingerprint);
+ break;
+ default:
+ throw new Error(`Unknown fingerprint type: ${type}`);
+ }
+
+ spinner.succeed("Analysis complete!");
+
+ if (options.json) {
+ console.log(
+ JSON.stringify(
+ {
+ fingerprint,
+ type: type.toUpperCase(),
+ analysis,
+ database: {
+ count: dbResults.length,
+ results: dbResults,
+ },
+ },
+ null,
+ 2,
+ ),
+ );
+ } else {
+ // Pretty output
+ console.log(chalk.cyan(`\n๐ ${type.toUpperCase()} Analysis`));
+ console.log(chalk.gray("โ".repeat(50)));
+ console.log(chalk.yellow("Fingerprint:"), fingerprint);
+ console.log(chalk.yellow("Format:"), analysis.format);
+
+ if (analysis.human_readable) {
+ console.log(chalk.yellow("Summary:"), analysis.human_readable);
+ }
+
+ console.log(chalk.cyan("\n๐ Breakdown:"));
+ Object.entries(analysis.breakdown).forEach(([part, data]) => {
+ console.log(chalk.magenta(` ${part.toUpperCase()}:`), data.raw);
+ if (options.verbose) {
+ Object.entries(data).forEach(([key, value]) => {
+ if (key !== "raw" && value !== undefined) {
+ console.log(chalk.gray(` ${key}:`), value);
+ }
+ });
+ }
+ });
+
+ if (analysis.use_cases) {
+ console.log(chalk.cyan("\n๐ก Use Cases:"));
+ analysis.use_cases.forEach((useCase) => {
+ console.log(chalk.green(` โข ${useCase}`));
+ });
+ }
+
+ if (dbResults.length > 0) {
+ console.log(chalk.cyan(`\n๐๏ธ Database Results (${dbResults.length} found):`));
+ dbResults.slice(0, 5).forEach((result, i) => {
+ console.log(chalk.green(` ${i + 1}.`), result.application || "Unknown Application");
+ if (result.os) console.log(chalk.gray(` OS: ${result.os}`));
+ if (result.verified) console.log(chalk.blue(" โ Verified"));
+ });
+ if (dbResults.length > 5) {
+ console.log(chalk.gray(` ... and ${dbResults.length - 5} more`));
+ }
+ } else if (options.db) {
+ console.log(chalk.yellow("\n๐๏ธ No database matches found"));
+ }
+ }
+ } catch (error) {
+ spinner.fail("Analysis failed");
+ console.error(chalk.red("Error:"), (error as Error).message);
+ process.exit(1);
+ }
+ });
+
+program
+ .command("compare")
+ .description("Compare two JA4 fingerprints")
+ .argument("<fingerprint1>", "First fingerprint")
+ .argument("<fingerprint2>", "Second fingerprint")
+ .option("-j, --json", "Output as JSON")
+ .action((fp1: string, fp2: string, options) => {
+ try {
+ const comparison = JA4Analyzer.compareFingerprints(fp1, fp2);
+
+ if (options.json) {
+ console.log(JSON.stringify(comparison, null, 2));
+ } else {
+ console.log(chalk.cyan("\n๐ Fingerprint Comparison"));
+ console.log(chalk.gray("โ".repeat(50)));
+
+ if (comparison.identical) {
+ console.log(chalk.green("โ
Fingerprints are identical!"));
+ } else {
+ console.log(chalk.yellow("โ ๏ธ Fingerprints differ"));
+ }
+
+ console.log(chalk.yellow("\nAnalysis:"), comparison.analysis);
+
+ if (comparison.similarities.length > 0) {
+ console.log(chalk.cyan("\nโ
Similarities:"));
+ comparison.similarities.forEach((sim) => {
+ console.log(chalk.green(` ${sim.part}: ${sim.value}`));
+ });
+ }
+
+ if (comparison.differences.length > 0) {
+ console.log(chalk.cyan("\nโ Differences:"));
+ comparison.differences.forEach((diff) => {
+ console.log(chalk.red(` ${diff.part}:`));
+ console.log(chalk.gray(` FP1: ${diff.fp1}`));
+ console.log(chalk.gray(` FP2: ${diff.fp2}`));
+ console.log(chalk.gray(` Note: ${diff.note}`));
+ });
+ }
+ }
+ } catch (error) {
+ console.error(chalk.red("Error:"), (error as Error).message);
+ process.exit(1);
+ }
+ });
+
+program
+ .command("search")
+ .description("Search the JA4 database")
+ .argument("<query>", "Search term")
+ .option("-t, --type <type>", "Search type (app or os)", "app")
+ .option("-l, --limit <number>", "Limit results", "50")
+ .option("-j, --json", "Output as JSON")
+ .action(async (query: string, options) => {
+ const spinner = ora("Searching database...").start();
+
+ try {
+ await db.loadDatabase();
+
+ let results: SearchResult;
+ const limit = parseInt(options.limit);
+
+ if (options.type === "os") {
+ results = db.searchByOS(query, limit);
+ } else {
+ results = db.searchByApplication(query, limit);
+ }
+
+ spinner.succeed(`Found ${results.count} results`);
+
+ if (options.json) {
+ console.log(JSON.stringify(results, null, 2));
+ } else {
+ console.log(chalk.cyan(`\n๐ Search Results for "${query}"`));
+ console.log(chalk.gray("โ".repeat(50)));
+ console.log(chalk.yellow(`Found ${results.count} matches\n`));
+
+ results.results.forEach((result, i) => {
+ console.log(chalk.green(`${i + 1}.`), result.application || "Unknown Application");
+ if (result.os) console.log(chalk.gray(` OS: ${result.os}`));
+ if (result.ja4) console.log(chalk.blue(` JA4: ${result.ja4}`));
+ if (result.verified) console.log(chalk.magenta(" โ Verified"));
+ console.log();
+ });
+ }
+ } catch (error) {
+ spinner.fail("Search failed");
+ console.error(chalk.red("Error:"), (error as Error).message);
+ process.exit(1);
+ }
+ });
+
+program
+ .command("stats")
+ .description("Show database statistics")
+ .option("-j, --json", "Output as JSON")
+ .action(async (options) => {
+ const spinner = ora("Loading database statistics...").start();
+
+ try {
+ await db.loadDatabase();
+ const stats = db.getStatistics();
+
+ spinner.succeed("Statistics loaded");
+
+ if (options.json) {
+ console.log(JSON.stringify(stats, null, 2));
+ } else {
+ console.log(chalk.cyan("\n๐ Database Statistics"));
+ console.log(chalk.gray("โ".repeat(50)));
+ console.log(chalk.yellow("Status:"), stats.status);
+ console.log(chalk.yellow("Total Records:"), stats.total_records.toLocaleString());
+ console.log(chalk.yellow("Last Update:"), new Date(stats.last_update).toLocaleString());
+
+ console.log(chalk.cyan("\n๐ข Fingerprint Counts:"));
+ console.log(chalk.green(" JA4:"), stats.ja4_count.toLocaleString());
+ console.log(chalk.green(" JA4S:"), stats.ja4s_count.toLocaleString());
+ console.log(chalk.green(" JA4H:"), stats.ja4h_count.toLocaleString());
+ console.log(chalk.green(" JA4X:"), stats.ja4x_count.toLocaleString());
+ console.log(chalk.green(" JA4T:"), stats.ja4t_count.toLocaleString());
+
+ console.log(chalk.cyan("\nโ
Quality Metrics:"));
+ console.log(chalk.blue(" Verified Records:"), stats.verified_count.toLocaleString());
+ console.log(chalk.blue(" Unique Applications:"), stats.applications.toLocaleString());
+ console.log(chalk.blue(" Unique Operating Systems:"), stats.operating_systems.toLocaleString());
+ }
+ } catch (error) {
+ spinner.fail("Failed to load statistics");
+ console.error(chalk.red("Error:"), (error as Error).message);
+ process.exit(1);
+ }
+ });
+
+program
+ .command("batch")
+ .description("Analyze multiple fingerprints from a file")
+ .argument("<file>", "File containing fingerprints (one per line)")
+ .option("-t, --type <type>", "Fingerprint type", "ja4")
+ .option("-o, --output <file>", "Output file (JSON format)")
+ .option("--no-db", "Skip database lookups")
+ .action(async (inputFile: string, options) => {
+ const spinner = ora("Processing batch file...").start();
+
+ try {
+ if (!existsSync(inputFile)) {
+ throw new Error(`Input file not found: ${inputFile}`);
+ }
+
+ const content = readFileSync(inputFile, "utf8");
+ const fingerprints = content.split("\n").filter((line) => line.trim());
+
+ if (options.db) {
+ spinner.text = "Loading database...";
+ await db.loadDatabase();
+ }
+
+ const results = [];
+ let processed = 0;
+
+ for (const fingerprint of fingerprints) {
+ try {
+ processed++;
+ spinner.text = `Processing ${processed}/${fingerprints.length}...`;
+
+ let analysis: JA4Analysis;
+ let dbResults: DatabaseResult[] = [];
+
+ switch (options.type.toLowerCase()) {
+ case "ja4":
+ analysis = JA4Analyzer.parseJA4(fingerprint.trim());
+ if (options.db) dbResults = db.lookupJA4(fingerprint.trim());
+ break;
+ case "ja4s":
+ analysis = JA4Analyzer.parseJA4S(fingerprint.trim());
+ if (options.db) dbResults = db.lookupJA4S(fingerprint.trim());
+ break;
+ case "ja4h":
+ analysis = JA4Analyzer.parseJA4H(fingerprint.trim());
+ if (options.db) dbResults = db.lookupJA4H(fingerprint.trim());
+ break;
+ case "ja4x":
+ analysis = JA4Analyzer.parseJA4X(fingerprint.trim());
+ if (options.db) dbResults = db.lookupJA4X(fingerprint.trim());
+ break;
+ case "ja4t":
+ analysis = JA4Analyzer.parseJA4T(fingerprint.trim());
+ if (options.db) dbResults = db.lookupJA4T(fingerprint.trim());
+ break;
+ default:
+ throw new Error(`Unknown fingerprint type: ${options.type}`);
+ }
+
+ results.push({
+ fingerprint: fingerprint.trim(),
+ type: options.type.toUpperCase(),
+ analysis,
+ database: {
+ count: dbResults.length,
+ results: dbResults,
+ },
+ });
+ } catch (error) {
+ results.push({
+ fingerprint: fingerprint.trim(),
+ type: options.type.toUpperCase(),
+ error: (error as Error).message,
+ });
+ }
+ }
+
+ spinner.succeed(`Processed ${fingerprints.length} fingerprints`);
+
+ const output = {
+ summary: {
+ total: fingerprints.length,
+ successful: results.filter((r) => !r.error).length,
+ failed: results.filter((r) => r.error).length,
+ timestamp: new Date().toISOString(),
+ },
+ results,
+ };
+
+ if (options.output) {
+ await fs.writeFile(options.output, JSON.stringify(output, null, 2));
+ console.log(chalk.green(`Results saved to: ${options.output}`));
+ } else {
+ console.log(JSON.stringify(output, null, 2));
+ }
+ } catch (error) {
+ spinner.fail("Batch processing failed");
+ console.error(chalk.red("Error:"), (error as Error).message);
+ process.exit(1);
+ }
+ });
+
+// Global error handler
+process.on("unhandledRejection", (error) => {
+ console.error(chalk.red("Unhandled error:"), error);
+ process.exit(1);
+});
+
+// Parse command line arguments
+program.parse();
index.js
package-lock.json
@@ -0,0 +1,1939 @@
+{
+ "name": "ja4-mcp-server",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "ja4-mcp-server",
+ "version": "0.1.0",
+ "license": "AGPL-3.0-or-later OR LicenseRef-Commercial",
+ "dependencies": {
+ "@modelcontextprotocol/sdk": "^1.0.4",
+ "chalk": "^5.3.0",
+ "commander": "^12.1.0",
+ "ora": "^8.1.0"
+ },
+ "bin": {
+ "ja4": "dist/cli.js",
+ "ja4-mcp-server": "index.js"
+ },
+ "devDependencies": {
+ "@types/node": "24.10.1",
+ "tsx": "^4.19.2",
+ "typescript": "^5.7.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk": {
+ "version": "1.22.0",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.22.0.tgz",
+ "integrity": "sha512-VUpl106XVTCpDmTBil2ehgJZjhyLY2QZikzF8NvTXtLRF1CvO5iEE2UNZdVIUer35vFOwMKYeUGbjJtvPWan3g==",
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.17.1",
+ "ajv-formats": "^3.0.1",
+ "content-type": "^1.0.5",
+ "cors": "^2.8.5",
+ "cross-spawn": "^7.0.5",
+ "eventsource": "^3.0.2",
+ "eventsource-parser": "^3.0.0",
+ "express": "^5.0.1",
+ "express-rate-limit": "^7.5.0",
+ "pkce-challenge": "^5.0.0",
+ "raw-body": "^3.0.0",
+ "zod": "^3.23.8",
+ "zod-to-json-schema": "^3.24.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@cfworker/json-schema": "^4.1.1"
+ },
+ "peerDependenciesMeta": {
+ "@cfworker/json-schema": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "24.10.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
+ "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
+ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
+ "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
+ "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.0",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.6.3",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.0",
+ "raw-body": "^3.0.0",
+ "type-is": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
+ "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/cli-cursor": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
+ "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
+ "license": "MIT",
+ "dependencies": {
+ "restore-cursor": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-spinners": {
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
+ "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
+ "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/emoji-regex": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
+ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.12",
+ "@esbuild/android-arm": "0.25.12",
+ "@esbuild/android-arm64": "0.25.12",
+ "@esbuild/android-x64": "0.25.12",
+ "@esbuild/darwin-arm64": "0.25.12",
+ "@esbuild/darwin-x64": "0.25.12",
+ "@esbuild/freebsd-arm64": "0.25.12",
+ "@esbuild/freebsd-x64": "0.25.12",
+ "@esbuild/linux-arm": "0.25.12",
+ "@esbuild/linux-arm64": "0.25.12",
+ "@esbuild/linux-ia32": "0.25.12",
+ "@esbuild/linux-loong64": "0.25.12",
+ "@esbuild/linux-mips64el": "0.25.12",
+ "@esbuild/linux-ppc64": "0.25.12",
+ "@esbuild/linux-riscv64": "0.25.12",
+ "@esbuild/linux-s390x": "0.25.12",
+ "@esbuild/linux-x64": "0.25.12",
+ "@esbuild/netbsd-arm64": "0.25.12",
+ "@esbuild/netbsd-x64": "0.25.12",
+ "@esbuild/openbsd-arm64": "0.25.12",
+ "@esbuild/openbsd-x64": "0.25.12",
+ "@esbuild/openharmony-arm64": "0.25.12",
+ "@esbuild/sunos-x64": "0.25.12",
+ "@esbuild/win32-arm64": "0.25.12",
+ "@esbuild/win32-ia32": "0.25.12",
+ "@esbuild/win32-x64": "0.25.12"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/eventsource": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
+ "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
+ "license": "MIT",
+ "dependencies": {
+ "eventsource-parser": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/eventsource-parser": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz",
+ "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/express": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
+ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.0",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express-rate-limit": {
+ "version": "7.5.1",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz",
+ "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": ">= 4.11"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
+ "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/finalhandler": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
+ "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-east-asian-width": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
+ "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-tsconfig": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
+ "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-interactive": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
+ "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
+ "node_modules/is-unicode-supported": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
+ "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT"
+ },
+ "node_modules/log-symbols": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz",
+ "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^5.3.0",
+ "is-unicode-supported": "^1.3.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-symbols/node_modules/is-unicode-supported": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
+ "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/mimic-function": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
+ "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
+ "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-function": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ora": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz",
+ "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^5.3.0",
+ "cli-cursor": "^5.0.0",
+ "cli-spinners": "^2.9.2",
+ "is-interactive": "^2.0.0",
+ "is-unicode-supported": "^2.0.0",
+ "log-symbols": "^6.0.0",
+ "stdin-discarder": "^0.2.2",
+ "string-width": "^7.2.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
+ "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/pkce-challenge": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz",
+ "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz",
+ "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.7.0",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/raw-body/node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/raw-body/node_modules/iconv-lite": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
+ "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/raw-body/node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/restore-cursor": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
+ "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
+ "license": "MIT",
+ "dependencies": {
+ "onetime": "^7.0.0",
+ "signal-exit": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/send": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
+ "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.5",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "mime-types": "^3.0.1",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/stdin-discarder": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz",
+ "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tsx": {
+ "version": "4.20.6",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz",
+ "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.25.0",
+ "get-tsconfig": "^4.7.5"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-to-json-schema": {
+ "version": "3.25.0",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz",
+ "integrity": "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==",
+ "license": "ISC",
+ "peerDependencies": {
+ "zod": "^3.25 || ^4"
+ }
+ }
+ }
+}
package.json
@@ -5,12 +5,15 @@
"type": "module",
"main": "index.js",
"bin": {
- "ja4-mcp-server": "./index.js"
+ "ja4-mcp-server": "./index.js",
+ "ja4": "./dist/cli.js"
},
"scripts": {
"start": "node index.js",
"dev": "node --watch index.js",
- "test": "node test.js"
+ "test": "node test.js",
+ "build:cli": "tsc cli.ts --outDir dist --target es2022 --module nodenext --moduleResolution nodenext",
+ "cli": "tsx cli.ts"
},
"keywords": [
"ja4",
@@ -25,7 +28,15 @@
"author": "hrbrmstr",
"license": "AGPL-3.0-or-later OR LicenseRef-Commercial",
"dependencies": {
- "@modelcontextprotocol/sdk": "^1.0.4"
+ "@modelcontextprotocol/sdk": "^1.0.4",
+ "chalk": "^5.3.0",
+ "commander": "^12.1.0",
+ "ora": "^8.1.0"
+ },
+ "devDependencies": {
+ "@types/node": "24.10.1",
+ "tsx": "^4.19.2",
+ "typescript": "^5.7.2"
},
"engines": {
"node": ">=20.0.0"
README.md
@@ -12,11 +12,46 @@ A Model Context Protocol (MCP) server that provides comprehensive analysis of JA
- **Auto-updating Database**: Daily refresh of fingerprint intelligence from ja4db.com
- **Human-Readable Output**: Technical but accessible explanations for all components
- **Analyst Prompts**: Built-in MCP prompts to guide AI assistants in effective JA4+ analysis
+- **Command Line Interface**: Standalone CLI tool for local JA4+ analysis
## What is JA4+?
JA4+ is a suite of network fingerprinting methods created by FoxIO that are both human and machine-readable. Unlike its predecessor JA3, JA4+ uses a modular `a_b_c` format that allows for:
+## Command Line Interface
+
+The project also includes a standalone command-line interface (CLI) tool for analyzing JA4+ fingerprints locally:
+
+```bash
+# Install the CLI globally
+npm install -g
+
+# Analyze a JA4 fingerprint
+ja4 analyze t13d1517h2_8daaf6152771_7128f82b508a
+
+# Compare two fingerprints
+ja4 compare t13d1517h2_8daaf6152771_7128f82b508a t13d1516h2_8daaf6152771_7128f82b508a
+
+# Search the JA4 database
+ja4 search "Chrome"
+
+# Analyze multiple fingerprints from a file
+ja4 batch fingerprints.txt
+
+# Show database statistics
+ja4 stats
+```
+
+For more information about the CLI commands and options, run:
+```bash
+ja4 --help
+ja4 analyze --help
+ja4 compare --help
+ja4 search --help
+ja4 batch --help
+ja4 stats --help
+```
+
- **Locality-preserving analysis**: Hunt on specific parts (e.g., `JA4_ac` to track actors who vary only cipher selection)
- **Resistant to evasion**: Sorted ciphers and extensions prevent simple randomization attacks
- **Multi-protocol support**: TLS (TCP/QUIC), HTTP, SSH, X.509 certificates
tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext",
+ "outDir": "./dist",
+ "rootDir": ".",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "types": ["node"],
+ "allowSyntheticDefaultImports": true
+ },
+ "include": [
+ "cli.ts"
+ ],
+ "exclude": [
+ "node_modules",
+ "dist"
+ ]
+}