conduct-cli 0.4.87__tar.gz → 0.4.89__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 (27) hide show
  1. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/PKG-INFO +1 -1
  2. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/pyproject.toml +2 -2
  3. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/src/conduct_cli/main.py +84 -81
  4. conduct_cli-0.4.89/src/conduct_cli/paxel.py +2702 -0
  5. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/src/conduct_cli.egg-info/PKG-INFO +1 -1
  6. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/src/conduct_cli.egg-info/SOURCES.txt +1 -0
  7. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/README.md +0 -0
  8. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/setup.cfg +0 -0
  9. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/setup.py +0 -0
  10. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/src/conduct_cli/__init__.py +0 -0
  11. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/src/conduct_cli/api.py +0 -0
  12. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/src/conduct_cli/guard.py +0 -0
  13. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/src/conduct_cli/guardmcp.py +0 -0
  14. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/src/conduct_cli/hook_precompact_template.py +0 -0
  15. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/src/conduct_cli/hook_session_start_template.py +0 -0
  16. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/src/conduct_cli/hook_stop_template.py +0 -0
  17. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/src/conduct_cli/hook_template.py +0 -0
  18. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/src/conduct_cli/mcp_server.py +0 -0
  19. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/src/conduct_cli/memory.py +0 -0
  20. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
  21. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/src/conduct_cli.egg-info/entry_points.txt +0 -0
  22. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/src/conduct_cli.egg-info/requires.txt +0 -0
  23. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/src/conduct_cli.egg-info/top_level.txt +0 -0
  24. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/tests/test_guard_policy.py +0 -0
  25. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/tests/test_guard_savings.py +0 -0
  26. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/tests/test_hook_syntax.py +0 -0
  27. {conduct_cli-0.4.87 → conduct_cli-0.4.89}/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.87
3
+ Version: 0.4.89
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.87"
7
+ version = "0.4.89"
8
8
  description = "CLI for Conduct AI — install agents, manage projects, run tests"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -43,4 +43,4 @@ conductguard-post = "conduct_cli.guard:post_usage_main"
43
43
  where = ["src"]
44
44
 
45
45
  [tool.setuptools.package-data]
46
- conduct_cli = ["hook_template.py", "hook_precompact_template.py", "hook_session_start_template.py", "hook_stop_template.py"]
46
+ conduct_cli = ["hook_template.py", "hook_precompact_template.py", "hook_session_start_template.py", "hook_stop_template.py", "paxel.py"]
@@ -2133,44 +2133,29 @@ def classify_finding(text: str) -> "dict | None":
2133
2133
 
2134
2134
 
2135
2135
  def cmd_session_report(args):
2136
- """
2137
- Run paxel-local against local Claude Code transcripts and send a
2138
- developer profile report to the workspace admin via the Conduct API.
2139
- """
2136
+ """Run paxel analysis and open an HTML report in the local browser."""
2140
2137
  import json as _json
2141
2138
  import shutil
2142
2139
  import subprocess
2143
2140
  import tempfile
2144
- import urllib.request
2145
-
2146
- server, workspace_id, api_key, token = _require_auth(args)
2147
- hdrs = api.headers(workspace_id, token, "application/json", api_key)
2141
+ import webbrowser
2142
+ import getpass
2148
2143
 
2149
- # ── 1. Fetch paxel-local ─────────────────────────────────────────────────
2150
- PAXEL_URL = "https://raw.githubusercontent.com/Photobombastic/paxel-local/main/paxel.py"
2144
+ # ── 1. Use bundled paxel ─────────────────────────────────────────────────
2145
+ bundled = Path(__file__).parent / "paxel.py"
2151
2146
  tmpdir = tempfile.mkdtemp(prefix="conduct-paxel-")
2152
2147
  paxel_script = Path(tmpdir) / "paxel.py"
2153
-
2154
- print("Downloading paxel-local…")
2155
- try:
2156
- urllib.request.urlretrieve(PAXEL_URL, paxel_script)
2157
- except Exception as exc:
2158
- print(f"ERROR: could not download paxel-local: {exc}")
2159
- shutil.rmtree(tmpdir, ignore_errors=True)
2160
- sys.exit(1)
2148
+ shutil.copy(bundled, paxel_script)
2161
2149
 
2162
2150
  # ── 2. Run paxel ─────────────────────────────────────────────────────────
2163
2151
  print("Analysing sessions…")
2164
2152
  try:
2165
2153
  result = subprocess.run(
2166
2154
  [sys.executable, str(paxel_script), "--no-open"],
2167
- cwd=tmpdir,
2168
- capture_output=True,
2169
- text=True,
2170
- timeout=120,
2155
+ cwd=tmpdir, capture_output=True, text=True, timeout=120,
2171
2156
  )
