monkeycode-sdk 1.7.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.
- monkeycode/__init__.py +58 -0
- monkeycode/client.py +2141 -0
- monkeycode/consts.py +29 -0
- monkeycode/errors.py +30 -0
- monkeycode/links.py +138 -0
- monkeycode/models.py +227 -0
- monkeycode/pow.py +65 -0
- monkeycode/session.py +140 -0
- monkeycode/stream.py +381 -0
- monkeycode_sdk-1.7.0.dist-info/METADATA +93 -0
- monkeycode_sdk-1.7.0.dist-info/RECORD +14 -0
- monkeycode_sdk-1.7.0.dist-info/WHEEL +5 -0
- monkeycode_sdk-1.7.0.dist-info/licenses/LICENSE +21 -0
- monkeycode_sdk-1.7.0.dist-info/top_level.txt +1 -0
monkeycode/stream.py
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"""MonkeyCode 任务 WebSocket 会话流。
|
|
2
|
+
|
|
3
|
+
基于 websockets 库,连接 /api/v1/users/tasks/stream?id=&mode=,
|
|
4
|
+
发送 user-input 让 agent 执行,并读取下行事件。
|
|
5
|
+
|
|
6
|
+
协议参考:前端 src/api/task-stream-client.ts 与桌面端实现。
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import base64
|
|
12
|
+
import json
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
import websockets
|
|
16
|
+
|
|
17
|
+
from monkeycode.models import TaskStreamChunk, UserInputPayload
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def b64e(s: str | bytes) -> str:
|
|
21
|
+
"""编码为 base64 字符串。"""
|
|
22
|
+
if isinstance(s, str):
|
|
23
|
+
s = s.encode("utf-8")
|
|
24
|
+
return base64.b64encode(s).decode("ascii")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def b64d(s: str) -> str:
|
|
28
|
+
"""解码 base64 为字符串。"""
|
|
29
|
+
return base64.b64decode(s).decode("utf-8")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _ws_url(base_url: str, path: str) -> str:
|
|
33
|
+
"""将 HTTP(S) base_url 转为 ws(s) 前缀并拼接 API 路径。
|
|
34
|
+
|
|
35
|
+
自托管部署时 base_url 传私有地址,协议自动转换。
|
|
36
|
+
"""
|
|
37
|
+
if base_url.startswith("https://"):
|
|
38
|
+
ws_base = "wss://" + base_url[len("https://"):]
|
|
39
|
+
elif base_url.startswith("http://"):
|
|
40
|
+
ws_base = "ws://" + base_url[len("http://"):]
|
|
41
|
+
else:
|
|
42
|
+
ws_base = base_url
|
|
43
|
+
return f"{ws_base}{path}"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _session_headers(client: Any) -> dict[str, str]:
|
|
47
|
+
"""提取登录会话 Cookie 作为 WebSocket 请求头。
|
|
48
|
+
|
|
49
|
+
同时兼容用户登录与团队登录两种会话 Cookie。
|
|
50
|
+
"""
|
|
51
|
+
cookies = []
|
|
52
|
+
for name in ("monkeycode_ai_session", "monkeycode_ai_team_session"):
|
|
53
|
+
cookie = client._client.cookies.get(name)
|
|
54
|
+
if cookie:
|
|
55
|
+
cookies.append(f"{name}={cookie}")
|
|
56
|
+
if cookies:
|
|
57
|
+
return {"Cookie": "; ".join(cookies)}
|
|
58
|
+
return {}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def encode_user_input(
|
|
62
|
+
content: str, attachments: list[dict] | None = None
|
|
63
|
+
) -> str:
|
|
64
|
+
"""构造上行 user-input 消息(外层 JSON 字符串)。
|
|
65
|
+
|
|
66
|
+
格式:{"type": "user-input", "data": "<base64(内层JSON)>"}
|
|
67
|
+
"""
|
|
68
|
+
payload = UserInputPayload(content=content, attachments=attachments or [])
|
|
69
|
+
outer = {"type": "user-input", "data": b64e(payload.encode())}
|
|
70
|
+
return json.dumps(outer, ensure_ascii=False)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def decode_chunk(raw: str) -> TaskStreamChunk:
|
|
74
|
+
"""解析服务器下行事件。data 可能是 base64 的 JSON 字符串。"""
|
|
75
|
+
try:
|
|
76
|
+
obj = json.loads(raw)
|
|
77
|
+
except json.JSONDecodeError:
|
|
78
|
+
return TaskStreamChunk(type="raw", data=raw, raw={"raw": raw})
|
|
79
|
+
|
|
80
|
+
ctype = str(obj.get("type", ""))
|
|
81
|
+
data = obj.get("data")
|
|
82
|
+
if isinstance(data, str):
|
|
83
|
+
try:
|
|
84
|
+
decoded = b64d(data)
|
|
85
|
+
try:
|
|
86
|
+
data = json.loads(decoded)
|
|
87
|
+
except json.JSONDecodeError:
|
|
88
|
+
data = decoded
|
|
89
|
+
except Exception:
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
return TaskStreamChunk(
|
|
93
|
+
type=ctype,
|
|
94
|
+
data=data,
|
|
95
|
+
timestamp=int(obj.get("timestamp", 0) or 0),
|
|
96
|
+
turn_seq=int(obj.get("turn_seq", 0) or 0),
|
|
97
|
+
raw=obj,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class TaskStream:
|
|
102
|
+
"""任务实时流(WebSocket 会话)。
|
|
103
|
+
|
|
104
|
+
用法:
|
|
105
|
+
stream = TaskStream(client, task_id, mode="new")
|
|
106
|
+
await stream.connect()
|
|
107
|
+
await stream.send_user_input("继续")
|
|
108
|
+
chunk = await stream.recv(timeout=60)
|
|
109
|
+
await stream.close()
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def __init__(self, client: Any, task_id: str, mode: str = "new") -> None:
|
|
113
|
+
self.client = client
|
|
114
|
+
self.task_id = task_id
|
|
115
|
+
self.mode = mode
|
|
116
|
+
self._ws: Any = None
|
|
117
|
+
|
|
118
|
+
def _url(self) -> str:
|
|
119
|
+
return _ws_url(
|
|
120
|
+
self.client.base_url,
|
|
121
|
+
f"/api/v1/users/tasks/stream?id={self.task_id}&mode={self.mode}",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
async def connect(self) -> None:
|
|
125
|
+
"""建立 WebSocket 连接(携带登录 Cookie)。"""
|
|
126
|
+
self._ws = await websockets.connect(
|
|
127
|
+
self._url(), additional_headers=_session_headers(self.client)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
async def send_user_input(
|
|
131
|
+
self, content: str, attachments: list[dict] | None = None
|
|
132
|
+
) -> None:
|
|
133
|
+
"""发送用户文字/附件到任务会话。"""
|
|
134
|
+
msg = encode_user_input(content, attachments)
|
|
135
|
+
await self._ws.send(msg)
|
|
136
|
+
|
|
137
|
+
async def send_cancel(self) -> None:
|
|
138
|
+
"""发送取消指令。"""
|
|
139
|
+
await self._ws.send(json.dumps({"type": "user-cancel", "data": ""}))
|
|
140
|
+
|
|
141
|
+
async def send_control(self, ctype: str, data: str = "") -> None:
|
|
142
|
+
"""发送任意控制类型消息(如 reply-question / auto-approve)。"""
|
|
143
|
+
await self._ws.send(json.dumps({"type": ctype, "data": data}))
|
|
144
|
+
|
|
145
|
+
async def send_reply_question(
|
|
146
|
+
self, request_id: str, answers: list[str]
|
|
147
|
+
) -> None:
|
|
148
|
+
"""回复 AI 的提问(reply-question)。"""
|
|
149
|
+
inner = json.dumps(
|
|
150
|
+
{"request_id": request_id, "answers": answers}, ensure_ascii=False
|
|
151
|
+
)
|
|
152
|
+
await self.send_control("reply-question", b64e(inner))
|
|
153
|
+
|
|
154
|
+
async def send_auto_approve(self, enable: bool = True) -> None:
|
|
155
|
+
"""开启/关闭自动批准(auto-approve / disable-auto-approve)。"""
|
|
156
|
+
ctype = "auto-approve" if enable else "disable-auto-approve"
|
|
157
|
+
await self.send_control(ctype, "")
|
|
158
|
+
|
|
159
|
+
async def recv(self, timeout: float | None = None) -> TaskStreamChunk:
|
|
160
|
+
"""读取并解码一条事件。"""
|
|
161
|
+
raw = await asyncio.wait_for(self._ws.recv(), timeout=timeout)
|
|
162
|
+
return decode_chunk(raw)
|
|
163
|
+
|
|
164
|
+
async def close(self) -> None:
|
|
165
|
+
if self._ws is not None:
|
|
166
|
+
await self._ws.close()
|
|
167
|
+
self._ws = None
|
|
168
|
+
|
|
169
|
+
async def __aenter__(self) -> "TaskStream":
|
|
170
|
+
await self.connect()
|
|
171
|
+
return self
|
|
172
|
+
|
|
173
|
+
async def __aexit__(self, *exc: Any) -> None:
|
|
174
|
+
await self.close()
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class ControlStream:
|
|
178
|
+
"""任务控制通道(WebSocket)。
|
|
179
|
+
|
|
180
|
+
用于同步调用类操作:switch_model / restart / 文件浏览 / 端口转发等。
|
|
181
|
+
端点:/api/v1/users/tasks/control?id=<task_id>
|
|
182
|
+
|
|
183
|
+
上行消息:
|
|
184
|
+
{"type": "call", "kind": "<kind>", "data": "<base64(JSON)>"}
|
|
185
|
+
下行消息:
|
|
186
|
+
{"type": "call-response", "kind": "<kind>",
|
|
187
|
+
"data": "<base64(响应JSON)>", "timestamp": 0}
|
|
188
|
+
|
|
189
|
+
注意:data 必须是 base64(JSON字符串),服务端 Go 的 []byte 字段
|
|
190
|
+
会自动 base64 解码后再反序列化。
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
def __init__(self, client: Any, task_id: str) -> None:
|
|
194
|
+
self.client = client
|
|
195
|
+
self.task_id = task_id
|
|
196
|
+
self._ws: Any = None
|
|
197
|
+
|
|
198
|
+
def _url(self) -> str:
|
|
199
|
+
return _ws_url(
|
|
200
|
+
self.client.base_url,
|
|
201
|
+
f"/api/v1/users/tasks/control?id={self.task_id}",
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
async def connect(self) -> None:
|
|
205
|
+
"""建立 WebSocket 连接(携带登录 Cookie)。"""
|
|
206
|
+
self._ws = await websockets.connect(
|
|
207
|
+
self._url(), additional_headers=_session_headers(self.client)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
async def send_call(self, kind: str, data: dict[str, Any]) -> None:
|
|
211
|
+
"""发送控制调用。data 会被 base64(JSON) 编码后放入外层 data。"""
|
|
212
|
+
inner = json.dumps(data, ensure_ascii=False)
|
|
213
|
+
msg = {"type": "call", "kind": kind, "data": b64e(inner)}
|
|
214
|
+
await self._ws.send(json.dumps(msg, ensure_ascii=False))
|
|
215
|
+
|
|
216
|
+
async def send_raw(self, ctype: str, data: str = "") -> None:
|
|
217
|
+
"""发送任意类型控制消息(非 call 调用,如 sync-my-ip)。
|
|
218
|
+
|
|
219
|
+
data 为原始字符串,会原样放入外层 data 字段。
|
|
220
|
+
"""
|
|
221
|
+
msg = {"type": ctype, "data": data}
|
|
222
|
+
await self._ws.send(json.dumps(msg, ensure_ascii=False))
|
|
223
|
+
|
|
224
|
+
async def recv(self, timeout: float | None = None) -> TaskStreamChunk:
|
|
225
|
+
"""读取并解码一条控制通道事件。"""
|
|
226
|
+
raw = await asyncio.wait_for(self._ws.recv(), timeout=timeout)
|
|
227
|
+
return decode_chunk(raw)
|
|
228
|
+
|
|
229
|
+
async def close(self) -> None:
|
|
230
|
+
if self._ws is not None:
|
|
231
|
+
await self._ws.close()
|
|
232
|
+
self._ws = None
|
|
233
|
+
|
|
234
|
+
async def __aenter__(self) -> "ControlStream":
|
|
235
|
+
await self.connect()
|
|
236
|
+
return self
|
|
237
|
+
|
|
238
|
+
async def __aexit__(self, *exc: Any) -> None:
|
|
239
|
+
await self.close()
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class TerminalStream:
|
|
243
|
+
"""虚拟机终端流(WebSocket)。
|
|
244
|
+
|
|
245
|
+
端点:/api/v1/users/hosts/vms/{vm_id}/terminals/connect
|
|
246
|
+
|
|
247
|
+
上行消息:
|
|
248
|
+
{"type": "data", "data": "<base64(输入字节)>"}
|
|
249
|
+
{"type": "resize", "data": "<base64({\"col\":..,\"row\":..})>"}
|
|
250
|
+
下行消息:
|
|
251
|
+
connected / data / resize / error / ping / pong
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
def __init__(
|
|
255
|
+
self,
|
|
256
|
+
client: Any,
|
|
257
|
+
vm_id: str,
|
|
258
|
+
terminal_id: str | None = None,
|
|
259
|
+
col: int = 80,
|
|
260
|
+
row: int = 24,
|
|
261
|
+
) -> None:
|
|
262
|
+
self.client = client
|
|
263
|
+
self.vm_id = vm_id
|
|
264
|
+
self.terminal_id = terminal_id
|
|
265
|
+
self.col = col
|
|
266
|
+
self.row = row
|
|
267
|
+
self._ws: Any = None
|
|
268
|
+
|
|
269
|
+
def _url(self) -> str:
|
|
270
|
+
path = f"/api/v1/users/hosts/vms/{self.vm_id}/terminals/connect"
|
|
271
|
+
params = [f"col={self.col}", f"row={self.row}"]
|
|
272
|
+
if self.terminal_id:
|
|
273
|
+
params.append(f"terminal_id={self.terminal_id}")
|
|
274
|
+
return _ws_url(self.client.base_url, f"{path}?{'&'.join(params)}")
|
|
275
|
+
|
|
276
|
+
async def connect(self) -> None:
|
|
277
|
+
"""建立 WebSocket 连接(携带登录 Cookie)。"""
|
|
278
|
+
self._ws = await websockets.connect(
|
|
279
|
+
self._url(), additional_headers=_session_headers(self.client)
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
async def send_data(self, data: bytes | str) -> None:
|
|
283
|
+
"""发送终端输入(字符串或字节)。"""
|
|
284
|
+
if isinstance(data, str):
|
|
285
|
+
data = data.encode("utf-8")
|
|
286
|
+
msg = {"type": "data", "data": b64e(data)}
|
|
287
|
+
await self._ws.send(json.dumps(msg, ensure_ascii=False))
|
|
288
|
+
|
|
289
|
+
async def send_resize(self, col: int, row: int) -> None:
|
|
290
|
+
"""调整终端尺寸。"""
|
|
291
|
+
inner = json.dumps({"col": col, "row": row})
|
|
292
|
+
msg = {"type": "resize", "data": b64e(inner)}
|
|
293
|
+
await self._ws.send(json.dumps(msg, ensure_ascii=False))
|
|
294
|
+
|
|
295
|
+
async def recv(self, timeout: float | None = None) -> TaskStreamChunk:
|
|
296
|
+
"""读取并解码一条终端事件(data 自动 base64 解码)。"""
|
|
297
|
+
raw = await asyncio.wait_for(self._ws.recv(), timeout=timeout)
|
|
298
|
+
return decode_chunk(raw)
|
|
299
|
+
|
|
300
|
+
async def close(self) -> None:
|
|
301
|
+
if self._ws is not None:
|
|
302
|
+
await self._ws.close()
|
|
303
|
+
self._ws = None
|
|
304
|
+
|
|
305
|
+
async def __aenter__(self) -> "TerminalStream":
|
|
306
|
+
await self.connect()
|
|
307
|
+
return self
|
|
308
|
+
|
|
309
|
+
async def __aexit__(self, *exc: Any) -> None:
|
|
310
|
+
await self.close()
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class JoinTerminalStream:
|
|
314
|
+
"""加入共享终端流(WebSocket)。
|
|
315
|
+
|
|
316
|
+
端点:/api/v1/users/hosts/vms/terminals/join
|
|
317
|
+
需要分享密码(share_terminal 返回的 password)。
|
|
318
|
+
|
|
319
|
+
上行/下行消息格式与 TerminalStream 一致(data / resize / 心跳)。
|
|
320
|
+
"""
|
|
321
|
+
|
|
322
|
+
def __init__(
|
|
323
|
+
self,
|
|
324
|
+
client: Any,
|
|
325
|
+
terminal_id: str,
|
|
326
|
+
password: str,
|
|
327
|
+
col: int = 80,
|
|
328
|
+
row: int = 24,
|
|
329
|
+
) -> None:
|
|
330
|
+
self.client = client
|
|
331
|
+
self.terminal_id = terminal_id
|
|
332
|
+
self.password = password
|
|
333
|
+
self.col = col
|
|
334
|
+
self.row = row
|
|
335
|
+
self._ws: Any = None
|
|
336
|
+
|
|
337
|
+
def _url(self) -> str:
|
|
338
|
+
path = "/api/v1/users/hosts/vms/terminals/join"
|
|
339
|
+
params = [
|
|
340
|
+
f"terminal_id={self.terminal_id}",
|
|
341
|
+
f"password={self.password}",
|
|
342
|
+
f"col={self.col}",
|
|
343
|
+
f"row={self.row}",
|
|
344
|
+
]
|
|
345
|
+
return _ws_url(self.client.base_url, f"{path}?{'&'.join(params)}")
|
|
346
|
+
|
|
347
|
+
async def connect(self) -> None:
|
|
348
|
+
"""建立 WebSocket 连接(携带登录 Cookie)。"""
|
|
349
|
+
self._ws = await websockets.connect(
|
|
350
|
+
self._url(), additional_headers=_session_headers(self.client)
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
async def send_data(self, data: bytes | str) -> None:
|
|
354
|
+
"""发送终端输入(字符串或字节)。"""
|
|
355
|
+
if isinstance(data, str):
|
|
356
|
+
data = data.encode("utf-8")
|
|
357
|
+
msg = {"type": "data", "data": b64e(data)}
|
|
358
|
+
await self._ws.send(json.dumps(msg, ensure_ascii=False))
|
|
359
|
+
|
|
360
|
+
async def send_resize(self, col: int, row: int) -> None:
|
|
361
|
+
"""调整终端尺寸。"""
|
|
362
|
+
inner = json.dumps({"col": col, "row": row})
|
|
363
|
+
msg = {"type": "resize", "data": b64e(inner)}
|
|
364
|
+
await self._ws.send(json.dumps(msg, ensure_ascii=False))
|
|
365
|
+
|
|
366
|
+
async def recv(self, timeout: float | None = None) -> TaskStreamChunk:
|
|
367
|
+
"""读取并解码一条终端事件(data 自动 base64 解码)。"""
|
|
368
|
+
raw = await asyncio.wait_for(self._ws.recv(), timeout=timeout)
|
|
369
|
+
return decode_chunk(raw)
|
|
370
|
+
|
|
371
|
+
async def close(self) -> None:
|
|
372
|
+
if self._ws is not None:
|
|
373
|
+
await self._ws.close()
|
|
374
|
+
self._ws = None
|
|
375
|
+
|
|
376
|
+
async def __aenter__(self) -> "JoinTerminalStream":
|
|
377
|
+
await self.connect()
|
|
378
|
+
return self
|
|
379
|
+
|
|
380
|
+
async def __aexit__(self, *exc: Any) -> None:
|
|
381
|
+
await self.close()
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: monkeycode-sdk
|
|
3
|
+
Version: 1.7.0
|
|
4
|
+
Summary: MonkeyCode 云平台 REST API 的 Python SDK(认证、任务提交、结果轮询)
|
|
5
|
+
Author: cyqmq
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/cyqmq/monkeycode-sdk
|
|
8
|
+
Project-URL: Repository, https://github.com/cyqmq/monkeycode-sdk
|
|
9
|
+
Keywords: monkeycode,ai,coding,agent,sdk
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: httpx>=0.27
|
|
21
|
+
Requires-Dist: websockets>=12
|
|
22
|
+
Provides-Extra: cli
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# monkeycode-sdk
|
|
26
|
+
|
|
27
|
+
MonkeyCode 云平台 REST API 的 Python SDK。用于在任意 Python 项目/聊天框架(AstrBot、NoneBot 等)中:
|
|
28
|
+
认证登录 → 创建 AI 编程任务 → 轮询结果,让机器人像人一样使用 MonkeyCode。
|
|
29
|
+
|
|
30
|
+
**特性**
|
|
31
|
+
- 密码登录 / 团队登录 / PoW 验证码全自动求解
|
|
32
|
+
- 会话持久化:Cookie 保存到文件,长期运行自动重登(401 自动恢复)
|
|
33
|
+
- 任务创建(附件、技能、默认档位)、列表、轮询、WebSocket 实时会话
|
|
34
|
+
- AgentSession 高层封装:断线自动重连 + 事件缓冲
|
|
35
|
+
- 任务控制通道:切换模型、重启、技能/插件、仓库文件 diff/changes 等同步调用
|
|
36
|
+
- 项目管理、Git 身份、Git Bots、钱包签到、文件/VM 管理、预览链接
|
|
37
|
+
- 虚拟机终端:会话管理(REST)+ 实时交互/共享加入(WebSocket)
|
|
38
|
+
- 用户与会话:whoami / logout / 团队会话列表
|
|
39
|
+
- 配置管理:模型/宿主机/VM/端口转发/Git 身份 CRUD 与健康检查
|
|
40
|
+
- 团队管理:用户/分组/宿主/镜像/模型/技能/MCP/通知/OAuth/扩展包完整控制面
|
|
41
|
+
- 分页迭代:`iter_tasks` / `iter_projects` / `paginate` 自动翻页
|
|
42
|
+
- 健壮性:只读请求自动重试(指数退避)、429 限流等待、错误提示友好化
|
|
43
|
+
- CLI:25+ 命令(任务/钱包/项目/终端/API Key/会话控制)
|
|
44
|
+
- 覆盖完整 REST API(镜像/MCP/通知/团队管理/License/社区等)
|
|
45
|
+
- 支持自托管部署
|
|
46
|
+
|
|
47
|
+
## 安装
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install monkeycode-sdk
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
或从源码安装:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/cyqmq/monkeycode-sdk.git
|
|
57
|
+
cd monkeycode-sdk
|
|
58
|
+
pip install -e .
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 快速开始
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from monkeycode import MonkeyCodeClient
|
|
65
|
+
from monkeycode.models import RepoReq
|
|
66
|
+
|
|
67
|
+
client = MonkeyCodeClient(email="you@example.com", password="your_password")
|
|
68
|
+
client.login() # PoW 验证码自动求解
|
|
69
|
+
|
|
70
|
+
# 创建任务(默认档位;repo 可空)
|
|
71
|
+
task = client.create_task(
|
|
72
|
+
content="修复登录接口的异常处理",
|
|
73
|
+
repo=RepoReq(repo_url="https://github.com/org/repo", branch="main"),
|
|
74
|
+
skill_ids=["feature-design", "project-wiki"],
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# 轮询结果
|
|
78
|
+
result = client.wait_task(task.id, timeout=1800, interval=10)
|
|
79
|
+
print(result.status, result.pr_url)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 文档导航
|
|
83
|
+
|
|
84
|
+
| 文档 | 内容 |
|
|
85
|
+
|---|---|
|
|
86
|
+
| [docs/认证说明.md](docs/认证说明.md) | 密码/PoW/团队登录、自托管、安全提示 |
|
|
87
|
+
| [docs/功能参考.md](docs/功能参考.md) | 全部功能方法示例(任务/项目/Git/钱包/文件/预览/管理) |
|
|
88
|
+
| [docs/API.md](docs/API.md) | 全部公开方法索引表(205 个) |
|
|
89
|
+
| [docs/架构说明.md](docs/架构说明.md) | 模块划分、WebSocket 协议摘要、认证流程 |
|
|
90
|
+
| [docs/CLI.md](docs/CLI.md) | 命令行使用 |
|
|
91
|
+
| [docs/已知问题.md](docs/已知问题.md) | 平台侧已知问题、开发与参考 |
|
|
92
|
+
|
|
93
|
+
完整变更见 [CHANGELOG.md](CHANGELOG.md)。
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
monkeycode/__init__.py,sha256=QYz3baeNWMkoJNj9WKoNJ0i3MJMN-i_A6apA2vhZHXk,1178
|
|
2
|
+
monkeycode/client.py,sha256=enLVQ-6EFX2jZP0Gld-_vul--F_kqebxFD7N4Fhl1jw,77664
|
|
3
|
+
monkeycode/consts.py,sha256=gcnkZeg5WodHhbSC61Tu6cxwGSLsS3Fow5EAOfE1U_M,582
|
|
4
|
+
monkeycode/errors.py,sha256=pwbWCEs_jRPuZVmQtvxQV9nYVdHWyIOBtnnr942MPTM,810
|
|
5
|
+
monkeycode/links.py,sha256=IbYGJLUBUMFXn6fnBNI2bMC4bMUy9A1f3L-GS5lMF2c,4139
|
|
6
|
+
monkeycode/models.py,sha256=YJOecnw0O7Ll_kVclFIy0UrdP6TicAg3pKZm9g7v6C4,5737
|
|
7
|
+
monkeycode/pow.py,sha256=60UtR3DsqEtAm0KhmLqoskYNFXtaWupFxLhZAEdxBT4,2005
|
|
8
|
+
monkeycode/session.py,sha256=1R_r5zEhGBD3rV4s20uRunZHxXwKCaQIJqiQUYc5Rfw,4746
|
|
9
|
+
monkeycode/stream.py,sha256=2NNyXYm4LcNMseXS4EO--cnIGN0ugPgpOkdYmhpeR-o,12695
|
|
10
|
+
monkeycode_sdk-1.7.0.dist-info/licenses/LICENSE,sha256=p_GGS7WUJg7iHTz9NKrgWgPB6c9v1y5z6MvhLMImiys,1061
|
|
11
|
+
monkeycode_sdk-1.7.0.dist-info/METADATA,sha256=K7VPbMqNgB3SPMtJaA8bo3v2w0IJnn3V1X7dxOjaIyM,3726
|
|
12
|
+
monkeycode_sdk-1.7.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
13
|
+
monkeycode_sdk-1.7.0.dist-info/top_level.txt,sha256=q6KuiZpjIBTI0prd_9gqwumf1aV9DQ9ANJEDQqy3FHs,11
|
|
14
|
+
monkeycode_sdk-1.7.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 cyqmq
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
monkeycode
|