ai-code-switcher 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ai_code_switcher-0.1.0/PKG-INFO +7 -0
- ai_code_switcher-0.1.0/README.md +98 -0
- ai_code_switcher-0.1.0/pyproject.toml +19 -0
- ai_code_switcher-0.1.0/setup.cfg +4 -0
- ai_code_switcher-0.1.0/src/ai_code_switcher.egg-info/PKG-INFO +7 -0
- ai_code_switcher-0.1.0/src/ai_code_switcher.egg-info/SOURCES.txt +13 -0
- ai_code_switcher-0.1.0/src/ai_code_switcher.egg-info/dependency_links.txt +1 -0
- ai_code_switcher-0.1.0/src/ai_code_switcher.egg-info/entry_points.txt +2 -0
- ai_code_switcher-0.1.0/src/ai_code_switcher.egg-info/requires.txt +1 -0
- ai_code_switcher-0.1.0/src/ai_code_switcher.egg-info/top_level.txt +1 -0
- ai_code_switcher-0.1.0/src/code_ai/__init__.py +1 -0
- ai_code_switcher-0.1.0/src/code_ai/cli.py +77 -0
- ai_code_switcher-0.1.0/src/code_ai/config.py +56 -0
- ai_code_switcher-0.1.0/src/code_ai/launcher.py +48 -0
- ai_code_switcher-0.1.0/src/code_ai/profiles.py +61 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# code-ai
|
|
2
|
+
|
|
3
|
+
一个用于切换 AI 编码工具配置文件并启动相应 CLI 的工具。
|
|
4
|
+
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
- 管理多个 AI 编码工具配置文件(Claude、Codex、Gemini)
|
|
8
|
+
- 快速切换不同的配置文件
|
|
9
|
+
- 统一的命令行接口
|
|
10
|
+
- 支持一键升级所有 AI CLI 工具
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install -e .
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 使用方法
|
|
19
|
+
|
|
20
|
+
### 列出所有配置文件
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
code-ai list
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 添加新配置文件
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
code-ai add
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 使用指定配置文件启动
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# 使用 fox-gemini 配置启动 Gemini CLI
|
|
36
|
+
code-ai fox-gemini
|
|
37
|
+
|
|
38
|
+
# 使用 4399 配置启动 Claude CLI
|
|
39
|
+
code-ai 4399
|
|
40
|
+
|
|
41
|
+
# 传递额外参数
|
|
42
|
+
code-ai fox-claude -p "hi"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 删除配置文件
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
code-ai remove <profile-name>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 升级 AI CLI 工具
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
code-ai upgrade
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
该命令会通过 npm 升级以下工具:
|
|
58
|
+
- @anthropic-ai/claude-code
|
|
59
|
+
- @openai/codex
|
|
60
|
+
- @google/gemini-cli
|
|
61
|
+
|
|
62
|
+
### 查看版本
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
code-ai --version
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 查看帮助
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
code-ai --help
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 配置
|
|
75
|
+
|
|
76
|
+
配置文件存储在用户目录下,包含各个配置文件的设置信息。
|
|
77
|
+
|
|
78
|
+
## 开发
|
|
79
|
+
|
|
80
|
+
### 项目结构
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
src/code_ai/
|
|
84
|
+
├── __init__.py # 包初始化
|
|
85
|
+
├── cli.py # 命令行入口
|
|
86
|
+
├── config.py # 配置管理
|
|
87
|
+
├── launcher.py # 启动器
|
|
88
|
+
└── profiles.py # 配置文件管理
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 依赖
|
|
92
|
+
|
|
93
|
+
- Python >= 3.8
|
|
94
|
+
- pyyaml >= 5.0
|
|
95
|
+
|
|
96
|
+
## 许可证
|
|
97
|
+
|
|
98
|
+
MIT
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ai-code-switcher"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Switch AI coding tool profiles and launch the correct CLI"
|
|
9
|
+
license = "MIT"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"pyyaml>=5.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
code-ai = "code_ai.cli:main"
|
|
17
|
+
|
|
18
|
+
[tool.setuptools.packages.find]
|
|
19
|
+
where = ["src"]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/ai_code_switcher.egg-info/PKG-INFO
|
|
4
|
+
src/ai_code_switcher.egg-info/SOURCES.txt
|
|
5
|
+
src/ai_code_switcher.egg-info/dependency_links.txt
|
|
6
|
+
src/ai_code_switcher.egg-info/entry_points.txt
|
|
7
|
+
src/ai_code_switcher.egg-info/requires.txt
|
|
8
|
+
src/ai_code_switcher.egg-info/top_level.txt
|
|
9
|
+
src/code_ai/__init__.py
|
|
10
|
+
src/code_ai/cli.py
|
|
11
|
+
src/code_ai/config.py
|
|
12
|
+
src/code_ai/launcher.py
|
|
13
|
+
src/code_ai/profiles.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyyaml>=5.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
code_ai
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
from .config import load_config, save_config
|
|
5
|
+
from .profiles import list_profiles, add_profile, remove_profile
|
|
6
|
+
from .launcher import launch
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
USAGE = """\
|
|
10
|
+
usage: code-ai <profile|command> [args...]
|
|
11
|
+
|
|
12
|
+
commands:
|
|
13
|
+
list List all profiles
|
|
14
|
+
add Add a new profile interactively
|
|
15
|
+
upgrade Upgrade claude, codex, and gemini CLI via npm
|
|
16
|
+
remove <name> Remove a profile
|
|
17
|
+
--version Show version
|
|
18
|
+
--help Show this help
|
|
19
|
+
|
|
20
|
+
examples:
|
|
21
|
+
code-ai fox-gemini Launch Gemini CLI with fox profile
|
|
22
|
+
code-ai 4399 Launch Claude CLI with 4399 profile
|
|
23
|
+
code-ai fox-claude -p "hi" Pass extra args to Claude CLI\
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main():
|
|
28
|
+
args = sys.argv[1:]
|
|
29
|
+
|
|
30
|
+
if not args or args[0] in ("-h", "--help"):
|
|
31
|
+
print(USAGE)
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
if args[0] == "--version":
|
|
35
|
+
from . import __version__
|
|
36
|
+
print(f"code-ai {__version__}")
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
cmd = args[0]
|
|
40
|
+
config = load_config()
|
|
41
|
+
|
|
42
|
+
if cmd == "list":
|
|
43
|
+
list_profiles(config)
|
|
44
|
+
elif cmd == "add":
|
|
45
|
+
config = add_profile(config)
|
|
46
|
+
save_config(config)
|
|
47
|
+
print("Profile added.")
|
|
48
|
+
elif cmd == "upgrade":
|
|
49
|
+
upgrade()
|
|
50
|
+
elif cmd == "remove":
|
|
51
|
+
if len(args) < 2:
|
|
52
|
+
print("Usage: code-ai remove <name>")
|
|
53
|
+
sys.exit(1)
|
|
54
|
+
config = remove_profile(config, args[1])
|
|
55
|
+
save_config(config)
|
|
56
|
+
else:
|
|
57
|
+
profiles = config.get("profiles", {})
|
|
58
|
+
if cmd not in profiles:
|
|
59
|
+
print(f"Unknown profile or command: '{cmd}'")
|
|
60
|
+
print("Run 'code-ai list' to see available profiles.")
|
|
61
|
+
sys.exit(1)
|
|
62
|
+
launch(profiles[cmd], args[1:])
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
UPGRADE_PACKAGES = [
|
|
66
|
+
"@anthropic-ai/claude-code",
|
|
67
|
+
"@openai/codex",
|
|
68
|
+
"@google/gemini-cli",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def upgrade():
|
|
73
|
+
print("Upgrading claude, codex, gemini CLI...")
|
|
74
|
+
result = subprocess.run(
|
|
75
|
+
["npm", "install", "-g"] + UPGRADE_PACKAGES,
|
|
76
|
+
)
|
|
77
|
+
sys.exit(result.returncode)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
CONFIG_DIR = Path.home() / ".code-ai"
|
|
8
|
+
CONFIG_FILE = CONFIG_DIR / "config.yaml"
|
|
9
|
+
|
|
10
|
+
DEFAULT_PROFILES = {
|
|
11
|
+
"fox-claude": {
|
|
12
|
+
"type": "claude",
|
|
13
|
+
"base_url": "https://code.newcli.com/claude/aws",
|
|
14
|
+
"token": "sk-ant-oat01-j6FU43eI3djEeLFWcw_2mtngnBqwSEsOXMYkJtidPDxeCiqIeaTtHg6p12wvi7HaYIsp7VsK5kVbdwYlBW_mCBnSYmZ-qAA",
|
|
15
|
+
},
|
|
16
|
+
"fox-gemini": {
|
|
17
|
+
"type": "gemini",
|
|
18
|
+
"base_url": "https://code.newcli.com/gemini",
|
|
19
|
+
"api_key": "sk-ant-oat01-j6FU43eI3djEeLFWcw_2mtngnBqwSEsOXMYkJtidPDxeCiqIeaTtHg6p12wvi7HaYIsp7VsK5kVbdwYlBW_mCBnSYmZ-qAA",
|
|
20
|
+
},
|
|
21
|
+
"fox-codex": {
|
|
22
|
+
"type": "codex",
|
|
23
|
+
"base_url": "https://code.newcli.com/codex/v1",
|
|
24
|
+
"api_key": "sk-ant-oat01-j6FU43eI3djEeLFWcw_2mtngnBqwSEsOXMYkJtidPDxeCiqIeaTtHg6p12wvi7HaYIsp7VsK5kVbdwYlBW_mCBnSYmZ-qAA",
|
|
25
|
+
},
|
|
26
|
+
"4399": {
|
|
27
|
+
"type": "claude",
|
|
28
|
+
"base_url": "https://4399code.com/claudecode",
|
|
29
|
+
"token": "sk-ant-oat01-ozUCp9rqL9sB46GoG5ISB1QPmlQPKZyk",
|
|
30
|
+
},
|
|
31
|
+
"2233": {
|
|
32
|
+
"type": "claude",
|
|
33
|
+
"base_url": "https://aicoding.2233.ai",
|
|
34
|
+
"token": "sk-Xn6481958ee796a17126b676467f0e06d5e6f39ac14Uusba",
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def load_config():
|
|
40
|
+
if not CONFIG_FILE.exists():
|
|
41
|
+
init_config()
|
|
42
|
+
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
|
43
|
+
data = yaml.safe_load(f)
|
|
44
|
+
if not data or "profiles" not in data:
|
|
45
|
+
data = {"profiles": {}}
|
|
46
|
+
return data
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def save_config(data):
|
|
50
|
+
os.makedirs(CONFIG_DIR, exist_ok=True)
|
|
51
|
+
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
|
|
52
|
+
yaml.dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def init_config():
|
|
56
|
+
save_config({"profiles": DEFAULT_PROFILES})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
|
|
6
|
+
ENV_MAP = {
|
|
7
|
+
"claude": {
|
|
8
|
+
"env": {"ANTHROPIC_BASE_URL": "base_url", "ANTHROPIC_AUTH_TOKEN": "token"},
|
|
9
|
+
"cmd": "claude",
|
|
10
|
+
},
|
|
11
|
+
"gemini": {
|
|
12
|
+
"env": {"GOOGLE_GEMINI_BASE_URL": "base_url", "GEMINI_API_KEY": "api_key"},
|
|
13
|
+
"cmd": "gemini",
|
|
14
|
+
},
|
|
15
|
+
"codex": {
|
|
16
|
+
"env": {"OPENAI_BASE_URL": "base_url", "OPENAI_API_KEY": "api_key"},
|
|
17
|
+
"cmd": "codex",
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def launch(profile, extra_args):
|
|
23
|
+
ptype = profile["type"]
|
|
24
|
+
if ptype not in ENV_MAP:
|
|
25
|
+
print(f"Error: unknown profile type '{ptype}'.")
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
spec = ENV_MAP[ptype]
|
|
29
|
+
cmd = spec["cmd"]
|
|
30
|
+
|
|
31
|
+
if not shutil.which(cmd):
|
|
32
|
+
print(f"Error: '{cmd}' not found in PATH. Install it first.")
|
|
33
|
+
sys.exit(1)
|
|
34
|
+
|
|
35
|
+
env = os.environ.copy()
|
|
36
|
+
for env_var, config_key in spec["env"].items():
|
|
37
|
+
value = profile.get(config_key)
|
|
38
|
+
if value:
|
|
39
|
+
env[env_var] = value
|
|
40
|
+
|
|
41
|
+
full_cmd = [cmd] + extra_args
|
|
42
|
+
|
|
43
|
+
if sys.platform == "win32":
|
|
44
|
+
result = subprocess.run(full_cmd, env=env)
|
|
45
|
+
sys.exit(result.returncode)
|
|
46
|
+
else:
|
|
47
|
+
os.environ.update(env)
|
|
48
|
+
os.execvp(cmd, full_cmd)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
VALID_TYPES = ("claude", "gemini", "codex")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def list_profiles(config):
|
|
8
|
+
profiles = config.get("profiles", {})
|
|
9
|
+
if not profiles:
|
|
10
|
+
print("No profiles configured.")
|
|
11
|
+
return
|
|
12
|
+
print(f"{'Name':<20} {'Type':<10} {'Base URL'}")
|
|
13
|
+
print("-" * 60)
|
|
14
|
+
for name, p in profiles.items():
|
|
15
|
+
print(f"{name:<20} {p['type']:<10} {p.get('base_url', '')}")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def add_profile(config):
|
|
19
|
+
name = input("Profile name: ").strip()
|
|
20
|
+
if not name:
|
|
21
|
+
print("Error: name cannot be empty.")
|
|
22
|
+
sys.exit(1)
|
|
23
|
+
if name in config.get("profiles", {}):
|
|
24
|
+
print(f"Error: profile '{name}' already exists.")
|
|
25
|
+
sys.exit(1)
|
|
26
|
+
|
|
27
|
+
ptype = input(f"Type ({'/'.join(VALID_TYPES)}): ").strip().lower()
|
|
28
|
+
if ptype not in VALID_TYPES:
|
|
29
|
+
print(f"Error: type must be one of {VALID_TYPES}.")
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
|
|
32
|
+
base_url = input("Base URL: ").strip()
|
|
33
|
+
if not base_url:
|
|
34
|
+
print("Error: base URL cannot be empty.")
|
|
35
|
+
sys.exit(1)
|
|
36
|
+
|
|
37
|
+
if ptype == "claude":
|
|
38
|
+
token = input("Auth token: ").strip()
|
|
39
|
+
config.setdefault("profiles", {})[name] = {
|
|
40
|
+
"type": ptype,
|
|
41
|
+
"base_url": base_url,
|
|
42
|
+
"token": token,
|
|
43
|
+
}
|
|
44
|
+
else:
|
|
45
|
+
api_key = input("API key: ").strip()
|
|
46
|
+
config.setdefault("profiles", {})[name] = {
|
|
47
|
+
"type": ptype,
|
|
48
|
+
"base_url": base_url,
|
|
49
|
+
"api_key": api_key,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return config
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def remove_profile(config, name):
|
|
56
|
+
if name not in config.get("profiles", {}):
|
|
57
|
+
print(f"Error: profile '{name}' not found.")
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
del config["profiles"][name]
|
|
60
|
+
print(f"Removed profile '{name}'.")
|
|
61
|
+
return config
|