2172
2157
  except subprocess.TimeoutExpired:
2173
- print("ERROR: paxel analysis timed out after 120 s.")
2158
+ print("ERROR: analysis timed out after 120 s.")
2174
2159
  shutil.rmtree(tmpdir, ignore_errors=True)
2175
2160
  sys.exit(1)
2176
2161
 
@@ -2178,7 +2163,7 @@ def cmd_session_report(args):
2178
2163
  report_path = Path(tmpdir) / "report.md"
2179
2164
 
2180
2165
  if not stats_path.exists():
2181
- print("ERROR: paxel did not produce stats.json — no transcripts found?")
2166
+ print("ERROR: no transcripts found.")
2182
2167
  if result.stderr:
2183
2168
  print(result.stderr[:500])
2184
2169
  shutil.rmtree(tmpdir, ignore_errors=True)
@@ -2186,28 +2171,22 @@ def cmd_session_report(args):
2186
2171
 
2187
2172
  with open(stats_path) as f:
2188
2173
  stats = _json.load(f)
2189
-
2190
2174
  report_md = report_path.read_text() if report_path.exists() else ""
2191
2175
 
2192
- # ── 3. Build summary payload ─────────────────────────────────────────────
2193
- import getpass, socket
2194
- developer = getattr(args, "developer", None) or getpass.getuser()
2195
- hostname = socket.gethostname()
2196
-
2197
- volume = stats.get("volume", {})
2198
- behavior = stats.get("behavior", {})
2199
- autonomy = stats.get("autonomy", {})
2200
- tools = stats.get("tools", {})
2201
- velocity = stats.get("velocity", {})
2202
-
2176
+ # ── 3. Extract stats ─────────────────────────────────────────────────────
2177
+ developer = getattr(args, "developer", None) or getpass.getuser()
2178
+ volume = stats.get("volume", {})
2179
+ behavior = stats.get("behavior", {})
2180
+ autonomy = stats.get("autonomy", {})
2181
+ tools = stats.get("tools", {})
2182
+ velocity = stats.get("velocity", {})
2203
2183
  sessions = volume.get("total_sessions", "?")
2204
2184
  prompts = volume.get("total_prompts", "?")
2205
2185
  autonomy_score = autonomy.get("autonomy_score_0_100", "?")
2206
- top_tools = [t[0] for t in (tools.get("top_tools") or [])[:5]]
2186
+ top_tools = [t[0] for t in (tools.get("top_tools") or [])[:8]]
2207
2187
  commits = velocity.get("git_commits_real", "?") if isinstance(velocity, dict) else "?"
2208
-
2209
- # Derive a simple archetype label from the data
2210
2188
  planning_ratio = behavior.get("planning_ratio_explore_to_doing", 0)
2189
+
2211
2190
  if autonomy_score != "?" and float(autonomy_score) >= 70:
2212
2191
  archetype = "Autonomous Builder"
2213
2192
  elif planning_ratio != 0 and float(planning_ratio) > 1.0:
@@ -2215,48 +2194,72 @@ def cmd_session_report(args):
2215
2194
  else:
2216
2195
  archetype = "Execution-Focused Builder"
2217
2196
 
