conduct-cli 0.6.25__tar.gz → 0.6.27__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 (42) hide show
  1. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/PKG-INFO +2 -2
  2. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/pyproject.toml +3 -3
  3. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli/guard.py +21 -26
  4. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli.egg-info/PKG-INFO +2 -2
  5. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli.egg-info/SOURCES.txt +0 -4
  6. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli.egg-info/requires.txt +1 -1
  7. conduct_cli-0.6.25/src/conduct_cli/hook_precompact_template.py +0 -78
  8. conduct_cli-0.6.25/src/conduct_cli/hook_session_start_template.py +0 -161
  9. conduct_cli-0.6.25/src/conduct_cli/hook_stop_template.py +0 -42
  10. conduct_cli-0.6.25/src/conduct_cli/hook_template.py +0 -987
  11. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/README.md +0 -0
  12. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/setup.cfg +0 -0
  13. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/setup.py +0 -0
  14. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli/__init__.py +0 -0
  15. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli/api.py +0 -0
  16. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli/guardmcp.py +0 -0
  17. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli/hooks/__init__.py +0 -0
  18. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli/hooks/base.py +0 -0
  19. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli/hooks/posttooluse.py +0 -0
  20. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli/hooks/precompact.py +0 -0
  21. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli/hooks/pretooluse.py +0 -0
  22. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli/hooks/session_parser.py +0 -0
  23. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli/hooks/session_start.py +0 -0
  24. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli/hooks/stop.py +0 -0
  25. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli/log_util.py +0 -0
  26. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli/main.py +0 -0
  27. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli/mcp_server.py +0 -0
  28. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli/memory.py +0 -0
  29. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli/paxel.py +0 -0
  30. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
  31. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli.egg-info/entry_points.txt +0 -0
  32. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/src/conduct_cli.egg-info/top_level.txt +0 -0
  33. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/tests/test_bash_operator_signature.py +0 -0
  34. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/tests/test_command_word_matcher.py +0 -0
  35. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/tests/test_guard_policy.py +0 -0
  36. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/tests/test_guard_savings.py +0 -0
  37. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/tests/test_hook_syntax.py +0 -0
  38. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/tests/test_journal_drain.py +0 -0
  39. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/tests/test_log_util.py +0 -0
  40. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/tests/test_proxy_env.py +0 -0
  41. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/tests/test_signed_policy.py +0 -0
  42. {conduct_cli-0.6.25 → conduct_cli-0.6.27}/tests/test_switch.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: conduct-cli
3
- Version: 0.6.25
3
+ Version: 0.6.27
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
@@ -23,7 +23,7 @@ Description-Content-Type: text/markdown
23
23
  Requires-Dist: pyyaml>=6.0
24
24
  Requires-Dist: rich>=13.0
25
25
  Provides-Extra: booster
26
- Requires-Dist: agent-booster[watch]>=0.2.23; extra == "booster"
26
+ Requires-Dist: agent-booster[watch]>=0.6.6; extra == "booster"
27
27
 
28
28
  <!-- mcp-name: io.github.sseshachala/conduct-cli -->
