conduct-cli 0.4.67__tar.gz → 0.4.68__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 (24) hide show
  1. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/PKG-INFO +1 -1
  2. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/pyproject.toml +1 -1
  3. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/src/conduct_cli/guardmcp.py +2 -2
  4. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/src/conduct_cli/hook_precompact_template.py +7 -3
  5. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/src/conduct_cli/main.py +109 -0
  6. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/src/conduct_cli.egg-info/PKG-INFO +1 -1
  7. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/README.md +0 -0
  8. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/setup.cfg +0 -0
  9. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/setup.py +0 -0
  10. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/src/conduct_cli/__init__.py +0 -0
  11. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/src/conduct_cli/api.py +0 -0
  12. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/src/conduct_cli/guard.py +0 -0
  13. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/src/conduct_cli/hook_session_start_template.py +0 -0
  14. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/src/conduct_cli/hook_template.py +0 -0
  15. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/src/conduct_cli/mcp_server.py +0 -0
  16. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/src/conduct_cli.egg-info/SOURCES.txt +0 -0
  17. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
  18. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/src/conduct_cli.egg-info/entry_points.txt +0 -0
  19. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/src/conduct_cli.egg-info/requires.txt +0 -0
  20. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/src/conduct_cli.egg-info/top_level.txt +0 -0
  21. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/tests/test_guard_policy.py +0 -0
  22. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/tests/test_guard_savings.py +0 -0
  23. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/tests/test_hook_syntax.py +0 -0
  24. {conduct_cli-0.4.67 → conduct_cli-0.4.68}/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.67
3
+ Version: 0.4.68
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "conduct-cli"
7
- version = "0.4.67"
7
+ version = "0.4.68"
8
8
  description = "CLI for Conduct AI — install agents, manage projects, run tests"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -2,8 +2,8 @@