2218
- scores_raw = stats.get("scores", {})
2219
- payload = {
2220
- "developer": developer,
2221
- "hostname": hostname,
2222
- "archetype": archetype,
2223
- "competency_scores": {
2224
- "Execution": scores_raw.get("Execution", scores_raw.get("execution", autonomy_score)),
2225
- "Planning": scores_raw.get("Planning", scores_raw.get("planning", planning_ratio)),
2226
- "Engineering": scores_raw.get("Engineering", scores_raw.get("engineering", commits)),
2227
- },
2228
- "total_sessions": sessions,
2229
- "tools_detected": top_tools,
2230
- "report_md": report_md[:4000],
2231
- "raw_stats": {
2232
- "volume": volume,
2233
- "autonomy": autonomy,
2234
- "top_tools": top_tools,
2235
- "velocity": velocity,
2236
- "scope": stats.get("scope", ""),
2237
- },
2238
- }
2239
-
2240
- # ── 4. Send to Conduct API ────────────────────────────────────────────────
2241
- print("Sending report to admin…")
2242
- resp = api.req(
2243
- "POST",
2244
- f"{server}/session-reports",
2245
- hdrs,
2246
- body=payload,
2247
- )
2248
-
2249
- shutil.rmtree(tmpdir, ignore_errors=True)
2250
-
2251
- if resp and resp.get("id"):
2252
- print(f"\nReport sent.")
2253
- print(f" Archetype : {archetype}")
2254
- print(f" Autonomy : {autonomy_score}/100 Planning ratio: {planning_ratio}")
2255
- print(f" Sessions : {sessions} Prompts: {prompts} Commits: {commits}")
2256
- if top_tools:
2257
- print(f" Top tools : {', '.join(top_tools)}")
2258
- else:
2259
- print(f"WARNING: server response unexpected: {resp}")
2197
+ tools_str = " ".join(f"<span class='tag'>{t}</span>" for t in top_tools)
2198
+ md_html = report_md.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\n", "<br>")
2199
+
2200
+ # ── 4. Generate HTML ─────────────────────────────────────────────────────
2201
+ html = f"""<!DOCTYPE html>
2202
+ <html lang="en">
2203
+ <head>
2204
+ <meta charset="UTF-8">
2205
+ <title>Session Report {developer}</title>
2206
+ <style>
2207
+ * {{ box-sizing: border-box; margin: 0; padding: 0; }}
2208
+ body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #0f0f11; color: #e2e2e8; min-height: 100vh; padding: 40px 24px; }}
2209
+ .wrap {{ max-width: 860px; margin: 0 auto; }}
2210
+ h1 {{ font-size: 26px; font-weight: 700; letter-spacing: -.02em; margin-bottom: 4px; }}
2211
+ .sub {{ font-size: 14px; color: #888; margin-bottom: 32px; }}
2212
+ .grid {{ display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 14px; margin-bottom: 32px; }}
2213
+ .card {{ background: #1a1a1f; border: 1px solid #2a2a33; border-radius: 10px; padding: 18px 20px; }}
2214
+ .card .label {{ font-size: 10px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: #666; margin-bottom: 8px; }}
2215
+ .card .value {{ font-size: 28px; font-weight: 700; color: #fff; letter-spacing: -.02em; }}
2216
+ .card .unit {{ font-size: 13px; color: #888; margin-top: 2px; }}
2217
+ .archetype {{ background: #1a1a1f; border: 1px solid #2a2a33; border-radius: 10px; padding: 20px 24px; margin-bottom: 32px; display: flex; align-items: center; gap: 16px; }}
2218
+ .archetype .badge {{ font-size: 13px; font-weight: 700; background: #7c3aed22; color: #a78bfa; border: 1px solid #7c3aed44; border-radius: 8px; padding: 6px 14px; white-space: nowrap; }}
2219
+ .archetype .desc {{ font-size: 13px; color: #aaa; }}
2220
+ .section {{ margin-bottom: 32px; }}
2221
+ .section h2 {{ font-size: 12px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: #666; margin-bottom: 12px; }}
2222
+ .tag {{ display: inline-block; font-size: 12px; font-weight: 600; background: #1e1e28; border: 1px solid #2a2a3a; border-radius: 6px; padding: 4px 10px; margin: 3px; color: #a0aec0; }}
2223
+ .report {{ background: #1a1a1f; border: 1px solid #2a2a33; border-radius: 10px; padding: 20px 24px; font-size: 13px; color: #ccc; line-height: 1.7; }}
2224
+ </style>
2225
+ </head>
2226
+ <body>
2227
+ <div class="wrap">
2228
+ <h1>Session Report</h1>
2229
+ <div class="sub">{developer} · generated just now</div>
2230
+
2231
+ <div class="archetype">
2232
+ <span class="badge">{archetype}</span>
2233
+ <span class="desc">Autonomy score {autonomy_score}/100 &nbsp;·&nbsp; Planning ratio {planning_ratio}</span>
2234
+ </div>
2235
+
2236
+ <div class="grid">
2237
+ <div class="card"><div class="label">Sessions</div><div class="value">{sessions}</div></div>
2238
+ <div class="card"><div class="label">Prompts</div><div class="value">{prompts}</div></div>
2239
+ <div class="card"><div class="label">Commits</div><div class="value">{commits}</div></div>
2240
+ <div class="card"><div class="label">Autonomy</div><div class="value">{autonomy_score}</div><div class="unit">/ 100</div></div>
2241
+ </div>
2242
+
2243
+ <div class="section">
2244
+ <h2>Top Tools</h2>
2245
+ {tools_str if tools_str else "<span class='tag'>—</span>"}
2246
+ </div>
2247
+
2248
+ <div class="section">
2249
+ <h2>Report</h2>
2250
+ <div class="report">{md_html or "No report generated."}</div>
2251
+ </div>
2252
+ </div>
2253
+ </body>
2254
+ </html>"""
2255
+
2256
+ html_path = Path(tmpdir) / "report.html"
2257
+ html_path.write_text(html)
2258
+
2259
+ print(f" Archetype : {archetype}")
2260
+ print(f" Autonomy : {autonomy_score}/100 Sessions: {sessions} Commits: {commits}")
2261
+ webbrowser.open(f"file://{html_path}")
2262
+ print("Opening report in browser…")
2260
2263
 
2261
2264
 
2262
2265
  def cmd_emit_finding(args):