29
29
  # conduct-cli
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "conduct-cli"
7
- version = "0.6.25"
7
+ version = "0.6.27"
8
8
  description = "CLI for Conduct AI — install agents, manage projects, run tests"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -26,7 +26,7 @@ classifiers = [
26
26
  dependencies = ["pyyaml>=6.0", "rich>=13.0"]
27
27
 
28
28
  [project.optional-dependencies]
29
- booster = ["agent-booster[watch]>=0.2.23"]
29
+ booster = ["agent-booster[watch]>=0.6.6"]
30
30
 
31
31
  [project.urls]
32
32
  Homepage = "https://conductai.ai"
@@ -44,4 +44,4 @@ where = ["src"]
44
44
  include = ["conduct_cli*"]
45
45
 
46
46
  [tool.setuptools.package-data]
47
- conduct_cli = ["hook_template.py", "hook_precompact_template.py", "hook_session_start_template.py", "hook_stop_template.py", "paxel.py"]
47
+ conduct_cli = ["paxel.py"]
@@ -22,11 +22,6 @@ CONFIG_PATH = GUARD_DIR / "config.json"
22
22
  POLICY_PATH = GUARD_DIR / "policy.json"
23
23
 
24
24
  # ── Hook templates — loaded from real .py files (no string embedding) ─────────
25
- _TEMPLATES_DIR = Path(__file__).parent
26
-
27
- def _read_template(name: str) -> str:
28
- return (_TEMPLATES_DIR / name).read_text()
29
-
30
25
 
31
26
  # ── Thin launcher content ─────────────────────────────────────────────────────
32
27
 
@@ -195,12 +190,7 @@ def _write_hook(path: Path) -> None:
195
190
  backup = path.read_text()
196
191
  path.parent.mkdir(parents=True, exist_ok=True)
197
192
 
198
- # Prefer thin launcher; fall back to full template if package not importable
199
- try:
200
- import conduct_cli.hooks.pretooluse # noqa: F401
201
- content = _THIN_LAUNCHERS["pretooluse"]
202
- except ImportError:
203
- content = _read_template("hook_template.py")
193
+ content = _THIN_LAUNCHERS["pretooluse"]
204
194
 
205
195
  path.write_text(content)
206
196
  path.chmod(0o755)
@@ -216,18 +206,8 @@ def _write_hook(path: Path) -> None:
216
206
  ) from exc
217
207
 
218
208
 
219
- def _write_session_hook(path: Path, launcher_key: str, template_name: str) -> None:
220
- """Write a thin launcher (or legacy template) for a session hook.
221
-
222
- If conduct_cli.hooks is importable, writes the thin launcher.
223
- Falls back to the full template so old installs continue to work.
224
- Also rewrites old-style (non-thin) hooks to thin launchers on sync.
225
- """
226
- try:
227
- import conduct_cli.hooks # noqa: F401
228
- content = _THIN_LAUNCHERS[launcher_key]
229
- except ImportError:
230
- content = _read_template(template_name)
209
+ def _write_session_hook(path: Path, launcher_key: str) -> None:
210
+ content = _THIN_LAUNCHERS[launcher_key]
231
211
  path.write_text(content)
232
212
  path.chmod(0o755)
233
213
 
@@ -240,9 +220,9 @@ def _install_session_hooks() -> None:
240
220
  session_start_path = GUARD_DIR / "guard-session-start.py"
241
221
  stop_path = GUARD_DIR / "guard-stop.py"
242
222
 
243
- _write_session_hook(precompact_path, "precompact", "hook_precompact_template.py")
244
- _write_session_hook(session_start_path, "session-start", "hook_session_start_template.py")
245
- _write_session_hook(stop_path, "stop", "hook_stop_template.py")
223
+ _write_session_hook(precompact_path, "precompact")
224
+ _write_session_hook(session_start_path, "session-start")
225
+ _write_session_hook(stop_path, "stop")
246
226
 
247
227
  claude_settings = Path.home() / ".claude" / "settings.json"
248
228
  settings: dict = {}
@@ -1508,6 +1488,21 @@ def _ensure_booster(root: Path) -> None:
1508
1488
  pass
1509
1489
  print(f" {GREEN}Agent Booster:{RESET} {symbols_count} symbols indexed — Read/Grep intercept active")
1510
1490
 
1491
+ # Keep BOOSTER_SECRET in .mcp.json in sync with ~/.booster/.secret
1492
+ secret_file = Path.home() / ".booster" / ".secret"
1493
+ mcp_json = root / ".mcp.json"
1494
+ if secret_file.exists() and mcp_json.exists():
1495
+ try:
1496
+ expected = secret_file.read_text().strip()
1497
+ data = json.loads(mcp_json.read_text())
1498
+ server = data.get("mcpServers", {}).get("agent-booster", {})
1499
+ if server.get("env", {}).get("BOOSTER_SECRET") != expected:
1500
+ server.setdefault("env", {})["BOOSTER_SECRET"] = expected
1501
+ mcp_json.write_text(json.dumps(data, indent=2) + "\n")
1502
+ print(f" {GREEN}Agent Booster:{RESET} refreshed BOOSTER_SECRET in .mcp.json")
1503
+ except Exception:
1504
+ pass
1505
+
1511
1506
 
