conduct-cli 0.4.69__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.
Files changed (26) hide show
  1. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/PKG-INFO +4 -1
  2. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/README.md +2 -0
  3. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/pyproject.toml +2 -2
  4. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/src/conduct_cli/hook_session_start_template.py +23 -0
  5. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/src/conduct_cli/hook_template.py +24 -0
  6. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/src/conduct_cli/main.py +36 -3
  7. conduct_cli-0.4.71/src/conduct_cli/memory.py +96 -0
  8. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/src/conduct_cli.egg-info/PKG-INFO +4 -1
  9. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/src/conduct_cli.egg-info/SOURCES.txt +1 -0
  10. conduct_cli-0.4.71/src/conduct_cli.egg-info/requires.txt +3 -0
  11. conduct_cli-0.4.69/src/conduct_cli.egg-info/requires.txt +0 -2
  12. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/setup.cfg +0 -0
  13. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/setup.py +0 -0
  14. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/src/conduct_cli/__init__.py +0 -0
  15. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/src/conduct_cli/api.py +0 -0
  16. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/src/conduct_cli/guard.py +0 -0
  17. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/src/conduct_cli/guardmcp.py +0 -0
  18. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/src/conduct_cli/hook_precompact_template.py +0 -0
  19. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/src/conduct_cli/mcp_server.py +0 -0
  20. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
  21. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/src/conduct_cli.egg-info/entry_points.txt +0 -0
  22. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/src/conduct_cli.egg-info/top_level.txt +0 -0
  23. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/tests/test_guard_policy.py +0 -0
  24. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/tests/test_guard_savings.py +0 -0
  25. {conduct_cli-0.4.69 → conduct_cli-0.4.71}/tests/test_hook_syntax.py +0 -0
  26. {conduct_cli-0.4.69 → 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.69
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
+ ![Conduct CLI demo — whoami, switch workspaces with Guard policy sync, and run an agent](assets/conduct-cli-demo.gif)
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
+ ![Conduct CLI demo — whoami, switch workspaces with Guard policy sync, and run an agent](assets/conduct-cli-demo.gif)
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.69"
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
 
@@ -2209,14 +2209,15 @@ def cmd_session_report(args):
2209
2209
  else:
2210
2210
  archetype = "Execution-Focused Builder"
2211
2211
 
2212
+ scores_raw = stats.get("scores", {})
2212
2213
  payload = {
2213
2214
  "developer": developer,
2214
2215
  "hostname": hostname,
2215
2216
  "archetype": archetype,
2216
2217
  "competency_scores": {
2217
- "autonomy": autonomy_score,
2218
- "planning_ratio": planning_ratio,
2219
- "commits": commits,
2218
+ "Execution": scores_raw.get("Execution", scores_raw.get("execution", autonomy_score)),
2219
+ "Planning": scores_raw.get("Planning", scores_raw.get("planning", planning_ratio)),
2220
+ "Engineering": scores_raw.get("Engineering", scores_raw.get("engineering", commits)),
2220
2221
  },
2221
2222
  "total_sessions": sessions,
2222
2223
  "tools_detected": top_tools,
@@ -2707,6 +2708,28 @@ def cmd_test_guard(args):
2707
2708
  print(f"\n {CYAN}→ View events: {api_url.replace('api.', 'app.')}/guard/activity{RESET}\n")
2708
2709
 
2709
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
+
2710
2733
  # ── Entry point ───────────────────────────────────────────────────────────────
2711
2734
 
2712
2735
  def main():
@@ -2859,6 +2882,14 @@ def main():
2859
2882
  sr_p = sub.add_parser("session-report", help="Analyse local AI coding sessions with paxel and send report to admin")
2860
2883
  sr_p.add_argument("--developer", default=None, help="Developer name (defaults to OS username)")
2861
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
+
2862
2893
  args = parser.parse_args()
2863
2894
 
2864
2895
  if args.command == "login":
@@ -2936,6 +2967,8 @@ def main():
2936
2967
  cmd_test_security_verify(args)
2937
2968
  elif args.command == "session-report":
2938
2969
  cmd_session_report(args)
2970
+ elif args.command == "memory":
2971
+ cmd_memory(args)
2939
2972
  else:
2940
2973
  parser.print_help()
2941
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.69
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
+ ![Conduct CLI demo — whoami, switch workspaces with Guard policy sync, and run an agent](assets/conduct-cli-demo.gif)
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
@@ -0,0 +1,3 @@
1
+ pyyaml>=6.0
2
+ rich>=13.0
3
+ agent-booster[watch]>=0.2.22
@@ -1,2 +0,0 @@
1
- pyyaml>=6.0
2
- rich>=13.0
File without changes
File without changes