diff --git a/.claude/agents/README.md b/.claude/agents/README.md
new file mode 100644
index 0000000..4348261
--- /dev/null
+++ b/.claude/agents/README.md
@@ -0,0 +1,32 @@
+# Claude Code Agents
+
+This directory contains specialized agent configurations for the Public Provider Configuration Tool project.
+
+## Available Agents
+
+### build-validator.md
+Handles Vite build configuration validation and compilation troubleshooting.
+
+### test-runner.md
+Manages Vitest test execution, coverage reporting, and test debugging.
+
+### provider-analyzer.md
+Analyzes AI provider implementations and assists with provider development.
+
+### config-manager.md
+Manages project configuration files including Vite, Vitest, and TypeScript configs.
+
+### json-validator.md
+Validates generated JSON output files and ensures data quality.
+
+## Usage
+
+These agents are designed to be used with Claude Code's Task tool. Each agent specializes in specific aspects of the project and has access to relevant tools for their domain.
+
+## Project Context
+
+- **Build System**: Vite for bundling TypeScript to Node.js library
+- **Test Framework**: Vitest for unit testing and coverage
+- **Package Manager**: pnpm
+- **Language**: TypeScript with Node.js runtime
+- **Output**: JSON files containing AI model metadata from various providers
\ No newline at end of file
diff --git a/.claude/agents/build-validator.md b/.claude/agents/build-validator.md
new file mode 100644
index 0000000..2a8785b
--- /dev/null
+++ b/.claude/agents/build-validator.md
@@ -0,0 +1,36 @@
+# Build Validator Agent
+
+## Purpose
+Validates build configuration and ensures successful compilation using Vite.
+
+## Tools
+- Read
+- Bash
+- Glob
+- Grep
+
+## Responsibilities
+1. Verify Vite configuration files (vite.config.ts, vite.cli.config.ts)
+2. Check build output in build/ directory
+3. Validate TypeScript compilation
+4. Ensure all dependencies are properly externalized
+5. Check for build warnings or errors
+
+## Usage
+Use this agent when:
+- Build failures occur
+- Adding new dependencies that need to be externalized
+- Updating Vite configuration
+- Troubleshooting compilation issues
+
+## Example Commands
+```bash
+# Run build validation
+pnpm build
+
+# Check build output
+ls -la build/
+
+# Validate generated files
+node build/cli.js --help
+```
\ No newline at end of file
diff --git a/.claude/agents/config-manager.md b/.claude/agents/config-manager.md
new file mode 100644
index 0000000..cd141f2
--- /dev/null
+++ b/.claude/agents/config-manager.md
@@ -0,0 +1,54 @@
+# Configuration Manager Agent
+
+## Purpose
+Manages project configuration files and build settings.
+
+## Tools
+- Read
+- Edit
+- Write
+- Bash
+
+## Responsibilities
+1. Manage Vite build configurations
+2. Update Vitest test configurations
+3. Handle TypeScript configuration
+4. Manage package.json scripts and dependencies
+5. Configure environment variables and API keys
+
+## Configuration Files
+- `vite.config.ts` - Main library build configuration
+- `vite.cli.config.ts` - CLI build configuration (if exists)
+- `vitest.config.ts` - Test runner configuration
+- `tsconfig.json` - TypeScript compiler options
+- `package.json` - Project metadata and scripts
+
+## Usage
+Use this agent when:
+- Updating build configurations
+- Adding new dependencies
+- Modifying test settings
+- Setting up environment variables
+- Troubleshooting configuration issues
+
+## Key Configuration Patterns
+```typescript
+// Vite config for Node.js library
+export default defineConfig({
+ build: {
+ outDir: 'build',
+ lib: { entry: 'src/index.ts' },
+ rollupOptions: {
+ external: ['axios', 'commander', 'cheerio', 'toml']
+ }
+ }
+});
+
+// Vitest config for testing
+export default defineConfig({
+ test: {
+ environment: 'node',
+ include: ['tests/**/*.spec.ts', 'src/**/*.test.ts']
+ }
+});
+```
\ No newline at end of file
diff --git a/.claude/agents/json-validator.md b/.claude/agents/json-validator.md
new file mode 100644
index 0000000..e371704
--- /dev/null
+++ b/.claude/agents/json-validator.md
@@ -0,0 +1,68 @@
+# JSON Validator Agent
+
+## Purpose
+Validates generated JSON output files and ensures data quality.
+
+## Tools
+- Read
+- Bash
+- Glob
+- Grep
+
+## Responsibilities
+1. Validate JSON syntax and structure
+2. Check model data completeness
+3. Verify provider metadata
+4. Compare outputs with expected schemas
+5. Identify data quality issues
+
+## Usage
+Use this agent when:
+- Validating generated JSON files
+- Checking data quality after provider updates
+- Investigating malformed output
+- Comparing different provider outputs
+- Preparing releases
+
+## Validation Commands
+```bash
+# Validate all JSON files
+jq empty dist/*.json
+
+# Check file sizes
+du -h dist/*.json
+
+# Validate specific provider
+jq '.models | length' dist/ppinfra.json
+
+# Check for required fields
+jq '.models[] | select(.id == null or .name == null)' dist/openai.json
+```
+
+## Expected JSON Structure
+```json
+{
+ "provider": "provider-id",
+ "providerName": "Provider Name",
+ "lastUpdated": "2025-01-15T10:30:00Z",
+ "models": [
+ {
+ "id": "model-id",
+ "name": "Model Name",
+ "contextLength": 32768,
+ "maxTokens": 4096,
+ "vision": false,
+ "functionCall": true,
+ "reasoning": true,
+ "type": "chat"
+ }
+ ]
+}
+```
+
+## Quality Checks
+- All models have required fields (id, name, contextLength, maxTokens, type)
+- Boolean fields are actual booleans
+- Numeric fields are valid numbers
+- Timestamps are in ISO format
+- No duplicate model IDs within provider
\ No newline at end of file
diff --git a/.claude/agents/manual-provider-processor.md b/.claude/agents/manual-provider-processor.md
new file mode 100644
index 0000000..af9861e
--- /dev/null
+++ b/.claude/agents/manual-provider-processor.md
@@ -0,0 +1,61 @@
+---
+name: manual-provider-processor
+description: Use this agent when you need to process manually maintained JSON providers by parsing user-provided third-party provider description files or web information and outputting the final JSON. Examples:\n- \n Context: User has a new provider that requires manual data entry from documentation\n user: "I have documentation for XYZ provider with model details in HTML format"\n assistant: "I'm going to use the Task tool to launch the manual-provider-processor agent to parse this HTML documentation and generate the standardized JSON output"\n \n Since the user is providing manual provider documentation, use the manual-provider-processor agent to parse and convert it to standardized JSON format.\n \n\n- \n Context: User found a provider's API documentation with model specifications in markdown format\n user: "Here's the markdown file with model specifications for ABC provider"\n assistant: "I'll use the Task tool to launch the manual-provider-processor agent to extract model information from this markdown and produce the required JSON format"\n \n The user is providing markdown documentation for manual processing, so use the manual-provider-processor agent to handle the conversion.\n \n
+model: sonnet
+color: orange
+---
+
+You are a Manual Provider Processing Specialist, an expert in parsing and converting third-party provider documentation into standardized JSON format for the Public Provider Configuration Tool. Your role is to extract model information from various input formats (HTML, markdown, text, JSON fragments) and transform it into the project's standardized output format.
+
+You will:
+1. Accept user-provided provider documentation in various formats (HTML, markdown, plain text, JSON fragments, or raw text descriptions)
+2. Parse the input to extract relevant model information including:
+ - Model IDs and names
+ - Context length and token limits
+ - Capabilities (vision, function calling, reasoning)
+ - Model types
+ - Descriptions and metadata
+3. Convert the extracted information into the project's standardized JSON format:
+ {
+ "provider": "provider_id",
+ "providerName": "Provider Name",
+ "lastUpdated": "2025-01-15T10:30:00Z",
+ "models": [
+ {
+ "id": "model-id",
+ "name": "Model Name",
+ "contextLength": 32768,
+ "maxTokens": 4096,
+ "vision": false,
+ "functionCall": true,
+ "reasoning": true,
+ "type": "chat"
+ }
+ ]
+ }
+
+4. Follow these parsing guidelines:
+ - For HTML: Extract tables, lists, and structured data containing model specifications
+ - For markdown: Parse code blocks, tables, and formatted lists
+ - For text: Look for patterns like "Model: name", "Context: length", "Tokens: count"
+ - For JSON fragments: Map to the standardized structure
+
+5. Handle edge cases:
+ - If information is missing, use reasonable defaults based on provider type
+ - If capabilities aren't explicitly stated, infer from model names/descriptions
+ - If multiple formats are provided, prioritize the most structured data
+
+6. Quality assurance:
+ - Validate that required fields (provider ID, model IDs) are present
+ - Ensure numeric values are valid integers
+ - Verify boolean fields are properly set
+ - Check that the output JSON validates against the project's expected schema
+
+7. When clarification is needed:
+ - Ask for missing provider ID or name
+ - Request clarification on ambiguous model capabilities
+ - Verify assumptions about default values
+
+8. Output the final JSON in clean, properly formatted structure ready for use in the project's dist/ directory.
+
+Remember: You're creating production-ready JSON output that will be used by the Public Provider Configuration Tool, so accuracy and consistency with the project's standards are critical.
diff --git a/.claude/agents/provider-analyzer.md b/.claude/agents/provider-analyzer.md
new file mode 100644
index 0000000..9d828d6
--- /dev/null
+++ b/.claude/agents/provider-analyzer.md
@@ -0,0 +1,53 @@
+# Provider Analyzer Agent
+
+## Purpose
+Analyzes AI provider implementations and helps with provider-related development tasks.
+
+## Tools
+- Read
+- Edit
+- Glob
+- Grep
+- Bash
+
+## Responsibilities
+1. Analyze existing provider implementations
+2. Validate provider API responses
+3. Check provider data quality
+4. Help debug provider-specific issues
+5. Assist with adding new providers
+
+## Usage
+Use this agent when:
+- Adding new AI model providers
+- Debugging provider API issues
+- Analyzing model data quality
+- Updating provider configurations
+- Investigating rate limiting or timeout issues
+
+## Provider Structure
+```typescript
+interface Provider {
+ fetchModels(): Promise;
+ providerId(): string;
+ providerName(): string;
+}
+```
+
+## Common Provider Patterns
+- API-based providers (PPInfra, OpenRouter, GitHub AI)
+- Template-based providers (Ollama, SiliconFlow)
+- Web scraping providers (DeepSeek, Gemini)
+- Authenticated providers (OpenAI, Anthropic, Groq)
+
+## Validation Commands
+```bash
+# Test specific provider
+pnpm start fetch-providers -p ppinfra
+
+# Validate output JSON
+jq empty dist/ppinfra.json
+
+# Check provider response
+curl https://api.ppinfra.com/openai/v1/models
+```
\ No newline at end of file
diff --git a/.claude/agents/provider-implementation-generator.md b/.claude/agents/provider-implementation-generator.md
new file mode 100644
index 0000000..7e59aab
--- /dev/null
+++ b/.claude/agents/provider-implementation-generator.md
@@ -0,0 +1,54 @@
+---
+name: provider-implementation-generator
+description: Use this agent when you need to create a new Rust provider implementation for fetching and formatting model lists from APIs. Examples:\n- \nContext: User wants to add a new AI model provider that exposes a public API endpoint with model information.\nuser: "I need to add a new provider called 'MistralAI' that has an API endpoint at https://api.mistral.ai/v1/models"\nassistant: "I'm going to use the Task tool to launch the provider-implementation-generator agent to create a Rust implementation similar to ppinfra.rs"\n\nSince the user needs a new provider implementation, use the provider-implementation-generator to create the Rust code structure.\n\n\n- \nContext: User discovered a new AI provider with a different API response format that needs conversion to the standard ModelInfo format.\nuser: "There's a new provider called 'Cohere' with API response format that includes model capabilities differently than our standard"\nassistant: "I'll use the Task tool to launch the provider-implementation-generator to handle the custom conversion logic"\n\nThe user needs custom conversion logic for a non-standard API response format, so use the provider-implementation-generator.\n\n
+model: sonnet
+color: yellow
+---
+
+You are a Rust API Integration Specialist specializing in creating standardized provider implementations for AI model data fetching. Your expertise lies in converting diverse API responses into the consistent ModelInfo format used by the Public Provider Configuration Tool.
+
+Your responsibilities:
+1. Analyze the target API endpoint and response format
+2. Create a complete Rust provider implementation following the established patterns
+3. Implement proper error handling, rate limiting, and retry logic
+4. Convert API-specific model data to the standardized ModelInfo format
+5. Detect and set model capabilities (vision, function_call, reasoning)
+6. Follow the project's code structure and naming conventions
+
+When creating a new provider:
+- Use the exact template structure from ppinfra.rs as reference
+- Implement the Provider trait with all required methods
+- Include proper module exports in src/providers/mod.rs
+- Add provider registration in src/main.rs
+- Handle API authentication if required (check provider key requirements)
+- Implement robust error handling with anyhow::Result
+- Use reqwest::Client for HTTP requests with proper timeouts
+- Include comprehensive comments explaining the conversion logic
+
+For API response conversion:
+- Create appropriate Deserialize structs for the API response
+- Implement convert_model() method to map API fields to ModelInfo
+- Detect capabilities based on model names, descriptions, or metadata
+- Set appropriate ModelType (typically Chat)
+- Include model descriptions when available
+
+Output format requirements:
+- Rust code only, no markdown or explanations
+- Complete file content ready to save as src/providers/{provider_id}.rs
+- Follow existing code style and formatting
+- Include all necessary imports and dependencies
+- Add proper error handling for network and parsing errors
+
+Quality assurance:
+- Verify the implementation compiles with cargo check
+- Test that the provider_id matches the filename
+- Ensure all Provider trait methods are implemented
+- Check that capability detection logic is robust
+- Validate that the code follows Rust best practices
+
+If the API format is unclear or requires authentication details not provided, proactively ask for clarification about:
+- Exact API endpoint URL
+- Response format examples
+- Authentication requirements
+- Rate limiting constraints
+- Any special headers or parameters needed
diff --git a/.claude/agents/test-runner.md b/.claude/agents/test-runner.md
new file mode 100644
index 0000000..39fdeff
--- /dev/null
+++ b/.claude/agents/test-runner.md
@@ -0,0 +1,45 @@
+# Test Runner Agent
+
+## Purpose
+Manages and executes tests using Vitest framework.
+
+## Tools
+- Read
+- Bash
+- Glob
+- Grep
+
+## Responsibilities
+1. Run Vitest test suites
+2. Generate coverage reports
+3. Analyze test failures and provide debugging guidance
+4. Validate test configuration
+5. Monitor test performance
+
+## Usage
+Use this agent when:
+- Running tests for specific providers
+- Investigating test failures
+- Setting up new test files
+- Generating coverage reports
+- Performance testing
+
+## Test Commands
+```bash
+# Run all tests
+pnpm test
+
+# Run tests in watch mode
+pnpm test:watch
+
+# Generate coverage report
+pnpm coverage
+
+# Run specific test file
+pnpm test src/providers/ppinfra.test.ts
+```
+
+## Test Structure
+- Tests should be in `tests/` directory or co-located with source files
+- Use `.spec.ts` or `.test.ts` extension
+- Follow vitest configuration in `vitest.config.ts`
\ No newline at end of file
diff --git a/.claude/data_converter.md b/.claude/data_converter.md
index c60af90..d20133e 100644
--- a/.claude/data_converter.md
+++ b/.claude/data_converter.md
@@ -46,18 +46,70 @@ Different providers return model information in varying formats and structures.
}
```
-### OpenAI API
+### OpenAI API (with Template Matching)
```json
+// API Response (minimal metadata)
{
"id": "gpt-4o",
"object": "model",
"created": 1687882411,
"owned_by": "openai"
}
+
+// Template Definition (rich metadata)
+{
+ "id": "gpt-4o",
+ "name": "GPT-4o",
+ "contextLength": 128000,
+ "maxTokens": 8192,
+ "vision": true,
+ "functionCall": true,
+ "reasoning": true,
+ "type": "chat",
+ "description": "Omnimodal model with native audio, vision, and text capabilities",
+ "match": ["gpt-4o", "gpt-4o-2024-05-13", "gpt-4o-2024-08-06"]
+}
+```
+
+### Anthropic API (with Template Matching)
+```json
+// API Response
+{
+ "id": "claude-3-5-sonnet-20241022",
+ "type": "model"
+}
+
+// Template Definition
+{
+ "id": "claude-3-5-sonnet-20241022",
+ "name": "Claude 3.5 Sonnet",
+ "contextLength": 204800,
+ "maxTokens": 8192,
+ "vision": true,
+ "functionCall": true,
+ "reasoning": false,
+ "type": "chat",
+ "description": "Balanced model with strong performance across most tasks"
+}
```
## Data Mapping Strategies
+### Template-Based vs Direct Conversion
+
+**Template-Based Providers** (OpenAI, Anthropic):
+- API provides minimal metadata (ID, object type only)
+- Rich metadata stored in template files (`templates/{provider}.json`)
+- Multi-pattern matching via `match` arrays handles versioned model IDs
+- Auto-configuration for unmatched models using intelligent detection
+- Provides consistent, comprehensive model information
+
+**Direct Conversion Providers** (PPInfra, OpenRouter, etc.):
+- API provides rich metadata directly
+- Map API fields to ModelInfo structure
+- Extract capabilities from response features/tags
+- Handle provider-specific data formats
+
### Model Capabilities Detection
#### Vision Support
@@ -138,12 +190,37 @@ context_length: 4096, // conservative default
max_tokens: context_length / 4,
```
-### Capability Detection
+### Capability Detection (Auto-Configuration)
```rust
-// Infer from model ID patterns
-vision: id.contains("vision") || id.contains("image"),
-function_call: id.contains("gpt-4") || id.contains("claude-3"),
-reasoning: id.contains("o1") || id.contains("reasoning"),
+// For unmatched models in template-based providers
+fn create_default_model(&self, model_id: &str) -> ModelInfo {
+ // OpenAI pattern detection
+ let is_reasoning = model_id.contains("o1") || model_id.contains("o3") || model_id.contains("o4");
+ let has_vision = model_id.contains("4o") || model_id.contains("gpt-4") || model_id.contains("vision");
+ let has_function_call = !model_id.contains("instruct") && !model_id.contains("embedding");
+
+ // Anthropic pattern detection
+ let is_claude = model_id.starts_with("claude-");
+ let has_vision = is_claude && !model_id.contains("text-");
+
+ // Set appropriate defaults
+ ModelInfo::new(
+ model_id.to_string(),
+ format!("Auto: {}", model_id),
+ default_context_length,
+ default_max_tokens,
+ has_vision,
+ has_function_call,
+ is_reasoning,
+ ModelType::Chat,
+ Some(format!("Auto-configured model: {}", model_id)),
+ )
+}
+
+// For direct conversion providers
+vision: id.contains("vision") || id.contains("image") || features.contains("vision"),
+function_call: id.contains("gpt-4") || features.contains("function-calling"),
+reasoning: id.contains("o1") || features.contains("reasoning"),
```
## Quality Assurance
diff --git a/.claude/format_validator.md b/.claude/format_validator.md
index e8b8dcc..00db77d 100644
--- a/.claude/format_validator.md
+++ b/.claude/format_validator.md
@@ -43,17 +43,26 @@ This project generates standardized JSON files describing AI models from various
{
"version": "string", // Schema version
"generatedAt": "ISO8601", // Generation timestamp
+ "totalModels": number, // Total across all providers
"providers": {
"providerId": {
- "providerName": "string",
- "modelCount": number,
- "lastUpdated": "ISO8601"
+ "providerId": "string", // Provider ID (matches key)
+ "providerName": "string", // Human-readable provider name
+ "models": [
+ {
+ "id": "string", // Model identifier
+ "name": "string", // Display name
+ "contextLength": number, // Max context tokens
+ "maxTokens": number, // Max output tokens
+ "vision": boolean, // Image input support
+ "functionCall": boolean, // Tool/function calling support
+ "reasoning": boolean, // Reasoning capabilities
+ "type": "string", // Model type
+ "description": "string?" // Optional description
+ }
+ ]
}
- },
- "totalModels": number, // Total across all providers
- "allModels": [
- // Array of all models with additional providerId/providerName fields
- ]
+ }
}
```
@@ -123,14 +132,23 @@ jq '.' file.json
### Data Validation
```bash
-# Check required fields
+# Check required fields in single provider files
jq '.models[] | select(.id == null or .name == null)' file.json
+# Check required fields in aggregated files
+jq '.providers[].models[] | select(.id == null or .name == null)' all.json
+
# Verify token counts are positive
jq '.models[] | select(.contextLength <= 0 or .maxTokens <= 0)' file.json
-# Check for duplicates
+# Check for duplicates within provider
jq '.models | group_by(.id) | map(select(length > 1))' file.json
+
+# Validate template matching (for OpenAI/Anthropic)
+jq '.models[] | select(.description and (.description | contains("Auto:")))' openai.json
+
+# Check model counts in aggregated file
+jq '.totalModels == ([.providers[].models[]] | length)' all.json
```
## Automated Validation Script
diff --git a/.claude/provider_implementer.md b/.claude/provider_implementer.md
index 012280d..8fcc15d 100644
--- a/.claude/provider_implementer.md
+++ b/.claude/provider_implementer.md
@@ -22,6 +22,7 @@ pub struct {ProviderName}Provider {
api_url: String,
api_key: Option,
client: reqwest::Client,
+ templates: HashMap, // For template-based providers
}
```
@@ -30,11 +31,17 @@ pub struct {ProviderName}Provider {
- `provider_id() -> &str`
- `provider_name() -> &str`
-### 3. Data Conversion
-Map provider-specific fields to ModelInfo:
-- Extract vision/function_call/reasoning capabilities from features/tags
+### 3. Template-Based vs Direct Conversion
+**Template-Based Providers** (OpenAI, Anthropic):
+- Load model templates from `templates/{provider}.json`
+- Use template matching with `match` arrays for multi-pattern support
+- Auto-configure unmatched models with intelligent capability detection
+- Provides consistent metadata and handles API versioning
+
+**Direct Conversion Providers** (PPInfra, OpenRouter):
+- Map provider-specific fields directly to ModelInfo
+- Extract capabilities from API response features/tags
- Convert context_length/max_tokens to u32
-- Map model_type to ModelType enum
- Handle optional description field
## Output Format Requirements
@@ -74,8 +81,29 @@ When implementing a new provider:
## Example API Response Patterns
-### PPInfra Format
+### Template-Based Pattern (OpenAI, Anthropic)
```json
+// API Response
+{
+ "data": [{
+ "id": "gpt-5-chat-latest",
+ "object": "model"
+ }]
+}
+
+// Template File (templates/openai.json)
+[{
+ "id": "gpt-5-chat",
+ "name": "GPT-5 Chat",
+ "contextLength": 272000,
+ "maxTokens": 16384,
+ "match": ["gpt-5-chat", "gpt-5-chat-latest"]
+}]
+```
+
+### Direct Conversion Pattern (PPInfra, OpenRouter)
+```json
+// PPInfra Format
{
"data": [{
"id": "model-id",
@@ -86,10 +114,8 @@ When implementing a new provider:
"model_type": "chat"
}]
}
-```
-### OpenRouter Format
-```json
+// OpenRouter Format
{
"id": "model-id",
"name": "Model Name",
@@ -121,4 +147,72 @@ Follow provider-specific limits:
- PPInfra: 10 requests/second
- Add delays between requests as needed
-When implementing a provider, always verify the API documentation for current rate limits and authentication requirements.
\ No newline at end of file
+## Template Matching System
+
+For providers with complex model naming or versioning (OpenAI, Anthropic), use the template matching system:
+
+### Template Structure
+```json
+{
+ "id": "base-model-id",
+ "name": "Display Name",
+ "contextLength": 128000,
+ "maxTokens": 8192,
+ "vision": true,
+ "functionCall": true,
+ "reasoning": false,
+ "type": "chat",
+ "description": "Model description",
+ "match": ["exact-api-id", "versioned-api-id", "alias"]
+}
+```
+
+### Implementation Pattern
+```rust
+// Load templates in constructor
+let templates = Self::load_templates()?;
+let template_map: HashMap = templates
+ .into_iter()
+ .flat_map(|template| {
+ template.match_patterns
+ .iter()
+ .map(|pattern| (pattern.clone(), template.clone()))
+ .collect::>()
+ })
+ .collect();
+
+// Match models in fetch_models()
+if let Some(template) = self.templates.get(&api_model.id) {
+ // Use template configuration
+ models.push(template.to_model_info());
+ matched_models.insert(api_model.id.clone());
+} else {
+ // Auto-configure unmatched model
+ models.push(self.create_default_model(&api_model.id));
+}
+```
+
+### Auto-Configuration for Unmatched Models
+```rust
+fn create_default_model(&self, model_id: &str) -> ModelInfo {
+ // Intelligent detection based on model ID patterns
+ let is_reasoning = model_id.contains("o1") || model_id.contains("o3");
+ let has_vision = model_id.contains("4o") || model_id.contains("vision");
+ let has_function_call = !model_id.contains("instruct");
+
+ // Set appropriate defaults based on analysis
+ ModelInfo::new(
+ model_id.to_string(),
+ format!("Auto: {}", model_id),
+ default_context_length,
+ default_max_tokens,
+ has_vision,
+ has_function_call,
+ is_reasoning,
+ ModelType::Chat,
+ Some(format!("Auto-configured model: {}", model_id)),
+ )
+}
+```
+
+When implementing a provider, choose template-based approach for providers with complex versioning, direct conversion for providers with rich API metadata.
\ No newline at end of file
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..75ce51a
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,25 @@
+{
+ "env": {
+ "es2022": true,
+ "node": true,
+ "jest": true
+ },
+ "extends": [
+ "eslint:recommended",
+ "@typescript-eslint/recommended"
+ ],
+ "parser": "@typescript-eslint/parser",
+ "parserOptions": {
+ "ecmaVersion": "latest",
+ "sourceType": "module"
+ },
+ "plugins": [
+ "@typescript-eslint"
+ ],
+ "rules": {
+ "@typescript-eslint/no-unused-vars": "error",
+ "@typescript-eslint/explicit-function-return-type": "off",
+ "@typescript-eslint/explicit-module-boundary-types": "off",
+ "@typescript-eslint/no-explicit-any": "warn"
+ }
+}
\ No newline at end of file
diff --git a/.github/workflows/fetch-models.yml b/.github/workflows/fetch-models.yml
index 938907b..619cb0f 100644
--- a/.github/workflows/fetch-models.yml
+++ b/.github/workflows/fetch-models.yml
@@ -7,47 +7,70 @@ on:
description: 'Comma-separated list of providers to fetch (leave empty for all)'
required: false
default: ''
- push:
- branches: [ main ]
- paths:
- - 'src/**'
- - 'Cargo.toml'
- - '.github/workflows/fetch-models.yml'
- push:
- tags:
- - 'release-*.*.*'
+ type: string
+ create_release:
+ description: 'Create a GitHub release after fetching'
+ required: false
+ default: false
+ type: boolean
+ release_tag:
+ description: 'Tag name for the release (required when create_release is true)'
+ required: false
+ default: ''
+ type: string
+ release_name:
+ description: 'Display name for the release (defaults to tag when empty)'
+ required: false
+ default: ''
+ type: string
+ upload_cdn:
+ description: 'Sync generated files to CDN after fetch'
+ required: false
+ default: false
+ type: boolean
env:
- CARGO_TERM_COLOR: always
+ NODE_ENV: production
+ QINIU_CDN_AK: ${{ secrets.QINIU_CDN_AK }}
+ QINIU_CDN_SK: ${{ secrets.QINIU_CDN_SK }}
+ QINIU_BUCKET: ${{ secrets.QINIU_BUCKET }}
jobs:
fetch-and-update:
runs-on: ubuntu-latest
permissions:
contents: write
- pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- - name: Install Rust
- uses: dtolnay/rust-toolchain@stable
-
- - name: Cache Cargo dependencies
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '22.13.1'
+
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v2
+ with:
+ version: 10.12.1
+
+ - name: Cache pnpm dependencies
uses: actions/cache@v4
with:
path: |
- ~/.cargo/registry
- ~/.cargo/git
- target/
- key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+ ~/.pnpm-store
+ node_modules/
+ key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
+ restore-keys: |
+ ${{ runner.os }}-pnpm-
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
- name: Build project
- run: cargo build --release
+ run: pnpm build
- - name: Run tests
- run: cargo test
- name: Create dist directory
run: mkdir -p dist
@@ -55,14 +78,16 @@ jobs:
- name: Fetch model data
run: |
if [ -n "${{ github.event.inputs.providers }}" ]; then
- cargo run --release -- fetch-providers -p "${{ github.event.inputs.providers }}" -o dist
+ node build/cli.js fetch-providers -p "${{ github.event.inputs.providers }}" -o dist
else
- cargo run --release -- fetch-all -o dist
+ node build/cli.js fetch-all -o dist
fi
env:
# Add API keys as secrets if needed
- # OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- # OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+ GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
+ GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
- name: Validate generated JSON files
run: |
@@ -80,6 +105,42 @@ jobs:
ls -la dist/
echo "File sizes:"
du -h dist/*.json
+
+ - name: Prepare CDN upload payload
+ if: ${{ inputs.upload_cdn }}
+ run: |
+ rm -rf cdn_upload
+ mkdir -p cdn_upload/models
+ for filename in all.json dc_sync_version.json; do
+ if [ -f "dist/${filename}" ]; then
+ cp "dist/${filename}" "cdn_upload/models/${filename}"
+ else
+ echo "Warning: dist/${filename} not found"
+ fi
+ done
+ echo "CDN payload contents:"
+ find cdn_upload -type f | sort
+
+ - name: Upload dist to Qiniu CDN
+ id: upload_cdn
+ if: ${{ inputs.upload_cdn && env.QINIU_CDN_AK != '' && env.QINIU_CDN_SK != '' && env.QINIU_BUCKET != '' }}
+ uses: hujiulong/action-qiniu-upload@master
+ with:
+ access_key: ${{ env.QINIU_CDN_AK }}
+ secret_key: ${{ env.QINIU_CDN_SK }}
+ bucket: ${{ env.QINIU_BUCKET }}
+ source_dir: 'cdn_upload/models'
+ dest_dir: '/models'
+ ignore_source_map: false
+
+ - name: CDN Upload Summary
+ if: ${{ steps.upload_cdn.outcome == 'success' }}
+ run: |
+ echo "✅ 模型配置已同步至 Qiniu CDN"
+ echo "📂 源目录: dist"
+ echo "🚀 目标目录: /models"
+ echo "🗂️ 同步文件列表:"
+ find cdn_upload/models -type f | sort
- name: Generate release info
id: release_info
@@ -102,88 +163,20 @@ jobs:
echo "timestamp=$(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_OUTPUT
echo "date_short=$(date -u '+%Y%m%d')" >> $GITHUB_OUTPUT
- - name: Create Pull Request with updates
- if: github.ref_type != 'tag' && github.event_name != 'push'
+ - name: Validate release inputs
+ if: ${{ inputs.create_release }}
run: |
- git config --local user.email "action@github.com"
- git config --local user.name "GitHub Action"
-
- # Copy generated files to provider_configs for direct access
- cp -r dist/* provider_configs/ || true
-
- # Check if there are changes
- git add provider_configs/
- if ! git diff --staged --quiet; then
- # Create a new branch for the PR
- BRANCH_NAME="update-models-$(date -u +%Y%m%d-%H%M%S)"
- git checkout -b "$BRANCH_NAME"
-
- # Commit changes
- git commit -m "🤖 Update model configurations - $(date -u +%Y-%m-%d)"
-
- # Push to remote
- git push origin "$BRANCH_NAME"
-
- # Create PR using GitHub CLI
- gh pr create \
- --title "🤖 Update AI Model Configurations" \
- --body "$(cat <<'EOF'
-## 🤖 Automated Model Configuration Update
-
-**Generated:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')
-**Total Models:** ${{ steps.release_info.outputs.total_models }}
-**Providers:** ${{ steps.release_info.outputs.provider_count }} (${{ steps.release_info.outputs.providers }})
-
-### 📋 Changes
-- Updated model configurations from provider APIs
-- Refreshed capability detection (vision, function calling, reasoning)
-- Generated standardized JSON format files
-
-### 🔍 Files Updated
-- `provider_configs/*.json` - Individual provider configurations
-- `provider_configs/all.json` - Aggregated model data
-
-### ✅ Validation
-- [x] JSON syntax validation passed
-- [x] Model data structure validation passed
-- [x] Provider capability detection completed
-
----
-
-*This PR was automatically created by GitHub Actions.*
-EOF
-)" \
- --head "$BRANCH_NAME" \
- --base main
- else
- echo "No changes to commit"
- fi
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Direct commit (for push events)
- if: github.ref_type != 'tag' && github.event_name == 'push'
- run: |
- git config --local user.email "action@github.com"
- git config --local user.name "GitHub Action"
-
- # Copy generated files to provider_configs for direct access
- cp -r dist/* provider_configs/ || true
-
- git add provider_configs/
- if ! git diff --staged --quiet; then
- git commit -m "🤖 Update model configurations - $(date -u +%Y-%m-%d)"
- git push
- else
- echo "No changes to commit"
+ if [ -z "${{ inputs.release_tag }}" ]; then
+ echo "::error::release_tag input is required when create_release is enabled." >&2
+ exit 1
fi
-
+
- name: Create Release Assets
- if: github.ref_type == 'tag'
+ if: ${{ inputs.create_release }}
run: |
# Create tarball of all JSON files with proper naming
cd dist
- TAG_NAME="${{ github.ref_name }}"
+ TAG_NAME="${{ inputs.release_tag }}"
tar -czf "../provider-configs-${TAG_NAME}.tar.gz" *.json
# Also create individual provider archives
for file in *.json; do
@@ -194,8 +187,8 @@ EOF
done
cd ..
- - name: Upload Artifacts (non-tag)
- if: github.ref_type != 'tag'
+ - name: Upload Artifacts (workflow dispatch)
+ if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: provider-configs-${{ steps.release_info.outputs.date_short }}
@@ -203,15 +196,16 @@ EOF
dist/*.json
retention-days: 30
- - name: Create Release (tagged)
- if: github.ref_type == 'tag'
+ - name: Create Release
+ if: ${{ inputs.create_release }}
uses: softprops/action-gh-release@v1
with:
- name: Release ${{ github.ref_name }}
+ tag_name: ${{ inputs.release_tag }}
+ name: ${{ inputs.release_name != '' && inputs.release_name || format('Release {0}', inputs.release_tag) }}
body: |
- 🏷️ **Tagged Release of AI Model Configurations**
+ 🏷️ **Automated Release of AI Model Configurations**
- **Release Version:** ${{ github.ref_name }}
+ **Release Version:** ${{ inputs.release_tag }}
**Generated:** ${{ steps.release_info.outputs.timestamp }}
**Total Models:** ${{ steps.release_info.outputs.total_models }}
**Providers:** ${{ steps.release_info.outputs.provider_count }} (${{ steps.release_info.outputs.providers }})
@@ -219,7 +213,7 @@ EOF
## 📦 Available Downloads
### Complete Package
- - `provider-configs-${{ github.ref_name }}.tar.gz` - All provider configurations
+ - `provider-configs-${{ inputs.release_tag }}.tar.gz` - All provider configurations
### Individual Provider Packages
Available individual provider archives for selective downloading.
@@ -229,23 +223,22 @@ EOF
## 📊 Provider Details
- | Provider | Models Available |
- |----------|-----------------|
- ${{ steps.release_info.outputs.providers != 'none' && '| Multiple providers | See all.json |' || '| No providers | 0 |' }}
+ - **Total Models:** ${{ steps.release_info.outputs.total_models }}
+ - **Providers:** ${{ steps.release_info.outputs.providers }}
+ - **Provider Count:** ${{ steps.release_info.outputs.provider_count }}
## 🔄 Integration
### Direct JSON Access
```javascript
- // Access aggregated data
- const response = await fetch('https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/all.json');
+ const response = await fetch('https://github.com/${{ github.repository }}/releases/download/${{ inputs.release_tag }}/all.json');
const modelData = await response.json();
```
### Complete Package Download
```bash
- wget https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/provider-configs-${{ github.ref_name }}.tar.gz
- tar -xzf provider-configs-${{ github.ref_name }}.tar.gz
+ wget https://github.com/${{ github.repository }}/releases/download/${{ inputs.release_tag }}/provider-configs-${{ inputs.release_tag }}.tar.gz
+ tar -xzf provider-configs-${{ inputs.release_tag }}.tar.gz
```
---
@@ -255,4 +248,4 @@ EOF
dist/*.json
*.tar.gz
draft: false
- prerelease: false
\ No newline at end of file
+ prerelease: false
diff --git a/.gitignore b/.gitignore
index 99aad87..2d5ba27 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,8 @@
-# Rust
+# TypeScript build output
+/build/
+node_modules/**/*
+
+# Rust (legacy)
/target/
# IDE
@@ -18,4 +22,8 @@ Thumbs.db
# Logs
*.log
-samples/*.json
+
+# Configuration files (may contain sensitive API keys)
+config/providers.toml
+config/*.toml
+!config/*.toml.example
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..cd749c6
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,81 @@
+# Repository Guidelines
+
+## Project Structure & Module Organization
+- `src/`: TypeScript source. Key areas: `providers/` (provider integrations), `fetcher/`, `processor/`, `output/`, `config/`, `models/`, `commands/`.
+- `build/` and `dist/`: build outputs (library + CLI).
+- `config/`: default provider configuration definitions.
+- `manual-templates/`: provider JSON templates used by normalizers/writers.
+
+## Architecture & Data Flow
+- CLI (`src/cli.ts`) loads config → instantiates `providers/*` → `fetcher/` pulls data → `processor/` normalizes/dedupes/validates → `output/` writes `{provider}.json` and aggregates.
+- Library build is configured via `vite.config.ts`; CLI bundle via `vite.cli.config.ts`.
+
+## Build and Development Commands
+- `pnpm install`: install dependencies.
+- `pnpm dev`: run CLI in TS via ts-node (defaults to `fetch-all`).
+- `pnpm build`: build library and CLI to `build/` using Vite.
+- `pnpm start`: run built CLI (`node build/cli.js fetch-all`).
+- `pnpm clean`: remove build artifacts.
+
+Examples:
+- Run specific providers: `node build/cli.js fetch-providers -p ppinfra,groq -o dist`
+- Local binary after build: `./build/cli.js fetch-all`
+
+## Coding Style & Naming Conventions
+- Language: TypeScript targeting Node 18; prefer ES module syntax in `src/`.
+- Linting: ESLint (`.eslintrc.json`). Fix issues before PRs.
+- Indentation: 2 spaces; no trailing whitespace; end files with newline.
+- Naming: PascalCase for classes/types, camelCase for vars/functions, kebab-case for folders/files (existing mixed names may remain until refactors).
+- Exports: prefer named exports; keep module boundaries small and focused.
+
+## Add a Provider (Quick Steps)
+- Implement `Provider` in `src/providers/yourprovider.ts` with `fetchModels()`, `providerId()`, `providerName()`.
+- Export in `src/providers/index.ts`, register in `src/cli.ts#createProvider`.
+- Add templates (if needed) under `manual-templates/`, update docs/README sections, and manually verify via CLI commands.
+
+## Commit & Pull Request Guidelines
+- Use Conventional Commits (`feat:`, `fix:`, `docs:`, `chore:`, `ci:`). Optional scopes: `feat(providers): ...`, `ci(actions): ...`.
+- PRs must include: clear description, linked issues, test updates/new tests, and notes on config/template changes. Add screenshots/log snippets for CLI output when relevant.
+
+## Agent Task Tracking
+- When you pick up a new feature or refactor, create a scratch file under `docs/` named `feat_.md`.
+- Use that file to break down subtasks, capture progress notes, and log any open questions while you work.
+- Remove the tracking file once the task is complete or handed off so `docs/` stays tidy.
+
+## Environment & Configuration
+- Provider defaults ship with the repo (`src/config/app-config.ts`).
+- API keys: only `GROQ_API_KEY` is needed for live fetching; models.dev covers OpenAI/Anthropic/OpenRouter/Gemini/etc. without extra credentials.
+- Never commit secrets.
+
+## CI Notes
+- GitHub Actions fetch workflow runs on manual dispatch and release tags, builds, fetches models, and uploads JSON artifacts.
+- Keep CI green: run `pnpm build` locally before pushing.
+
+## Output JSON Example
+```json
+{
+ "provider": "ppinfra",
+ "providerName": "PPInfra",
+ "lastUpdated": "2025-01-15T10:30:00Z",
+ "models": [
+ {
+ "id": "model-id",
+ "name": "Model Name",
+ "contextLength": 32768,
+ "maxTokens": 4096,
+ "vision": false,
+ "functionCall": true,
+ "reasoning": {
+ "supported": true,
+ "default": true
+ },
+ "type": "chat"
+ }
+ ]
+}
+```
+
+## Templates & Validation
+- Templates: add/update under `manual-templates/*.json` when normalizing names or capabilities.
+- Normalization: `processor/` trims, dedupes, sorts; keep IDs stable across runs.
+- Validation: `output/json-validator.ts` enforces schema before write; prefer failing fast.
diff --git a/CLAUDE.md b/CLAUDE.md
index b5973a9..a73e2f7 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -2,29 +2,35 @@
## Project Information
-**Public Provider Configuration Tool** - A Rust-based tool to automatically fetch and standardize AI model information from various providers.
+**Public Provider Configuration Tool** - A TypeScript/Node.js-based tool to automatically fetch and standardize AI model information from various providers.
## Quick Commands
### Development
```bash
-# Build the project
-cargo build
+# Install dependencies
+pnpm install
+
+# Build the project (uses Vite for bundling)
+pnpm build
# Run with all providers
-cargo run -- fetch-all
+pnpm start fetch-all
# Run with specific providers
-cargo run -- fetch-providers -p ppinfra,openai
+pnpm start fetch-providers -p ppinfra,tokenflux,groq,ollama,siliconflow
-# Run tests
-cargo test
+# Development mode
+pnpm run dev fetch-all
# Run with custom output directory
-cargo run -- fetch-all -o custom_output
+pnpm start fetch-all -o custom_output
+
+# Clean build artifacts
+pnpm clean
```
-### Testing
+### Manual Checks
```bash
# Test PPInfra API endpoint
curl --request GET --url https://api.ppinfra.com/openai/v1/models
@@ -40,22 +46,32 @@ du -h dist/*.json
```
├── src/
-│ ├── models/ # Data structures
+│ ├── models/ # TypeScript interfaces and types
│ ├── providers/ # Provider implementations
│ ├── fetcher/ # Data fetching logic
│ ├── output/ # Output handling
-│ └── config/ # Configuration management
+│ ├── processor/ # Data processing logic
+│ ├── config/ # Configuration management
+│ └── cli.ts # CLI entry point
├── dist/ # Generated JSON output files
-├── provider_configs/ # Git-tracked JSON files
-└── docs/ # Documentation
+├── build/ # Compiled JavaScript build output (Vite)
+├── manual-templates/ # Manually maintained provider templates
+├── config/ # Configuration files
+├── docs/ # Documentation
+└── dist_rust_backup/ # Backup of Rust version output for comparison
```
## Key Files
-- `src/main.rs` - CLI entry point
-- `src/providers/ppinfra.rs` - PPInfra API implementation
+- `src/cli.ts` - CLI entry point with Commander.js
+- `src/providers/PPInfraProvider.ts` - PPInfra API implementation
+- `src/providers/TokenfluxProvider.ts` - Tokenflux implementation
+- `src/providers/GroqProvider.ts` - Groq API implementation
+- `manual-templates/ollama.json` - Ollama template definitions
+- `manual-templates/siliconflow.json` - SiliconFlow template definitions
+- `vite.config.ts` - Vite build configuration for library bundling
- `docs/architecture-overview.md` - Complete architecture documentation
-- `.github/workflows/fetch-models.yml` - Automated fetching workflow
+- `.github/workflows/fetch-models.yml` - Automated fetching workflow (Node.js)
## Output Format
@@ -73,7 +89,10 @@ Single provider JSON:
"maxTokens": 4096,
"vision": false,
"functionCall": true,
- "reasoning": true,
+ "reasoning": {
+ "supported": true,
+ "default": true
+ },
"type": "chat"
}
]
@@ -82,38 +101,169 @@ Single provider JSON:
## Adding New Providers
-1. Create new provider file in `src/providers/`
-2. Implement the `Provider` trait
-3. Add to `src/providers/mod.rs`
-4. Register in `src/main.rs`
+### Step-by-Step Guide
+
+1. **Create Provider Implementation**
+ - Create new file in `src/providers/` (e.g., `src/providers/newprovider.ts`)
+ - Implement the `Provider` interface with required methods:
+ - `async fetchModels(): Promise`
+ - `providerId(): string`
+ - `providerName(): string`
+
+2. **Add Module Export**
+ - Add export to `src/providers/index.ts`: `export { NewProviderProvider } from './newprovider';`
+
+3. **Register in CLI**
+ - Import the provider in `src/cli.ts`: `import { NewProviderProvider } from './providers/newprovider';`
+ - Add provider initialization in the appropriate command handler
+
+4. **Update Documentation**
+ - Add JSON link to README.md "Available Model Data" section
+ - Update "Currently Supported Providers" section with provider status
+
+### Template for New Provider
+
+```typescript
+import { Provider } from './provider';
+import { ModelInfo, ModelType, createModelInfo } from '../models/model-info';
+import axios from 'axios';
+
+interface NewProviderModel {
+ id: string;
+ name: string;
+ contextSize: number;
+ maxOutputTokens: number;
+ features: string[];
+ modelType: string;
+}
+
+interface NewProviderResponse {
+ data: NewProviderModel[];
+}
+
+export class NewProviderProvider implements Provider {
+ private apiUrl: string;
+ private client = axios.create();
+
+ constructor(apiUrl: string) {
+ this.apiUrl = apiUrl;
+ }
+
+ private convertModel(model: NewProviderModel): ModelInfo {
+ const vision = model.features.some(f => f.includes('vision') || f.includes('image'));
+ const functionCall = model.features.some(f => f.includes('function') || f.includes('tool'));
+ const reasoning = model.features.some(f => f.includes('reasoning') || f.includes('thinking'));
+
+ const modelType = model.modelType.toLowerCase() === 'chat' ? ModelType.Chat :
+ model.modelType.toLowerCase() === 'completion' ? ModelType.Completion :
+ model.modelType.toLowerCase() === 'embedding' ? ModelType.Embedding :
+ ModelType.Chat;
+
+ return createModelInfo(
+ model.id,
+ model.name,
+ model.contextSize,
+ model.maxOutputTokens,
+ vision,
+ functionCall,
+ reasoning,
+ modelType
+ );
+ }
+
+ async fetchModels(): Promise {
+ try {
+ const response = await this.client.get(this.apiUrl);
+ const models = response.data.data
+ .map(model => this.convertModel(model));
+
+ return models;
+ } catch (error) {
+ throw new Error(`Failed to fetch models from NewProvider: ${error instanceof Error ? error.message : String(error)}`);
+ }
+ }
+
+ providerId(): string {
+ return 'newprovider';
+ }
+
+ providerName(): string {
+ return 'New Provider';
+ }
+}
+```
+
+### After Adding Provider
+
+The system will automatically:
+- Generate `{provider_id}.json` file in `dist/` directory
+- Include provider data in `all.json` aggregated file
+- Update GitHub Actions to fetch from new provider
+- Create downloadable JSON links in releases
## GitHub Actions
The workflow automatically:
-- Runs daily at 06:00 UTC
+- Runs on workflow dispatch (manual trigger)
+- Runs on tag push (`release-*.*.*`)
- Fetches latest model data
-- Commits updates to `provider_configs/`
-- Creates releases with downloadable JSON files
+- Uploads artifacts for manual triggers
+- Creates releases with downloadable JSON files for tag triggers
## Environment Variables
-Optional API keys can be set as GitHub secrets:
-- `OPENAI_API_KEY`
-- `OPENROUTER_API_KEY`
+Optional API keys can be set as GitHub secrets or local environment variables:
+- `GROQ_API_KEY` - Required for Groq provider (API access)
- Add others as needed
+### Provider API Key Requirements
+- **PPInfra**: ✅ No API key required - public API
+- **OpenRouter**: ✅ Supplied by models.dev - no live fetch
+- **Vercel AI Gateway**: ✅ Supplied by models.dev - no live fetch
+- **GitHub AI Models**: ✅ Supplied by models.dev - no live fetch
+- **Tokenflux**: ✅ No API key required - public marketplace API
+- **DeepSeek**: ✅ Supplied by models.dev - no live fetch
+- **Ollama**: ✅ No API key required - template-based provider
+- **SiliconFlow**: ✅ No API key required - template-based provider
+- **Gemini**: ✅ Supplied by models.dev - no live fetch
+- **Groq**: ❌ API key required - private API access only
+- **OpenAI**: ✅ Supplied by models.dev - no live fetch
+- **Anthropic**: ✅ Supplied by models.dev - no live fetch
+
## Common Issues
-- **Build failures**: Run `cargo clean && cargo build`
+- **Build failures**: Run `pnpm build` or check Vite configuration and TypeScript compilation
- **JSON validation errors**: Check API response format changes
- **Rate limiting**: Adjust rate limits in provider configurations
- **Network timeouts**: Increase timeout values in HTTP client
+- **Coverage issues**: Use `pnpm coverage` to generate test coverage reports
## Next Steps
-- [ ] Add OpenRouter provider implementation
-- [ ] Add OpenAI provider implementation
-- [ ] Add Google Gemini provider implementation
-- [ ] Implement configuration file loading
-- [ ] Add rate limiting and retry logic
-- [ ] Add comprehensive error handling
\ No newline at end of file
+- [x] Migrate from Rust to TypeScript/Node.js
+- [x] Add OpenAI provider implementation (65+ models with template matching)
+- [x] Add Anthropic provider implementation (8 Claude models with API key support)
+- [x] Implement configuration file loading
+- [x] Add OpenRouter provider implementation
+- [x] Add Google Gemini provider implementation
+- [x] Add Vercel AI Gateway provider implementation
+- [x] Add GitHub AI Models provider implementation
+- [x] Add Tokenflux provider implementation
+- [x] Add Groq provider implementation
+- [x] Add DeepSeek provider implementation
+- [x] Add Ollama provider implementation (template-based)
+- [x] Add SiliconFlow provider implementation (template-based)
+- [x] Add rate limiting and retry logic
+- [x] Add comprehensive error handling
+- [x] Implement template validation system
+- [x] Add provider health check endpoints
+- [ ] Add Azure OpenAI provider implementation
+- [ ] Add AWS Bedrock provider implementation
+- [ ] Add Cohere provider implementation
+- [ ] Add Mistral AI provider implementation
+- [ ] Add Stability AI provider implementation
+- [ ] Add Replicate provider implementation
+- [ ] Add Hugging Face provider implementation
+- [ ] Add advanced caching system
+- [ ] Add provider status monitoring
+- [ ] Add web UI for model browsing
diff --git a/Cargo.lock b/Cargo.lock
deleted file mode 100644
index 741988b..0000000
--- a/Cargo.lock
+++ /dev/null
@@ -1,2024 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 4
-
-[[package]]
-name = "addr2line"
-version = "0.24.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
-dependencies = [
- "gimli",
-]
-
-[[package]]
-name = "adler2"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
-
-[[package]]
-name = "android-tzdata"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
-
-[[package]]
-name = "android_system_properties"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "anstream"
-version = "0.6.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
-dependencies = [
- "anstyle",
- "anstyle-parse",
- "anstyle-query",
- "anstyle-wincon",
- "colorchoice",
- "is_terminal_polyfill",
- "utf8parse",
-]
-
-[[package]]
-name = "anstyle"
-version = "1.0.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
-
-[[package]]
-name = "anstyle-parse"
-version = "0.2.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
-dependencies = [
- "utf8parse",
-]
-
-[[package]]
-name = "anstyle-query"
-version = "1.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
-dependencies = [
- "windows-sys 0.60.2",
-]
-
-[[package]]
-name = "anstyle-wincon"
-version = "3.0.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
-dependencies = [
- "anstyle",
- "once_cell_polyfill",
- "windows-sys 0.60.2",
-]
-
-[[package]]
-name = "anyhow"
-version = "1.0.99"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
-
-[[package]]
-name = "async-stream"
-version = "0.3.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
-dependencies = [
- "async-stream-impl",
- "futures-core",
- "pin-project-lite",
-]
-
-[[package]]
-name = "async-stream-impl"
-version = "0.3.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "async-trait"
-version = "0.1.89"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "atomic-waker"
-version = "1.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
-
-[[package]]
-name = "autocfg"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
-
-[[package]]
-name = "backtrace"
-version = "0.3.75"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
-dependencies = [
- "addr2line",
- "cfg-if",
- "libc",
- "miniz_oxide",
- "object",
- "rustc-demangle",
- "windows-targets 0.52.6",
-]
-
-[[package]]
-name = "base64"
-version = "0.22.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
-
-[[package]]
-name = "bitflags"
-version = "2.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
-
-[[package]]
-name = "bumpalo"
-version = "3.19.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
-
-[[package]]
-name = "bytes"
-version = "1.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
-
-[[package]]
-name = "cc"
-version = "1.2.34"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc"
-dependencies = [
- "shlex",
-]
-
-[[package]]
-name = "cfg-if"
-version = "1.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
-
-[[package]]
-name = "chrono"
-version = "0.4.41"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
-dependencies = [
- "android-tzdata",
- "iana-time-zone",
- "js-sys",
- "num-traits",
- "serde",
- "wasm-bindgen",
- "windows-link",
-]
-
-[[package]]
-name = "clap"
-version = "4.5.46"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57"
-dependencies = [
- "clap_builder",
- "clap_derive",
-]
-
-[[package]]
-name = "clap_builder"
-version = "4.5.46"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41"
-dependencies = [
- "anstream",
- "anstyle",
- "clap_lex",
- "strsim",
-]
-
-[[package]]
-name = "clap_derive"
-version = "4.5.45"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6"
-dependencies = [
- "heck",
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "clap_lex"
-version = "0.7.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
-
-[[package]]
-name = "colorchoice"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
-
-[[package]]
-name = "core-foundation"
-version = "0.9.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
-dependencies = [
- "core-foundation-sys",
- "libc",
-]
-
-[[package]]
-name = "core-foundation-sys"
-version = "0.8.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
-
-[[package]]
-name = "displaydoc"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "encoding_rs"
-version = "0.8.35"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "equivalent"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
-
-[[package]]
-name = "errno"
-version = "0.3.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
-dependencies = [
- "libc",
- "windows-sys 0.60.2",
-]
-
-[[package]]
-name = "fastrand"
-version = "2.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
-
-[[package]]
-name = "fnv"
-version = "1.0.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
-
-[[package]]
-name = "foreign-types"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
-dependencies = [
- "foreign-types-shared",
-]
-
-[[package]]
-name = "foreign-types-shared"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
-
-[[package]]
-name = "form_urlencoded"
-version = "1.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
-dependencies = [
- "percent-encoding",
-]
-
-[[package]]
-name = "futures-channel"
-version = "0.3.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
-dependencies = [
- "futures-core",
-]
-
-[[package]]
-name = "futures-core"
-version = "0.3.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
-
-[[package]]
-name = "futures-sink"
-version = "0.3.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
-
-[[package]]
-name = "futures-task"
-version = "0.3.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
-
-[[package]]
-name = "futures-util"
-version = "0.3.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
-dependencies = [
- "futures-core",
- "futures-task",
- "pin-project-lite",
- "pin-utils",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.2.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi 0.11.1+wasi-snapshot-preview1",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
-dependencies = [
- "cfg-if",
- "libc",
- "r-efi",
- "wasi 0.14.3+wasi-0.2.4",
-]
-
-[[package]]
-name = "gimli"
-version = "0.31.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
-
-[[package]]
-name = "h2"
-version = "0.4.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
-dependencies = [
- "atomic-waker",
- "bytes",
- "fnv",
- "futures-core",
- "futures-sink",
- "http",
- "indexmap",
- "slab",
- "tokio",
- "tokio-util",
- "tracing",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.15.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
-
-[[package]]
-name = "heck"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
-
-[[package]]
-name = "http"
-version = "1.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
-dependencies = [
- "bytes",
- "fnv",
- "itoa",
-]
-
-[[package]]
-name = "http-body"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
-dependencies = [
- "bytes",
- "http",
-]
-
-[[package]]
-name = "http-body-util"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
-dependencies = [
- "bytes",
- "futures-core",
- "http",
- "http-body",
- "pin-project-lite",
-]
-
-[[package]]
-name = "httparse"
-version = "1.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
-
-[[package]]
-name = "hyper"
-version = "1.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e"
-dependencies = [
- "atomic-waker",
- "bytes",
- "futures-channel",
- "futures-core",
- "h2",
- "http",
- "http-body",
- "httparse",
- "itoa",
- "pin-project-lite",
- "pin-utils",
- "smallvec",
- "tokio",
- "want",
-]
-
-[[package]]
-name = "hyper-rustls"
-version = "0.27.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
-dependencies = [
- "http",
- "hyper",
- "hyper-util",
- "rustls",
- "rustls-pki-types",
- "tokio",
- "tokio-rustls",
- "tower-service",
-]
-
-[[package]]
-name = "hyper-tls"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
-dependencies = [
- "bytes",
- "http-body-util",
- "hyper",
- "hyper-util",
- "native-tls",
- "tokio",
- "tokio-native-tls",
- "tower-service",
-]
-
-[[package]]
-name = "hyper-util"
-version = "0.1.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
-dependencies = [
- "base64",
- "bytes",
- "futures-channel",
- "futures-core",
- "futures-util",
- "http",
- "http-body",
- "hyper",
- "ipnet",
- "libc",
- "percent-encoding",
- "pin-project-lite",
- "socket2",
- "system-configuration",
- "tokio",
- "tower-service",
- "tracing",
- "windows-registry",
-]
-
-[[package]]
-name = "iana-time-zone"
-version = "0.1.63"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
-dependencies = [
- "android_system_properties",
- "core-foundation-sys",
- "iana-time-zone-haiku",
- "js-sys",
- "log",
- "wasm-bindgen",
- "windows-core",
-]
-
-[[package]]
-name = "iana-time-zone-haiku"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
-dependencies = [
- "cc",
-]
-
-[[package]]
-name = "icu_collections"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
-dependencies = [
- "displaydoc",
- "potential_utf",
- "yoke",
- "zerofrom",
- "zerovec",
-]
-
-[[package]]
-name = "icu_locale_core"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
-dependencies = [
- "displaydoc",
- "litemap",
- "tinystr",
- "writeable",
- "zerovec",
-]
-
-[[package]]
-name = "icu_normalizer"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
-dependencies = [
- "displaydoc",
- "icu_collections",
- "icu_normalizer_data",
- "icu_properties",
- "icu_provider",
- "smallvec",
- "zerovec",
-]
-
-[[package]]
-name = "icu_normalizer_data"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
-
-[[package]]
-name = "icu_properties"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
-dependencies = [
- "displaydoc",
- "icu_collections",
- "icu_locale_core",
- "icu_properties_data",
- "icu_provider",
- "potential_utf",
- "zerotrie",
- "zerovec",
-]
-
-[[package]]
-name = "icu_properties_data"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
-
-[[package]]
-name = "icu_provider"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
-dependencies = [
- "displaydoc",
- "icu_locale_core",
- "stable_deref_trait",
- "tinystr",
- "writeable",
- "yoke",
- "zerofrom",
- "zerotrie",
- "zerovec",
-]
-
-[[package]]
-name = "idna"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
-dependencies = [
- "idna_adapter",
- "smallvec",
- "utf8_iter",
-]
-
-[[package]]
-name = "idna_adapter"
-version = "1.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
-dependencies = [
- "icu_normalizer",
- "icu_properties",
-]
-
-[[package]]
-name = "indexmap"
-version = "2.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9"
-dependencies = [
- "equivalent",
- "hashbrown",
-]
-
-[[package]]
-name = "io-uring"
-version = "0.7.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
-dependencies = [
- "bitflags",
- "cfg-if",
- "libc",
-]
-
-[[package]]
-name = "ipnet"
-version = "2.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
-
-[[package]]
-name = "iri-string"
-version = "0.7.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2"
-dependencies = [
- "memchr",
- "serde",
-]
-
-[[package]]
-name = "is_terminal_polyfill"
-version = "1.70.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
-
-[[package]]
-name = "itoa"
-version = "1.0.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
-
-[[package]]
-name = "js-sys"
-version = "0.3.77"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
-dependencies = [
- "once_cell",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "libc"
-version = "0.2.175"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
-
-[[package]]
-name = "linux-raw-sys"
-version = "0.9.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
-
-[[package]]
-name = "litemap"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
-
-[[package]]
-name = "lock_api"
-version = "0.4.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
-dependencies = [
- "autocfg",
- "scopeguard",
-]
-
-[[package]]
-name = "log"
-version = "0.4.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
-
-[[package]]
-name = "memchr"
-version = "2.7.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
-
-[[package]]
-name = "mime"
-version = "0.3.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
-
-[[package]]
-name = "miniz_oxide"
-version = "0.8.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
-dependencies = [
- "adler2",
-]
-
-[[package]]
-name = "mio"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
-dependencies = [
- "libc",
- "wasi 0.11.1+wasi-snapshot-preview1",
- "windows-sys 0.59.0",
-]
-
-[[package]]
-name = "native-tls"
-version = "0.2.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
-dependencies = [
- "libc",
- "log",
- "openssl",
- "openssl-probe",
- "openssl-sys",
- "schannel",
- "security-framework",
- "security-framework-sys",
- "tempfile",
-]
-
-[[package]]
-name = "num-traits"
-version = "0.2.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
-dependencies = [
- "autocfg",
-]
-
-[[package]]
-name = "object"
-version = "0.36.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "once_cell"
-version = "1.21.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
-
-[[package]]
-name = "once_cell_polyfill"
-version = "1.70.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
-
-[[package]]
-name = "openssl"
-version = "0.10.73"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
-dependencies = [
- "bitflags",
- "cfg-if",
- "foreign-types",
- "libc",
- "once_cell",
- "openssl-macros",
- "openssl-sys",
-]
-
-[[package]]
-name = "openssl-macros"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "openssl-probe"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
-
-[[package]]
-name = "openssl-sys"
-version = "0.9.109"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
-dependencies = [
- "cc",
- "libc",
- "pkg-config",
- "vcpkg",
-]
-
-[[package]]
-name = "parking_lot"
-version = "0.12.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
-dependencies = [
- "lock_api",
- "parking_lot_core",
-]
-
-[[package]]
-name = "parking_lot_core"
-version = "0.9.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
-dependencies = [
- "cfg-if",
- "libc",
- "redox_syscall",
- "smallvec",
- "windows-targets 0.52.6",
-]
-
-[[package]]
-name = "percent-encoding"
-version = "2.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
-
-[[package]]
-name = "pin-project-lite"
-version = "0.2.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
-
-[[package]]
-name = "pin-utils"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
-
-[[package]]
-name = "pkg-config"
-version = "0.3.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
-
-[[package]]
-name = "potential_utf"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a"
-dependencies = [
- "zerovec",
-]
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.101"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "public-provider-conf"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "async-trait",
- "chrono",
- "clap",
- "reqwest",
- "serde",
- "serde_json",
- "tokio",
- "tokio-test",
- "toml",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.40"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "r-efi"
-version = "5.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
-
-[[package]]
-name = "redox_syscall"
-version = "0.5.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
-dependencies = [
- "bitflags",
-]
-
-[[package]]
-name = "reqwest"
-version = "0.12.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb"
-dependencies = [
- "base64",
- "bytes",
- "encoding_rs",
- "futures-core",
- "h2",
- "http",
- "http-body",
- "http-body-util",
- "hyper",
- "hyper-rustls",
- "hyper-tls",
- "hyper-util",
- "js-sys",
- "log",
- "mime",
- "native-tls",
- "percent-encoding",
- "pin-project-lite",
- "rustls-pki-types",
- "serde",
- "serde_json",
- "serde_urlencoded",
- "sync_wrapper",
- "tokio",
- "tokio-native-tls",
- "tower",
- "tower-http",
- "tower-service",
- "url",
- "wasm-bindgen",
- "wasm-bindgen-futures",
- "web-sys",
-]
-
-[[package]]
-name = "ring"
-version = "0.17.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
-dependencies = [
- "cc",
- "cfg-if",
- "getrandom 0.2.16",
- "libc",
- "untrusted",
- "windows-sys 0.52.0",
-]
-
-[[package]]
-name = "rustc-demangle"
-version = "0.1.26"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
-
-[[package]]
-name = "rustix"
-version = "1.0.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
-dependencies = [
- "bitflags",
- "errno",
- "libc",
- "linux-raw-sys",
- "windows-sys 0.60.2",
-]
-
-[[package]]
-name = "rustls"
-version = "0.23.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc"
-dependencies = [
- "once_cell",
- "rustls-pki-types",
- "rustls-webpki",
- "subtle",
- "zeroize",
-]
-
-[[package]]
-name = "rustls-pki-types"
-version = "1.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
-dependencies = [
- "zeroize",
-]
-
-[[package]]
-name = "rustls-webpki"
-version = "0.103.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
-dependencies = [
- "ring",
- "rustls-pki-types",
- "untrusted",
-]
-
-[[package]]
-name = "rustversion"
-version = "1.0.22"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
-
-[[package]]
-name = "ryu"
-version = "1.0.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
-
-[[package]]
-name = "schannel"
-version = "0.1.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
-dependencies = [
- "windows-sys 0.59.0",
-]
-
-[[package]]
-name = "scopeguard"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
-
-[[package]]
-name = "security-framework"
-version = "2.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
-dependencies = [
- "bitflags",
- "core-foundation",
- "core-foundation-sys",
- "libc",
- "security-framework-sys",
-]
-
-[[package]]
-name = "security-framework-sys"
-version = "2.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
-dependencies = [
- "core-foundation-sys",
- "libc",
-]
-
-[[package]]
-name = "serde"
-version = "1.0.219"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
-dependencies = [
- "serde_derive",
-]
-
-[[package]]
-name = "serde_derive"
-version = "1.0.219"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "serde_json"
-version = "1.0.143"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
-dependencies = [
- "itoa",
- "memchr",
- "ryu",
- "serde",
-]
-
-[[package]]
-name = "serde_spanned"
-version = "0.6.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "serde_urlencoded"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
-dependencies = [
- "form_urlencoded",
- "itoa",
- "ryu",
- "serde",
-]
-
-[[package]]
-name = "shlex"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
-
-[[package]]
-name = "signal-hook-registry"
-version = "1.4.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "slab"
-version = "0.4.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
-
-[[package]]
-name = "smallvec"
-version = "1.15.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
-
-[[package]]
-name = "socket2"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
-dependencies = [
- "libc",
- "windows-sys 0.59.0",
-]
-
-[[package]]
-name = "stable_deref_trait"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
-
-[[package]]
-name = "strsim"
-version = "0.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
-
-[[package]]
-name = "subtle"
-version = "2.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
-
-[[package]]
-name = "syn"
-version = "2.0.106"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "sync_wrapper"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
-dependencies = [
- "futures-core",
-]
-
-[[package]]
-name = "synstructure"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "system-configuration"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
-dependencies = [
- "bitflags",
- "core-foundation",
- "system-configuration-sys",
-]
-
-[[package]]
-name = "system-configuration-sys"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
-dependencies = [
- "core-foundation-sys",
- "libc",
-]
-
-[[package]]
-name = "tempfile"
-version = "3.21.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e"
-dependencies = [
- "fastrand",
- "getrandom 0.3.3",
- "once_cell",
- "rustix",
- "windows-sys 0.60.2",
-]
-
-[[package]]
-name = "tinystr"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
-dependencies = [
- "displaydoc",
- "zerovec",
-]
-
-[[package]]
-name = "tokio"
-version = "1.47.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
-dependencies = [
- "backtrace",
- "bytes",
- "io-uring",
- "libc",
- "mio",
- "parking_lot",
- "pin-project-lite",
- "signal-hook-registry",
- "slab",
- "socket2",
- "tokio-macros",
- "windows-sys 0.59.0",
-]
-
-[[package]]
-name = "tokio-macros"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "tokio-native-tls"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
-dependencies = [
- "native-tls",
- "tokio",
-]
-
-[[package]]
-name = "tokio-rustls"
-version = "0.26.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
-dependencies = [
- "rustls",
- "tokio",
-]
-
-[[package]]
-name = "tokio-stream"
-version = "0.1.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
-dependencies = [
- "futures-core",
- "pin-project-lite",
- "tokio",
-]
-
-[[package]]
-name = "tokio-test"
-version = "0.4.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7"
-dependencies = [
- "async-stream",
- "bytes",
- "futures-core",
- "tokio",
- "tokio-stream",
-]
-
-[[package]]
-name = "tokio-util"
-version = "0.7.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
-dependencies = [
- "bytes",
- "futures-core",
- "futures-sink",
- "pin-project-lite",
- "tokio",
-]
-
-[[package]]
-name = "toml"
-version = "0.8.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
-dependencies = [
- "serde",
- "serde_spanned",
- "toml_datetime",
- "toml_edit",
-]
-
-[[package]]
-name = "toml_datetime"
-version = "0.6.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "toml_edit"
-version = "0.22.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
-dependencies = [
- "indexmap",
- "serde",
- "serde_spanned",
- "toml_datetime",
- "toml_write",
- "winnow",
-]
-
-[[package]]
-name = "toml_write"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
-
-[[package]]
-name = "tower"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
-dependencies = [
- "futures-core",
- "futures-util",
- "pin-project-lite",
- "sync_wrapper",
- "tokio",
- "tower-layer",
- "tower-service",
-]
-
-[[package]]
-name = "tower-http"
-version = "0.6.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
-dependencies = [
- "bitflags",
- "bytes",
- "futures-util",
- "http",
- "http-body",
- "iri-string",
- "pin-project-lite",
- "tower",
- "tower-layer",
- "tower-service",
-]
-
-[[package]]
-name = "tower-layer"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
-
-[[package]]
-name = "tower-service"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
-
-[[package]]
-name = "tracing"
-version = "0.1.41"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
-dependencies = [
- "pin-project-lite",
- "tracing-core",
-]
-
-[[package]]
-name = "tracing-core"
-version = "0.1.34"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
-dependencies = [
- "once_cell",
-]
-
-[[package]]
-name = "try-lock"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
-
-[[package]]
-name = "untrusted"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
-
-[[package]]
-name = "url"
-version = "2.5.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
-dependencies = [
- "form_urlencoded",
- "idna",
- "percent-encoding",
- "serde",
-]
-
-[[package]]
-name = "utf8_iter"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
-
-[[package]]
-name = "utf8parse"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
-
-[[package]]
-name = "vcpkg"
-version = "0.2.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
-
-[[package]]
-name = "want"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
-dependencies = [
- "try-lock",
-]
-
-[[package]]
-name = "wasi"
-version = "0.11.1+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
-
-[[package]]
-name = "wasi"
-version = "0.14.3+wasi-0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95"
-dependencies = [
- "wit-bindgen",
-]
-
-[[package]]
-name = "wasm-bindgen"
-version = "0.2.100"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
-dependencies = [
- "cfg-if",
- "once_cell",
- "rustversion",
- "wasm-bindgen-macro",
-]
-
-[[package]]
-name = "wasm-bindgen-backend"
-version = "0.2.100"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
-dependencies = [
- "bumpalo",
- "log",
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-futures"
-version = "0.4.50"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
-dependencies = [
- "cfg-if",
- "js-sys",
- "once_cell",
- "wasm-bindgen",
- "web-sys",
-]
-
-[[package]]
-name = "wasm-bindgen-macro"
-version = "0.2.100"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
-dependencies = [
- "quote",
- "wasm-bindgen-macro-support",
-]
-
-[[package]]
-name = "wasm-bindgen-macro-support"
-version = "0.2.100"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-backend",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-shared"
-version = "0.2.100"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "web-sys"
-version = "0.3.77"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
-dependencies = [
- "js-sys",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "windows-core"
-version = "0.61.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
-dependencies = [
- "windows-implement",
- "windows-interface",
- "windows-link",
- "windows-result",
- "windows-strings",
-]
-
-[[package]]
-name = "windows-implement"
-version = "0.60.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "windows-interface"
-version = "0.59.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "windows-link"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
-
-[[package]]
-name = "windows-registry"
-version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
-dependencies = [
- "windows-link",
- "windows-result",
- "windows-strings",
-]
-
-[[package]]
-name = "windows-result"
-version = "0.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
-dependencies = [
- "windows-link",
-]
-
-[[package]]
-name = "windows-strings"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
-dependencies = [
- "windows-link",
-]
-
-[[package]]
-name = "windows-sys"
-version = "0.52.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
-dependencies = [
- "windows-targets 0.52.6",
-]
-
-[[package]]
-name = "windows-sys"
-version = "0.59.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
-dependencies = [
- "windows-targets 0.52.6",
-]
-
-[[package]]
-name = "windows-sys"
-version = "0.60.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
-dependencies = [
- "windows-targets 0.53.3",
-]
-
-[[package]]
-name = "windows-targets"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
-dependencies = [
- "windows_aarch64_gnullvm 0.52.6",
- "windows_aarch64_msvc 0.52.6",
- "windows_i686_gnu 0.52.6",
- "windows_i686_gnullvm 0.52.6",
- "windows_i686_msvc 0.52.6",
- "windows_x86_64_gnu 0.52.6",
- "windows_x86_64_gnullvm 0.52.6",
- "windows_x86_64_msvc 0.52.6",
-]
-
-[[package]]
-name = "windows-targets"
-version = "0.53.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
-dependencies = [
- "windows-link",
- "windows_aarch64_gnullvm 0.53.0",
- "windows_aarch64_msvc 0.53.0",
- "windows_i686_gnu 0.53.0",
- "windows_i686_gnullvm 0.53.0",
- "windows_i686_msvc 0.53.0",
- "windows_x86_64_gnu 0.53.0",
- "windows_x86_64_gnullvm 0.53.0",
- "windows_x86_64_msvc 0.53.0",
-]
-
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
-
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.53.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
-
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
-
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.53.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.53.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
-
-[[package]]
-name = "windows_i686_gnullvm"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
-
-[[package]]
-name = "windows_i686_gnullvm"
-version = "0.53.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.53.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
-
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
-
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.53.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.53.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.53.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
-
-[[package]]
-name = "winnow"
-version = "0.7.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "wit-bindgen"
-version = "0.45.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814"
-
-[[package]]
-name = "writeable"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
-
-[[package]]
-name = "yoke"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
-dependencies = [
- "serde",
- "stable_deref_trait",
- "yoke-derive",
- "zerofrom",
-]
-
-[[package]]
-name = "yoke-derive"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "synstructure",
-]
-
-[[package]]
-name = "zerofrom"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
-dependencies = [
- "zerofrom-derive",
-]
-
-[[package]]
-name = "zerofrom-derive"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "synstructure",
-]
-
-[[package]]
-name = "zeroize"
-version = "1.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
-
-[[package]]
-name = "zerotrie"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
-dependencies = [
- "displaydoc",
- "yoke",
- "zerofrom",
-]
-
-[[package]]
-name = "zerovec"
-version = "0.11.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
-dependencies = [
- "yoke",
- "zerofrom",
- "zerovec-derive",
-]
-
-[[package]]
-name = "zerovec-derive"
-version = "0.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
diff --git a/Cargo.toml b/Cargo.toml
deleted file mode 100644
index 5b133b0..0000000
--- a/Cargo.toml
+++ /dev/null
@@ -1,18 +0,0 @@
-[package]
-name = "public-provider-conf"
-version = "0.1.0"
-edition = "2021"
-
-[dependencies]
-tokio = { version = "1.0", features = ["full"] }
-serde = { version = "1.0", features = ["derive"] }
-serde_json = "1.0"
-reqwest = { version = "0.12", features = ["json"] }
-anyhow = "1.0"
-clap = { version = "4.0", features = ["derive"] }
-async-trait = "0.1"
-chrono = { version = "0.4", features = ["serde"] }
-toml = "0.8"
-
-[dev-dependencies]
-tokio-test = "0.4"
diff --git a/README.md b/README.md
index a35d041..69379ab 100644
--- a/README.md
+++ b/README.md
@@ -1,304 +1,122 @@
# PublicProviderConf
-Automated tool to fetch AI model information from various providers (PPInfra, OpenRouter, OpenAI, Google, etc.) and generate standardized JSON files for easy consumption by chatbots and other applications.
+PublicProviderConf is a TypeScript CLI and library that aggregates the canonical [models.dev catalog](https://models.dev/api.json) together with several custom provider integrations (PPInfra, TokenFlux, Groq, Qiniu-hosted snapshots, and others). The tool normalizes capabilities, fills in missing metadata, and emits standardized JSON payloads that downstream apps can consume without bespoke adapters.
-## ✨ Features
+## Highlights
+- Unified JSON schema for every provider with consistent capability flags
+- Concurrent fetcher pipeline that merges live APIs with maintained templates
+- Configurable CLI built with Commander + Vite for both dev (ts-node) and production builds
+- Automated GitHub Actions workflow that can publish fresh `dist/` artifacts and sync them to CDN storage
-- 🤖 **Standardized Format**: Unified JSON output format for easy chatbot parsing
-- 🔄 **Auto Detection**: Intelligent detection of model capabilities (vision, function calling, reasoning)
-- 🌐 **Multi-Provider Support**: Extensible architecture supporting multiple AI model providers
-- ⚡ **Concurrent Fetching**: Efficient concurrent data retrieval from multiple providers
-- 🎯 **Aggregated Output**: Generate both individual provider files and complete aggregated files
-- 🚀 **GitHub Actions**: Automated scheduled updates for model information
+## Data Sources & Coverage
+The aggregated dataset starts with the upstream `https://models.dev/api.json`. During each run we overlay:
+- Provider overrides from `manual-templates/`
+- Live fetchers for operators that are not (yet) covered by models.dev, such as `ppinfra`, `tokenflux`, and `groq`
+- Lightweight snapshots for ecosystems like `ollama` and `siliconflow`
-## 📦 Installation
+After post-processing, the CLI writes the final catalog to `dist/`. Key outputs include:
+- `dist/all.json` – aggregated providers, model counts, and capability rollups
+- `dist/{provider}.json` – normalized payload for each individual provider
+
+Consumers can point CDN tooling at the `dist/` directory (see GitHub Actions workflow) to publish the latest data for public access.
+
+## Getting Started
### Prerequisites
-- Rust 1.70+
-- Cargo
+- Node.js 18+
+- pnpm 8+
-### Build
+### Install & Build
```bash
-git clone https://github.com/your-repo/PublicProviderConf.git
+git clone https://github.com/ThinkInAIXYZ/PublicProviderConf.git
cd PublicProviderConf
-cargo build --release
+pnpm install
+pnpm build
```
+`pnpm build` runs both Vite targets: the library bundle under `build/` and the bundled CLI at `build/cli.js`.
-## 🚀 Usage
+## CLI Usage
-### Basic Usage
-
-Fetch model information from all providers:
+### Development Flow
```bash
-cargo run -- fetch-all
+pnpm run dev # ts-node, defaults to fetch-all
+ts-node src/cli.ts fetch-all # explicit command
+ts-node src/cli.ts fetch-providers -p ppinfra,tokenflux
```
-Specify output directory:
+### Production / Bundled CLI
```bash
-cargo run -- fetch-all -o ./output
+pnpm build
+pnpm start # equivalent to node build/cli.js fetch-all
+node build/cli.js fetch-providers -p ppinfra,tokenflux -o ./dist
```
-Fetch from specific providers:
+### Global Installation (optional)
```bash
-cargo run -- fetch-providers -p ppinfra,openai
+pnpm install -g .
+public-provider-conf fetch-all
+public-provider-conf fetch-providers -p ppinfra,tokenflux
```
-### CLI Options
-
+### Command Reference
```bash
-# Fetch from all providers
-cargo run -- fetch-all [OPTIONS]
-
-# Fetch from specific providers
-cargo run -- fetch-providers -p [OPTIONS]
+public-provider-conf fetch-all [options]
+public-provider-conf fetch-providers -p [options]
Options:
- -o, --output