conduct-cli 0.4.70__tar.gz → 0.4.71__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.
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/PKG-INFO +4 -1
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/README.md +2 -0
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/pyproject.toml +2 -2
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/src/conduct_cli/hook_session_start_template.py +23 -0
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/src/conduct_cli/hook_template.py +24 -0
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/src/conduct_cli/main.py +32 -0
- conduct_cli-0.4.71/src/conduct_cli/memory.py +96 -0
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/src/conduct_cli.egg-info/PKG-INFO +4 -1
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/src/conduct_cli.egg-info/SOURCES.txt +1 -0
- conduct_cli-0.4.71/src/conduct_cli.egg-info/requires.txt +3 -0
- conduct_cli-0.4.70/src/conduct_cli.egg-info/requires.txt +0 -2
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/setup.cfg +0 -0
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/setup.py +0 -0
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/src/conduct_cli/__init__.py +0 -0
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/src/conduct_cli/api.py +0 -0
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/src/conduct_cli/guard.py +0 -0
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/src/conduct_cli/guardmcp.py +0 -0
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/src/conduct_cli/hook_precompact_template.py +0 -0
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/src/conduct_cli/mcp_server.py +0 -0
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/src/conduct_cli.egg-info/entry_points.txt +0 -0
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/src/conduct_cli.egg-info/top_level.txt +0 -0
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/tests/test_guard_policy.py +0 -0
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/tests/test_guard_savings.py +0 -0
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/tests/test_hook_syntax.py +0 -0
- {conduct_cli-0.4.70 → conduct_cli-0.4.71}/tests/test_switch.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: conduct-cli
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.71
|
|
4
4
|
Summary: CLI for Conduct AI — install agents, manage projects, run tests
|
|
5
5
|
Author-email: Conduct AI <hello@conductai.ai>
|
|
6
6
|
License: MIT
|
|
@@ -22,11 +22,14 @@ Requires-Python: >=3.9
|
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
Requires-Dist: pyyaml>=6.0
|
|
24
24
|
Requires-Dist: rich>=13.0
|
|
25
|
+
Requires-Dist: agent-booster[watch]>=0.2.22
|
|
25
26
|
|
|
26
27
|
# conduct-cli
|
|
27
28
|
|
|
28
29
|
Official CLI for [Conduct AI](https://conductai.ai) — install AI agents, manage projects, run end-to-end tests, and enforce team AI policies with ConductGuard.
|
|
29
30
|
|
|
31
|
+

|
|
32
|
+
|
|
30
33
|
## Install
|
|
31
34
|
|
|
32
35
|
```bash
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Official CLI for [Conduct AI](https://conductai.ai) — install AI agents, manage projects, run end-to-end tests, and enforce team AI policies with ConductGuard.
|
|
4
4
|
|
|
5
|
+

|
|
6
|
+
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```bash
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "conduct-cli"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.71"
|
|
8
8
|
description = "CLI for Conduct AI — install agents, manage projects, run tests"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -23,7 +23,7 @@ classifiers = [
|
|
|
23
23
|
"Programming Language :: Python :: 3.12",
|
|
24
24
|
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
25
25
|
]
|
|
26
|
-
dependencies = ["pyyaml>=6.0", "rich>=13.0"]
|
|
26
|
+
dependencies = ["pyyaml>=6.0", "rich>=13.0", "agent-booster[watch]>=0.2.22"]
|
|
27
27
|
|
|
28
28
|
[project.urls]
|
|
29
29
|
Homepage = "https://conductai.ai"
|
|
@@ -46,6 +46,29 @@ def main():
|
|
|
46
46
|
else:
|
|
47
47
|
lines.append("- Memory index:\n (none)")
|
|
48
48
|
|
|
49
|
+
# Inject relevant team memories for the current repo
|
|
50
|
+
try:
|
|
51
|
+
repo = None
|
|
52
|
+
try:
|
|
53
|
+
import subprocess
|
|
54
|
+
out = subprocess.check_output(["git", "remote", "get-url", "origin"],
|
|
55
|
+
stderr=subprocess.DEVNULL, text=True).strip()
|
|
56
|
+
if "github.com" in out:
|
|
57
|
+
repo = out.split("github.com")[-1].lstrip("/:").rstrip(".git")
|
|
58
|
+
except Exception:
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
from conduct_cli.memory import search_team_memory
|
|
62
|
+
results = search_team_memory("recent learnings patterns bugs", repo=repo, limit=3)
|
|
63
|
+
if results:
|
|
64
|
+
lines.append("- Team knowledge:")
|
|
65
|
+
for r in results[:3]:
|
|
66
|
+
dev = r.get("developer_id", "teammate")[:8]
|
|
67
|
+
summary = r.get("summary", "")[:120]
|
|
68
|
+
lines.append(f" {dev}: {summary}")
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
71
|
+
|
|
49
72
|
print("\n".join(lines))
|
|
50
73
|
except Exception:
|
|
51
74
|
pass
|
|
@@ -131,6 +131,20 @@ except Exception:
|
|
|
131
131
|
return None, "allow", None, None
|
|
132
132
|
|
|
133
133
|
|
|
134
|
+
def _detect_repo() -> str | None:
|
|
135
|
+
try:
|
|
136
|
+
import subprocess
|
|
137
|
+
out = subprocess.check_output(["git", "remote", "get-url", "origin"],
|
|
138
|
+
stderr=subprocess.DEVNULL, text=True).strip()
|
|
139
|
+
# github.com/owner/repo or git@github.com:owner/repo
|
|
140
|
+
if "github.com" in out:
|
|
141
|
+
parts = out.split("github.com")[-1].lstrip("/:").rstrip(".git")
|
|
142
|
+
return parts # owner/repo
|
|
143
|
+
except Exception:
|
|
144
|
+
pass
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
|
|
134
148
|
def _detect_ai_tool():
|
|
135
149
|
import os
|
|
136
150
|
if os.environ.get("CLAUDECODE") or os.environ.get("CLAUDE_CODE_ENTRYPOINT"):
|
|
@@ -535,6 +549,16 @@ def main():
|
|
|
535
549
|
except Exception:
|
|
536
550
|
sys.exit(0)
|
|
537
551
|
|
|
552
|
+
# Stop hook — session ended, capture for team memory
|
|
553
|
+
if data.get("hook_event_name") == "Stop" or data.get("stop_hook_active"):
|
|
554
|
+
session_id = data.get("session_id", "")
|
|
555
|
+
transcript_path = data.get("transcript_path")
|
|
556
|
+
# Detect repo from CWD git remote
|
|
557
|
+
repo = _detect_repo()
|
|
558
|
+
from conduct_cli.memory import post_session_to_api
|
|
559
|
+
post_session_to_api(session_id, transcript_path, repo)
|
|
560
|
+
sys.exit(0)
|
|
561
|
+
|
|
538
562
|
# Policy version check (cached 60s) — auto-syncs if server version differs
|
|
539
563
|
_maybe_sync_policy()
|
|
540
564
|
|
|
@@ -2708,6 +2708,28 @@ def cmd_test_guard(args):
|
|
|
2708
2708
|
print(f"\n {CYAN}→ View events: {api_url.replace('api.', 'app.')}/guard/activity{RESET}\n")
|
|
2709
2709
|
|
|
2710
2710
|
|
|
2711
|
+
def cmd_memory(args):
|
|
2712
|
+
from conduct_cli.memory import search_team_memory
|
|
2713
|
+
memory_command = getattr(args, "memory_command", None)
|
|
2714
|
+
if memory_command == "search":
|
|
2715
|
+
query = " ".join(args.query)
|
|
2716
|
+
results = search_team_memory(query, repo=getattr(args, "repo", None), limit=args.limit)
|
|
2717
|
+
if not results:
|
|
2718
|
+
print("No team memories found.")
|
|
2719
|
+
return
|
|
2720
|
+
for r in results:
|
|
2721
|
+
repo = r.get("repo_full_name", "unknown")
|
|
2722
|
+
summary = r.get("summary", "")
|
|
2723
|
+
tags = ", ".join(r.get("topic_tags") or [])
|
|
2724
|
+
created = r.get("created_at", "")[:10]
|
|
2725
|
+
print(f"\n{BOLD}{repo}{RESET} {GRAY}{created}{RESET}")
|
|
2726
|
+
if tags:
|
|
2727
|
+
print(f" Tags: {tags}")
|
|
2728
|
+
print(f" {summary}")
|
|
2729
|
+
else:
|
|
2730
|
+
print("Usage: conduct memory search <query>")
|
|
2731
|
+
|
|
2732
|
+
|
|
2711
2733
|
# ── Entry point ───────────────────────────────────────────────────────────────
|
|
2712
2734
|
|
|
2713
2735
|
def main():
|
|
@@ -2860,6 +2882,14 @@ def main():
|
|
|
2860
2882
|
sr_p = sub.add_parser("session-report", help="Analyse local AI coding sessions with paxel and send report to admin")
|
|
2861
2883
|
sr_p.add_argument("--developer", default=None, help="Developer name (defaults to OS username)")
|
|
2862
2884
|
|
|
2885
|
+
# conduct memory
|
|
2886
|
+
memory_p = sub.add_parser("memory", help="Search team session memories")
|
|
2887
|
+
memory_sub = memory_p.add_subparsers(dest="memory_command")
|
|
2888
|
+
mem_search_p = memory_sub.add_parser("search", help="Search team memories")
|
|
2889
|
+
mem_search_p.add_argument("query", nargs="+", help="Search query")
|
|
2890
|
+
mem_search_p.add_argument("--repo", help="Filter by repo (owner/repo)")
|
|
2891
|
+
mem_search_p.add_argument("--limit", type=int, default=5, help="Max results")
|
|
2892
|
+
|
|
2863
2893
|
args = parser.parse_args()
|
|
2864
2894
|
|
|
2865
2895
|
if args.command == "login":
|
|
@@ -2937,6 +2967,8 @@ def main():
|
|
|
2937
2967
|
cmd_test_security_verify(args)
|
|
2938
2968
|
elif args.command == "session-report":
|
|
2939
2969
|
cmd_session_report(args)
|
|
2970
|
+
elif args.command == "memory":
|
|
2971
|
+
cmd_memory(args)
|
|
2940
2972
|
else:
|
|
2941
2973
|
parser.print_help()
|
|
2942
2974
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Team session memory helpers for Conduct CLI."""
|
|
2
|
+
import json
|
|
3
|
+
import threading
|
|
4
|
+
import urllib.request
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _load_config():
|
|
9
|
+
cfg_path = Path.home() / ".conduct" / "config.json"
|
|
10
|
+
if not cfg_path.exists():
|
|
11
|
+
return None
|
|
12
|
+
try:
|
|
13
|
+
return json.loads(cfg_path.read_text())
|
|
14
|
+
except Exception:
|
|
15
|
+
return None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def post_session_to_api(session_id: str, transcript_path: str | None, repo: str | None) -> bool:
|
|
19
|
+
"""Fire-and-forget POST to /team-memory/sessions. Returns True if thread started."""
|
|
20
|
+
cfg = _load_config()
|
|
21
|
+
if not cfg:
|
|
22
|
+
return False
|
|
23
|
+
server = cfg.get("server", "").rstrip("/")
|
|
24
|
+
api_key = cfg.get("api_key", "")
|
|
25
|
+
workspace_id = cfg.get("workspace_id", "")
|
|
26
|
+
if not server or not workspace_id:
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
raw_transcript = None
|
|
30
|
+
if transcript_path:
|
|
31
|
+
try:
|
|
32
|
+
text = Path(transcript_path).read_text(errors="ignore")
|
|
33
|
+
raw_transcript = text[:12000]
|
|
34
|
+
except Exception:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
payload = json.dumps({
|
|
38
|
+
"session_id": session_id,
|
|
39
|
+
"tool": "claude_code",
|
|
40
|
+
"repo_full_name": repo,
|
|
41
|
+
"raw_transcript": raw_transcript,
|
|
42
|
+
"files_touched": [],
|
|
43
|
+
}).encode()
|
|
44
|
+
|
|
45
|
+
def _send():
|
|
46
|
+
try:
|
|
47
|
+
req = urllib.request.Request(
|
|
48
|
+
f"{server}/team-memory/sessions",
|
|
49
|
+
data=payload,
|
|
50
|
+
headers={
|
|
51
|
+
"Content-Type": "application/json",
|
|
52
|
+
"X-API-Key": api_key,
|
|
53
|
+
"X-Workspace-ID": workspace_id,
|
|
54
|
+
},
|
|
55
|
+
method="POST",
|
|
56
|
+
)
|
|
57
|
+
urllib.request.urlopen(req, timeout=10)
|
|
58
|
+
except Exception:
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
t = threading.Thread(target=_send, daemon=True)
|
|
62
|
+
t.start()
|
|
63
|
+
return True
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def search_team_memory(query: str, repo: str | None = None, limit: int = 5) -> list[dict]:
|
|
67
|
+
"""Search team session memories. Returns list of result dicts, never raises."""
|
|
68
|
+
cfg = _load_config()
|
|
69
|
+
if not cfg:
|
|
70
|
+
return []
|
|
71
|
+
server = cfg.get("server", "").rstrip("/")
|
|
72
|
+
api_key = cfg.get("api_key", "")
|
|
73
|
+
workspace_id = cfg.get("workspace_id", "")
|
|
74
|
+
if not server or not workspace_id:
|
|
75
|
+
return []
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
import urllib.parse
|
|
79
|
+
params = {"q": query, "limit": str(limit)}
|
|
80
|
+
if repo:
|
|
81
|
+
params["repo"] = repo
|
|
82
|
+
url = f"{server}/team-memory/search?{urllib.parse.urlencode(params)}"
|
|
83
|
+
req = urllib.request.Request(
|
|
84
|
+
url,
|
|
85
|
+
headers={
|
|
86
|
+
"X-API-Key": api_key,
|
|
87
|
+
"X-Workspace-ID": workspace_id,
|
|
88
|
+
},
|
|
89
|
+
)
|
|
90
|
+
with urllib.request.urlopen(req, timeout=5) as resp:
|
|
91
|
+
data = json.loads(resp.read())
|
|
92
|
+
if isinstance(data, list):
|
|
93
|
+
return data
|
|
94
|
+
return data.get("results", []) if isinstance(data, dict) else []
|
|
95
|
+
except Exception:
|
|
96
|
+
return []
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: conduct-cli
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.71
|
|
4
4
|
Summary: CLI for Conduct AI — install agents, manage projects, run tests
|
|
5
5
|
Author-email: Conduct AI <hello@conductai.ai>
|
|
6
6
|
License: MIT
|
|
@@ -22,11 +22,14 @@ Requires-Python: >=3.9
|
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
Requires-Dist: pyyaml>=6.0
|
|
24
24
|
Requires-Dist: rich>=13.0
|
|
25
|
+
Requires-Dist: agent-booster[watch]>=0.2.22
|
|
25
26
|
|
|
26
27
|
# conduct-cli
|
|
27
28
|
|
|
28
29
|
Official CLI for [Conduct AI](https://conductai.ai) — install AI agents, manage projects, run end-to-end tests, and enforce team AI policies with ConductGuard.
|
|
29
30
|
|
|
31
|
+

|
|
32
|
+
|
|
30
33
|
## Install
|
|
31
34
|
|
|
32
35
|
```bash
|
|
@@ -10,6 +10,7 @@ src/conduct_cli/hook_session_start_template.py
|
|
|
10
10
|
src/conduct_cli/hook_template.py
|
|
11
11
|
src/conduct_cli/main.py
|
|
12
12
|
src/conduct_cli/mcp_server.py
|
|
13
|
+
src/conduct_cli/memory.py
|
|
13
14
|
src/conduct_cli.egg-info/PKG-INFO
|
|
14
15
|
src/conduct_cli.egg-info/SOURCES.txt
|
|
15
16
|
src/conduct_cli.egg-info/dependency_links.txt
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|