2
2
  """
3
3
  conductguard-mcp — ConductGuard MCP server.
4
4
 
5
- Runs as a subprocess started by Claude Code / Cursor / Windsurf via the
6
- mcpServers config written by `conduct guard sync`. Communicates over
5
+ Runs as a subprocess started by Claude Code / Codex / Cursor / Windsurf via
6
+ the mcpServers config written by `conduct guard sync`. Communicates over
7
7
  stdin/stdout using JSON-RPC 2.0 (MCP stdio transport).
8
8
 
9
9
  Exposes three tools:
@@ -35,9 +35,13 @@ def _memory_headline():
35
35
  try:
36
36
  root = Path.cwd()
37
37
  mem_key = str(root).replace("/", "-").lstrip("-")
38
- mem_path = Path.home() / ".claude" / "projects" / mem_key / "memory" / "MEMORY.md"
39
- if mem_path.exists():
40
- return "\n".join(mem_path.read_text().splitlines()[:10])
38
+ candidates = [
39
+ Path.home() / ".claude" / "projects" / mem_key / "memory" / "MEMORY.md",
40
+ Path.home() / ".codex" / "projects" / mem_key / "memory" / "MEMORY.md",
41
+ ]
42
+ for mem_path in candidates:
43
+ if mem_path.exists():
44
+ return "\n".join(mem_path.read_text().splitlines()[:10])
41
45
  except Exception:
42
46
  pass
43
47
  return ""
@@ -2126,6 +2126,111 @@ def classify_finding(text: str) -> "dict | None":
2126
2126
  return None
2127
2127
 
2128
2128
 
2129
+ def cmd_session_report(args):
2130
+ """
2131
+ Run paxel-local against local Claude Code transcripts and send a
2132
+ developer profile report to the workspace admin via the Conduct API.
2133
+ """
2134
+ import json as _json
2135
+ import shutil
2136
+ import subprocess
2137
+ import tempfile
2138
+ import urllib.request
2139
+
2140
+ server, workspace_id, api_key, token = _require_auth(args)
2141
+ hdrs = api.headers(workspace_id, token, "application/json", api_key)
2142
+
2143
+ # ── 1. Fetch paxel-local ─────────────────────────────────────────────────
2144
+ PAXEL_URL = "https://raw.githubusercontent.com/Photobombastic/paxel-local/main/paxel.py"
2145
+ tmpdir = tempfile.mkdtemp(prefix="conduct-paxel-")
2146
+ paxel_script = Path(tmpdir) / "paxel.py"
2147
+
2148
+ print("Downloading paxel-local…")
2149
+ try:
2150
+ urllib.request.urlretrieve(PAXEL_URL, paxel_script)
2151
+ except Exception as exc:
2152
+ print(f"ERROR: could not download paxel-local: {exc}")
2153
+ shutil.rmtree(tmpdir, ignore_errors=True)
2154
+ sys.exit(1)
2155
+
2156
+ # ── 2. Run paxel ─────────────────────────────────────────────────────────
2157
+ print("Analysing sessions…")
2158
+ try:
2159
+ result = subprocess.run(
2160
+ [sys.executable, str(paxel_script), "--no-open"],
2161
+ cwd=tmpdir,
2162
+ capture_output=True,
2163
+ text=True,
2164
+ timeout=120,
2165
+ )
2166
+ except subprocess.TimeoutExpired:
2167
+ print("ERROR: paxel analysis timed out after 120 s.")
2168
+ shutil.rmtree(tmpdir, ignore_errors=True)
2169
+ sys.exit(1)
2170
+
2171
+ stats_path = Path(tmpdir) / "stats.json"
2172
+ report_path = Path(tmpdir) / "report.md"
2173
+
2174
+ if not stats_path.exists():
2175
+ print("ERROR: paxel did not produce stats.json — no transcripts found?")
2176
+ if result.stderr:
2177
+ print(result.stderr[:500])
2178
+ shutil.rmtree(tmpdir, ignore_errors=True)
2179
+ sys.exit(1)
2180
+
2181
+ with open(stats_path) as f:
2182
+ stats = _json.load(f)
2183
+
2184
+ report_md = report_path.read_text() if report_path.exists() else ""
2185
+
2186
+ # ── 3. Build summary payload ─────────────────────────────────────────────
2187
+ import getpass, socket
2188
+ developer = getattr(args, "developer", None) or getpass.getuser()
2189
+ hostname = socket.gethostname()
2190
+
2191
+ archetype = stats.get("archetype", {})
2192
+ competency = stats.get("competency_scores", stats.get("competencies", {}))
2193
+ sessions = stats.get("total_sessions", stats.get("session_count", "?"))
2194
+ tools_used = stats.get("tools_detected", stats.get("tools", []))
2195
+
2196
+ payload = {
2197
+ "developer": developer,
2198
+ "hostname": hostname,
2199
+ "archetype": archetype.get("name", archetype) if isinstance(archetype, dict) else str(archetype),
2200
+ "archetype_description": archetype.get("description", "") if isinstance(archetype, dict) else "",
2201
+ "competency_scores": competency,
2202
+ "total_sessions": sessions,
2203
+ "tools_detected": tools_used,
2204
+ "report_md": report_md[:4000], # cap to avoid huge payloads
2205
+ "raw_stats": stats,
2206
+ }
2207
+
2208
+ # ── 4. Send to Conduct API ────────────────────────────────────────────────
2209
+ print("Sending report to admin…")
2210
+ resp = api.post(
2211
+ server,
2212
+ "/session-reports",
2213
+ payload,
2214
+ hdrs,
2215
+ )
2216
+
2217
+ shutil.rmtree(tmpdir, ignore_errors=True)
2218
+
2219
+ if resp and resp.get("id"):
2220
+ arch = payload["archetype"]
2221
+ exec_score = competency.get("execution", competency.get("Execution", "?"))
2222
+ plan_score = competency.get("planning", competency.get("Planning", "?"))
2223
+ eng_score = competency.get("engineering", competency.get("Engineering", "?"))
2224
+ print(f"\nReport sent.")
2225
+ print(f" Archetype : {arch}")
2226
+ print(f" Execution : {exec_score} Planning: {plan_score} Engineering: {eng_score}")
2227
+ print(f" Sessions : {sessions}")
2228
+ if tools_used:
2229
+ print(f" Tools : {', '.join(tools_used) if isinstance(tools_used, list) else tools_used}")
2230
+ else:
2231
+ print(f"WARNING: server response unexpected: {resp}")
2232
+
2233
+
2129
2234
  def cmd_emit_finding(args):
2130
2235
  """POST a security finding to /security-findings."""
2131
2236
  server, workspace_id, api_key, token = _require_auth(args)
@@ -2730,6 +2835,8 @@ def main():
2730
2835
  sub.add_parser("test-guard", help="Fire a synthetic event per guard policy rule and show decisions")
2731
2836
  sub.add_parser("test-security", help="Post a synthetic finding per security classifier pattern")
2732
2837
  sub.add_parser("test-security-verify", help="Post test findings and verify full triage pipeline end-to-end")
2838
+ sr_p = sub.add_parser("session-report", help="Analyse local AI coding sessions with paxel and send report to admin")
2839
+ sr_p.add_argument("--developer", default=None, help="Developer name (defaults to OS username)")
2733
2840
 
2734
2841
  args = parser.parse_args()
2735
2842
 
@@ -2806,6 +2913,8 @@ def main():
2806
2913
  cmd_test_security(args)
2807
2914
  elif args.command == "test-security-verify":
2808
2915
  cmd_test_security_verify(args)
2916
+ elif args.command == "session-report":
2917
+ cmd_session_report(args)
2809
2918
  else:
2810
2919
  parser.print_help()
2811
2920
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: conduct-cli
3
- Version: 0.4.67
3
+ Version: 0.4.68
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
File without changes
File without changes
File without changes