1512
1507
  def _report_savings(cfg: dict, base_url: str, api_key: str) -> None:
1513
1508
  import subprocess
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: conduct-cli
3
- Version: 0.6.25
3
+ Version: 0.6.27
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
@@ -23,7 +23,7 @@ Description-Content-Type: text/markdown
23
23
  Requires-Dist: pyyaml>=6.0
24
24
  Requires-Dist: rich>=13.0
25
25
  Provides-Extra: booster
26
- Requires-Dist: agent-booster[watch]>=0.2.23; extra == "booster"
26
+ Requires-Dist: agent-booster[watch]>=0.6.6; extra == "booster"
27
27
 
28
28
  <!-- mcp-name: io.github.sseshachala/conduct-cli -->
29
29
  # conduct-cli
@@ -5,10 +5,6 @@ src/conduct_cli/__init__.py
5
5
  src/conduct_cli/api.py
6
6
  src/conduct_cli/guard.py
7
7
  src/conduct_cli/guardmcp.py
8
- src/conduct_cli/hook_precompact_template.py
9
- src/conduct_cli/hook_session_start_template.py
10
- src/conduct_cli/hook_stop_template.py
11
- src/conduct_cli/hook_template.py
12
8
  src/conduct_cli/log_util.py
13
9
  src/conduct_cli/main.py
14
10
  src/conduct_cli/mcp_server.py
@@ -2,4 +2,4 @@ pyyaml>=6.0
2
2
  rich>=13.0
3
3
 
4
4
  [booster]
