aicortexpy 0.1.0__tar.gz → 0.2.1__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.
- aicortexpy-0.2.1/PKG-INFO +87 -0
- aicortexpy-0.2.1/README.md +70 -0
- aicortexpy-0.2.1/aicortexpy/__init__.py +3 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/acp_client.py +20 -3
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/acp_conn.py +2 -2
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/agent_env.py +49 -6
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/attachments.py +3 -1
- aicortexpy-0.2.1/aicortexpy/cli.py +177 -0
- aicortexpy-0.2.1/aicortexpy/cli_context.py +99 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/client.py +9 -2
- aicortexpy-0.2.1/aicortexpy/commands/__init__.py +1 -0
- aicortexpy-0.2.1/aicortexpy/commands/agent_cmd.py +146 -0
- aicortexpy-0.2.1/aicortexpy/commands/attachment_cmd.py +45 -0
- aicortexpy-0.2.1/aicortexpy/commands/auth_cmd.py +167 -0
- aicortexpy-0.2.1/aicortexpy/commands/autopilot_cmd.py +184 -0
- aicortexpy-0.2.1/aicortexpy/commands/config_cmd.py +50 -0
- aicortexpy-0.2.1/aicortexpy/commands/daemon_cmd.py +228 -0
- aicortexpy-0.2.1/aicortexpy/commands/graph_cmd.py +69 -0
- aicortexpy-0.2.1/aicortexpy/commands/issue_cmd.py +436 -0
- aicortexpy-0.2.1/aicortexpy/commands/label_cmd.py +68 -0
- aicortexpy-0.2.1/aicortexpy/commands/meeting_cmd.py +125 -0
- aicortexpy-0.2.1/aicortexpy/commands/memory_cmd.py +45 -0
- aicortexpy-0.2.1/aicortexpy/commands/project_cmd.py +97 -0
- aicortexpy-0.2.1/aicortexpy/commands/repo_cmd.py +48 -0
- aicortexpy-0.2.1/aicortexpy/commands/runtime_cmd.py +48 -0
- aicortexpy-0.2.1/aicortexpy/commands/setup_cmd.py +159 -0
- aicortexpy-0.2.1/aicortexpy/commands/skill_cmd.py +121 -0
- aicortexpy-0.2.1/aicortexpy/commands/squad_cmd.py +114 -0
- aicortexpy-0.2.1/aicortexpy/commands/update_cmd.py +46 -0
- aicortexpy-0.2.1/aicortexpy/commands/version_cmd.py +17 -0
- aicortexpy-0.2.1/aicortexpy/commands/workspace_cmd.py +62 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/config.py +82 -8
- aicortexpy-0.2.1/aicortexpy/id_resolve.py +233 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/models.py +8 -0
- aicortexpy-0.2.1/aicortexpy/oauth_login.py +183 -0
- aicortexpy-0.2.1/aicortexpy/output.py +77 -0
- aicortexpy-0.2.1/aicortexpy/platform_client.py +210 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/process.py +2 -1
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/runner.py +50 -2
- aicortexpy-0.2.1/aicortexpy.egg-info/PKG-INFO +87 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy.egg-info/SOURCES.txt +27 -0
- aicortexpy-0.2.1/aicortexpy.egg-info/entry_points.txt +3 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/pyproject.toml +7 -7
- aicortexpy-0.1.0/PKG-INFO +0 -106
- aicortexpy-0.1.0/README.md +0 -89
- aicortexpy-0.1.0/aicortexpy/__init__.py +0 -3
- aicortexpy-0.1.0/aicortexpy/cli.py +0 -133
- aicortexpy-0.1.0/aicortexpy.egg-info/PKG-INFO +0 -106
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/MANIFEST.in +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/__main__.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/acp_errors.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/artifact_ws.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/artifacts.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/auto_commit.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/capabilities.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/chat_acp_pool.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/codex_config.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/daemon.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/data/default_skills/agent-memory/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/data/default_skills/aicorkit-atlassian/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/data/default_skills/aicorkit-graph/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/data/default_skills/aicorkit-outlook/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/data/default_skills/aicorkit-wxmsg/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/data/default_skills/aicortex-basics/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/data/default_skills/aicortex-bridge-cdp/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/data/default_skills/aicortex-cli-ops/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/data/default_skills/aicortex-graph/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/data/default_skills/graphify/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/data/default_skills/question-form/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/data/default_skills/test-report-generator/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/data/default_skills/test-report-generator/scripts/generate_cases.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/data/default_skills/test-report-generator/scripts/generate_report.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/design_seed.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/diskusage.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/execenv.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/gc.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/handoff_bundle.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/health.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/local_skills.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/openclaw_config.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/pinned_workdir.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/platform_info.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/poisoned.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/procutil.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/prompt.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/repo.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/runtime_config.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/sandbox.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/stream.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/terminal.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/tunnel.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy/wakeup.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy.egg-info/dependency_links.txt +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy.egg-info/requires.txt +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/aicortexpy.egg-info/top_level.txt +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.1}/setup.cfg +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: aicortexpy
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: AICortex Python CLI and local agent runtime daemon (Go CLI compatible)
|
|
5
|
+
Author: AICortex
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://gitee.com/seacozz2007/aicortex
|
|
8
|
+
Project-URL: Repository, https://gitee.com/seacozz2007/aicortex
|
|
9
|
+
Project-URL: Issues, https://gitee.com/seacozz2007/aicortex/issues
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: websocket-client>=1.6
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
15
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
16
|
+
Requires-Dist: twine>=5.0; extra == "dev"
|
|
17
|
+
|
|
18
|
+
# aicortexpy
|
|
19
|
+
|
|
20
|
+
Python **CLI + local agent runtime** for AICortex。在无法使用 Go `aicortex` 二进制时,可作为完整替代:
|
|
21
|
+
|
|
22
|
+
```powershell
|
|
23
|
+
pip install -U aicortexpy
|
|
24
|
+
aicortex login --token mul_...
|
|
25
|
+
aicortex daemon start
|
|
26
|
+
aicortex issue list --output json
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
也可用 `python -m aicortexpy`(与 `aicortex` / `aicortexpy` 入口等价)。
|
|
30
|
+
|
|
31
|
+
| | |
|
|
32
|
+
|--|--|
|
|
33
|
+
| PyPI / 模块 | `aicortexpy` |
|
|
34
|
+
| Console scripts | `aicortex`、`aicortexpy`(`aicortex` 刻意同名,供 Agent 提示词零改动) |
|
|
35
|
+
| 配置 | 与 Go 共用 `~/.aicortex/config.json` |
|
|
36
|
+
|
|
37
|
+
## 安装
|
|
38
|
+
|
|
39
|
+
```powershell
|
|
40
|
+
pip install -U aicortexpy
|
|
41
|
+
# 源码:
|
|
42
|
+
cd python-daemon
|
|
43
|
+
pip install -e .
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 常用命令(对齐 Go CLI)
|
|
47
|
+
|
|
48
|
+
```powershell
|
|
49
|
+
aicortex login --token mul_xxx
|
|
50
|
+
aicortex auth status
|
|
51
|
+
aicortex config show
|
|
52
|
+
aicortex workspace list --output json
|
|
53
|
+
aicortex issue get MUL-123 --output json
|
|
54
|
+
aicortex issue comment add MUL-123 --content "done"
|
|
55
|
+
aicortex daemon start
|
|
56
|
+
aicortex daemon status
|
|
57
|
+
aicortex version
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Agent 任务内:daemon 会把 `aicortex` shim 前置到 PATH,子进程执行 `aicortex issue ...` 即本包,无需 Go 二进制。
|
|
61
|
+
|
|
62
|
+
## Daemon
|
|
63
|
+
|
|
64
|
+
```powershell
|
|
65
|
+
$env:AICORTEX_DAEMON_BACKEND = "python" # 若仍用 Go 包装器拉起
|
|
66
|
+
aicortex daemon start
|
|
67
|
+
# 或
|
|
68
|
+
python -m aicortexpy daemon start --foreground
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Health 默认端口 **19514**。不要与 Go daemon 同时跑。
|
|
72
|
+
|
|
73
|
+
## 发布
|
|
74
|
+
|
|
75
|
+
```powershell
|
|
76
|
+
cd python-daemon
|
|
77
|
+
python -m build
|
|
78
|
+
python -m twine upload dist/*
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
版本:`pyproject.toml` 与 `aicortexpy/__init__.py` 的 `__version__` 保持一致。
|
|
82
|
+
|
|
83
|
+
## 相对 Go CLI
|
|
84
|
+
|
|
85
|
+
已覆盖 Agent/日常平台命令(issue/workspace/agent/squad/project/label/attachment/autopilot/meeting/skill/memory/graph/runtime/repo/auth/config/daemon)。
|
|
86
|
+
|
|
87
|
+
尚未:浏览器 OAuth login、`update` 二进制自更新(请用 `pip install -U aicortexpy`)。
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# aicortexpy
|
|
2
|
+
|
|
3
|
+
Python **CLI + local agent runtime** for AICortex。在无法使用 Go `aicortex` 二进制时,可作为完整替代:
|
|
4
|
+
|
|
5
|
+
```powershell
|
|
6
|
+
pip install -U aicortexpy
|
|
7
|
+
aicortex login --token mul_...
|
|
8
|
+
aicortex daemon start
|
|
9
|
+
aicortex issue list --output json
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
也可用 `python -m aicortexpy`(与 `aicortex` / `aicortexpy` 入口等价)。
|
|
13
|
+
|
|
14
|
+
| | |
|
|
15
|
+
|--|--|
|
|
16
|
+
| PyPI / 模块 | `aicortexpy` |
|
|
17
|
+
| Console scripts | `aicortex`、`aicortexpy`(`aicortex` 刻意同名,供 Agent 提示词零改动) |
|
|
18
|
+
| 配置 | 与 Go 共用 `~/.aicortex/config.json` |
|
|
19
|
+
|
|
20
|
+
## 安装
|
|
21
|
+
|
|
22
|
+
```powershell
|
|
23
|
+
pip install -U aicortexpy
|
|
24
|
+
# 源码:
|
|
25
|
+
cd python-daemon
|
|
26
|
+
pip install -e .
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 常用命令(对齐 Go CLI)
|
|
30
|
+
|
|
31
|
+
```powershell
|
|
32
|
+
aicortex login --token mul_xxx
|
|
33
|
+
aicortex auth status
|
|
34
|
+
aicortex config show
|
|
35
|
+
aicortex workspace list --output json
|
|
36
|
+
aicortex issue get MUL-123 --output json
|
|
37
|
+
aicortex issue comment add MUL-123 --content "done"
|
|
38
|
+
aicortex daemon start
|
|
39
|
+
aicortex daemon status
|
|
40
|
+
aicortex version
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Agent 任务内:daemon 会把 `aicortex` shim 前置到 PATH,子进程执行 `aicortex issue ...` 即本包,无需 Go 二进制。
|
|
44
|
+
|
|
45
|
+
## Daemon
|
|
46
|
+
|
|
47
|
+
```powershell
|
|
48
|
+
$env:AICORTEX_DAEMON_BACKEND = "python" # 若仍用 Go 包装器拉起
|
|
49
|
+
aicortex daemon start
|
|
50
|
+
# 或
|
|
51
|
+
python -m aicortexpy daemon start --foreground
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Health 默认端口 **19514**。不要与 Go daemon 同时跑。
|
|
55
|
+
|
|
56
|
+
## 发布
|
|
57
|
+
|
|
58
|
+
```powershell
|
|
59
|
+
cd python-daemon
|
|
60
|
+
python -m build
|
|
61
|
+
python -m twine upload dist/*
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
版本:`pyproject.toml` 与 `aicortexpy/__init__.py` 的 `__version__` 保持一致。
|
|
65
|
+
|
|
66
|
+
## 相对 Go CLI
|
|
67
|
+
|
|
68
|
+
已覆盖 Agent/日常平台命令(issue/workspace/agent/squad/project/label/attachment/autopilot/meeting/skill/memory/graph/runtime/repo/auth/config/daemon)。
|
|
69
|
+
|
|
70
|
+
尚未:浏览器 OAuth login、`update` 二进制自更新(请用 `pip install -U aicortexpy`)。
|
|
@@ -236,7 +236,9 @@ class ACPClient:
|
|
|
236
236
|
if self.on_message:
|
|
237
237
|
self.on_message(
|
|
238
238
|
{
|
|
239
|
-
|
|
239
|
+
# Server/UI wire format uses underscores (Go daemon maps
|
|
240
|
+
# internal "tool-use" → "tool_use" before ReportTaskMessages).
|
|
241
|
+
"type": "tool_use",
|
|
240
242
|
"tool": tool_name,
|
|
241
243
|
"call_id": call_id,
|
|
242
244
|
"input": raw_input,
|
|
@@ -271,8 +273,18 @@ class ACPClient:
|
|
|
271
273
|
output = str(data.get("rawOutput") or data.get("output") or "")
|
|
272
274
|
if not output:
|
|
273
275
|
output = _extract_tool_call_text(content)
|
|
276
|
+
tool_name = ""
|
|
277
|
+
if pending is not None:
|
|
278
|
+
tool_name = pending.tool_name or ""
|
|
274
279
|
if self.on_message:
|
|
275
|
-
self.on_message(
|
|
280
|
+
self.on_message(
|
|
281
|
+
{
|
|
282
|
+
"type": "tool_result",
|
|
283
|
+
"tool": tool_name or "tool",
|
|
284
|
+
"call_id": call_id,
|
|
285
|
+
"output": output,
|
|
286
|
+
}
|
|
287
|
+
)
|
|
276
288
|
|
|
277
289
|
def _emit_deferred_tool_use(self, pending: _PendingTool | None, call_id: str, data: dict) -> None:
|
|
278
290
|
if pending is not None and pending.emitted:
|
|
@@ -300,7 +312,12 @@ class ACPClient:
|
|
|
300
312
|
inp = {}
|
|
301
313
|
if self.on_message:
|
|
302
314
|
self.on_message(
|
|
303
|
-
{
|
|
315
|
+
{
|
|
316
|
+
"type": "tool_use",
|
|
317
|
+
"tool": tool_name,
|
|
318
|
+
"call_id": call_id,
|
|
319
|
+
"input": inp,
|
|
320
|
+
}
|
|
304
321
|
)
|
|
305
322
|
|
|
306
323
|
def _track_tool(self, call_id: str, p: _PendingTool) -> None:
|
|
@@ -226,9 +226,9 @@ class _BaseACPConn:
|
|
|
226
226
|
typ = msg.get("type")
|
|
227
227
|
if typ == "text":
|
|
228
228
|
output_parts.append(str(msg.get("content") or ""))
|
|
229
|
-
elif typ
|
|
229
|
+
elif typ in ("tool_use", "tool-use"):
|
|
230
230
|
in_flight_tools["n"] += 1
|
|
231
|
-
elif typ
|
|
231
|
+
elif typ in ("tool_result", "tool-result"):
|
|
232
232
|
in_flight_tools["n"] = max(0, in_flight_tools["n"] - 1)
|
|
233
233
|
messages.append(msg)
|
|
234
234
|
if on_message:
|
|
@@ -33,12 +33,49 @@ def is_blocked_env_key(key: str) -> bool:
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def _aicortex_bin_dir() -> str | None:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
"""Prefer Scripts dir that contains console_scripts aicortex / aicortexpy."""
|
|
37
|
+
for name in ("aicortex", "aicortexpy"):
|
|
38
|
+
which = shutil.which(name)
|
|
39
|
+
if which:
|
|
40
|
+
return str(Path(which).resolve().parent)
|
|
41
|
+
# Fallback: directory of this interpreter's Scripts (pip install --user / venv)
|
|
42
|
+
try:
|
|
43
|
+
import sysconfig
|
|
44
|
+
|
|
45
|
+
scripts = sysconfig.get_path("scripts")
|
|
46
|
+
if scripts and Path(scripts).is_dir():
|
|
47
|
+
return scripts
|
|
48
|
+
except Exception:
|
|
49
|
+
pass
|
|
39
50
|
return None
|
|
40
51
|
|
|
41
52
|
|
|
53
|
+
def ensure_cli_shim_dir(state_dir: Path) -> Path | None:
|
|
54
|
+
"""Write a tiny aicortex shim so agents resolve to python -m aicortexpy."""
|
|
55
|
+
shim_dir = state_dir / "py-cli-shim"
|
|
56
|
+
try:
|
|
57
|
+
shim_dir.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
except OSError:
|
|
59
|
+
return None
|
|
60
|
+
import sys
|
|
61
|
+
|
|
62
|
+
py = sys.executable
|
|
63
|
+
if sys.platform == "win32":
|
|
64
|
+
cmd = shim_dir / "aicortex.cmd"
|
|
65
|
+
cmd.write_text(
|
|
66
|
+
f'@echo off\r\n"{py}" -m aicortexpy %*\r\n',
|
|
67
|
+
encoding="utf-8",
|
|
68
|
+
)
|
|
69
|
+
else:
|
|
70
|
+
sh = shim_dir / "aicortex"
|
|
71
|
+
sh.write_text(f'#!/bin/sh\nexec "{py}" -m aicortexpy "$@"\n', encoding="utf-8")
|
|
72
|
+
try:
|
|
73
|
+
sh.chmod(0o755)
|
|
74
|
+
except OSError:
|
|
75
|
+
pass
|
|
76
|
+
return shim_dir
|
|
77
|
+
|
|
78
|
+
|
|
42
79
|
def build_agent_environ(
|
|
43
80
|
base: dict[str, str],
|
|
44
81
|
*,
|
|
@@ -55,7 +92,7 @@ def build_agent_environ(
|
|
|
55
92
|
|
|
56
93
|
environ["AICORTEX_TOKEN"] = cfg.token
|
|
57
94
|
environ["AICORTEX_SERVER_URL"] = cfg.server_base_url
|
|
58
|
-
environ["
|
|
95
|
+
environ["AICORTEX_DAEMON_PORT"] = str(cfg.health_port)
|
|
59
96
|
environ["AICORTEX_WORKSPACE_ID"] = task.workspace_id or ""
|
|
60
97
|
environ["AICORTEX_AGENT_NAME"] = agent_name
|
|
61
98
|
environ["AICORTEX_AGENT_ID"] = agent_id
|
|
@@ -69,10 +106,16 @@ def build_agent_environ(
|
|
|
69
106
|
if task.quick_create_prompt:
|
|
70
107
|
environ["AICORTEX_QUICK_CREATE_TASK_ID"] = task.id
|
|
71
108
|
|
|
109
|
+
prepend: list[str] = []
|
|
110
|
+
shim = ensure_cli_shim_dir(cfg.state_dir)
|
|
111
|
+
if shim:
|
|
112
|
+
prepend.append(str(shim))
|
|
72
113
|
bin_dir = _aicortex_bin_dir()
|
|
73
|
-
if bin_dir:
|
|
114
|
+
if bin_dir and bin_dir not in prepend:
|
|
115
|
+
prepend.append(bin_dir)
|
|
116
|
+
if prepend:
|
|
74
117
|
prev = environ.get("PATH") or os.environ.get("PATH") or ""
|
|
75
|
-
environ["PATH"] =
|
|
118
|
+
environ["PATH"] = os.pathsep.join(prepend) + os.pathsep + prev
|
|
76
119
|
|
|
77
120
|
if extra:
|
|
78
121
|
environ.update({str(k): str(v) for k, v in extra.items()})
|
|
@@ -61,7 +61,9 @@ def materialize_attachments(
|
|
|
61
61
|
if not att_id:
|
|
62
62
|
continue
|
|
63
63
|
try:
|
|
64
|
-
body, header_name = client.fetch_attachment_bytes(
|
|
64
|
+
body, header_name = client.fetch_attachment_bytes(
|
|
65
|
+
att_id, task.workspace_id
|
|
66
|
+
)
|
|
65
67
|
except Exception as e:
|
|
66
68
|
log.warning("fetch attachment failed id=%s: %s", att_id, e)
|
|
67
69
|
continue
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Root CLI: python -m aicortexpy / aicortex — Go CLI compatible surface + daemon."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import logging
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from . import __version__
|
|
10
|
+
from .commands import (
|
|
11
|
+
agent_cmd,
|
|
12
|
+
attachment_cmd,
|
|
13
|
+
auth_cmd,
|
|
14
|
+
autopilot_cmd,
|
|
15
|
+
config_cmd,
|
|
16
|
+
daemon_cmd,
|
|
17
|
+
graph_cmd,
|
|
18
|
+
issue_cmd,
|
|
19
|
+
label_cmd,
|
|
20
|
+
meeting_cmd,
|
|
21
|
+
memory_cmd,
|
|
22
|
+
project_cmd,
|
|
23
|
+
repo_cmd,
|
|
24
|
+
runtime_cmd,
|
|
25
|
+
setup_cmd,
|
|
26
|
+
skill_cmd,
|
|
27
|
+
squad_cmd,
|
|
28
|
+
update_cmd,
|
|
29
|
+
version_cmd,
|
|
30
|
+
workspace_cmd,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Legacy top-level daemon verbs (pre-0.2) → forward to daemon *
|
|
34
|
+
_LEGACY_DAEMON = {
|
|
35
|
+
"start",
|
|
36
|
+
"run",
|
|
37
|
+
"stop",
|
|
38
|
+
"status",
|
|
39
|
+
"restart",
|
|
40
|
+
"capabilities",
|
|
41
|
+
"sandbox-info",
|
|
42
|
+
"disk-usage",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _setup_logging(
|
|
47
|
+
verbose: bool = False,
|
|
48
|
+
*,
|
|
49
|
+
log_level: str = "",
|
|
50
|
+
daemon_mode: bool = False,
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Configure root logging.
|
|
53
|
+
|
|
54
|
+
- CLI one-shots default to WARNING (quiet).
|
|
55
|
+
- Daemon run defaults to INFO (useful in python-daemon.log).
|
|
56
|
+
- `-v` or `--log-level DEBUG` enables DEBUG.
|
|
57
|
+
"""
|
|
58
|
+
if log_level:
|
|
59
|
+
level = getattr(logging, log_level.upper(), logging.INFO)
|
|
60
|
+
elif verbose:
|
|
61
|
+
level = logging.DEBUG
|
|
62
|
+
elif daemon_mode:
|
|
63
|
+
level = logging.INFO
|
|
64
|
+
else:
|
|
65
|
+
level = logging.WARNING
|
|
66
|
+
logging.basicConfig(
|
|
67
|
+
level=level,
|
|
68
|
+
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
|
69
|
+
datefmt="%H:%M:%S",
|
|
70
|
+
force=True,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
75
|
+
p = argparse.ArgumentParser(
|
|
76
|
+
prog="aicortexpy",
|
|
77
|
+
description="AICortex Python CLI + local agent runtime (Go CLI compatible)",
|
|
78
|
+
)
|
|
79
|
+
p.add_argument("--version", action="version", version=f"aicortexpy {__version__}")
|
|
80
|
+
p.add_argument("--profile", default="", help="Configuration profile name")
|
|
81
|
+
p.add_argument("--server-url", default="", help="Override server URL")
|
|
82
|
+
p.add_argument("--workspace-id", default="", help="Override workspace id")
|
|
83
|
+
p.add_argument("-v", "--verbose", action="store_true")
|
|
84
|
+
|
|
85
|
+
sub = p.add_subparsers(dest="command", required=True)
|
|
86
|
+
|
|
87
|
+
issue_cmd.add_parser(sub)
|
|
88
|
+
agent_cmd.add_parser(sub)
|
|
89
|
+
workspace_cmd.add_parser(sub)
|
|
90
|
+
project_cmd.add_parser(sub)
|
|
91
|
+
squad_cmd.add_parser(sub)
|
|
92
|
+
label_cmd.add_parser(sub)
|
|
93
|
+
autopilot_cmd.add_parser(sub)
|
|
94
|
+
meeting_cmd.add_parser(sub)
|
|
95
|
+
skill_cmd.add_parser(sub)
|
|
96
|
+
memory_cmd.add_parser(sub)
|
|
97
|
+
graph_cmd.add_parser(sub)
|
|
98
|
+
repo_cmd.add_parser(sub)
|
|
99
|
+
attachment_cmd.add_parser(sub)
|
|
100
|
+
auth_cmd.add_parser(sub)
|
|
101
|
+
config_cmd.add_parser(sub)
|
|
102
|
+
daemon_cmd.add_parser(sub)
|
|
103
|
+
runtime_cmd.add_parser(sub)
|
|
104
|
+
setup_cmd.add_parser(sub)
|
|
105
|
+
update_cmd.add_parser(sub)
|
|
106
|
+
version_cmd.add_parser(sub)
|
|
107
|
+
return p
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def main(argv: list[str] | None = None) -> int:
|
|
111
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
112
|
+
# rewrite legacy: aicortexpy start → daemon start
|
|
113
|
+
if argv and argv[0] in _LEGACY_DAEMON:
|
|
114
|
+
print(
|
|
115
|
+
f"note: `{argv[0]}` is deprecated; use `aicortexpy daemon {argv[0]}`",
|
|
116
|
+
file=sys.stderr,
|
|
117
|
+
)
|
|
118
|
+
argv = ["daemon", *argv]
|
|
119
|
+
|
|
120
|
+
parser = build_parser()
|
|
121
|
+
try:
|
|
122
|
+
ns = parser.parse_args(argv)
|
|
123
|
+
except SystemExit as e:
|
|
124
|
+
return int(e.code) if isinstance(e.code, int) else 1
|
|
125
|
+
|
|
126
|
+
cmd = ns.command
|
|
127
|
+
_setup_logging(
|
|
128
|
+
getattr(ns, "verbose", False),
|
|
129
|
+
log_level=str(getattr(ns, "log_level", "") or ""),
|
|
130
|
+
daemon_mode=(
|
|
131
|
+
cmd == "daemon"
|
|
132
|
+
and getattr(ns, "daemon_command", "") in {"start", "run", "restart"}
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
handlers = {
|
|
137
|
+
"issue": issue_cmd.run,
|
|
138
|
+
"agent": agent_cmd.run,
|
|
139
|
+
"workspace": workspace_cmd.run,
|
|
140
|
+
"project": project_cmd.run,
|
|
141
|
+
"squad": squad_cmd.run,
|
|
142
|
+
"label": label_cmd.run,
|
|
143
|
+
"autopilot": autopilot_cmd.run,
|
|
144
|
+
"meeting": meeting_cmd.run,
|
|
145
|
+
"skill": skill_cmd.run,
|
|
146
|
+
"memory": memory_cmd.run,
|
|
147
|
+
"graph": graph_cmd.run,
|
|
148
|
+
"repo": repo_cmd.run,
|
|
149
|
+
"attachment": attachment_cmd.run,
|
|
150
|
+
"config": config_cmd.run,
|
|
151
|
+
"daemon": daemon_cmd.run,
|
|
152
|
+
"runtime": runtime_cmd.run,
|
|
153
|
+
"version": version_cmd.run,
|
|
154
|
+
"login": auth_cmd.run_login,
|
|
155
|
+
"auth": auth_cmd.run_auth,
|
|
156
|
+
"setup": setup_cmd.run,
|
|
157
|
+
"update": update_cmd.run,
|
|
158
|
+
}
|
|
159
|
+
handler = handlers.get(cmd)
|
|
160
|
+
if not handler:
|
|
161
|
+
parser.print_help()
|
|
162
|
+
return 2
|
|
163
|
+
try:
|
|
164
|
+
return int(handler(ns) or 0)
|
|
165
|
+
except SystemExit as e:
|
|
166
|
+
if e.code not in (0, None):
|
|
167
|
+
msg = e.args[0] if e.args else str(e.code)
|
|
168
|
+
if isinstance(msg, str) and msg not in {"0", "1", "2"}:
|
|
169
|
+
print(msg, file=sys.stderr)
|
|
170
|
+
return int(e.code) if isinstance(e.code, int) else 1
|
|
171
|
+
return 0
|
|
172
|
+
except KeyboardInterrupt:
|
|
173
|
+
return 130
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
if __name__ == "__main__":
|
|
177
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Resolve PlatformClient from flags / env / ~/.aicortex/config.json."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from . import __version__
|
|
12
|
+
from .config import (
|
|
13
|
+
CLIConfig,
|
|
14
|
+
DEFAULT_SERVER_URL,
|
|
15
|
+
cli_config_path,
|
|
16
|
+
load_cli_config,
|
|
17
|
+
normalize_http_base,
|
|
18
|
+
profile_dir,
|
|
19
|
+
)
|
|
20
|
+
from .platform_client import PlatformClient
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def save_cli_config(cfg: CLIConfig, profile: str = "") -> Path:
|
|
24
|
+
path = cli_config_path(profile)
|
|
25
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
data: dict[str, Any] = {}
|
|
27
|
+
if cfg.server_url:
|
|
28
|
+
data["server_url"] = cfg.server_url
|
|
29
|
+
if cfg.app_url:
|
|
30
|
+
data["app_url"] = cfg.app_url
|
|
31
|
+
if cfg.workspace_id:
|
|
32
|
+
data["workspace_id"] = cfg.workspace_id
|
|
33
|
+
if cfg.token:
|
|
34
|
+
data["token"] = cfg.token
|
|
35
|
+
tmp = path.with_suffix(".json.tmp")
|
|
36
|
+
tmp.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
37
|
+
tmp.replace(path)
|
|
38
|
+
return path
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def profile_of(ns: argparse.Namespace) -> str:
|
|
42
|
+
return str(getattr(ns, "profile", None) or "").strip()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def resolve_server_url(ns: argparse.Namespace) -> str:
|
|
46
|
+
flag = str(getattr(ns, "server_url", None) or "").strip()
|
|
47
|
+
if flag:
|
|
48
|
+
return normalize_http_base(flag)
|
|
49
|
+
env = os.environ.get("AICORTEX_SERVER_URL", "").strip()
|
|
50
|
+
if env:
|
|
51
|
+
return normalize_http_base(env)
|
|
52
|
+
cfg = load_cli_config(profile_of(ns))
|
|
53
|
+
return normalize_http_base(cfg.server_url or DEFAULT_SERVER_URL)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def resolve_workspace_id(ns: argparse.Namespace) -> str:
|
|
57
|
+
flag = str(getattr(ns, "workspace_id", None) or "").strip()
|
|
58
|
+
if flag:
|
|
59
|
+
return flag
|
|
60
|
+
env = os.environ.get("AICORTEX_WORKSPACE_ID", "").strip()
|
|
61
|
+
if env:
|
|
62
|
+
return env
|
|
63
|
+
return load_cli_config(profile_of(ns)).workspace_id
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def resolve_token(ns: argparse.Namespace) -> str:
|
|
67
|
+
env = os.environ.get("AICORTEX_TOKEN", "").strip()
|
|
68
|
+
if env:
|
|
69
|
+
return env
|
|
70
|
+
return load_cli_config(profile_of(ns)).token
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def require_client(ns: argparse.Namespace, *, require_workspace: bool = True) -> PlatformClient:
|
|
74
|
+
token = resolve_token(ns)
|
|
75
|
+
if not token:
|
|
76
|
+
raise SystemExit("Not authenticated. Run `aicortexpy login --token mul_...` or set AICORTEX_TOKEN.")
|
|
77
|
+
ws = resolve_workspace_id(ns)
|
|
78
|
+
if require_workspace and not ws:
|
|
79
|
+
raise SystemExit("No workspace_id. Set via `aicortexpy config set workspace_id <id>` or --workspace-id.")
|
|
80
|
+
return PlatformClient(
|
|
81
|
+
resolve_server_url(ns),
|
|
82
|
+
token,
|
|
83
|
+
ws,
|
|
84
|
+
version=__version__,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def read_text_flag(ns: argparse.Namespace, name: str = "content") -> str:
|
|
89
|
+
"""Resolve --content / --content-stdin / --content-file (and description*)."""
|
|
90
|
+
file_attr = f"{name}_file"
|
|
91
|
+
stdin_attr = f"{name}_stdin"
|
|
92
|
+
if getattr(ns, stdin_attr, False):
|
|
93
|
+
import sys
|
|
94
|
+
|
|
95
|
+
return sys.stdin.read()
|
|
96
|
+
path = getattr(ns, file_attr, None)
|
|
97
|
+
if path:
|
|
98
|
+
return Path(path).read_text(encoding="utf-8")
|
|
99
|
+
return str(getattr(ns, name, None) or "")
|
|
@@ -422,10 +422,17 @@ class DaemonClient:
|
|
|
422
422
|
data = self.get(f"/api/daemon/workspaces/{workspace_id}/repos")
|
|
423
423
|
return data if isinstance(data, dict) else {}
|
|
424
424
|
|
|
425
|
-
def fetch_attachment_bytes(
|
|
425
|
+
def fetch_attachment_bytes(
|
|
426
|
+
self, attachment_id: str, workspace_id: str = ""
|
|
427
|
+
) -> tuple[bytes, str]:
|
|
426
428
|
path = f"/api/daemon/attachments/{attachment_id}/bytes"
|
|
427
429
|
url = self.base_url + path
|
|
428
|
-
|
|
430
|
+
headers = self._headers()
|
|
431
|
+
wid = (workspace_id or "").strip()
|
|
432
|
+
if wid:
|
|
433
|
+
# PAT/JWT daemon auth has no workspace in context; server requires it.
|
|
434
|
+
headers["X-Workspace-ID"] = wid
|
|
435
|
+
req = urllib.request.Request(url, headers=headers, method="GET")
|
|
429
436
|
try:
|
|
430
437
|
with urllib.request.urlopen(req, timeout=self.timeout, context=self._ctx) as resp:
|
|
431
438
|
body = resp.read(MAX_ATTACHMENT_BYTES + 1)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI command modules."""
|