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.
Files changed (45) hide show
  1. coderfleet/__init__.py +1 -0
  2. coderfleet/__main__.py +4 -0
  3. coderfleet/cli.py +212 -0
  4. coderfleet/compose.py +176 -0
  5. coderfleet/config.py +69 -0
  6. coderfleet/config_cmds.py +243 -0
  7. coderfleet/data/Dockerfile +92 -0
  8. coderfleet/data/__init__.py +0 -0
  9. coderfleet/data/accounts.conf.example +26 -0
  10. coderfleet/data/config.conf.example +31 -0
  11. coderfleet/data/entrypoint.sh +56 -0
  12. coderfleet/data/projects.conf.example +17 -0
  13. coderfleet/data/scripts/coderfleet_usage_status.py +138 -0
  14. coderfleet/docker_ops.py +385 -0
  15. coderfleet/init_wizard.py +227 -0
  16. coderfleet/login_cmd.py +168 -0
  17. coderfleet/server/__init__.py +0 -0
  18. coderfleet/server/docker_mgr.py +45 -0
  19. coderfleet/server/main.py +546 -0
  20. coderfleet/server/models.py +285 -0
  21. coderfleet/server/scheduler.py +1219 -0
  22. coderfleet/server/static/css/main.css +2906 -0
  23. coderfleet/server/static/index.html +378 -0
  24. coderfleet/server/static/js/accounts.js +85 -0
  25. coderfleet/server/static/js/app.js +28 -0
  26. coderfleet/server/static/js/chat.js +743 -0
  27. coderfleet/server/static/js/log.js +145 -0
  28. coderfleet/server/static/js/nav.js +46 -0
  29. coderfleet/server/static/js/projects.js +298 -0
  30. coderfleet/server/static/js/renderer.js +586 -0
  31. coderfleet/server/static/js/state.js +76 -0
  32. coderfleet/server/static/js/submit.js +200 -0
  33. coderfleet/server/static/js/tasks.js +92 -0
  34. coderfleet/server/static/js/terminal.js +347 -0
  35. coderfleet/server/static/js/utils.js +147 -0
  36. coderfleet/server/static/vendor/marked.min.js +6 -0
  37. coderfleet/server/static/vendor/xterm/addon-fit.js +2 -0
  38. coderfleet/server/static/vendor/xterm/xterm.css +218 -0
  39. coderfleet/server/static/vendor/xterm/xterm.js +2 -0
  40. coderfleet/server/terminal.py +129 -0
  41. coderfleet/task_cmds.py +311 -0
  42. coderfleet-0.1.0.dist-info/METADATA +492 -0
  43. coderfleet-0.1.0.dist-info/RECORD +45 -0
  44. coderfleet-0.1.0.dist-info/WHEEL +4 -0
  45. coderfleet-0.1.0.dist-info/entry_points.txt +2 -0
@@ -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