5
- agent-booster[watch]>=0.2.23
5
+ agent-booster[watch]>=0.6.6
@@ -1,78 +0,0 @@
1
- #!/usr/bin/env python3
2
- """ConductGuard PreCompact hook — persists session context before compaction."""
3
- import json
4
- import os
5
- import subprocess
6
- import sys
7
- from datetime import datetime, timezone
8
- from pathlib import Path
9
-
10
- GUARD_DIR = Path.home() / ".conductguard"
11
- SNAPSHOT_PATH = GUARD_DIR / "session_snapshot.json"
12
-
13
-
14
- def _git(cmd):
15
- try:
16
- return subprocess.check_output(
17
- ["git"] + cmd, stderr=subprocess.DEVNULL, text=True, timeout=3
18
- ).strip()
19
- except Exception:
20
- return ""
21
-
22
-
23
- def _guard_status():
24
- try:
25
- out = subprocess.check_output(
26
- ["conductguard", "status", "--json"],
27
- stderr=subprocess.DEVNULL, text=True, timeout=3,
28
- )
29
- return json.loads(out.strip())
30
- except Exception:
31
- return None
32
-
33
-
34
- def _memory_headline():
35
- try:
36
- root = Path.cwd()
37
- mem_key = str(root).replace("/", "-").lstrip("-")
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])
45
- except Exception:
46
- pass
47
- return ""
48
-
49
-
50
- def main():
51
- try:
52
- sys.stdin.read()
53
- except Exception:
54
- pass
55
-
56
- try:
57
- GUARD_DIR.mkdir(parents=True, exist_ok=True)
58
- snapshot = {
59
- "compacted_at": datetime.now(timezone.utc).isoformat(),
60
- "tier1": {
61
- "git_branch": _git(["branch", "--show-current"]),
62
- "recent_commits": _git(["log", "--oneline", "-3"]),
63
- "memory_headline": _memory_headline(),
64
- },
65
- "tier2": {"guard_status": _guard_status()},
66
- "tier3": {"cwd": str(Path.cwd()), "python": sys.version.split()[0]},
67
- }
68
- tmp = GUARD_DIR / "session_snapshot.tmp"
69
- tmp.write_text(json.dumps(snapshot, indent=2))
70
- tmp.rename(SNAPSHOT_PATH)
71
- except Exception:
72
- pass
73
-
74
- sys.exit(0)
75
-
76
-
77
- if __name__ == "__main__":
78
- main()
@@ -1,161 +0,0 @@
1
- #!/usr/bin/env python3
2
- """ConductGuard SessionStart hook — prints context after compaction."""
3
- import json
4
- import sys
5
- from datetime import datetime, timezone
6
- from pathlib import Path
7
-
8
- SNAPSHOT_PATH = Path.home() / ".conductguard" / "session_snapshot.json"
9
- MAX_AGE_HOURS = 2
10
-
11
-
12
- CONDUCT_ENV_PATH = Path.home() / ".conduct" / "env"
13
- GUARD_CONFIG_PATH = Path.home() / ".conductguard" / "config.json"
14
-
15
-
16
- def _check_proxy_token() -> None:
17
- """Warn + alert server if the proxy token is missing or malformed.
18
-
19
- Reads ~/.conduct/env directly (not shell env) so this works regardless
20
- of whether the user sourced their rc file in this session.
21
- """
22
- if not CONDUCT_ENV_PATH.exists():
23
- return
24
- env_text = CONDUCT_ENV_PATH.read_text()
25
- # Check if proxy is configured and token is valid
26
- has_base_url = 'ANTHROPIC_BASE_URL=' in env_text
27
- has_valid_token = 'ANTHROPIC_API_KEY="guard-mt-' in env_text or "ANTHROPIC_API_KEY='guard-mt-" in env_text
28
- if not has_base_url:
29
- return # proxy not configured — nothing to check
30
- if has_valid_token:
31
- return # all good
32
-
33
- # Token is missing or malformed — warn locally and ping the server
34
- print("## ConductGuard: proxy token missing or malformed")
35
- print("Run `conduct guard sync` to refresh your token before making LLM calls.\n")
36
-
37
- # Alert the server so the admin can prompt the developer to re-sync
38
- try:
39
- cfg: dict = {}
40
- if GUARD_CONFIG_PATH.exists():
41
- cfg = json.loads(GUARD_CONFIG_PATH.read_text())
42
- workspace_id = cfg.get("workspace_id", "")
43
- clerk_user_id = cfg.get("clerk_user_id", "") or cfg.get("user_email", "")
44
- api_url = cfg.get("api_url", "https://api.conductai.ai").rstrip("/")
45
- member_token = cfg.get("member_token", "")
46
- if workspace_id and member_token:
47
- import urllib.request
48
- payload = json.dumps({
49
- "workspace_id": workspace_id,
50
- "clerk_user_id": clerk_user_id,
51
- "ai_tool": "claude_code",
52
- "tool_call": "session_start",
53
- "decision": "warned",
54
- "rule_id": "proxy_token_missing",
55
- "rule_message": "Developer proxy token missing or malformed — run conduct guard sync",
56
- "input_summary": "session_start: proxy token not found in ~/.conduct/env",
57
- }).encode()
58
- req = urllib.request.Request(
59
- f"{api_url}/guard/events",
60
- data=payload,
61
- headers={"Content-Type": "application/json"},
62
- method="POST",
63
- )
64
- urllib.request.urlopen(req, timeout=3)
65
- except Exception:
66
- pass # best-effort — never block the session
67
-
68
-
69
- def main():
70
- try:
71
- sys.stdin.read()
72
- except Exception:
73
- pass
74
-
75
- _check_proxy_token()
76
-
77
- if not SNAPSHOT_PATH.exists():
78
- sys.exit(0)
79
-
80
- try:
81
- snapshot = json.loads(SNAPSHOT_PATH.read_text())
82
- compacted_at = datetime.fromisoformat(snapshot.get("compacted_at", ""))
83
- age_hours = (datetime.now(timezone.utc) - compacted_at).total_seconds() / 3600
84
- if age_hours > MAX_AGE_HOURS:
85
- sys.exit(0)
86
-
87
- t1 = snapshot.get("tier1", {})
88
- branch = t1.get("git_branch", "")
89
- commits = t1.get("recent_commits", "")
90
- headline = t1.get("memory_headline", "")
91
- t2 = snapshot.get("tier2", {})
92
- guard = t2.get("guard_status") or {}
93
-
94
- lines = [f"## Session resumed (snapshot from {compacted_at.strftime('%Y-%m-%d %H:%M')} UTC)"]
95
- if branch:
96
- last = commits.splitlines()[0] if commits else ""
97
- lines.append(f"- Branch: {branch}" + (f" | Last: {last}" if last else ""))
98
- budget = guard.get("budget_pct")
99
- if budget is not None:
100
- lines.append(f"- Guard: {budget}% budget used")
101
- else:
102
- lines.append("- Guard: state unavailable")
103
- if headline:
104
- lines.append(f"- Memory index:\n {headline}")
105
- else:
106
- lines.append("- Memory index:\n (none)")
107
-
108
- # Booster intercept status
109
- try:
110
- import shutil, sqlite3
111
- root = Path.cwd()
112
- if shutil.which("booster"):
113
- db_path = root / ".booster" / "symbols.db"
114
- hooks_path = root / ".claude" / "hooks" / "booster-gate.py"
115
- if hooks_path.exists() and db_path.exists():
116
- try:
117
- conn = sqlite3.connect(str(db_path))
118
- n = conn.execute("SELECT COUNT(*) FROM symbols").fetchone()[0]
119
- conn.close()
120
- lines.append(f"- Agent Booster: ACTIVE — {n} symbols indexed, Read/Grep intercept ON")
121
- except Exception:
122
- lines.append("- Agent Booster: installed (index unavailable)")
123
- elif shutil.which("booster"):
124
- lines.append("- Agent Booster: installed but NOT wired — run: conduct guard sync")
125
- else:
126
- lines.append("- Agent Booster: not installed (pip install 'conduct-cli[booster]')")
127
- except Exception:
128
- pass
129
-
130
- # Inject relevant team memories for the current repo
131
- try:
132
- repo = None
133
- try:
134
- import subprocess
135
- out = subprocess.check_output(["git", "remote", "get-url", "origin"],
136
- stderr=subprocess.DEVNULL, text=True).strip()
137
- if "github.com" in out:
138
- repo = out.split("github.com")[-1].lstrip("/:").rstrip(".git")
139
- except Exception:
140
- pass
141
-
142
- from conduct_cli.memory import search_team_memory
143
- results = search_team_memory("recent learnings patterns bugs", repo=repo, limit=3)
144
- if results:
145
- lines.append("- Team knowledge:")
146
- for r in results[:3]:
147
- dev = r.get("developer_id", "teammate")[:8]
148
- summary = r.get("summary", "")[:120]
149
- lines.append(f" {dev}: {summary}")
150
- except Exception:
151
- pass
152
-
153
- print("\n".join(lines))
154
- except Exception:
155
- pass
156
-
157
- sys.exit(0)
158
-
159
-
160
- if __name__ == "__main__":
161
- main()
@@ -1,42 +0,0 @@
1
- #!/usr/bin/env python3
2
- """ConductGuard Stop hook — captures session transcript for team memory."""
3
- import json
4
- import subprocess
5
- import sys
6
- from pathlib import Path
7
-
8
-
9
- def _detect_repo():
10
- try:
11
- out = subprocess.check_output(
12
- ["git", "remote", "get-url", "origin"],
13
- stderr=subprocess.DEVNULL, text=True,
14
- ).strip()
15
- if "github.com" in out:
16
- return out.split("github.com")[-1].lstrip("/:").rstrip(".git")
17
- except Exception:
18
- pass
19
- return None
20
-
21
-
22
- def main():
23
- try:
24
- raw = sys.stdin.read()
25
- data = json.loads(raw) if raw.strip() else {}
26
- except Exception:
27
- data = {}
28
-
29
- session_id = data.get("session_id", "")
30
- transcript_path = data.get("transcript_path") or data.get("transcriptPath")
31
-
32
- try:
33
- from conduct_cli.memory import post_session_to_api
34
- post_session_to_api(session_id, transcript_path, _detect_repo())
35
- except Exception:
36
- pass
37
-
38
- sys.exit(0)
39
-
40
-
41
- if __name__ == "__main__":
42
- main()