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.
Files changed (93) hide show
  1. aicortexpy-0.2.0/PKG-INFO +87 -0
  2. aicortexpy-0.2.0/README.md +70 -0
  3. aicortexpy-0.2.0/aicortexpy/__init__.py +3 -0
  4. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/agent_env.py +49 -6
  5. aicortexpy-0.2.0/aicortexpy/cli.py +145 -0
  6. aicortexpy-0.2.0/aicortexpy/cli_context.py +99 -0
  7. aicortexpy-0.2.0/aicortexpy/commands/__init__.py +1 -0
  8. aicortexpy-0.2.0/aicortexpy/commands/agent_cmd.py +109 -0
  9. aicortexpy-0.2.0/aicortexpy/commands/attachment_cmd.py +45 -0
  10. aicortexpy-0.2.0/aicortexpy/commands/auth_cmd.py +87 -0
  11. aicortexpy-0.2.0/aicortexpy/commands/autopilot_cmd.py +100 -0
  12. aicortexpy-0.2.0/aicortexpy/commands/config_cmd.py +50 -0
  13. aicortexpy-0.2.0/aicortexpy/commands/daemon_cmd.py +97 -0
  14. aicortexpy-0.2.0/aicortexpy/commands/graph_cmd.py +69 -0
  15. aicortexpy-0.2.0/aicortexpy/commands/issue_cmd.py +436 -0
  16. aicortexpy-0.2.0/aicortexpy/commands/label_cmd.py +68 -0
  17. aicortexpy-0.2.0/aicortexpy/commands/meeting_cmd.py +125 -0
  18. aicortexpy-0.2.0/aicortexpy/commands/memory_cmd.py +45 -0
  19. aicortexpy-0.2.0/aicortexpy/commands/project_cmd.py +97 -0
  20. aicortexpy-0.2.0/aicortexpy/commands/repo_cmd.py +48 -0
  21. aicortexpy-0.2.0/aicortexpy/commands/runtime_cmd.py +48 -0
  22. aicortexpy-0.2.0/aicortexpy/commands/skill_cmd.py +99 -0
  23. aicortexpy-0.2.0/aicortexpy/commands/squad_cmd.py +93 -0
  24. aicortexpy-0.2.0/aicortexpy/commands/version_cmd.py +17 -0
  25. aicortexpy-0.2.0/aicortexpy/commands/workspace_cmd.py +62 -0
  26. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/config.py +1 -1
  27. aicortexpy-0.2.0/aicortexpy/id_resolve.py +216 -0
  28. aicortexpy-0.2.0/aicortexpy/output.py +41 -0
  29. aicortexpy-0.2.0/aicortexpy/platform_client.py +203 -0
  30. aicortexpy-0.2.0/aicortexpy.egg-info/PKG-INFO +87 -0
  31. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy.egg-info/SOURCES.txt +24 -0
  32. aicortexpy-0.2.0/aicortexpy.egg-info/entry_points.txt +3 -0
  33. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/pyproject.toml +7 -7
  34. aicortexpy-0.1.0/PKG-INFO +0 -106
  35. aicortexpy-0.1.0/README.md +0 -89
  36. aicortexpy-0.1.0/aicortexpy/__init__.py +0 -3
  37. aicortexpy-0.1.0/aicortexpy/cli.py +0 -133
  38. aicortexpy-0.1.0/aicortexpy.egg-info/PKG-INFO +0 -106
  39. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/MANIFEST.in +0 -0
  40. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/__main__.py +0 -0
  41. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/acp_client.py +0 -0
  42. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/acp_conn.py +0 -0
  43. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/acp_errors.py +0 -0
  44. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/artifact_ws.py +0 -0
  45. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/artifacts.py +0 -0
  46. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/attachments.py +0 -0
  47. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/auto_commit.py +0 -0
  48. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/capabilities.py +0 -0
  49. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/chat_acp_pool.py +0 -0
  50. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/client.py +0 -0
  51. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/codex_config.py +0 -0
  52. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/daemon.py +0 -0
  53. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/agent-memory/SKILL.md +0 -0
  54. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/aicorkit-atlassian/SKILL.md +0 -0
  55. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/aicorkit-graph/SKILL.md +0 -0
  56. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/aicorkit-outlook/SKILL.md +0 -0
  57. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/aicorkit-wxmsg/SKILL.md +0 -0
  58. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/aicortex-basics/SKILL.md +0 -0
  59. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/aicortex-bridge-cdp/SKILL.md +0 -0
  60. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/aicortex-cli-ops/SKILL.md +0 -0
  61. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/aicortex-graph/SKILL.md +0 -0
  62. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/graphify/SKILL.md +0 -0
  63. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/question-form/SKILL.md +0 -0
  64. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/test-report-generator/SKILL.md +0 -0
  65. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/test-report-generator/scripts/generate_cases.py +0 -0
  66. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/data/default_skills/test-report-generator/scripts/generate_report.py +0 -0
  67. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/design_seed.py +0 -0
  68. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/diskusage.py +0 -0
  69. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/execenv.py +0 -0
  70. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/gc.py +0 -0
  71. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/handoff_bundle.py +0 -0
  72. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/health.py +0 -0
  73. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/local_skills.py +0 -0
  74. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/models.py +0 -0
  75. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/openclaw_config.py +0 -0
  76. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/pinned_workdir.py +0 -0
  77. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/platform_info.py +0 -0
  78. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/poisoned.py +0 -0
  79. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/process.py +0 -0
  80. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/procutil.py +0 -0
  81. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/prompt.py +0 -0
  82. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/repo.py +0 -0
  83. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/runner.py +0 -0
  84. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/runtime_config.py +0 -0
  85. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/sandbox.py +0 -0
  86. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/stream.py +0 -0
  87. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/terminal.py +0 -0
  88. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/tunnel.py +0 -0
  89. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy/wakeup.py +0 -0
  90. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy.egg-info/dependency_links.txt +0 -0
  91. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy.egg-info/requires.txt +0 -0
  92. {aicortexpy-0.1.0 → aicortexpy-0.2.0}/aicortexpy.egg-info/top_level.txt +0 -0
  93. {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`)。
@@ -0,0 +1,3 @@
1
+ """AICortex Python CLI + local agent runtime daemon."""
2
+
3
+ __version__ = "0.2.0"
@@ -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
- which = shutil.which("aicortexpy")
37
- if which:
38
- return str(Path(which).resolve().parent)
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["aicortex_PORT"] = str(cfg.health_port)
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"] = bin_dir + os.pathsep + prev
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