aicortexpy 0.1.0__tar.gz → 0.2.0__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.0/PKG-INFO +87 -0
- aicortexpy-0.2.0/README.md +70 -0
- aicortexpy-0.2.0/aicortexpy/__init__.py +3 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/agent_env.py +49 -6
- aicortexpy-0.2.0/aicortexpy/cli.py +145 -0
- aicortexpy-0.2.0/aicortexpy/cli_context.py +99 -0
- aicortexpy-0.2.0/aicortexpy/commands/__init__.py +1 -0
- aicortexpy-0.2.0/aicortexpy/commands/agent_cmd.py +109 -0
- aicortexpy-0.2.0/aicortexpy/commands/attachment_cmd.py +45 -0
- aicortexpy-0.2.0/aicortexpy/commands/auth_cmd.py +87 -0
- aicortexpy-0.2.0/aicortexpy/commands/autopilot_cmd.py +100 -0
- aicortexpy-0.2.0/aicortexpy/commands/config_cmd.py +50 -0
- aicortexpy-0.2.0/aicortexpy/commands/daemon_cmd.py +97 -0
- aicortexpy-0.2.0/aicortexpy/commands/graph_cmd.py +69 -0
- aicortexpy-0.2.0/aicortexpy/commands/issue_cmd.py +436 -0
- aicortexpy-0.2.0/aicortexpy/commands/label_cmd.py +68 -0
- aicortexpy-0.2.0/aicortexpy/commands/meeting_cmd.py +125 -0
- aicortexpy-0.2.0/aicortexpy/commands/memory_cmd.py +45 -0
- aicortexpy-0.2.0/aicortexpy/commands/project_cmd.py +97 -0
- aicortexpy-0.2.0/aicortexpy/commands/repo_cmd.py +48 -0
- aicortexpy-0.2.0/aicortexpy/commands/runtime_cmd.py +48 -0
- aicortexpy-0.2.0/aicortexpy/commands/skill_cmd.py +99 -0
- aicortexpy-0.2.0/aicortexpy/commands/squad_cmd.py +93 -0
- aicortexpy-0.2.0/aicortexpy/commands/version_cmd.py +17 -0
- aicortexpy-0.2.0/aicortexpy/commands/workspace_cmd.py +62 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/config.py +1 -1
- aicortexpy-0.2.0/aicortexpy/id_resolve.py +216 -0
- aicortexpy-0.2.0/aicortexpy/output.py +41 -0
- aicortexpy-0.2.0/aicortexpy/platform_client.py +203 -0
- aicortexpy-0.2.0/aicortexpy.egg-info/PKG-INFO +87 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy.egg-info/SOURCES.txt +24 -0
- aicortexpy-0.2.0/aicortexpy.egg-info/entry_points.txt +3 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/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.0}/MANIFEST.in +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/__main__.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/acp_client.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/acp_conn.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/acp_errors.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/artifact_ws.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/artifacts.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/attachments.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/auto_commit.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/capabilities.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/chat_acp_pool.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/client.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/codex_config.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/daemon.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/agent-memory/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/aicorkit-atlassian/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/aicorkit-graph/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/aicorkit-outlook/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/aicorkit-wxmsg/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/aicortex-basics/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/aicortex-bridge-cdp/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/aicortex-cli-ops/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/aicortex-graph/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/graphify/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/question-form/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/test-report-generator/SKILL.md +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/test-report-generator/scripts/generate_cases.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/test-report-generator/scripts/generate_report.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/design_seed.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/diskusage.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/execenv.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/gc.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/handoff_bundle.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/health.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/local_skills.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/models.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/openclaw_config.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/pinned_workdir.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/platform_info.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/poisoned.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/process.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/procutil.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/prompt.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/repo.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/runner.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/runtime_config.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/sandbox.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/stream.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/terminal.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/tunnel.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/wakeup.py +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy.egg-info/dependency_links.txt +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy.egg-info/requires.txt +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy.egg-info/top_level.txt +0 -0
- {aicortexpy-0.1.0 → aicortexpy-0.2.0}/setup.cfg +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: aicortexpy
|
|
3
|
+
Version: 0.2.0
|
|
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`)。
|
|
@@ -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()})
|
|
@@ -0,0 +1,145 @@
|
|
|
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
|
+
skill_cmd,
|
|
26
|
+
squad_cmd,
|
|
27
|
+
version_cmd,
|
|
28
|
+
workspace_cmd,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Legacy top-level daemon verbs (pre-0.2) → forward to daemon *
|
|
32
|
+
_LEGACY_DAEMON = {
|
|
33
|
+
"start",
|
|
34
|
+
"run",
|
|
35
|
+
"stop",
|
|
36
|
+
"status",
|
|
37
|
+
"restart",
|
|
38
|
+
"capabilities",
|
|
39
|
+
"sandbox-info",
|
|
40
|
+
"disk-usage",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _setup_logging(verbose: bool = False) -> None:
|
|
45
|
+
level = logging.DEBUG if verbose else logging.WARNING
|
|
46
|
+
logging.basicConfig(
|
|
47
|
+
level=level,
|
|
48
|
+
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
|
49
|
+
datefmt="%H:%M:%S",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
54
|
+
p = argparse.ArgumentParser(
|
|
55
|
+
prog="aicortexpy",
|
|
56
|
+
description="AICortex Python CLI + local agent runtime (Go CLI compatible)",
|
|
57
|
+
)
|
|
58
|
+
p.add_argument("--version", action="version", version=f"aicortexpy {__version__}")
|
|
59
|
+
p.add_argument("--profile", default="", help="Configuration profile name")
|
|
60
|
+
p.add_argument("--server-url", default="", help="Override server URL")
|
|
61
|
+
p.add_argument("--workspace-id", default="", help="Override workspace id")
|
|
62
|
+
p.add_argument("-v", "--verbose", action="store_true")
|
|
63
|
+
|
|
64
|
+
sub = p.add_subparsers(dest="command", required=True)
|
|
65
|
+
|
|
66
|
+
issue_cmd.add_parser(sub)
|
|
67
|
+
agent_cmd.add_parser(sub)
|
|
68
|
+
workspace_cmd.add_parser(sub)
|
|
69
|
+
project_cmd.add_parser(sub)
|
|
70
|
+
squad_cmd.add_parser(sub)
|
|
71
|
+
label_cmd.add_parser(sub)
|
|
72
|
+
autopilot_cmd.add_parser(sub)
|
|
73
|
+
meeting_cmd.add_parser(sub)
|
|
74
|
+
skill_cmd.add_parser(sub)
|
|
75
|
+
memory_cmd.add_parser(sub)
|
|
76
|
+
graph_cmd.add_parser(sub)
|
|
77
|
+
repo_cmd.add_parser(sub)
|
|
78
|
+
attachment_cmd.add_parser(sub)
|
|
79
|
+
auth_cmd.add_parser(sub)
|
|
80
|
+
config_cmd.add_parser(sub)
|
|
81
|
+
daemon_cmd.add_parser(sub)
|
|
82
|
+
runtime_cmd.add_parser(sub)
|
|
83
|
+
version_cmd.add_parser(sub)
|
|
84
|
+
return p
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def main(argv: list[str] | None = None) -> int:
|
|
88
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
89
|
+
# rewrite legacy: aicortexpy start → daemon start
|
|
90
|
+
if argv and argv[0] in _LEGACY_DAEMON:
|
|
91
|
+
print(
|
|
92
|
+
f"note: `{argv[0]}` is deprecated; use `aicortexpy daemon {argv[0]}`",
|
|
93
|
+
file=sys.stderr,
|
|
94
|
+
)
|
|
95
|
+
argv = ["daemon", *argv]
|
|
96
|
+
|
|
97
|
+
parser = build_parser()
|
|
98
|
+
try:
|
|
99
|
+
ns = parser.parse_args(argv)
|
|
100
|
+
except SystemExit as e:
|
|
101
|
+
return int(e.code) if isinstance(e.code, int) else 1
|
|
102
|
+
|
|
103
|
+
_setup_logging(getattr(ns, "verbose", False))
|
|
104
|
+
cmd = ns.command
|
|
105
|
+
|
|
106
|
+
handlers = {
|
|
107
|
+
"issue": issue_cmd.run,
|
|
108
|
+
"agent": agent_cmd.run,
|
|
109
|
+
"workspace": workspace_cmd.run,
|
|
110
|
+
"project": project_cmd.run,
|
|
111
|
+
"squad": squad_cmd.run,
|
|
112
|
+
"label": label_cmd.run,
|
|
113
|
+
"autopilot": autopilot_cmd.run,
|
|
114
|
+
"meeting": meeting_cmd.run,
|
|
115
|
+
"skill": skill_cmd.run,
|
|
116
|
+
"memory": memory_cmd.run,
|
|
117
|
+
"graph": graph_cmd.run,
|
|
118
|
+
"repo": repo_cmd.run,
|
|
119
|
+
"attachment": attachment_cmd.run,
|
|
120
|
+
"config": config_cmd.run,
|
|
121
|
+
"daemon": daemon_cmd.run,
|
|
122
|
+
"runtime": runtime_cmd.run,
|
|
123
|
+
"version": version_cmd.run,
|
|
124
|
+
"login": auth_cmd.run_login,
|
|
125
|
+
"auth": auth_cmd.run_auth,
|
|
126
|
+
}
|
|
127
|
+
handler = handlers.get(cmd)
|
|
128
|
+
if not handler:
|
|
129
|
+
parser.print_help()
|
|
130
|
+
return 2
|
|
131
|
+
try:
|
|
132
|
+
return int(handler(ns) or 0)
|
|
133
|
+
except SystemExit as e:
|
|
134
|
+
if e.code not in (0, None):
|
|
135
|
+
msg = e.args[0] if e.args else str(e.code)
|
|
136
|
+
if isinstance(msg, str) and msg not in {"0", "1", "2"}:
|
|
137
|
+
print(msg, file=sys.stderr)
|
|
138
|
+
return int(e.code) if isinstance(e.code, int) else 1
|
|
139
|
+
return 0
|
|
140
|
+
except KeyboardInterrupt:
|
|
141
|
+
return 130
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
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 "")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI command modules."""
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""agent commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from ..cli_context import require_client
|
|
9
|
+
from ..id_resolve import list_agents, resolve_by_prefix
|
|
10
|
+
from ..output import print_json, str_val
|
|
11
|
+
from ..platform_client import PlatformError, q
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def add_parser(sub: argparse._SubParsersAction) -> None:
|
|
15
|
+
p = sub.add_parser("agent", help="Work with agents")
|
|
16
|
+
asub = p.add_subparsers(dest="agent_command", required=True)
|
|
17
|
+
lst = asub.add_parser("list", help="List agents")
|
|
18
|
+
lst.add_argument("--full-id", action="store_true")
|
|
19
|
+
lst.add_argument("--output", choices=["json", "table"], default="table")
|
|
20
|
+
get = asub.add_parser("get", help="Get agent")
|
|
21
|
+
get.add_argument("agent_id")
|
|
22
|
+
get.add_argument("--output", choices=["json", "table"], default="json")
|
|
23
|
+
create = asub.add_parser("create", help="Create agent")
|
|
24
|
+
create.add_argument("--name", required=True)
|
|
25
|
+
create.add_argument("--instructions", default="")
|
|
26
|
+
create.add_argument("--from-template", default="")
|
|
27
|
+
create.add_argument("--output", choices=["json", "table"], default="json")
|
|
28
|
+
update = asub.add_parser("update", help="Update agent")
|
|
29
|
+
update.add_argument("agent_id")
|
|
30
|
+
update.add_argument("--name", default="")
|
|
31
|
+
update.add_argument("--instructions", default="")
|
|
32
|
+
update.add_argument("--output", choices=["json", "table"], default="json")
|
|
33
|
+
archive = asub.add_parser("archive", help="Archive agent")
|
|
34
|
+
archive.add_argument("agent_id")
|
|
35
|
+
restore = asub.add_parser("restore", help="Restore agent")
|
|
36
|
+
restore.add_argument("agent_id")
|
|
37
|
+
tasks = asub.add_parser("tasks", help="List agent tasks")
|
|
38
|
+
tasks.add_argument("agent_id")
|
|
39
|
+
tasks.add_argument("--output", choices=["json", "table"], default="json")
|
|
40
|
+
skills = asub.add_parser("skills", help="Agent skills")
|
|
41
|
+
ssub = skills.add_subparsers(dest="agent_skills_command", required=True)
|
|
42
|
+
sl = ssub.add_parser("list")
|
|
43
|
+
sl.add_argument("agent_id")
|
|
44
|
+
sl.add_argument("--output", choices=["json", "table"], default="json")
|
|
45
|
+
ss = ssub.add_parser("set")
|
|
46
|
+
ss.add_argument("agent_id")
|
|
47
|
+
ss.add_argument("--skill-ids", default="", help="comma-separated skill ids")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _aid(client, ref: str) -> str:
|
|
51
|
+
return resolve_by_prefix(client, "agent", ref, list_agents(client))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def run(ns: argparse.Namespace) -> int:
|
|
55
|
+
client = require_client(ns)
|
|
56
|
+
try:
|
|
57
|
+
if ns.agent_command == "list":
|
|
58
|
+
agents = list_agents(client)
|
|
59
|
+
if ns.output == "json":
|
|
60
|
+
print_json(agents)
|
|
61
|
+
else:
|
|
62
|
+
for a in agents:
|
|
63
|
+
aid = str_val(a, "id")
|
|
64
|
+
if not ns.full_id and len(aid) > 8:
|
|
65
|
+
aid = aid[:8]
|
|
66
|
+
print(f"{str_val(a, 'name')}\t{aid}")
|
|
67
|
+
return 0
|
|
68
|
+
if ns.agent_command == "get":
|
|
69
|
+
print_json(client.get(f"/api/agents/{_aid(client, ns.agent_id)}"))
|
|
70
|
+
return 0
|
|
71
|
+
if ns.agent_command == "create":
|
|
72
|
+
if ns.from_template:
|
|
73
|
+
body = {"name": ns.name, "template": ns.from_template, "workspace_id": client.workspace_id}
|
|
74
|
+
print_json(client.post("/api/agents/from-template", body))
|
|
75
|
+
else:
|
|
76
|
+
body = {"name": ns.name, "workspace_id": client.workspace_id}
|
|
77
|
+
if ns.instructions:
|
|
78
|
+
body["instructions"] = ns.instructions
|
|
79
|
+
print_json(client.post("/api/agents", body))
|
|
80
|
+
return 0
|
|
81
|
+
if ns.agent_command == "update":
|
|
82
|
+
body = {}
|
|
83
|
+
if ns.name:
|
|
84
|
+
body["name"] = ns.name
|
|
85
|
+
if ns.instructions:
|
|
86
|
+
body["instructions"] = ns.instructions
|
|
87
|
+
print_json(client.put(f"/api/agents/{_aid(client, ns.agent_id)}", body))
|
|
88
|
+
return 0
|
|
89
|
+
if ns.agent_command == "archive":
|
|
90
|
+
print_json(client.post(f"/api/agents/{_aid(client, ns.agent_id)}/archive", {}))
|
|
91
|
+
return 0
|
|
92
|
+
if ns.agent_command == "restore":
|
|
93
|
+
print_json(client.post(f"/api/agents/{_aid(client, ns.agent_id)}/restore", {}))
|
|
94
|
+
return 0
|
|
95
|
+
if ns.agent_command == "tasks":
|
|
96
|
+
print_json(client.get(f"/api/agents/{_aid(client, ns.agent_id)}/tasks"))
|
|
97
|
+
return 0
|
|
98
|
+
if ns.agent_command == "skills":
|
|
99
|
+
aid = _aid(client, ns.agent_id)
|
|
100
|
+
if ns.agent_skills_command == "list":
|
|
101
|
+
print_json(client.get(f"/api/agents/{aid}/skills"))
|
|
102
|
+
return 0
|
|
103
|
+
ids = [x.strip() for x in (ns.skill_ids or "").split(",") if x.strip()]
|
|
104
|
+
print_json(client.put(f"/api/agents/{aid}/skills", {"skill_ids": ids}))
|
|
105
|
+
return 0
|
|
106
|
+
except PlatformError as e:
|
|
107
|
+
print(str(e), file=sys.stderr)
|
|
108
|
+
return 1
|
|
109
|
+
return 2
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""attachment download."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from ..cli_context import require_client
|
|
10
|
+
from ..output import str_val
|
|
11
|
+
from ..platform_client import PlatformError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def add_parser(sub: argparse._SubParsersAction) -> None:
|
|
15
|
+
p = sub.add_parser("attachment", help="Work with attachments")
|
|
16
|
+
asub = p.add_subparsers(dest="attachment_command", required=True)
|
|
17
|
+
dl = asub.add_parser("download", help="Download attachment by id")
|
|
18
|
+
dl.add_argument("attachment_id")
|
|
19
|
+
dl.add_argument("-o", "--output-dir", default=".")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def run(ns: argparse.Namespace) -> int:
|
|
23
|
+
client = require_client(ns, require_workspace=False)
|
|
24
|
+
try:
|
|
25
|
+
if ns.attachment_command == "download":
|
|
26
|
+
att = client.get(f"/api/attachments/{ns.attachment_id}")
|
|
27
|
+
if not isinstance(att, dict):
|
|
28
|
+
print("attachment not found", file=sys.stderr)
|
|
29
|
+
return 1
|
|
30
|
+
url = str_val(att, "download_url") or str_val(att, "url")
|
|
31
|
+
if not url:
|
|
32
|
+
print("no download_url on attachment", file=sys.stderr)
|
|
33
|
+
return 1
|
|
34
|
+
data = client.download_file(url)
|
|
35
|
+
name = str_val(att, "filename") or ns.attachment_id
|
|
36
|
+
out_dir = Path(ns.output_dir)
|
|
37
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
path = out_dir / name
|
|
39
|
+
path.write_bytes(data)
|
|
40
|
+
print(str(path.resolve()))
|
|
41
|
+
return 0
|
|
42
|
+
except PlatformError as e:
|
|
43
|
+
print(str(e), file=sys.stderr)
|
|
44
|
+
return 1
|
|
45
|
+
return 2
|