coderfleet 0.1.0__py3-none-any.whl
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.
- coderfleet/__init__.py +1 -0
- coderfleet/__main__.py +4 -0
- coderfleet/cli.py +212 -0
- coderfleet/compose.py +176 -0
- coderfleet/config.py +69 -0
- coderfleet/config_cmds.py +243 -0
- coderfleet/data/Dockerfile +92 -0
- coderfleet/data/__init__.py +0 -0
- coderfleet/data/accounts.conf.example +26 -0
- coderfleet/data/config.conf.example +31 -0
- coderfleet/data/entrypoint.sh +56 -0
- coderfleet/data/projects.conf.example +17 -0
- coderfleet/data/scripts/coderfleet_usage_status.py +138 -0
- coderfleet/docker_ops.py +385 -0
- coderfleet/init_wizard.py +227 -0
- coderfleet/login_cmd.py +168 -0
- coderfleet/server/__init__.py +0 -0
- coderfleet/server/docker_mgr.py +45 -0
- coderfleet/server/main.py +546 -0
- coderfleet/server/models.py +285 -0
- coderfleet/server/scheduler.py +1219 -0
- coderfleet/server/static/css/main.css +2906 -0
- coderfleet/server/static/index.html +378 -0
- coderfleet/server/static/js/accounts.js +85 -0
- coderfleet/server/static/js/app.js +28 -0
- coderfleet/server/static/js/chat.js +743 -0
- coderfleet/server/static/js/log.js +145 -0
- coderfleet/server/static/js/nav.js +46 -0
- coderfleet/server/static/js/projects.js +298 -0
- coderfleet/server/static/js/renderer.js +586 -0
- coderfleet/server/static/js/state.js +76 -0
- coderfleet/server/static/js/submit.js +200 -0
- coderfleet/server/static/js/tasks.js +92 -0
- coderfleet/server/static/js/terminal.js +347 -0
- coderfleet/server/static/js/utils.js +147 -0
- coderfleet/server/static/vendor/marked.min.js +6 -0
- coderfleet/server/static/vendor/xterm/addon-fit.js +2 -0
- coderfleet/server/static/vendor/xterm/xterm.css +218 -0
- coderfleet/server/static/vendor/xterm/xterm.js +2 -0
- coderfleet/server/terminal.py +129 -0
- coderfleet/task_cmds.py +311 -0
- coderfleet-0.1.0.dist-info/METADATA +492 -0
- coderfleet-0.1.0.dist-info/RECORD +45 -0
- coderfleet-0.1.0.dist-info/WHEEL +4 -0
- coderfleet-0.1.0.dist-info/entry_points.txt +2 -0
coderfleet/login_cmd.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""
|
|
2
|
+
login_cmd.py — coderfleet login 命令
|
|
3
|
+
|
|
4
|
+
交互式账号登录,通过 docker exec(已运行容器)或 docker run(临时容器)
|
|
5
|
+
执行 claude login / codex login --device-auth。
|
|
6
|
+
|
|
7
|
+
关键技术:os.execvp() 替换当前进程,确保 TTY 完整继承。
|
|
8
|
+
当 login all 时改用 subprocess.run() 以便在循环中逐一执行。
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import subprocess
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import click
|
|
17
|
+
|
|
18
|
+
from coderfleet.config import load_config, parse_conf
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ── helpers ───────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _is_running(container: str) -> bool:
|
|
25
|
+
r = subprocess.run(
|
|
26
|
+
["docker", "inspect", container, "--format", "{{.State.Running}}"],
|
|
27
|
+
capture_output=True, text=True,
|
|
28
|
+
)
|
|
29
|
+
return r.stdout.strip() == "true"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _cli_and_args(acc_type: str) -> tuple[str, list[str]]:
|
|
33
|
+
"""Return (cli_binary, login_subcommand_args) for the given account type."""
|
|
34
|
+
if acc_type == "claude":
|
|
35
|
+
return "claude", ["login"]
|
|
36
|
+
return "codex", ["login", "--device-auth"]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _login_single(ws: Path, target: str, *, replace_process: bool = True) -> int:
|
|
40
|
+
"""
|
|
41
|
+
Login one account.
|
|
42
|
+
replace_process=True → os.execvp (never returns, proper TTY for single login)
|
|
43
|
+
replace_process=False → subprocess.run (returns exit code, used by 'all' loop)
|
|
44
|
+
"""
|
|
45
|
+
accounts = {r["NAME"]: r for r in parse_conf(ws / "accounts.conf") if "NAME" in r}
|
|
46
|
+
if target not in accounts:
|
|
47
|
+
raise click.ClickException(f"账号 '{target}' 不在 accounts.conf 中")
|
|
48
|
+
|
|
49
|
+
acc = accounts[target]
|
|
50
|
+
acc_type = acc.get("TYPE", "")
|
|
51
|
+
auth = acc.get("AUTH", "login")
|
|
52
|
+
env_file = acc.get("ENV_FILE", "")
|
|
53
|
+
|
|
54
|
+
if auth == "env":
|
|
55
|
+
click.secho(
|
|
56
|
+
f"账号 {target} 使用环境变量认证(ENV_FILE={env_file}),无需交互登录",
|
|
57
|
+
fg="cyan",
|
|
58
|
+
)
|
|
59
|
+
click.secho(
|
|
60
|
+
"Claude Code 会优先使用 ANTHROPIC_API_KEY,可能按 API 计费;"
|
|
61
|
+
"请用 /status 确认认证方式",
|
|
62
|
+
fg="yellow",
|
|
63
|
+
)
|
|
64
|
+
return 0
|
|
65
|
+
|
|
66
|
+
cli, login_args = _cli_and_args(acc_type)
|
|
67
|
+
|
|
68
|
+
# Find a running project container for this account
|
|
69
|
+
running_ctr: str | None = None
|
|
70
|
+
for p in parse_conf(ws / "projects.conf"):
|
|
71
|
+
if p.get("ACCOUNT") != target:
|
|
72
|
+
continue
|
|
73
|
+
pname = p.get("NAME", "")
|
|
74
|
+
ctr = f"{acc_type}-{pname}"
|
|
75
|
+
if _is_running(ctr):
|
|
76
|
+
running_ctr = ctr
|
|
77
|
+
break
|
|
78
|
+
|
|
79
|
+
click.echo(f"登录账号 {target}(类型:{acc_type})...")
|
|
80
|
+
click.secho(" 提示:浏览器打开授权 URL → 复制 code → 粘贴回来", fg="yellow")
|
|
81
|
+
click.echo()
|
|
82
|
+
|
|
83
|
+
if running_ctr:
|
|
84
|
+
cmd = ["docker", "exec", "-it", running_ctr, cli] + login_args
|
|
85
|
+
if replace_process:
|
|
86
|
+
os.execvp(cmd[0], cmd)
|
|
87
|
+
return subprocess.run(cmd).returncode
|
|
88
|
+
|
|
89
|
+
# No running container → start a temporary one
|
|
90
|
+
cfg = load_config(ws)
|
|
91
|
+
image = f"{cfg.get('IMAGE_NAME', 'coderfleet')}:{cfg.get('IMAGE_TAG', 'latest')}"
|
|
92
|
+
platform = cfg.get("BUILD_PLATFORM", "linux/amd64")
|
|
93
|
+
proxy_host = cfg.get("PROXY_HOST", "host.docker.internal")
|
|
94
|
+
proxy_port = cfg.get("PROXY_HTTP_PORT", "7890")
|
|
95
|
+
proxy_url = f"http://{proxy_host}:{proxy_port}"
|
|
96
|
+
auth_dst = "/home/byclaw/.codex" if acc_type == "codex" else "/home/byclaw/.claude"
|
|
97
|
+
auth_src = str(ws / "accounts" / target)
|
|
98
|
+
login_ctr = f"coderfleet-login-{acc_type}-{target}"
|
|
99
|
+
|
|
100
|
+
r = subprocess.run(["docker", "image", "inspect", image], capture_output=True)
|
|
101
|
+
if r.returncode != 0:
|
|
102
|
+
raise click.ClickException(f"镜像 {image} 不存在,请先执行:coderfleet build")
|
|
103
|
+
|
|
104
|
+
Path(auth_src).mkdir(parents=True, exist_ok=True)
|
|
105
|
+
subprocess.run(["docker", "rm", "-f", login_ctr], capture_output=True)
|
|
106
|
+
|
|
107
|
+
click.secho(f"账号容器未运行,启动临时认证容器 {login_ctr}...", fg="cyan")
|
|
108
|
+
|
|
109
|
+
cmd = [
|
|
110
|
+
"docker", "run", "--rm", "-it",
|
|
111
|
+
"--name", login_ctr,
|
|
112
|
+
"--platform", platform,
|
|
113
|
+
"--add-host", "host.docker.internal:host-gateway",
|
|
114
|
+
"-e", f"HTTP_PROXY={proxy_url}",
|
|
115
|
+
"-e", f"HTTPS_PROXY={proxy_url}",
|
|
116
|
+
"-e", f"http_proxy={proxy_url}",
|
|
117
|
+
"-e", f"https_proxy={proxy_url}",
|
|
118
|
+
"-e", f"ALL_PROXY={proxy_url}",
|
|
119
|
+
"-e", f"all_proxy={proxy_url}",
|
|
120
|
+
"-e", "NO_PROXY=localhost,127.0.0.1",
|
|
121
|
+
"-e", "no_proxy=localhost,127.0.0.1",
|
|
122
|
+
"-e", "CODEX_HOME=/home/byclaw/.codex",
|
|
123
|
+
"-e", "CLAUDE_CONFIG_DIR=/home/byclaw/.claude",
|
|
124
|
+
"-e", f"CODERFLEET_ACCOUNT_NAME={target}",
|
|
125
|
+
"-e", f"CODERFLEET_ACCOUNT_TYPE={acc_type}",
|
|
126
|
+
"-e", f"CODERFLEET_RELAY_IP={proxy_host}",
|
|
127
|
+
"-e", f"CODERFLEET_RELAY_PORT={proxy_port}",
|
|
128
|
+
"-v", f"{auth_src}:{auth_dst}",
|
|
129
|
+
"-w", "/workspace",
|
|
130
|
+
image, cli,
|
|
131
|
+
] + login_args
|
|
132
|
+
|
|
133
|
+
if replace_process:
|
|
134
|
+
os.execvp(cmd[0], cmd)
|
|
135
|
+
return subprocess.run(cmd).returncode
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# ── Click command ─────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@click.command("login")
|
|
142
|
+
@click.argument("target", metavar="<account|all>")
|
|
143
|
+
@click.pass_context
|
|
144
|
+
def cmd_login(ctx: click.Context, target: str) -> None:
|
|
145
|
+
"""Login an account (or all accounts) interactively.
|
|
146
|
+
|
|
147
|
+
Uses an existing project container if one is running, otherwise
|
|
148
|
+
starts a temporary container for the login flow.
|
|
149
|
+
"""
|
|
150
|
+
ws: Path = ctx.obj["workspace"]
|
|
151
|
+
|
|
152
|
+
if target != "all":
|
|
153
|
+
_login_single(ws, target, replace_process=True)
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
accounts = [r for r in parse_conf(ws / "accounts.conf") if "NAME" in r]
|
|
157
|
+
if not accounts:
|
|
158
|
+
raise click.ClickException("accounts.conf 中没有账号")
|
|
159
|
+
|
|
160
|
+
total = len(accounts)
|
|
161
|
+
for i, acc in enumerate(accounts, 1):
|
|
162
|
+
name = acc["NAME"]
|
|
163
|
+
click.echo()
|
|
164
|
+
click.secho(f" ── [{i}/{total}] 账号:{name} " + "─" * 30, bold=True)
|
|
165
|
+
_login_single(ws, name, replace_process=False)
|
|
166
|
+
|
|
167
|
+
click.echo()
|
|
168
|
+
click.secho("✓ 所有账号登录完成", fg="green")
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
docker_mgr.py — Docker 操作封装
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _dc_cmd() -> str:
|
|
11
|
+
result = subprocess.run(
|
|
12
|
+
["docker", "compose", "version"],
|
|
13
|
+
capture_output=True, text=True,
|
|
14
|
+
)
|
|
15
|
+
if result.returncode == 0:
|
|
16
|
+
return "docker compose"
|
|
17
|
+
if shutil.which("docker-compose"):
|
|
18
|
+
return "docker-compose"
|
|
19
|
+
raise RuntimeError("找不到 docker compose 或 docker-compose")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_DC: str | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _get_dc() -> str:
|
|
26
|
+
global _DC
|
|
27
|
+
if _DC is None:
|
|
28
|
+
_DC = _dc_cmd()
|
|
29
|
+
return _DC
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def is_container_running(container_name: str) -> bool:
|
|
33
|
+
result = subprocess.run(
|
|
34
|
+
["docker", "inspect", container_name, "--format", "{{.State.Running}}"],
|
|
35
|
+
capture_output=True, text=True,
|
|
36
|
+
)
|
|
37
|
+
return result.stdout.strip() == "true"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def exec_sync(container_name: str, cmd: str) -> tuple[int, str, str]:
|
|
41
|
+
result = subprocess.run(
|
|
42
|
+
["docker", "exec", container_name, "bash", "-c", cmd],
|
|
43
|
+
capture_output=True, text=True,
|
|
44
|
+
)
|
|
45
|
+
return result.returncode, result.stdout, result.stderr
|