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,285 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from datetime import datetime
5
+ from enum import Enum
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ from pydantic import BaseModel, Field
10
+
11
+
12
+ class TaskStatus(str, Enum):
13
+ scheduled = "scheduled"
14
+ pending = "pending"
15
+ running = "running"
16
+ done = "done"
17
+ failed = "failed"
18
+ killed = "killed"
19
+
20
+
21
+ class ConversationStatus(str, Enum):
22
+ active = "active"
23
+ archived = "archived"
24
+
25
+
26
+ class AccountType(str, Enum):
27
+ codex = "codex"
28
+ claude = "claude"
29
+
30
+
31
+ class AccountAuth(str, Enum):
32
+ login = "login"
33
+ env = "env"
34
+
35
+
36
+ class AccountProxy(str, Enum):
37
+ relay = "relay"
38
+ off = "off"
39
+
40
+
41
+ # ── 账号 ──────────────────────────────────────────────────
42
+
43
+ class Account(BaseModel):
44
+ name: str
45
+ type: AccountType
46
+ auth: AccountAuth = AccountAuth.login
47
+ env_file: str = ""
48
+ proxy: AccountProxy = AccountProxy.relay
49
+
50
+
51
+ class Project(BaseModel):
52
+ name: str
53
+ account: str
54
+ path: str
55
+
56
+ @property
57
+ def mount_path(self) -> str:
58
+ return "/workspace"
59
+
60
+ def container_name(self, account_type: AccountType) -> str:
61
+ return f"{account_type.value}-{self.name}"
62
+
63
+ def service_name(self, account_type: AccountType) -> str:
64
+ return f"{account_type.value}-project-{self.name}"
65
+
66
+
67
+ # ── 任务 ──────────────────────────────────────────────────
68
+
69
+ class Conversation(BaseModel):
70
+ id: str
71
+ name: str
72
+ account: str
73
+ type: AccountType
74
+ project: str
75
+ project_name: str = ""
76
+ native_session_id: str = ""
77
+ status: ConversationStatus = ConversationStatus.active
78
+ created: str = Field(default_factory=lambda: datetime.now().isoformat(timespec="seconds"))
79
+ updated: str = Field(default_factory=lambda: datetime.now().isoformat(timespec="seconds"))
80
+ last_task_id: str = ""
81
+
82
+ def save(self, conversations_dir: Path) -> None:
83
+ conversations_dir.mkdir(parents=True, exist_ok=True)
84
+ path = conversations_dir / f"{self.id}.json"
85
+ path.write_text(
86
+ json.dumps(self.model_dump(), ensure_ascii=False, indent=2),
87
+ encoding="utf-8",
88
+ )
89
+
90
+ @classmethod
91
+ def load(cls, path: Path) -> "Conversation":
92
+ data = json.loads(path.read_text(encoding="utf-8"))
93
+ return cls(**data)
94
+
95
+ @classmethod
96
+ def load_all(cls, conversations_dir: Path) -> list["Conversation"]:
97
+ if not conversations_dir.exists():
98
+ return []
99
+ conversations = []
100
+ for p in sorted(conversations_dir.glob("*.json"), key=lambda x: x.stat().st_mtime, reverse=True):
101
+ try:
102
+ conversations.append(cls.load(p))
103
+ except Exception:
104
+ pass
105
+ return conversations
106
+
107
+ def touch(
108
+ self,
109
+ conversations_dir: Path,
110
+ native_session_id: Optional[str] = None,
111
+ last_task_id: Optional[str] = None,
112
+ ) -> None:
113
+ if native_session_id:
114
+ self.native_session_id = native_session_id
115
+ if last_task_id:
116
+ self.last_task_id = last_task_id
117
+ self.updated = datetime.now().isoformat(timespec="seconds")
118
+ self.save(conversations_dir)
119
+
120
+
121
+ class Task(BaseModel):
122
+ id: str
123
+ status: TaskStatus
124
+ account: str
125
+ type: AccountType
126
+ prompt: str
127
+ project: str
128
+ project_name: str = ""
129
+ conversation_id: str = ""
130
+ native_session_id: str = ""
131
+ pid: str = ""
132
+ created: str = Field(default_factory=lambda: datetime.now().isoformat(timespec="seconds"))
133
+ finished: Optional[str] = None
134
+ archived: bool = False
135
+ auto: bool = False
136
+ images: list[str] = Field(default_factory=list)
137
+ execute_at: Optional[str] = None
138
+
139
+ # ── 持久化 ────────────────────────────────────────────
140
+
141
+ def save(self, tasks_dir: Path) -> None:
142
+ tasks_dir.mkdir(parents=True, exist_ok=True)
143
+ path = tasks_dir / f"{self.id}.json"
144
+ path.write_text(
145
+ json.dumps(self.model_dump(), ensure_ascii=False, indent=2),
146
+ encoding="utf-8",
147
+ )
148
+
149
+ @classmethod
150
+ def load(cls, path: Path) -> "Task":
151
+ data = json.loads(path.read_text(encoding="utf-8"))
152
+ return cls(**data)
153
+
154
+ @classmethod
155
+ def load_all(cls, tasks_dir: Path) -> list["Task"]:
156
+ if not tasks_dir.exists():
157
+ return []
158
+ tasks = []
159
+ for p in sorted(tasks_dir.glob("*.json"), key=lambda x: x.stat().st_mtime, reverse=True):
160
+ try:
161
+ tasks.append(cls.load(p))
162
+ except Exception:
163
+ pass
164
+ return tasks
165
+
166
+ def update_status(self, status: TaskStatus, tasks_dir: Path) -> None:
167
+ self.status = status
168
+ if status in (TaskStatus.done, TaskStatus.failed, TaskStatus.killed):
169
+ self.finished = datetime.now().isoformat(timespec="seconds")
170
+ self.save(tasks_dir)
171
+
172
+ @property
173
+ def log_path_str(self) -> str:
174
+ return f"tasks/{self.id}.log"
175
+
176
+
177
+ # ── HTTP 请求/响应结构 ─────────────────────────────────────
178
+
179
+ class TaskCreateRequest(BaseModel):
180
+ prompt: str
181
+ account: Optional[str] = None # 指定账号名
182
+ project: Optional[str] = None # 按项目路径匹配
183
+ project_name: Optional[str] = None # 按项目配置名匹配
184
+ type: Optional[AccountType] = None
185
+ auto: bool = False # 全自动模式(--full-auto / --dangerously-skip-permissions)
186
+ conversation_id: Optional[str] = None
187
+ conversation_name: Optional[str] = None
188
+ images: list[str] = [] # 容器内图片路径列表(--image 参数)
189
+ execute_at: Optional[str] = None # 定时执行时间
190
+
191
+
192
+ class TaskResponse(BaseModel):
193
+ id: str
194
+ status: TaskStatus
195
+ account: str
196
+ type: AccountType
197
+ prompt: str
198
+ project: str
199
+ project_name: str = ""
200
+ conversation_id: str = ""
201
+ native_session_id: str = ""
202
+ created: str
203
+ finished: Optional[str] = None
204
+ log_path: str
205
+ archived: bool = False
206
+ auto: bool = False
207
+ images: list[str] = []
208
+ execute_at: Optional[str] = None
209
+
210
+ @classmethod
211
+ def from_task(cls, t: Task) -> "TaskResponse":
212
+ return cls(
213
+ id = t.id,
214
+ status = t.status,
215
+ account = t.account,
216
+ type = t.type,
217
+ prompt = t.prompt,
218
+ project = t.project,
219
+ project_name = t.project_name,
220
+ conversation_id = t.conversation_id,
221
+ native_session_id = t.native_session_id,
222
+ created = t.created,
223
+ finished = t.finished,
224
+ log_path = t.log_path_str,
225
+ archived = t.archived,
226
+ auto = getattr(t, "auto", False),
227
+ images = getattr(t, "images", []),
228
+ execute_at = getattr(t, "execute_at", None),
229
+ )
230
+
231
+
232
+ class ConversationResponse(BaseModel):
233
+ id: str
234
+ name: str
235
+ account: str
236
+ type: AccountType
237
+ project: str
238
+ project_name: str = ""
239
+ native_session_id: str
240
+ status: ConversationStatus
241
+ created: str
242
+ updated: str
243
+ last_task_id: str
244
+
245
+ @classmethod
246
+ def from_conversation(cls, c: Conversation) -> "ConversationResponse":
247
+ return cls(
248
+ id = c.id,
249
+ name = c.name,
250
+ account = c.account,
251
+ type = c.type,
252
+ project = c.project,
253
+ project_name = c.project_name,
254
+ native_session_id = c.native_session_id,
255
+ status = c.status,
256
+ created = c.created,
257
+ updated = c.updated,
258
+ last_task_id = c.last_task_id,
259
+ )
260
+
261
+
262
+ class ProjectResponse(BaseModel):
263
+ name: str
264
+ account: str
265
+ path: str
266
+
267
+ @classmethod
268
+ def from_project(cls, p: Project) -> "ProjectResponse":
269
+ return cls(name=p.name, account=p.account, path=p.path)
270
+
271
+
272
+ class AccountResponse(BaseModel):
273
+ name: str
274
+ type: AccountType
275
+ auth: AccountAuth
276
+ env_file: str = ""
277
+ proxy: AccountProxy
278
+ projects: list[str]
279
+ running: bool # 容器是否在线
280
+ busy: bool # 是否有 running 任务占用
281
+ container: str
282
+ running_task_id: str = ""
283
+ running_task_prompt: str = ""
284
+ task_done_count: int = 0
285
+ task_failed_count: int = 0