fix: Update runner Docker socket configuration and Gitea instance URL

- Changed docker-compose.yml to mount OrbStack Docker socket path (/Users/brunogao/.orbstack/run/docker.sock)
- Updated GITEA_INSTANCE_URL to use 192.168.1.83:33000 instead of gitea:3000 for proper DNS resolution
- Both runner-docker and runner-k8s now correctly point to OrbStack Docker daemon
- This resolves the 'Cannot connect to the Docker daemon' error and DNS resolution failures
This commit is contained in:
2025-11-13 17:59:28 +08:00
parent 24bd7e93eb
commit 6ebfb24fd9
2195 changed files with 164168 additions and 17 deletions
File diff suppressed because one or more lines are too long
+8
View File
@@ -0,0 +1,8 @@
# Environment variables for AI CLI
# Copy this file to .env and update with your actual keys
# OpenRouter API Key
OPENROUTER_API_KEY=sk-or-xxx
# Ollama settings (if using)
OLLAMA_HOST=http://localhost:11434
+187
View File
@@ -0,0 +1,187 @@
# LJWX AI CLI - Your Intelligent Development Companion
LJWX AI CLI is a powerful command-line tool that brings AI assistance directly to your terminal. It supports multiple AI providers (OpenRouter, Ollama) and maintains context across sessions, making it perfect for sustained development work.
## Features
- **Multi-Provider Support**: Works with OpenRouter (Claude 4.5*, GPT-4, etc.) and Ollama (local models)
- **Persistent Context**: Maintains conversation history across CLI sessions
- **Project Awareness**: Automatically reads git diffs, file trees, and project structure
- **Multiple Commands**: Chat, edit, review, plan, and configure
- **Claude-Style Interface**: Familiar experience similar to Claude Code
*Note: Claude 4.5 models may require special access. Claude 3.5 models are generally available.
## Installation
### Quick Install (Recommended)
1. Clone or download this repository
2. Run the installation script:
```bash
chmod +x install.sh
./install.sh
```
The installation script will:
- Install Python dependencies
- Create a symlink to make `ljwx-ai` globally available
- Provide instructions to update your shell configuration
### Manual Installation
1. Clone or download this repository
2. Make the CLI executable:
```bash
chmod +x ljwx-ai
```
3. Install Python dependencies:
```bash
pip3 install -r requirements.txt --break-system-packages
```
4. Make globally available by adding to your PATH:
- Add the script's directory to your PATH in `~/.zshrc` or `~/.bashrc`:
```bash
export PATH="$PATH:/path/to/ljwx-ai/directory"
```
- Or create a symlink in a directory already in PATH:
```bash
ln -s /full/path/to/ljwx-ai ~/.local/bin/ljwx-ai
```
## Setup
Initialize the CLI with default configuration:
```bash
./ljwx-ai init
```
This creates a configuration file at `~/.ai-cli/config.yaml`.
### Configuration
Update your `~/.ai-cli/config.yaml` with your API keys:
```yaml
default_provider: openrouter
default_model: anthropic/claude-sonnet-4.5 # Note: Claude 4.5 may require special access
max_context_messages: 10
providers:
openrouter:
base_url: https://openrouter.ai/api/v1/chat/completions
api_key: sk-or-xxx # Replace with your OpenRouter API key
ollama:
base_url: http://192.168.1.83:11434/api/chat # Internal IP address
model: qwen3-coder:30b-a3b-q8_0
```
**Note on Claude 4.5 Access**: The Claude 4.5 models (sonnet-4.5 and haiku-4.5) may be in restricted access. If you encounter issues accessing them, consider using Claude 3.5 models as alternatives:
- `anthropic/claude-3.5-sonnet`
- `anthropic/claude-3.5-haiku`
**Note on Ollama Configuration**: The default Ollama configuration uses internal IP address 192.168.1.83 and the qwen3-coder:30b-a3b-q8_0 model. Update these values based on your Ollama setup.
## Usage
### Chat
Chat with the AI assistant:
```bash
./ljwx-ai chat "Explain how this function works"
```
### Edit
Get AI assistance for editing files:
```bash
./ljwx-ai edit src/main.py
./ljwx-ai edit src/main.py --prompt "Refactor this to improve performance"
```
### Review
Review code for issues and improvements:
```bash
./ljwx-ai review src/
./ljwx-ai review src/main.py --prompt "Check for security issues"
```
### Plan
Generate development plans:
```bash
./ljwx-ai plan "Implement user authentication system"
```
### Configuration Management
Manage CLI configuration:
```bash
./ljwx-ai config list # List current configuration
# For Claude 4.5 Haiku (if available):
./ljwx-ai config set model anthropic/claude-haiku-4.5
# Or for Claude 4.5 Sonnet (if available):
# ./ljwx-ai config set model anthropic/claude-sonnet-4.5
# Or for generally available Claude 3.5 Sonnet (recommended):
./ljwx-ai config set model anthropic/claude-3.5-sonnet
./ljwx-ai config reset # Reset configuration
```
### Provider Override
You can override the default provider/model for any command:
```bash
./ai --provider ollama chat "Explain this Python code"
./ai --model anthropic/claude-4.5-haiku chat "Short response needed"
```
## Commands
### init
Initialize configuration and cache directories.
### chat
Start a conversation with the AI assistant. The AI has access to your project context.
### edit
Get AI assistance for editing specific files or directories.
### review
Review code for potential issues, improvements, and best practices.
### plan
Generate detailed development plans for features or tasks.
### config
Manage configuration settings.
## Context Management
The CLI maintains conversation context in `.aicache/context.json` in your project directory. This allows for sustained conversations with memory of previous interactions.
## Project Awareness
When interacting with the AI, it automatically understands your project structure:
- Git diffs
- File tree
- Current working directory
## Providers
### OpenRouter
Supports Claude 4.5* (Sonnet, Haiku), GPT-4, and many other models through OpenRouter's unified API.
*Note: Claude 4.5 models may require special access from OpenRouter. Claude 3.5 models (sonnet, haiku) are generally available.
### Ollama
Run local models through Ollama for privacy-focused development.
## Contributing
Contributions are welcome! Feel free to submit issues and pull requests.
## License
MIT License
+159
View File
@@ -0,0 +1,159 @@
# LJWX AI CLI - Your Intelligent Development Companion
LJWX AI CLI is a powerful command-line tool that brings AI assistance directly to your terminal. It supports multiple AI providers (OpenRouter, Ollama) and maintains context across sessions, making it perfect for sustained development work.
## Features
- **Multi-Provider Support**: Works with OpenRouter (Claude 4.5*, GPT-4, etc.) and Ollama (local models)
- **Persistent Context**: Maintains conversation history across CLI sessions
- **Project Awareness**: Automatically reads git diffs, file trees, and project structure
- **Multiple Commands**: Chat, edit, review, plan, and configure
- **Claude-Style Interface**: Familiar experience similar to Claude Code
*Note: Claude 4.5 models may require special access. Claude 3.5 models are generally available.
## Installation
1. Clone or download this repository
2. Make the CLI executable:
```bash
chmod +x ai
```
3. Install Python dependencies:
```bash
pip install -r requirements.txt
```
## Setup
Initialize the CLI with default configuration:
```bash
./ai init
```
This creates a configuration file at `~/.ai-cli/config.yaml`.
### Configuration
Update your `~/.ai-cli/config.yaml` with your API keys:
```yaml
default_provider: openrouter
default_model: anthropic/claude-sonnet-4.5 # Note: Claude 4.5 may require special access
max_context_messages: 10
providers:
openrouter:
base_url: https://openrouter.ai/api/v1/chat/completions
api_key: sk-or-xxx # Replace with your OpenRouter API key
ollama:
base_url: http://localhost:11434/api/chat
model: qwen2.5-coder:32b
```
**Note on Claude 4.5 Access**: The Claude 4.5 models (sonnet-4.5 and haiku-4.5) may be in restricted access. If you encounter issues accessing them, consider using Claude 3.5 models as alternatives:
- `anthropic/claude-3.5-sonnet`
- `anthropic/claude-3.5-haiku`
## Usage
### Chat
Chat with the AI assistant:
```bash
./ai chat "Explain how this function works"
```
### Edit
Get AI assistance for editing files:
```bash
./ai edit src/main.py
./ai edit src/main.py --prompt "Refactor this to improve performance"
```
### Review
Review code for issues and improvements:
```bash
./ai review src/
./ai review src/main.py --prompt "Check for security issues"
```
### Plan
Generate development plans:
```bash
./ai plan "Implement user authentication system"
```
### Configuration Management
Manage CLI configuration:
```bash
./ai config list # List current configuration
./ai config set model anthropic/claude-haiku-4.5 # Change to Claude 4.5 Haiku (if available)
# Or for Claude 4.5 Sonnet:
# ./ai config set model anthropic/claude-sonnet-4.5
# Or for generally available Claude 3.5:
# ./ai config set model anthropic/claude-3.5-sonnet
./ai config reset # Reset configuration
```
### Provider Override
You can override the default provider/model for any command:
```bash
./ai --provider ollama chat "Explain this Python code"
./ai --model anthropic/claude-4.5-haiku chat "Short response needed"
```
## Commands
### init
Initialize configuration and cache directories.
### chat
Start a conversation with the AI assistant. The AI has access to your project context.
### edit
Get AI assistance for editing specific files or directories.
### review
Review code for potential issues, improvements, and best practices.
### plan
Generate detailed development plans for features or tasks.
### config
Manage configuration settings.
## Context Management
The CLI maintains conversation context in `.aicache/context.json` in your project directory. This allows for sustained conversations with memory of previous interactions.
## Project Awareness
When interacting with the AI, it automatically understands your project structure:
- Git diffs
- File tree
- Current working directory
## Providers
### OpenRouter
Supports Claude 4.5* (Sonnet, Haiku), GPT-4, and many other models through OpenRouter's unified API.
*Note: Claude 4.5 models may require special access from OpenRouter. Claude 3.5 models (sonnet, haiku) are generally available.
### Ollama
Run local models through Ollama for privacy-focused development.
## Contributing
Contributions are welcome! Feel free to submit issues and pull requests.
## License
MIT License
Submodule ai/ai-cli/cc-switch added at b617879035
+10
View File
@@ -0,0 +1,10 @@
default_provider: openrouter
default_model: anthropic/claude-haiku-4.5
max_context_messages: 10
providers:
openrouter:
base_url: https://openrouter.ai/api/v1/chat/completions
api_key: sk-or-xxx # Replace with your OpenRouter API key
ollama:
base_url: http://192.168.1.83:11434/api/chat
model: qwen3-coder:30b-a3b-q8_0
+126
View File
@@ -0,0 +1,126 @@
#!/usr/bin/env python3
"""Debug script to test OpenRouter API call."""
import requests
import os
import yaml
from pathlib import Path
import sys
# Add src to path for imports
sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
from src.utils.config import load_config, get_provider_config
from src.providers.base import OpenRouterProvider
def test_api_call():
"""Test the OpenRouter API directly."""
print("🔍 Testing OpenRouter API call...")
# Load config
config = load_config()
if not config:
print("❌ Configuration not found")
return
provider_config = get_provider_config('openrouter')
if not provider_config:
print("❌ OpenRouter provider config not found")
return
# Check API key
api_key = provider_config.get('api_key', 'sk-or-xxx')
if api_key == 'sk-or-xxx' or not api_key:
print("❌ No valid API key found in config")
print(" Please update your config.yaml file or set OPENROUTER_API_KEY environment variable")
return
print(f"✅ API key found (first 8 chars: {api_key[:8]}...)")
# Check model - environment variable takes precedence over config file
env_model = os.environ.get('OPENROUTER_MODEL')
model = env_model if env_model else config.get('default_model', 'anthropic/claude-4.5-sonnet')
print(f"✅ Using model: {model} (from {'env' if env_model else 'config'})")
# Test the API call directly
headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json',
'HTTP-Referer': 'https://github.com/your-username/ai-cli',
'X-Title': 'AI CLI Tool'
}
# Simple test message
messages = [
{"role": "user", "content": "Hello, test message!"}
]
payload = {
'model': model,
'messages': messages,
}
print(f"📡 Sending request to: {provider_config['base_url']}")
print(f"📦 Payload: {payload}")
try:
response = requests.post(provider_config['base_url'], headers=headers, json=payload)
print(f"📥 Response Status: {response.status_code}")
print(f"📥 Response Headers: {dict(response.headers)}")
print(f"📥 Response Text: {response.text[:500]}...") # First 500 chars
if response.status_code != 200:
print("❌ API call failed with error above")
else:
print("✅ API call successful!")
except Exception as e:
print(f"❌ Exception during API call: {str(e)}")
def check_model_availability():
"""Check if the model exists on OpenRouter."""
print("\n🔍 Checking model availability...")
config = load_config()
model = config.get('default_model', 'anthropic/claude-4.5-sonnet')
provider_config = get_provider_config('openrouter')
api_key = provider_config.get('api_key', 'sk-or-xxx')
if api_key == 'sk-or-xxx' or not api_key:
print("❌ Cannot check model - no valid API key")
return
headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
try:
# Check model endpoint
response = requests.get('https://openrouter.ai/api/v1/models', headers=headers)
if response.status_code == 200:
models_data = response.json()
available_models = [m['id'] for m in models_data.get('data', [])]
print(f"📊 Found {len(available_models)} available models")
# Check if our model is available
if model in available_models:
print(f"✅ Model {model} is available")
else:
print(f"❌ Model {model} is NOT available")
print(" Available Claude models might include:")
claude_models = [m for m in available_models if 'claude' in m.lower()]
for m in claude_models[:5]: # Show first 5
print(f" - {m}")
else:
print(f"❌ Could not fetch models list: {response.status_code}")
except Exception as e:
print(f"❌ Error checking model availability: {str(e)}")
if __name__ == '__main__':
test_api_call()
check_model_availability()
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env python3
"""Debug script to test what environment variables the Python script sees."""
import os
print(f"OPENROUTER_MODEL in Python: {os.environ.get('OPENROUTER_MODEL', 'NOT SET')}")
print(f"OPENROUTER_API_KEY in Python: {'SET' if os.environ.get('OPENROUTER_API_KEY') else 'NOT SET'}")
# Check config file too
import sys
import yaml
from pathlib import Path
# Add src to path for imports
sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
from src.utils.config import load_config
config = load_config()
print(f"Config default_model: {config.get('default_model', 'NOT SET') if config else 'CONFIG NOT LOADED'}")
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env python3
"""Debug script to see the full environment variables."""
import os
import subprocess
# Check environment variable directly
model = os.environ.get('OPENROUTER_MODEL')
print(f"OPENROUTER_MODEL = {model}")
# Also check via shell command
result = subprocess.run(['echo', '$OPENROUTER_MODEL'], capture_output=True, text=True, shell=True)
print(f"Shell echo result: {result.stdout.strip()}")
# Also try bash to check environment
bash_result = subprocess.run(['bash', '-c', 'echo $OPENROUTER_MODEL'], capture_output=True, text=True)
print(f"Bash result: {bash_result.stdout.strip()}")
# Check the original shell variable in a direct way
os.environ['OPENROUTER_MODEL'] = 'anthropic/claude-haiku-4.5' # Override in Python
print(f"Python set model to: {os.environ['OPENROUTER_MODEL']}")
+57
View File
@@ -0,0 +1,57 @@
#!/bin/bash
# Installation script for LJWX AI CLI
set -e
echo "Installing LJWX AI CLI..."
# Detect OS
OS=$(uname -s)
# Find the script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Check if we're in the correct directory
if [ ! -f "ljwx-ai" ]; then
echo "ljwx-ai executable not found in current directory!"
echo "Please run this script from the ljwx-ai project directory."
exit 1
fi
# Make sure the script is executable
chmod +x ljwx-ai
echo "ljwx-ai found in: $SCRIPT_DIR"
# Try to install using one of these methods:
if command -v pip3 &> /dev/null; then
echo "Installing Python dependencies..."
pip3 install -r requirements.txt --break-system-packages 2>/dev/null || pip3 install -r requirements.txt
else
echo "pip3 not found. Please install Python dependencies manually."
fi
# Option 1: Create symlink in ~/.local/bin (user-specific, safe)
LOCAL_BIN="$HOME/.local/bin"
if [ -d "$LOCAL_BIN" ]; then
echo "Creating symlink in $LOCAL_BIN..."
ln -sf "$SCRIPT_DIR/ljwx-ai" "$LOCAL_BIN/ljwx-ai"
echo "LJWX AI CLI installed! You can now use 'ljwx-ai' command."
echo "Make sure $LOCAL_BIN is in your PATH by adding this to your shell profile (e.g., ~/.zshrc or ~/.bashrc):"
echo "export PATH=\"\$HOME/.local/bin:\$PATH\""
else
echo "Directory $LOCAL_BIN does not exist. Creating it..."
mkdir -p "$LOCAL_BIN"
echo "Creating symlink in $LOCAL_BIN..."
ln -sf "$SCRIPT_DIR/ljwx-ai" "$LOCAL_BIN/ljwx-ai"
echo "LJWX AI CLI installed! You can now use 'ljwx-ai' command."
echo "Add this to your shell profile (e.g., ~/.zshrc or ~/.bashrc):"
echo "export PATH=\"\$HOME/.local/bin:\$PATH\""
fi
echo ""
echo "To complete the installation, run:"
echo "source ~/.zshrc # or ~/.bashrc, depending on your shell"
echo ""
echo "Then you can run:"
echo "ljwx-ai init # to initialize the configuration"
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""
LJWX AI CLI - Your intelligent development companion.
Supports multiple providers (OpenRouter, Ollama) with persistent context.
"""
import sys
import os
import argparse
import json
from pathlib import Path
# Add src to path for imports
sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
from src.commands.init import init_command
from src.commands.chat import chat_command
from src.commands.edit import edit_command
from src.commands.review import review_command
from src.commands.plan import plan_command
from src.commands.config import config_command
def main():
"""Main CLI entry point."""
parser = argparse.ArgumentParser(
description="LJWX AI CLI - Your intelligent development companion",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
ljwx-ai init # Initialize configuration
ljwx-ai chat "Explain this code" # Chat with AI
ljwx-ai edit src/file.py # Edit a file
ljwx-ai review . # Review current directory
ljwx-ai plan "Feature request" # Generate development plan
ljwx-ai config set model anthropic/claude-4.5-sonnet
"""
)
# Global options
parser.add_argument('--provider', help='Override default provider')
parser.add_argument('--model', help='Override default model')
# Subcommands
subparsers = parser.add_subparsers(dest='command', help='Available commands')
# Init command
subparsers.add_parser('init', help='Initialize AI CLI configuration')
# Chat command
chat_parser = subparsers.add_parser('chat', help='Chat with AI assistant')
chat_parser.add_argument('message', nargs='*', help='Message to send to AI')
# Edit command
edit_parser = subparsers.add_parser('edit', help='Edit files with AI assistance')
edit_parser.add_argument('target', help='File or directory to edit')
edit_parser.add_argument('--prompt', '-p', help='Custom prompt for editing')
# Review command
review_parser = subparsers.add_parser('review', help='Review code with AI')
review_parser.add_argument('target', help='File or directory to review')
review_parser.add_argument('--prompt', '-p', help='Custom prompt for review')
# Plan command
plan_parser = subparsers.add_parser('plan', help='Generate development plan')
plan_parser.add_argument('prompt', help='Prompt for plan generation')
# Config command
config_parser = subparsers.add_parser('config', help='Manage configuration')
config_subparsers = config_parser.add_subparsers(dest='config_subcommand', help='Config subcommands')
config_subparsers.add_parser('list', help='List current configuration')
set_parser = config_subparsers.add_parser('set', help='Set configuration value')
set_parser.add_argument('key', help='Configuration key')
set_parser.add_argument('value', help='Configuration value')
config_subparsers.add_parser('reset', help='Reset configuration')
# Parse arguments
args = parser.parse_args()
# Run appropriate command
if args.command == 'init':
init_command()
elif args.command == 'chat':
message = ' '.join(args.message) if args.message else None
chat_command(message, args.provider, args.model)
elif args.command == 'edit':
edit_command(args.target, args.prompt, args.provider, args.model)
elif args.command == 'review':
review_command(args.target, args.prompt, args.provider, args.model)
elif args.command == 'plan':
plan_command(args.prompt, args.provider, args.model)
elif args.command == 'config':
config_command(args.config_subcommand, args.key if hasattr(args, 'key') else None,
args.value if hasattr(args, 'value') else None)
else:
parser.print_help()
sys.exit(1)
if __name__ == '__main__':
main()
+27
View File
@@ -0,0 +1,27 @@
{
"name": "ljwx-ai-cli",
"version": "1.0.0",
"description": "Your intelligent development companion CLI tool",
"main": "ljwx-ai",
"scripts": {
"start": "./ljwx-ai",
"dev": "python ./ljwx-ai"
},
"keywords": [
"ai",
"cli",
"development",
"assistant",
"openrouter",
"claude",
"ollama",
"ljwx-ai"
],
"author": "LJWX AI CLI Developer",
"license": "MIT",
"dependencies": {
"requests": "^2.31.0",
"colorama": "^0.4.6",
"pyyaml": "^6.0"
}
}
+4
View File
@@ -0,0 +1,4 @@
# AI CLI Requirements
requests>=2.31.0
colorama>=0.4.6
pyyaml>=6.0
+40
View File
@@ -0,0 +1,40 @@
from setuptools import setup, find_packages
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
setup(
name="ljwx-ai-cli",
version="1.0.0",
author="AI CLI Developer",
author_email="",
description="Your intelligent development companion CLI tool",
long_description=long_description,
long_description_content_type="text/markdown",
url="",
packages=find_packages(where="src"),
package_dir={"": "src"},
classifiers=[
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
],
python_requires=">=3.8",
install_requires=[
"requests>=2.31.0",
"colorama>=0.4.6",
"pyyaml>=6.0",
],
entry_points={
"console_scripts": [
"ljwx-ai=ljwx_ai:main", # This may need to be adjusted
],
},
)
View File
Binary file not shown.
View File
+90
View File
@@ -0,0 +1,90 @@
"""Chat command for AI CLI."""
import json
import sys
import os
from pathlib import Path
# Add src to path for imports
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
from src.utils.config import load_config, get_provider_config
from src.providers.base import get_provider
from src.utils.context import load_context, save_context, add_message_to_context
from src.utils.formatting import print_claude_header
from src.utils.project_context import get_project_context
def chat_command(message, provider_override=None, model_override=None):
"""Chat with AI assistant."""
if not message:
print("❌ Please provide a message to send to AI.")
return
config = load_config()
if not config:
print("❌ Configuration not found. Please run 'ai init' first.")
return
# Determine provider and model
provider_name = provider_override or config.get('default_provider', 'openrouter')
provider_config = get_provider_config(provider_name)
if not provider_config:
print(f"❌ Provider '{provider_name}' not configured.")
return
# Get model - either from override, provider config, or default
model = model_override
if not model:
# Try to get from provider config first, then fallback to default
if provider_name in config.get('providers', {}):
provider_specific = config['providers'][provider_name]
if 'model' in provider_specific:
model = provider_specific['model']
if not model:
model = config.get('default_model', 'anthropic/claude-4.5-sonnet')
# Initialize provider
provider = get_provider(provider_name, provider_config)
# Load context
context_messages = load_context()
# Add user message to context
user_message = {"role": "user", "content": message}
context_messages.append(user_message)
# Add project context if this is the first message or context is empty
if len(context_messages) == 1: # First message
project_context = get_project_context()
if project_context.strip():
context_messages.insert(0, {
"role": "system",
"content": f"Project context:\n{project_context}"
})
# Prepare messages for API
# Limit context to max_context_messages
max_context = config.get('max_context_messages', 10)
recent_messages = context_messages[-max_context:]
# Print Claude-style header
print_claude_header(provider_name, model)
try:
# Get response from provider
response = provider.get_completion(recent_messages, model)
# Add AI response to context
ai_message = {"role": "assistant", "content": response}
context_messages.append(ai_message)
# Save updated context
save_context(context_messages)
# Print response
print(f"💬 Answer:\n{response}")
except Exception as e:
print(f"❌ Error getting response: {str(e)}")
+69
View File
@@ -0,0 +1,69 @@
"""Config command for AI CLI."""
import sys
import os
# Add src to path for imports
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
from src.utils.config import load_config, save_config, get_config_path
def config_command(subcommand, key=None, value=None):
"""Manage configuration."""
if subcommand == 'list' or subcommand is None:
config = load_config()
if config:
print("Current configuration:")
for k, v in config.items():
if k == 'providers':
print(f" {k}:")
for provider_name, provider_config in v.items():
print(f" {provider_name}:")
for config_key, config_value in provider_config.items():
# Hide API keys
if 'key' in config_key.lower():
print(f" {config_key}: {'*' * len(str(config_value))}")
else:
print(f" {config_key}: {config_value}")
else:
print(f" {k}: {v}")
else:
print("No configuration found. Please run 'ai init' first.")
elif subcommand == 'set':
if not key or not value:
print("❌ Please provide both key and value for config set command.")
return
config = load_config()
if not config:
print("❌ Configuration not found. Please run 'ai init' first.")
return
# Handle nested keys like 'providers.openrouter.api_key'
keys = key.split('.')
current = config
# Navigate to the parent of the target key
for k in keys[:-1]:
if k not in current:
current[k] = {}
current = current[k]
# Set the final key
current[keys[-1]] = value
save_config(config)
print(f"✅ Configuration updated: {key} = {value}")
elif subcommand == 'reset':
config_path = get_config_path()
if config_path.exists():
config_path.unlink()
print("✅ Configuration reset. Please run 'ljwx-ai init' to set up again.")
else:
print("✅ No configuration to reset.")
else:
print(f"❌ Unknown config subcommand: {subcommand}")
return
+122
View File
@@ -0,0 +1,122 @@
"""Edit command for AI CLI."""
import sys
import os
from pathlib import Path
# Add src to path for imports
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
from src.utils.config import load_config, get_provider_config
from src.providers.base import get_provider
from src.utils.context import load_context, save_context
from src.utils.formatting import print_claude_header, print_info
from src.utils.project_context import get_project_context, read_file_content
def edit_command(target, custom_prompt, provider_override=None, model_override=None):
"""Edit files with AI assistance."""
config = load_config()
if not config:
print("❌ Configuration not found. Please run 'ai init' first.")
return
# Determine provider and model
provider_name = provider_override or config.get('default_provider', 'openrouter')
provider_config = get_provider_config(provider_name)
if not provider_config:
print(f"❌ Provider '{provider_name}' not configured.")
return
# Get model - either from override, provider config, or default
model = model_override
if not model:
# Try to get from provider config first, then fallback to default
if provider_name in config.get('providers', {}):
provider_specific = config['providers'][provider_name]
if 'model' in provider_specific:
model = provider_specific['model']
if not model:
model = config.get('default_model', 'anthropic/claude-4.5-sonnet')
# Initialize provider
provider = get_provider(provider_name, provider_config)
target_path = Path(target)
if not target_path.exists():
print(f"❌ Target '{target}' does not exist.")
return
# Read file content
if target_path.is_file():
file_content = read_file_content(str(target_path))
file_context = f"File: {target_path}\n\n```{target_path.suffix[1:] if target_path.suffix else 'txt'}\n{file_content}\n```"
elif target_path.is_dir():
# For directories, get file tree and maybe first few files
file_context = f"Directory: {target_path}\n\nFiles in directory:\n"
for file_path in target_path.rglob('*'):
if file_path.is_file() and file_path.suffix in ['.py', '.js', '.ts', '.jsx', '.tsx', '.md', '.txt', '.json', '.yaml', '.yml']:
file_context += f"- {file_path}\n"
# Only add content for small files
if file_path.stat().st_size < 5000: # Less than 5KB
content = read_file_content(str(file_path))
file_context += f"\nContent of {file_path}:\n```\n{content}\n```\n"
if len(file_context) > 10000: # Limit context size
file_context += "\n...(truncated due to size)"
break
else:
print(f"❌ Target '{target}' is neither a file nor a directory.")
return
# Create prompt for editing
if custom_prompt:
full_prompt = f"{custom_prompt}\n\nHere is the target:\n{file_context}"
else:
full_prompt = f"I want you to help edit the following code/file. Please suggest improvements, fix issues, or implement features as needed.\n\n{file_context}"
# Load context
context_messages = load_context()
# Add project context if this is the first message
if not context_messages:
project_context = get_project_context()
if project_context.strip():
context_messages.append({
"role": "system",
"content": f"Project context:\n{project_context}"
})
# Add user message to context
user_message = {"role": "user", "content": full_prompt}
context_messages.append(user_message)
# Prepare messages for API
# Limit context to max_context_messages
max_context = config.get('max_context_messages', 10)
recent_messages = context_messages[-max_context:]
# Print Claude-style header
print_claude_header(provider_name, model)
try:
# Get response from provider
response = provider.get_completion(recent_messages, model)
# Add AI response to context
ai_message = {"role": "assistant", "content": response}
context_messages.append(ai_message)
# Save updated context
save_context(context_messages)
# Print response
print(f"💬 Answer:\n{response}")
# If target is a file and response looks like code, ask if user wants to apply changes
if target_path.is_file():
print_info(f"\n💡 To apply changes to {target_path}, you can copy the code above and update the file manually.")
except Exception as e:
print(f"❌ Error getting response: {str(e)}")
+53
View File
@@ -0,0 +1,53 @@
"""Initialize command for AI CLI."""
import os
import sys
import yaml
from pathlib import Path
# Add src to path for imports
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
from src.utils.config import get_config_path, get_cache_dir
def init_command():
"""Initialize AI CLI configuration."""
print("🚀 Initializing AI CLI...")
config_path = get_config_path()
cache_dir = get_cache_dir()
# Create cache directory
cache_dir.mkdir(parents=True, exist_ok=True)
print(f"📁 Created cache directory: {cache_dir}")
# Create default config if it doesn't exist
if not config_path.exists():
default_config = {
'default_provider': 'openrouter',
'default_model': 'anthropic/claude-sonnet-4.5',
'max_context_messages': 10,
'providers': {
'openrouter': {
'base_url': 'https://openrouter.ai/api/v1/chat/completions',
'api_key': 'sk-or-xxx'
},
'ollama': {
'base_url': 'http://192.168.1.83:11434/api/chat',
'model': 'qwen3-coder:30b-a3b-q8_0'
}
}
}
# Create parent directories if they don't exist
config_path.parent.mkdir(parents=True, exist_ok=True)
with open(config_path, 'w', encoding='utf-8') as f:
yaml.dump(default_config, f, default_flow_style=False, allow_unicode=True)
print(f"⚙️ Created default configuration: {config_path}")
print("\n📝 Please update your API keys in the configuration file.")
else:
print(f"✅ Configuration already exists: {config_path}")
print("✨ Initialization complete!")
+87
View File
@@ -0,0 +1,87 @@
"""Plan command for AI CLI."""
import sys
import os
# Add src to path for imports
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
from src.utils.config import load_config, get_provider_config
from src.providers.base import get_provider
from src.utils.context import load_context, save_context
from src.utils.formatting import print_claude_header
from src.utils.project_context import get_project_context
def plan_command(prompt, provider_override=None, model_override=None):
"""Generate development plan."""
config = load_config()
if not config:
print("❌ Configuration not found. Please run 'ai init' first.")
return
# Determine provider and model
provider_name = provider_override or config.get('default_provider', 'openrouter')
provider_config = get_provider_config(provider_name)
if not provider_config:
print(f"❌ Provider '{provider_name}' not configured.")
return
# Get model - either from override, provider config, or default
model = model_override
if not model:
# Try to get from provider config first, then fallback to default
if provider_name in config.get('providers', {}):
provider_specific = config['providers'][provider_name]
if 'model' in provider_specific:
model = provider_specific['model']
if not model:
model = config.get('default_model', 'anthropic/claude-4.5-sonnet')
# Initialize provider
provider = get_provider(provider_name, provider_config)
# Create prompt for planning
full_prompt = f"Generate a detailed development plan for the following requirement:\n\n{prompt}\n\nInclude task breakdown, implementation steps, potential challenges, and recommendations."
# Load context
context_messages = load_context()
# Add project context if this is the first message
if not context_messages:
project_context = get_project_context()
if project_context.strip():
context_messages.append({
"role": "system",
"content": f"Project context:\n{project_context}"
})
# Add user message to context
user_message = {"role": "user", "content": full_prompt}
context_messages.append(user_message)
# Prepare messages for API
# Limit context to max_context_messages
max_context = config.get('max_context_messages', 10)
recent_messages = context_messages[-max_context:]
# Print Claude-style header
print_claude_header(provider_name, model)
try:
# Get response from provider
response = provider.get_completion(recent_messages, model)
# Add AI response to context
ai_message = {"role": "assistant", "content": response}
context_messages.append(ai_message)
# Save updated context
save_context(context_messages)
# Print response
print(f"📋 Development Plan:\n{response}")
except Exception as e:
print(f"❌ Error getting response: {str(e)}")
+120
View File
@@ -0,0 +1,120 @@
"""Review command for AI CLI."""
import sys
import os
from pathlib import Path
# Add src to path for imports
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
from src.utils.config import load_config, get_provider_config
from src.providers.base import get_provider
from src.utils.context import load_context, save_context
from src.utils.formatting import print_claude_header, print_info
from src.utils.project_context import get_project_context, read_file_content
def review_command(target, custom_prompt, provider_override=None, model_override=None):
"""Review code with AI."""
config = load_config()
if not config:
print("❌ Configuration not found. Please run 'ai init' first.")
return
# Determine provider and model
provider_name = provider_override or config.get('default_provider', 'openrouter')
provider_config = get_provider_config(provider_name)
if not provider_config:
print(f"❌ Provider '{provider_name}' not configured.")
return
# Get model - either from override, provider config, or default
model = model_override
if not model:
# Try to get from provider config first, then fallback to default
if provider_name in config.get('providers', {}):
provider_specific = config['providers'][provider_name]
if 'model' in provider_specific:
model = provider_specific['model']
if not model:
model = config.get('default_model', 'anthropic/claude-4.5-sonnet')
# Initialize provider
provider = get_provider(provider_name, provider_config)
target_path = Path(target)
if not target_path.exists():
print(f"❌ Target '{target}' does not exist.")
return
# Read content based on target type
if target_path.is_file():
file_content = read_file_content(str(target_path))
target_context = f"File: {target_path}\n\n```{target_path.suffix[1:] if target_path.suffix else 'txt'}\n{file_content}\n```"
elif target_path.is_dir():
# For directories, get a sample of files
target_context = f"Directory: {target_path}\n\nContent review:\n"
file_count = 0
for file_path in target_path.rglob('*'):
if file_path.is_file() and file_path.suffix in ['.py', '.js', '.ts', '.jsx', '.tsx', '.md', '.txt', '.json', '.yaml', '.yml']:
content = read_file_content(str(file_path))
target_context += f"\nFile: {file_path}\n```\n{content}\n```\n"
file_count += 1
if file_count >= 5 or len(target_context) > 10000: # Limit files or size
if file_count >= 5:
target_context += "\n...(additional files not included in review)"
else:
target_context += "\n...(truncated due to size)"
break
else:
print(f"❌ Target '{target}' is neither a file nor a directory.")
return
# Create prompt for review
if custom_prompt:
full_prompt = f"{custom_prompt}\n\nHere is the target to review:\n{target_context}"
else:
full_prompt = f"Please perform a code review on the following code/files. Identify potential issues, suggest improvements, and provide feedback on code quality, security, performance, and best practices.\n\n{target_context}"
# Load context
context_messages = load_context()
# Add project context if this is the first message
if not context_messages:
project_context = get_project_context()
if project_context.strip():
context_messages.append({
"role": "system",
"content": f"Project context:\n{project_context}"
})
# Add user message to context
user_message = {"role": "user", "content": full_prompt}
context_messages.append(user_message)
# Prepare messages for API
# Limit context to max_context_messages
max_context = config.get('max_context_messages', 10)
recent_messages = context_messages[-max_context:]
# Print Claude-style header
print_claude_header(provider_name, model)
try:
# Get response from provider
response = provider.get_completion(recent_messages, model)
# Add AI response to context
ai_message = {"role": "assistant", "content": response}
context_messages.append(ai_message)
# Save updated context
save_context(context_messages)
# Print response
print(f"📊 Review Report:\n{response}")
except Exception as e:
print(f"❌ Error getting response: {str(e)}")
View File
+77
View File
@@ -0,0 +1,77 @@
"""Base provider interface and concrete implementations."""
import requests
import json
from abc import ABC, abstractmethod
from typing import List, Dict, Any
class BaseProvider(ABC):
"""Base class for AI providers."""
@abstractmethod
def get_completion(self, messages: List[Dict[str, str]], model: str) -> str:
"""Get completion from the provider."""
pass
class OpenRouterProvider(BaseProvider):
"""OpenRouter provider implementation."""
def __init__(self, config: Dict[str, Any]):
self.base_url = config['base_url']
self.api_key = config['api_key']
def get_completion(self, messages: List[Dict[str, str]], model: str) -> str:
"""Get completion from OpenRouter API."""
headers = {
'Authorization': f'Bearer {self.api_key}',
'Content-Type': 'application/json',
'HTTP-Referer': 'https://github.com/your-username/ai-cli', # Optional, for usage tracking
'X-Title': 'AI CLI Tool' # Optional, for usage tracking
}
payload = {
'model': model,
'messages': messages,
}
response = requests.post(self.base_url, headers=headers, json=payload)
response.raise_for_status()
data = response.json()
return data['choices'][0]['message']['content']
class OllamaProvider(BaseProvider):
"""Ollama provider implementation."""
def __init__(self, config: Dict[str, Any]):
self.base_url = config['base_url']
self.default_model = config.get('model', 'llama3.1')
def get_completion(self, messages: List[Dict[str, str]], model: str) -> str:
"""Get completion from Ollama API."""
# Use provided model or default
actual_model = model if model else self.default_model
payload = {
'model': actual_model,
'messages': messages,
'stream': False
}
response = requests.post(self.base_url, json=payload)
response.raise_for_status()
data = response.json()
return data['message']['content']
def get_provider(provider_name: str, config: Dict[str, Any]) -> BaseProvider:
"""Factory function to get the appropriate provider."""
if provider_name == 'openrouter':
return OpenRouterProvider(config)
elif provider_name == 'ollama':
return OllamaProvider(config)
else:
raise ValueError(f"Unsupported provider: {provider_name}")
View File
+72
View File
@@ -0,0 +1,72 @@
"""Configuration utilities for AI CLI."""
import os
import yaml
from pathlib import Path
def get_config_path():
"""Get the path to the configuration file."""
home_dir = Path.home()
return home_dir / '.ai-cli' / 'config.yaml'
def get_cache_dir():
"""Get the path to the cache directory."""
current_dir = Path.cwd()
return current_dir / '.aicache'
def load_config():
"""Load configuration from file and environment variables."""
config_path = get_config_path()
if not config_path.exists():
return None
with open(config_path, 'r', encoding='utf-8') as f:
config = yaml.safe_load(f)
# Override with environment variables if they exist
openrouter_api_key = os.environ.get('OPENROUTER_API_KEY')
if openrouter_api_key:
if 'providers' not in config:
config['providers'] = {}
if 'openrouter' not in config['providers']:
config['providers']['openrouter'] = {}
config['providers']['openrouter']['api_key'] = openrouter_api_key
openrouter_model = os.environ.get('OPENROUTER_MODEL')
if openrouter_model:
config['default_model'] = openrouter_model
return config
def save_config(config):
"""Save configuration to file."""
config_path = get_config_path()
config_path.parent.mkdir(parents=True, exist_ok=True)
with open(config_path, 'w', encoding='utf-8') as f:
yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
def get_provider_config(provider_name=None):
"""Get provider configuration."""
config = load_config()
if not config:
return None
if provider_name:
provider_config = config.get('providers', {}).get(provider_name)
# Override with environment variable if this is openrouter
if provider_name == 'openrouter':
openrouter_api_key = os.environ.get('OPENROUTER_API_KEY')
if openrouter_api_key:
if provider_config is None:
provider_config = {}
provider_config['api_key'] = openrouter_api_key
return provider_config
else:
default_provider = config.get('default_provider', 'openrouter')
return get_provider_config(default_provider)
+37
View File
@@ -0,0 +1,37 @@
"""Context management utilities."""
import json
from pathlib import Path
from ..utils.config import get_cache_dir
def get_context_file_path():
"""Get path to context cache file."""
cache_dir = get_cache_dir()
return cache_dir / 'context.json'
def load_context():
"""Load conversation context from cache."""
context_file = get_context_file_path()
if context_file.exists():
with open(context_file, 'r', encoding='utf-8') as f:
return json.load(f)
else:
return []
def save_context(messages):
"""Save conversation context to cache."""
context_file = get_context_file_path()
context_file.parent.mkdir(parents=True, exist_ok=True)
with open(context_file, 'w', encoding='utf-8') as f:
json.dump(messages, f, ensure_ascii=False, indent=2)
def add_message_to_context(role, content):
"""Add a message to the context."""
messages = load_context()
messages.append({"role": role, "content": content})
save_context(messages)
+31
View File
@@ -0,0 +1,31 @@
"""Formatting utilities for Claude-style output."""
import sys
def print_claude_header(provider_name, model):
"""Print Claude-style header with provider and model info."""
header = f"Claude-Code Mode · {provider_name.upper()}({model})"
thinking_msg = "🧠 Thinking..."
print(header)
print(thinking_msg)
def print_success(message):
"""Print success message."""
print(f"✅ {message}")
def print_error(message):
"""Print error message."""
print(f"❌ {message}")
def print_info(message):
"""Print info message."""
print(f"ℹ️ {message}")
def print_warning(message):
"""Print warning message."""
print(f"⚠️ {message}")
+96
View File
@@ -0,0 +1,96 @@
"""Project-aware context utilities."""
import os
import subprocess
from pathlib import Path
from typing import List, Dict, Any
def get_git_diff():
"""Get current git diff if in a git repository."""
try:
result = subprocess.run(['git', 'diff'], capture_output=True, text=True, cwd=Path.cwd())
if result.returncode == 0 and result.stdout:
return result.stdout
return ""
except FileNotFoundError:
# Git not installed
return ""
def get_file_tree(max_depth=3):
"""Get file tree of current directory."""
try:
result = subprocess.run(
['find', '.', '-maxdepth', str(max_depth), '-type', 'f'],
capture_output=True,
text=True,
cwd=Path.cwd()
)
if result.returncode == 0:
files = [line for line in result.stdout.split('\n') if line]
return '\n'.join(files[:50]) # Limit to 50 files
return ""
except FileNotFoundError:
return ""
def get_project_structure():
"""Get project structure information."""
project_info = {
'git_diff': get_git_diff(),
'file_tree': get_file_tree(),
'directories': [],
'files': []
}
# Get directories and files
for root, dirs, files in os.walk('.'):
# Skip hidden directories
dirs[:] = [d for d in dirs if not d.startswith('.')]
rel_root = os.path.relpath(root, '.')
if rel_root != '.':
project_info['directories'].append(rel_root)
for file in files:
if not file.startswith('.'): # Skip hidden files
file_path = os.path.join(rel_root, file) if rel_root != '.' else file
project_info['files'].append(file_path)
# Limit to reasonable amounts
project_info['directories'] = project_info['directories'][:20]
project_info['files'] = project_info['files'][:50]
return project_info
def read_file_content(file_path: str, max_lines=100):
"""Read content of a specific file."""
try:
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.readlines()
return ''.join(lines[:max_lines])
except Exception:
return ""
def get_project_context():
"""Get comprehensive project context."""
context = f"# Project Context\n\n"
# Add file tree
file_tree = get_file_tree()
if file_tree:
context += f"## Directory Structure\n```\n{file_tree}\n```\n\n"
# Add git diff if available
git_diff = get_git_diff()
if git_diff:
context += f"## Current Changes (Git Diff)\n```\n{git_diff}\n```\n\n"
# Add project files list
project_info = get_project_structure()
if project_info['files']:
context += f"## Project Files\n```\n" + "\n".join(project_info['files'][:30]) + "\n```\n\n"
return context
+6
View File
@@ -0,0 +1,6 @@
def hello_world():
"""Simple function to say hello."""
print("Hello, World!")
if __name__ == "__main__":
hello_world()
+42
View File
@@ -0,0 +1,42 @@
import requests
import os
import json
def test_model_access(model_id):
api_key = os.getenv('OPENROUTER_API_KEY')
headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
messages = [
{"role": "user", "content": "Test"}
]
payload = {
'model': model_id,
'messages': messages,
}
try:
response = requests.post("https://openrouter.ai/api/v1/chat/completions", headers=headers, json=payload)
print(f"Model {model_id}: Status {response.status_code}")
if response.status_code != 200:
print(f" Error: {response.text}")
else:
print(f" Success: Model accessible")
return response.status_code
except Exception as e:
print(f"Model {model_id}: Exception {str(e)}")
return None
# Test different models
test_models = [
"anthropic/claude-sonnet-4.5",
"anthropic/claude-haiku-4.5",
"anthropic/claude-3.5-sonnet",
"anthropic/claude-3.5-haiku"
]
for model in test_models:
test_model_access(model)
Submodule ai/ai-cli/y-router added at 6ef40e4ebe
Submodule ai/claude-bluebook added at cbc925f514
+1
Submodule ai/claude-code added at 7a05427a4b
Submodule ai/claude-code-cli/y-router added at 6ef40e4ebe
+121
View File
@@ -0,0 +1,121 @@
# Cursor AI 配置工具
通过 cursor-agent 调用 OpenRouter API 的完整解决方案。
## 快速开始
### 使用 OpenRouter API
```bash
./cursor-agent.sh openrouter
```
或
```bash
./cursor-agent.sh cloud
```
### 使用本地 Ollama
```bash
./cursor-agent.sh local
```
或
```bash
./cursor-agent.sh ollama
```
## 配置说明
### 统一配置文件
所有配置变量统一在 `config.sh` 中管理,避免重复定义:
- `OPENROUTER_KEY`: OpenRouter API Key
- `LOCAL_API`: 本地 Ollama API 地址
- `CLOUD_API`: OpenRouter API 地址
- `LOCAL_MODEL`: 本地模型名称
- `CLOUD_MODEL`: 云端模型名称
### 修改 API Key
编辑 `config.sh` 文件,修改 `OPENROUTER_KEY` 变量:
```bash
OPENROUTER_KEY="sk-or-v1-your-api-key-here"
```
## 工具说明
### cursor-agent.sh
**主要工具** - 通过环境变量配置并启动 Cursor:
- 自动设置 `OPENAI_API_KEY` 和 `OPENAI_API_BASE` 环境变量
- 支持 OpenRouter 和本地 Ollama 两种模式
- 自动检查并启动 Ollama 服务(本地模式)
**使用方法:**
```bash
./cursor-agent.sh openrouter # 使用 OpenRouter
./cursor-agent.sh local # 使用本地 Ollama
```
### cursor-model-switch.sh
**模型切换工具** - 快速切换 Cursor 使用的模型来源:
```bash
./cursor-model-switch.sh cloud # 切换到 OpenRouter
./cursor-model-switch.sh local # 切换到本地 Ollama
```
### cursor-openrouter-proxy.sh
**OpenRouter 代理服务器** - 通过本地代理转发请求到 OpenRouter(实验性):
```bash
./cursor-openrouter-proxy.sh start # 启动代理并打开 Cursor
./cursor-openrouter-proxy.sh stop # 停止代理服务
./cursor-openrouter-proxy.sh restart # 重启代理服务
./cursor-openrouter-proxy.sh status # 查看代理状态
```
**注意**:由于 Cursor 在应用层面检查订阅状态,此方案可能仍会被拦截。
### git-proxy-fix.sh
**Git 代理管理工具**:
```bash
./git-proxy-fix.sh on # 开启代理
./git-proxy-fix.sh off # 关闭代理
./git-proxy-fix.sh # 自动检测并修复
```
## 工作原理
Cursor 通过环境变量 `OPENAI_API_KEY` 和 `OPENAI_API_BASE` 来配置 API 连接:
- **OpenRouter 模式**: 设置 `OPENAI_API_KEY` 为 OpenRouter API Key,`OPENAI_API_BASE` 为 `https://openrouter.ai/api/v1`,模型为 `anthropic/claude-4.5-sonnet`
- **本地模式**: 设置 `OPENAI_API_KEY` 为 `ollama`,`OPENAI_API_BASE` 为 `http://localhost:11434/v1`
`cursor-agent.sh` 脚本会:
1. 设置环境变量并通过 `launchctl setenv` 设置系统级环境变量
2. 在启动 Cursor 时传递环境变量,确保 Cursor 使用正确的 API 配置
### 免费用户使用 Claude 4.5
即使你是 Cursor 免费用户,通过 OpenRouter API 仍然可以访问 Claude 4.5:
- Cursor 免费版限制:只能使用 GPT 4.1 或 Auto
- 通过 OpenRouter:可以使用任何 OpenRouter 支持的模型(包括 Claude 4.5)
- 使用方法:运行 `./cursor-agent.sh openrouter` 即可通过 OpenRouter API 使用 Claude 4.5
## 注意事项
1. 首次使用前请修改 `config.sh` 中的 `OPENROUTER_KEY` 为你的实际 API Key
2. 使用本地模式前确保已安装并运行 Ollama
3. 所有脚本需要执行权限,已自动设置
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
# 统一配置文件 - 管理所有变量避免重复定义
OPENROUTER_KEY="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" # OpenRouter API Key
LOCAL_API="http://localhost:11434/v1" # 本地 Ollama API
CLOUD_API="https://openrouter.ai/api/v1" # OpenRouter API
LOCAL_MODEL="deepseek-coder:33b" # 本地模型名称
CLOUD_MODEL="anthropic/claude-4.5-sonnet" # 云端模型名称
+43
View File
@@ -0,0 +1,43 @@
#!/bin/bash
# Cursor Agent - 通过环境变量配置 OpenRouter API 并启动 Cursor
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/config.sh" # 加载统一配置
case "$1" in
openrouter|cloud) # 使用 OpenRouter
export OPENAI_API_KEY="$OPENROUTER_KEY"
export OPENAI_API_BASE="$CLOUD_API"
export MODEL="$CLOUD_MODEL"
echo "🌐 已配置 OpenRouter API: $CLOUD_API"
echo "🤖 模型: $CLOUD_MODEL"
echo "🔑 API Key: ${OPENROUTER_KEY:0:20}..."
# 设置系统环境变量供 Cursor 读取
launchctl setenv OPENAI_API_KEY "$OPENROUTER_KEY" 2>/dev/null || true
launchctl setenv OPENAI_API_BASE "$CLOUD_API" 2>/dev/null || true
launchctl setenv MODEL "$CLOUD_MODEL" 2>/dev/null || true
# 通过环境变量启动 Cursor
env OPENAI_API_KEY="$OPENROUTER_KEY" OPENAI_API_BASE="$CLOUD_API" MODEL="$CLOUD_MODEL" open -a "Cursor"
;;
local|ollama) # 使用本地 Ollama
if ! pgrep -x "ollama" >/dev/null; then ollama serve >/dev/null 2>&1 & sleep 2; fi # 检查并启动 Ollama
if ! curl -s "$LOCAL_API/models" >/dev/null; then echo "❌ Ollama 未运行"; exit 1; fi # 检查连接
export OPENAI_API_KEY="ollama"
export OPENAI_API_BASE="$LOCAL_API"
export MODEL="$LOCAL_MODEL"
echo "✅ 已配置本地 Ollama: $LOCAL_API"
echo "🤖 模型: $LOCAL_MODEL"
# 设置系统环境变量供 Cursor 读取
launchctl setenv OPENAI_API_KEY "ollama" 2>/dev/null || true
launchctl setenv OPENAI_API_BASE "$LOCAL_API" 2>/dev/null || true
launchctl setenv MODEL "$LOCAL_MODEL" 2>/dev/null || true
# 通过环境变量启动 Cursor
env OPENAI_API_KEY="ollama" OPENAI_API_BASE="$LOCAL_API" MODEL="$LOCAL_MODEL" open -a "Cursor"
;;
*) # 显示用法
echo "用法: ./cursor-agent.sh [openrouter|local]"
echo " openrouter - 使用 OpenRouter API (Claude 4.5/Qwen/DeepSeek)"
echo " local - 使用本地 Ollama (DeepSeek)"
echo ""
echo "💡 提示: 作为 Cursor 免费用户,通过 OpenRouter API 可以访问 Claude 4.5"
;;
esac
+67
View File
@@ -0,0 +1,67 @@
#!/bin/bash
# Created Time: 2025-11-09 06:27:13
# Modified Time: 2025-11-09 06:28:44
#set -e # 这告诉bash一但有任何一个语句返回非真的值,则退出bash。
#!/bin/bash
# ===============================================
# 🧠 Cursor AI 模型切换脚本
# 作者: Bruno Gao (灵境万象)
# 功能: 一键切换 Cursor 使用的模型来源
# - 本地 DeepSeek(通过 Ollama)
# - OpenRouter Claude (Claude 4.5 / Qwen / DeepSeek)
# ===============================================
# 加载统一配置
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/config.sh"
# 检查 Ollama 是否运行
check_ollama() {
if ! pgrep -x "ollama" >/dev/null; then
echo "🟡 Ollama 未运行,正在启动..."
ollama serve >/dev/null 2>&1 &
sleep 2
fi
# 检查端口是否可访问
if ! curl -s "$LOCAL_API/models" >/dev/null; then
echo "❌ 无法连接到 Ollama 接口 ($LOCAL_API)"
echo "请确认 Ollama 已启动并监听 11434 端口"
exit 1
fi
}
# 切换到本地模型
switch_local() {
check_ollama
export OPENAI_API_KEY="ollama"
export OPENAI_API_BASE="$LOCAL_API"
echo "✅ 已切换到本地 DeepSeek 模型 (Ollama)"
echo "🚀 正在打开 Cursor..."
open -a "Cursor"
}
# 切换到云端模型
switch_cloud() {
export OPENAI_API_KEY="$OPENROUTER_KEY"
export OPENAI_API_BASE="$CLOUD_API"
echo "🌐 已切换到 OpenRouter Claude/Qwen/DeepSeek"
echo "🚀 正在打开 Cursor..."
open -a "Cursor"
}
# 脚本参数控制
case "$1" in
local)
switch_local
;;
cloud)
switch_cloud
;;
*)
echo "用法: ./cursor-model-switch.sh [local|cloud]"
echo "示例:"
echo " ./cursor-model-switch.sh local # 切换到本地 DeepSeek 模型"
echo " ./cursor-model-switch.sh cloud # 切换到 OpenRouter Claude"
;;
esac
+116
View File
@@ -0,0 +1,116 @@
#!/bin/bash
# Cursor OpenRouter 代理 - 通过本地代理服务器绕过 Cursor 订阅检查
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/config.sh" # 加载统一配置
PROXY_PORT=11435 # 本地代理端口
PROXY_PID_FILE="/tmp/cursor-openrouter-proxy.pid"
start_proxy() {
if [ -f "$PROXY_PID_FILE" ] && kill -0 "$(cat "$PROXY_PID_FILE")" 2>/dev/null; then
echo "✅ 代理服务已在运行 (PID: $(cat "$PROXY_PID_FILE"))"
return 0
fi
# 启动 Python 代理服务器
python3 -c "
import http.server
import socketserver
import json
import urllib.request
import urllib.parse
import sys
import os
class ProxyHandler(http.server.BaseHTTPRequestHandler):
def do_POST(self):
content_length = int(self.headers.get('Content-Length', 0))
body = self.rfile.read(content_length)
try:
data = json.loads(body.decode('utf-8'))
# 修改请求以使用 OpenRouter
data['model'] = '$CLOUD_MODEL'
# 转发到 OpenRouter
req = urllib.request.Request('$CLOUD_API/chat/completions',
data=json.dumps(data).encode('utf-8'),
headers={
'Content-Type': 'application/json',
'Authorization': 'Bearer $OPENROUTER_KEY',
'HTTP-Referer': 'https://cursor.sh',
'X-Title': 'Cursor OpenRouter Proxy'
})
with urllib.request.urlopen(req) as response:
resp_data = response.read()
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(resp_data)
except Exception as e:
self.send_response(500)
self.end_headers()
self.wfile.write(json.dumps({'error': str(e)}).encode('utf-8'))
def log_message(self, format, *args):
pass # 静默日志
with socketserver.TCPServer(('', $PROXY_PORT), ProxyHandler) as httpd:
with open('$PROXY_PID_FILE', 'w') as f:
f.write(str(os.getpid()))
httpd.serve_forever()
" >/dev/null 2>&1 &
sleep 1
if [ -f "$PROXY_PID_FILE" ]; then
echo "✅ OpenRouter 代理服务已启动 (端口: $PROXY_PORT)"
echo "🌐 代理地址: http://localhost:$PROXY_PORT"
else
echo "❌ 代理服务启动失败"
exit 1
fi
}
stop_proxy() {
if [ -f "$PROXY_PID_FILE" ]; then
PID=$(cat "$PROXY_PID_FILE")
kill "$PID" 2>/dev/null && rm "$PROXY_PID_FILE" && echo "✅ 代理服务已停止" || echo "❌ 停止代理服务失败"
else
echo "ℹ️ 代理服务未运行"
fi
}
case "$1" in
start)
start_proxy
export OPENAI_API_KEY="dummy" # Cursor 需要这个变量存在
export OPENAI_API_BASE="http://localhost:$PROXY_PORT"
launchctl setenv OPENAI_API_KEY "dummy" 2>/dev/null || true
launchctl setenv OPENAI_API_BASE "http://localhost:$PROXY_PORT" 2>/dev/null || true
echo "🚀 正在启动 Cursor..."
env OPENAI_API_KEY="dummy" OPENAI_API_BASE="http://localhost:$PROXY_PORT" open -a "Cursor"
;;
stop)
stop_proxy
;;
restart)
stop_proxy
sleep 1
start_proxy
;;
status)
if [ -f "$PROXY_PID_FILE" ] && kill -0 "$(cat "$PROXY_PID_FILE")" 2>/dev/null; then
echo "✅ 代理服务运行中 (PID: $(cat "$PROXY_PID_FILE"), 端口: $PROXY_PORT)"
else
echo "❌ 代理服务未运行"
fi
;;
*)
echo "用法: ./cursor-openrouter-proxy.sh [start|stop|restart|status]"
echo " start - 启动代理服务并打开 Cursor"
echo " stop - 停止代理服务"
echo " restart - 重启代理服务"
echo " status - 查看代理服务状态"
;;
esac
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
# Git代理修复脚本
PROXY="http://127.0.0.1:7890"
check_proxy(){ lsof -i :7890 >/dev/null 2>&1; } # 检查代理是否运行
unset_proxy(){ git config --global --unset http.proxy && git config --global --unset https.proxy && echo "✅ 已取消git代理设置"; } # 取消代理
set_proxy(){ git config --global http.proxy "$PROXY" && git config --global https.proxy "$PROXY" && echo "✅ 已设置git代理: $PROXY"; } # 设置代理
case "$1" in
off) unset_proxy;; # 取消代理
on) set_proxy;; # 设置代理
*) if check_proxy;then echo "✅ 代理服务运行中 ($PROXY)";else echo "❌ 代理服务未运行,正在取消git代理设置..." && unset_proxy;fi;; # 自动检测并修复
esac
+1
Submodule ai/deepseek-cli added at db2733f745
Submodule ai/devops-ai-stack-finalized added at 6dbe573d0a
Submodule
+1
Submodule ai/qwen-code added at c5286c0b3e
Submodule
+1
Submodule ai/y-router added at 6ef40e4ebe
Submodule
+1
Submodule dify_github added at c7980d1b48
+25
View File
@@ -0,0 +1,25 @@
[common]
server_addr = x.x.x.x # 国内服务器公网IP
server_port = 7000
authentication_method = "token"
token = "lingjingwanxiang2024"
# WireGuard 控制端口转发
[wireguard]
type = udp
local_ip = 127.0.0.1
local_port = 51820
remote_port = 51820
# SSH / VNC 远程访问隧道
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 2222
[vnc]
type = tcp
local_ip = 127.0.0.1
local_port = 5900
remote_port = 5900
+9 -8
View File
@@ -32,6 +32,7 @@ services:
- GITEA__service__DISABLE_REGISTRATION=false # 允许注册
- GITEA__service__REQUIRE_SIGNIN_VIEW=false
- GITEA__actions__ENABLED=true # 启用Actions
- GITEA__actions__DEFAULT_ACTIONS_URL=https://github.com
# 禁用代理,确保健康检查和内部通信不受影响
- http_proxy=
- https_proxy=
@@ -51,7 +52,7 @@ services:
# Gitea Action Runner 1 - Docker 构建
# ==========================================
runner-docker:
image: ghcr.io/go-gitea/act_runner:latest
image: docker.m.daocloud.io/gitea/act_runner:latest
container_name: gitea-runner-docker
restart: unless-stopped
depends_on:
@@ -59,18 +60,18 @@ services:
condition: service_healthy
environment:
- CONFIG_FILE=/config/runner-config.yaml
- GITEA_INSTANCE_URL=http://gitea:3000
- GITEA_INSTANCE_URL=http://192.168.1.83:33000
- GITEA_RUNNER_REGISTRATION_TOKEN=${GITEA_RUNNER_TOKEN:-}
- GITEA_RUNNER_NAME=runner-docker-01
- GITEA_RUNNER_LABELS=docker:docker://ghcr.io/go-gitea/act_runner/act_runner:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /Users/brunogao/.orbstack/run/docker.sock:/var/run/docker.sock
- ./runner-docker:/data
- ./runner-config.yaml:/config/runner-config.yaml:ro
networks:
- gitea-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/healthz"]
test: ["CMD", "curl", "-f", "http://gitea:3000/api/healthz"]
interval: 30s
timeout: 10s
retries: 3
@@ -79,7 +80,7 @@ services:
# Gitea Action Runner 2 - Kubernetes 部署
# ==========================================
runner-k8s:
image: ghcr.io/go-gitea/act_runner:latest
image: docker.m.daocloud.io/gitea/act_runner:latest
container_name: gitea-runner-k8s
restart: unless-stopped
depends_on:
@@ -87,19 +88,19 @@ services:
condition: service_healthy
environment:
- CONFIG_FILE=/config/runner-k8s-config.yaml
- GITEA_INSTANCE_URL=http://gitea:3000
- GITEA_INSTANCE_URL=http://192.168.1.83:33000
- GITEA_RUNNER_REGISTRATION_TOKEN=${GITEA_RUNNER_TOKEN:-}
- GITEA_RUNNER_NAME=runner-k8s-01
- GITEA_RUNNER_LABELS=kubernetes:docker://ghcr.io/go-gitea/act_runner/act_runner:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /Users/brunogao/.orbstack/run/docker.sock:/var/run/docker.sock
- $HOME/.kube/config:/root/.kube/config:ro
- ./runner-k8s:/data
- ./runner-k8s-config.yaml:/config/runner-k8s-config.yaml:ro
networks:
- gitea-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/healthz"]
test: ["CMD", "curl", "-f", "http://gitea:3000/api/healthz"]
interval: 30s
timeout: 10s
retries: 3
+5 -5
View File
@@ -29,9 +29,9 @@ cache:
port: 0
password: ""
container:
network: "host"
network: "container:gitea-runner-docker"
privileged: false
options: "user: root"
options: "user: root,add-host=gitea:127.0.0.1"
valid_volumes:
- "/var/run/docker.sock"
docker_host: "-"
@@ -39,8 +39,8 @@ container:
pull_policy: "if-not-present"
# 确保使用正确的镜像,避免使用 gitea 官方镜像
default_image: "node:20-alpine"
# Optional gitea host
host: ""
# Optional gitea host - runner 容器内访问 Gitea (使用 Docker 宿主别名)
host: "host.docker.internal"
# Optional gitea token
token: ""
# Optional gitea client id
@@ -48,4 +48,4 @@ container:
# Optional gitea client secret
client_secret: ""
# Optional gitea server url
server_url: "http://192.168.1.83:33000/"
server_url: "http://127.0.0.1:33000/"
+12
View File
@@ -0,0 +1,12 @@
{
"WARNING": "This file is automatically generated by act-runner. Do not edit it manually unless you know what you are doing. Removing this file will cause act runner to re-register as a new runner.",
"id": 6,
"uuid": "e995ea42-183e-4318-bbad-f9c59404394a",
"name": "runner-docker-01",
"token": "ac834988870d9ddc859f6e862ed5401732b4640c",
"address": "http://gitea:3000",
"labels": [
"ubuntu-latest:docker://192.168.1.83:8443/bookstore/ci-simple:latest"
],
"ephemeral": false
}
+4 -4
View File
@@ -24,9 +24,9 @@ cache:
- "/var/run/docker.sock"
container:
network: "host"
network: "container:gitea-runner-k8s"
privileged: false
options: "user: root"
options: "user: root,add-host=gitea:127.0.0.1"
valid_volumes:
- "/var/run/docker.sock"
- "/root/.kube"
@@ -34,8 +34,8 @@ container:
force_pull: false
pull_policy: "if-not-present"
default_image: "alpine:3.18"
host: ""
host: "host.docker.internal"
token: ""
client_id: ""
client_secret: ""
server_url: "http://gitea:3000"
server_url: "http://127.0.0.1:33000/"
+12
View File
@@ -0,0 +1,12 @@
{
"WARNING": "This file is automatically generated by act-runner. Do not edit it manually unless you know what you are doing. Removing this file will cause act runner to re-register as a new runner.",
"id": 5,
"uuid": "67da5597-d992-4570-9f34-3e55599c0898",
"name": "runner-k8s-01",
"token": "88cc1e823bf560c8099992c5a81775c237722caf",
"address": "http://gitea:3000",
"labels": [
"kubernetes:docker://ghcr.io/go-gitea/act_runner/act_runner:latest"
],
"ephemeral": false
}
BIN
View File
Binary file not shown.
Submodule
+1
Submodule lmrouter added at 9f3650db56
Submodule
+1
Submodule lmstudio-cli added at 42c4bf356c
Submodule performance/.devperf_tmp/git/angular added at ec9dc94cee
Submodule performance/.devperf_tmp/git/linux added at 4427259cc7
Submodule performance/.devperf_tmp/git/react added at 100fc4a8cf
Submodule performance/.devperf_tmp/git/vscode added at ad3ca06ea4
@@ -0,0 +1,4 @@
#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice.
#Tue Nov 11 18:42:04 CST 2025
aopalliance-1.0.jar>alimaven=
aopalliance-1.0.pom>alimaven=
@@ -0,0 +1 @@
0235ba8b489512805ac13a8f9ea77a1ca5ebe3e8
@@ -0,0 +1,15 @@
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<name>AOP alliance</name>
<version>1.0</version>
<description>AOP Alliance</description>
<url>http://aopalliance.sourceforge.net</url>
<licenses>
<license>
<name>Public Domain</name>
</license>
</licenses>
</project>
@@ -0,0 +1 @@
5128a2b0efbba460a1178d07773618e0986ea152
@@ -0,0 +1,3 @@
#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice.
#Tue Nov 11 18:41:55 CST 2025
avalon-framework-4.1.3.pom>alimaven=
@@ -0,0 +1,6 @@
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>avalon-framework</groupId>
<artifactId>avalon-framework</artifactId>
<version>4.1.3</version>
</project>
@@ -0,0 +1 @@
853c9df18e44caf0bab1eab8be0d482f9ec9bcd7
@@ -0,0 +1,4 @@
#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice.
#Tue Nov 11 18:42:30 CST 2025
logback-classic-1.4.11.jar>alimaven=
logback-classic-1.4.11.pom>alimaven=
@@ -0,0 +1 @@
54450c0c783e896a1a6d88c043bd2f1daba1c382
@@ -0,0 +1,367 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-parent</artifactId>
<version>1.4.11</version>
</parent>
<artifactId>logback-classic</artifactId>
<packaging>jar</packaging>
<name>Logback Classic Module</name>
<description>logback-classic module</description>
<properties>
<module-name>ch.qos.logback.classic</module-name>
</properties>
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<!-- <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-ext</artifactId>
<version>${slf4j.version}</version>
<scope>test</scope>
</dependency> -->
<!-- <dependency>
<groupId>ch.qos.cal10n.plugins</groupId>
<artifactId>maven-cal10n-plugin</artifactId>
<version>${cal10n.version}</version>
<scope>test</scope>
</dependency> -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<type>test-jar</type>
<version>${slf4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>${slf4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>${slf4j.version}</version>
<scope>test</scope>
</dependency>
<!-- Must be after log4j-over-slf4j:
* we want to use the classes from log4j-over-slf4j (so it must come first);
* we want to use log4j.dtd from log4j. -->
<dependency>
<groupId>ch.qos.reload4j</groupId>
<artifactId>reload4j</artifactId>
<version>1.2.18.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.angus</groupId>
<artifactId>angus-mail</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>janino</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.main</artifactId>
<version>5.6.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
</manifestEntries>
<manifestFile>
${project.build.outputDirectory}/META-INF/MANIFEST.MF
</manifestFile>
</archive>
</configuration>
<executions>
<execution>
<id>bundle-test-jar</id>
<phase>package</phase>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>${maven-antrun-plugin.version}</version>
<dependencies>
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant-junit</artifactId>
<version>${ant.version}</version>
</dependency>
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant-junitlauncher</artifactId>
<version>${ant.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit-jupiter-api.version}</version>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>${junit-vintage-engine.version}</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>${hamcrest.version}</version>
</dependency>
</dependencies>
<executions>
<!-- Disabled osgi tests given current OSGi/ServiceLoader integration failure -->
<!--
<execution>
<id>ant-osgi-test</id>
<phase>package</phase>
<configuration>
<target>
<property name="currentVersion" value="${project.version}"/>
<property name="slf4j.version" value="${slf4j.version}"/>
<property name="basedir" value="${basedir}"/>
<ant antfile="${basedir}/osgi-build.xml"/>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
-->
<execution>
<id>ant-integration-test</id>
<phase>package</phase>
<configuration>
<target>
<property name="slf4j.version" value="${slf4j.version}"/>
<ant antfile="${basedir}/integration.xml"/>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<!--
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration combine.self="append">
<compilerArgs>
<arg>add-reads</arg>
<arg>ch.qos.logback.core=ch.qos.logback.core=org.codehaus.janino.commons.compiler,org.codehaus.janino.janino</arg>
</compilerArgs>
</configuration>
</plugin>
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<execution>
<id>default-test</id>
<configuration>
<!-- x-show-module-resolution -->
<argLine>
--add-modules jakarta.mail
--add-modules jakarta.servlet
--add-opens ch.qos.logback.core/ch.qos.logback.core.testUtil=java.naming
--add-opens ch.qos.logback.classic/ch.qos.logback.classic.testUtil=ch.qos.logback.core
--add-opens ch.qos.logback.classic/ch.qos.logback.classic.jsonTest=ALL-UNNAMED
</argLine>
<parallel>classes</parallel>
<threadCount>8</threadCount>
<!--<useUnlimitedThreads>false</useUnlimitedThreads>-->
<forkCount>1C</forkCount>
<reuseForks>true</reuseForks>
<reportFormat>plain</reportFormat>
<trimStackTrace>false</trimStackTrace>
<!-- See https://issues.apache.org/jira/browse/SUREFIRE-1265 -->
<!--<childDelegation>true</childDelegation>-->
<useModulePath>true</useModulePath>
<excludes>
<exclude>**/test_osgi/BundleTest.java</exclude>
<exclude>org.slf4j.implTest.MultithreadedInitializationTest.java</exclude>
<exclude>org.slf4j.implTest.InitializationOutputTest.java</exclude>
<exclude>ch.qos.logback.classic.util.ContextInitializerTest.java</exclude>
<exclude>ch.qos.logback.classic.spi.InvocationTest.java</exclude>
</excludes>
</configuration>
</execution>
<execution>
<id>singleJVM</id>
<goals>
<goal>test</goal>
</goals>
<configuration>
<forkCount>4</forkCount>
<reuseForks>false</reuseForks>
<includes>
<include>org.slf4j.implTest.MultithreadedInitializationTest.java</include>
<include>org.slf4j.implTest.InitializationOutputTest.java</include>
<include>ch.qos.logback.classic.util.ContextInitializerTest.java</include>
<include>ch.qos.logback.classic.spi.InvocationTest.java</include>
</includes>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
<configuration>
<instructions>
<Export-Package>ch.qos.logback.classic*</Export-Package>
<!-- LB-CLASSIC It is necessary to specify the rolling
file packages as classes are created via IOC (xml
config files). They won't be found by Bnd's analysis
of java code. -->
<Import-Package>
ch.qos.logback.classic*;version="${range;[==,+);${version_cleanup;${project.version}}}",
sun.reflect;resolution:=optional,
jakarta.*;resolution:=optional,
org.xml.*;resolution:=optional,
ch.qos.logback.core.rolling,
ch.qos.logback.core.rolling.helper,
ch.qos.logback.core.read,
*
</Import-Package>
<!-- Needed to integrate ServiceLoader mechanism with OSGi -->
<Require-Capability><![CDATA[
osgi.extender;filter:="(&(osgi.extender=osgi.serviceloader.processor)(version>=1.0.0)(!(version>=2.0.0)))";resolution:=optional,
osgi.extender;filter:="(&(osgi.extender=osgi.serviceloader.registrar)(version>=1.0.0)(!(version>=2.0.0)))",
osgi.serviceloader;filter:="(osgi.serviceloader=ch.qos.logback.classic.spi.Configurator)";osgi.serviceloader="ch.qos.logback.classic.spi.Configurator";resolution:=optional;cardinality:=multiple
]]></Require-Capability>
<Provide-Capability><![CDATA[
osgi.service;objectClass:List<String>="jakarta.servlet.ServletContainerInitializer";effective:=active,
osgi.service;objectClass:List<String>="org.slf4j.spi.SLF4JServiceProvider";effective:=active,
osgi.serviceloader;osgi.serviceloader="jakarta.servlet.ServletContainerInitializer";register:="ch.qos.logback.classic.servlet.LogbackServletContainerInitializer",
osgi.serviceloader;osgi.serviceloader="org.slf4j.spi.SLF4JServiceProvider";register:="ch.qos.logback.classic.spi.LogbackServiceProvider"
]]></Provide-Capability>
</instructions>
</configuration>
</plugin>
</plugins>
<pluginManagement>
<plugins>
</plugins>
</pluginManagement>
</build>
</project>
@@ -0,0 +1 @@
455cc31d881b6d0c2da70975899bcd5fe714273f
@@ -0,0 +1,4 @@
#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice.
#Tue Nov 11 18:42:30 CST 2025
logback-core-1.4.11.jar>alimaven=
logback-core-1.4.11.pom>alimaven=
@@ -0,0 +1 @@
2f9f280219a9922a74200eaf7138c4c17fb87c0f
@@ -0,0 +1,158 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-parent</artifactId>
<version>1.4.11</version>
</parent>
<artifactId>logback-core</artifactId>
<packaging>jar</packaging>
<name>Logback Core Module</name>
<description>logback-core module</description>
<properties>
<module-name>ch.qos.logback.core</module-name>
</properties>
<dependencies>
<dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>janino</artifactId>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>commons-compiler</artifactId>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.fusesource.jansi</groupId>
<artifactId>jansi</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.angus</groupId>
<artifactId>angus-mail</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!--<argLine>XXadd-opens ch.qos.logback.core/ch.qos.logback.core.testUtil=java.naming</argLine>-->
<!--<argLine>add-opens ch.qos.logback.core/ch.qos.logback.core.testUtil=java.naming
add-reads ch.qos.logback.core=ALL-UNNAMED</argLine>-->
<argLine>
--add-opens ch.qos.logback.core/ch.qos.logback.core.testUtil=java.naming
--add-reads ch.qos.logback.core=ALL-UNNAMED
</argLine>
<parallel>classes</parallel>
<threadCount>8</threadCount>
<!--<useUnlimitedThreads>false</useUnlimitedThreads>-->
<forkCount>1</forkCount>
<reuseForks>true</reuseForks>
<reportFormat>plain</reportFormat>
<trimStackTrace>false</trimStackTrace>
<!-- See https://issues.apache.org/jira/browse/SUREFIRE-1265 -->
<childDelegation>true</childDelegation>
<excludes>
<exclude>**/All*Test.java</exclude>
<exclude>**/PackageTest.java</exclude>
<!-- ConsoleAppenderTest redirects System.out which is not well tolerated by Maven -->
<exclude>**/ConsoleAppenderTest.java</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
<executions>
<execution>
<id>bundle-test-jar</id>
<phase>package</phase>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
<configuration>
<instructions>
<Export-Package>ch.qos.logback.core*</Export-Package>
<Import-Package>
ch.qos.logback.core*;version="${range;[==,+);${version_cleanup;${project.version}}}",
jakarta.*;resolution:=optional,
org.xml.*;resolution:=optional,
org.fusesource.jansi;resolution:=optional,
org.codehaus.janino;resolution:=optional,
org.codehaus.commons.compiler;resolution:=optional,
*
</Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1 @@
a7aa7a33d13883c3e557aa431f49607d8452b591
@@ -0,0 +1,3 @@
#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice.
#Tue Nov 11 18:42:21 CST 2025
logback-parent-1.4.11.pom>alimaven=
@@ -0,0 +1,584 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-parent</artifactId>
<version>1.4.11</version>
<packaging>pom</packaging>
<name>Logback-Parent</name>
<description>logback project pom.xml file</description>
<url>http://logback.qos.ch</url>
<organization>
<name>QOS.ch</name>
<url>http://www.qos.ch</url>
</organization>
<inceptionYear>2005</inceptionYear>
<licenses>
<license>
<name>Eclipse Public License - v 1.0</name>
<url>http://www.eclipse.org/legal/epl-v10.html</url>
</license>
<license>
<name>GNU Lesser General Public License</name>
<url>http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html</url>
</license>
</licenses>
<scm>
<url>https://github.com/qos-ch/logback</url>
<connection>scm:git@github.com:qos-ch/logback.git</connection>
</scm>
<modules>
<module>logback-core</module>
<module>logback-core-blackbox</module>
<module>logback-classic</module>
<module>logback-classic-blackbox</module>
<module>logback-access</module>
<module>logback-examples</module>
</modules>
<properties>
<!-- yyyy-MM-dd'T'HH:mm:ss'Z' -->
<project.build.outputTimestamp>2023-08-09T19:23:17Z</project.build.outputTimestamp>
<jdk.version>11</jdk.version>
<maven.compiler.release>${jdk.version}</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>4.13.1</junit.version>
<junit-jupiter-api.version>5.9.1</junit-jupiter-api.version>
<junit-vintage-engine.version>5.9.1</junit-vintage-engine.version>
<junit-jupiter-params.version>5.9.1</junit-jupiter-params.version>
<assertj-core.version>3.23.1</assertj-core.version>
<hamcrest.version>2.2</hamcrest.version>
<jakarta.mail.version>2.1.0</jakarta.mail.version>
<jakarta.activation.version>2.1.0</jakarta.activation.version>
<jakarta.angus-mail.version>1.0.0</jakarta.angus-mail.version>
<jakarta.servlet.version>5.0.0</jakarta.servlet.version>
<greenmail.version>2.0.0-alpha-1</greenmail.version>
<janino.version>3.1.8</janino.version>
<!-- slf4j.version property is used below, in
logback-classic/pom.xml, /logback-examples/src/main/resources/setClasspath.cmd, download.html
-->
<slf4j.version>2.0.7</slf4j.version>
<cal10n.version>0.8.1</cal10n.version>
<consolePlugin.version>1.1.0</consolePlugin.version>
<tomcat.version>10.0.10</tomcat.version>
<jetty.version>11.0.12</jetty.version>
<jackson.version>2.15.0</jackson.version>
<!--<jansi.version>1.18</jansi.version>-->
<jansi.version>2.4.0</jansi.version>
<mockito-core.version>4.8.0</mockito-core.version>
<byte-buddy.version>1.12.14</byte-buddy.version>
<maven-compiler-plugin.version>3.10.1</maven-compiler-plugin.version>
<maven-surefire-plugin.version>3.0.0-M7</maven-surefire-plugin.version>
<maven-site-plugin.version>3.7.1</maven-site-plugin.version>
<maven-install-plugin.version>3.0.0-M1</maven-install-plugin.version>
<maven-javadoc-plugin.version>3.3.0</maven-javadoc-plugin.version>
<maven-source-plugin.version>3.2.0</maven-source-plugin.version>
<maven-resources-plugin.version>3.1.0</maven-resources-plugin.version>
<license-maven-plugin.version>3.0</license-maven-plugin.version>
<maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
<maven-jxr-plugin.version>3.1.1</maven-jxr-plugin.version>
<maven-release-plugin.version>3.0.0-M4</maven-release-plugin.version>
<maven-deploy-plugin.version>3.0.0-M1</maven-deploy-plugin.version>
<maven-dependency-plugin.version>3.2.0</maven-dependency-plugin.version>
<maven-bundle-plugin.version>5.1.8</maven-bundle-plugin.version>
<maven-antrun-plugin.version>3.1.0</maven-antrun-plugin.version>
<ant.version>1.10.12</ant.version>
<cobertura.maven.plugin.version>2.7</cobertura.maven.plugin.version>
</properties>
<developers>
<developer>
<id>ceki</id>
<name>Ceki Gulcu</name>
<email>ceki@qos.ch</email>
</developer>
<developer>
<id>hixi</id>
<name>Joern Huxhorn</name>
<email>huxi@undisclosed.org</email>
</developer>
</developers>
<dependencies>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj-core.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit-jupiter-api.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit-jupiter-api.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- start of dependencies duplicated from logback-bom/pom.xml -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
</dependency>
<!-- end of dependencies duplicated from logback-bom/pom.xml -->
<dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>janino</artifactId>
<version>${janino.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>commons-compiler</artifactId>
<version>${janino.version}</version>
</dependency>
<dependency>
<groupId>org.fusesource.jansi</groupId>
<artifactId>jansi</artifactId>
<version>${jansi.version}</version>
</dependency>
<dependency>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
<version>${jakarta.mail.version}</version>
</dependency>
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<version>${jakarta.activation.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.angus</groupId>
<artifactId>angus-mail</artifactId>
<version>${jakarta.angus-mail.version}</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>${jakarta.servlet.version}</version>
</dependency>
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>
<version>${greenmail.version}</version>
</dependency>
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-coyote</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito-core.version}</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>${byte-buddy.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<extensions>
<extension>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ssh</artifactId>
<version>2.10</version>
</extension>
</extensions>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>${maven-deploy-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>${maven-install-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>${maven-resources-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>${maven-site-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
<configuration>
<outputTimestamp>${project.build.outputTimestamp}</outputTimestamp>
<archive>
<index>true</index>
<manifest>
<!-- Add version information into the MANIFEST so it is accessible by
calling Package#getImplementationVersion()
-->
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>${maven-javadoc-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>${maven-dependency-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>${maven-release-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${maven-source-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>3.0.4</version>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>${maven-bundle-plugin.version}</version>
</plugin>
<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>${jdk.version}</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!--
<plugin>
<groupId>org.simplify4u.plugins</groupId>
<artifactId>pgpverify-maven-plugin</artifactId>
<version>1.16.0</version>
</plugin>
-->
</plugins>
</build>
<distributionManagement>
<repository>
<id>sonatype-nexus-staging</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
<reporting>
<plugins>
<!--
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jxr-plugin</artifactId>
<version>2.5</version>
<configuration>
<aggregate>true</aggregate>
<javadocDir>target/site/apidocs/</javadocDir>
<linkJavadoc>true</linkJavadoc>
</configuration>
</plugin>
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jxr-plugin</artifactId>
<version>${maven-jxr-plugin.version}</version>
<configuration>
<aggregate>true</aggregate>
<javadocDir>target/site/apidocs/</javadocDir>
<linkJavadoc>true</linkJavadoc>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<aggregate>true</aggregate>
<linkJavadoc>true</linkJavadoc>
<linksource>true</linksource>
<doclint>none</doclint>
<sourceFileExcludes>
<sourceFileExclude>**/module-info.java</sourceFileExclude>
</sourceFileExcludes>
<links>
<link>
http://docs.oracle.com/javase/7/docs/api/
</link>
</links>
<groups>
<group>
<title>Logback Core</title>
<packages>ch.qos.logback.core:ch.qos.logback.core.*
</packages>
</group>
<group>
<title>Logback Classic</title>
<packages>
ch.qos.logback:ch.qos.logback.classic:ch.qos.logback.classic.*
</packages>
</group>
<group>
<title>Logback Access</title>
<packages>ch.qos.logback.access:ch.qos.logback.access.*
</packages>
</group>
<group>
<title>Examples</title>
<packages>chapter*:joran*</packages>
</group>
</groups>
</configuration>
</plugin>
</plugins>
</reporting>
<profiles>
<profile>
<id>testSkip</id>
<properties>
<maven.test.skip>true</maven.test.skip>
</properties>
</profile>
<profile>
<id>license</id>
<build>
<plugins>
<plugin>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
<version>${license-maven-plugin.version}</version>
<configuration>
<header>src/main/licenseHeader.txt</header>
<quiet>false</quiet>
<failIfMissing>true</failIfMissing>
<aggregate>true</aggregate>
<includes>
<include>src/**/*.java</include>
<include>src/**/*.groovy</include>
</includes>
<useDefaultExcludes>true</useDefaultExcludes>
<useDefaultMapping>true</useDefaultMapping>
<properties>
<year>1999</year>
</properties>
<headerDefinitions>
<headerDefinition>src/main/javadocHeaders.xml</headerDefinition>
</headerDefinitions>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>javadocjar</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>${maven-javadoc-plugin.version}</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
<configuration>
<doclint>none</doclint>
<sourceFileExcludes>
<sourceFileExclude>**/module-info.java</sourceFileExclude>
</sourceFileExcludes>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>sign-artifacts</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
@@ -0,0 +1 @@
0217a638da743248ecd5c6093ed9e169510bd781
@@ -0,0 +1,3 @@
#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice.
#Tue Nov 11 18:42:09 CST 2025
classworlds-1.1-alpha-2.pom>alimaven=
@@ -0,0 +1,95 @@
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>classworlds</groupId>
<artifactId>classworlds</artifactId>
<name>classworlds</name>
<version>1.1-alpha-2</version>
<description></description>
<url>http://classworlds.codehaus.org/</url>
<ciManagement>
<notifiers>
<notifier>
<address>classworlds-scm@lists.codehaus.org</address>
</notifier>
</notifiers>
</ciManagement>
<inceptionYear>2002</inceptionYear>
<mailingLists>
<mailingList>
<name>classworlds users</name>
<subscribe>http://lists.codehaus.org/mailman/listinfo/classworlds-user</subscribe>
<unsubscribe>http://lists.codehaus.org/mailman/listinfo/classworlds-user</unsubscribe>
<archive>http://lists.codehaus.org/pipermail/classworlds-user/</archive>
</mailingList>
<mailingList>
<name>classworlds developers</name>
<subscribe>http://lists.codehaus.org/mailman/listinfo/classworlds-dev</subscribe>
<unsubscribe>http://lists.codehaus.org/mailman/listinfo/classworlds-dev</unsubscribe>
<archive>http://lists.codehaus.org/pipermail/classworlds-dev/</archive>
</mailingList>
<mailingList>
<name>classworlds commit messages</name>
<subscribe>http://lists.codehaus.org/mailman/listinfo/classworlds-scm</subscribe>
<unsubscribe>http://lists.codehaus.org/mailman/listinfo/classworlds-scm</unsubscribe>
<archive>http://lists.codehaus.org/pipermail/classworlds-scm/</archive>
</mailingList>
</mailingLists>
<developers>
<developer>
<id>bob</id>
<name>bob mcwhirter</name>
<email>bob@werken.com</email>
<organization>The Werken Company</organization>
<roles>
<role>Founder</role>
</roles>
</developer>
<developer>
<id>jvanzyl</id>
<name>Jason van Zyl</name>
<email>jason@zenplex.com</email>
<organization>Zenplex</organization>
<roles>
<role>Developer</role>
</roles>
</developer>
<developer>
<id>bwalding</id>
<name>Ben Walding</name>
<email>ben@walding.com</email>
<organization>Walding Consulting Services</organization>
<roles>
<role>Developer</role>
</roles>
</developer>
</developers>
<scm>
<connection>scm:cvs:pserver:anonymous@cvs.codehaus.org:/scm/cvspublic/:classworlds</connection>
<url>http://cvs.classworlds.codehaus.org/</url>
</scm>
<organization>
<name>The Codehaus</name>
<url>http://codehaus.org/</url>
</organization>
<build>
<sourceDirectory>src/java/main</sourceDirectory>
<testSourceDirectory>src/java/test</testSourceDirectory>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>**/*Test.java</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
<distributionManagement>
<site>
<id>default</id>
<name>Default Site</name>
<url>scp://classworlds.codehaus.org//www/classworlds.codehaus.org</url>
</site>
</distributionManagement>
</project>
@@ -0,0 +1 @@
8c8ad6a96a8c1168f8b12ec8a227b8261b160b26
@@ -0,0 +1,4 @@
#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice.
#Tue Nov 11 18:42:20 CST 2025
classmate-1.5.1.jar>alimaven=
classmate-1.5.1.pom>alimaven=

Some files were not shown because too many files have changed in this diff Show More