conduct-cli 0.6.34__tar.gz → 0.6.35__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 (38) hide show
  1. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/PKG-INFO +1 -1
  2. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/pyproject.toml +1 -1
  3. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/guard.py +6 -49
  4. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/guardmcp.py +11 -36
  5. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/hooks/base.py +4 -24
  6. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/mcp_server.py +2 -16
  7. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli.egg-info/PKG-INFO +1 -1
  8. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/README.md +0 -0
  9. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/setup.cfg +0 -0
  10. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/setup.py +0 -0
  11. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/__init__.py +0 -0
  12. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/api.py +0 -0
  13. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/hooks/__init__.py +0 -0
  14. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/hooks/posttooluse.py +0 -0
  15. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/hooks/precompact.py +0 -0
  16. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/hooks/pretooluse.py +0 -0
  17. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/hooks/session_parser.py +0 -0
  18. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/hooks/session_start.py +0 -0
  19. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/hooks/stop.py +0 -0
  20. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/log_util.py +0 -0
  21. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/main.py +0 -0
  22. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/memory.py +0 -0
  23. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/paxel.py +0 -0
  24. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli.egg-info/SOURCES.txt +0 -0
  25. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
  26. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli.egg-info/entry_points.txt +0 -0
  27. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli.egg-info/requires.txt +0 -0
  28. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli.egg-info/top_level.txt +0 -0
  29. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/tests/test_bash_operator_signature.py +0 -0
  30. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/tests/test_command_word_matcher.py +0 -0
  31. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/tests/test_guard_policy.py +0 -0
  32. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/tests/test_guard_savings.py +0 -0
  33. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/tests/test_hook_syntax.py +0 -0
  34. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/tests/test_journal_drain.py +0 -0
  35. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/tests/test_log_util.py +0 -0
  36. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/tests/test_proxy_env.py +0 -0
  37. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/tests/test_signed_policy.py +0 -0
  38. {conduct_cli-0.6.34 → conduct_cli-0.6.35}/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.34
3
+ Version: 0.6.35
4
4
  Summary: CLI for Conduct AI — secure, govern, 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.6.34"
7
+ version = "0.6.35"
8
8
  description = "CLI for Conduct AI — secure, govern, install agents, manage projects, run tests"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -18,35 +18,11 @@ CYAN = "\033[36m"
18
18
  YELLOW = "\033[33m"
19
19
 
20
20
  GUARD_DIR = Path.home() / ".conductguard"
21
- CONFIG_PATH = GUARD_DIR / "config.json" # legacy / fallback
22
- POLICY_PATH = GUARD_DIR / "policy.json" # legacy / fallback
23
- _CONDUCT_CFG = Path.home() / ".conduct" / "config.json"
24
-
25
-
26
- def _active_workspace_id() -> str | None:
27
- """Active workspace from conduct switch — updates at runtime."""
28
- try:
29
- if _CONDUCT_CFG.exists():
30
- return json.loads(_CONDUCT_CFG.read_text()).get("workspace")
31
- except Exception:
32
- pass
33
- return None
34
-
35
-
36
- def _ws_config_path(ws_id: str) -> Path:
37
- return GUARD_DIR / "workspaces" / f"{ws_id}.json"
38
-
39
-
40
- def _ws_policy_path(ws_id: str) -> Path:
41
- return GUARD_DIR / f"policy_{ws_id}.json"
21
+ CONFIG_PATH = GUARD_DIR / "config.json"
22
+ POLICY_PATH = GUARD_DIR / "policy.json"
42
23
 
43
24
 
44
25
  def active_policy_path() -> Path:
45
- ws_id = _active_workspace_id()
46
- if ws_id:
47
- p = _ws_policy_path(ws_id)
48
- if p.exists():
49
- return p
50
26
  return POLICY_PATH
51
27
 
52
28
  # ── Hook templates — loaded from real .py files (no string embedding) ─────────
@@ -331,26 +307,16 @@ def _ensure_persona(workspace_id: str, api_key: str, base_url: str) -> str:
331
307
 
332
308
 
333
309
  def _load_guard_config(workspace_id: str | None = None) -> dict:
334
- ws_id = workspace_id or _active_workspace_id()
335
- if ws_id:
336
- p = _ws_config_path(ws_id)
337
- if p.exists():
338
- return json.loads(p.read_text())
339
- # fallback to legacy single-workspace config
340
310
  if CONFIG_PATH.exists():
341
- return json.loads(CONFIG_PATH.read_text())
311
+ try:
312
+ return json.loads(CONFIG_PATH.read_text())
313
+ except Exception:
314
+ pass
342
315
  return {}
343
316
 
344
317
 
345
318
  def _save_guard_config(data: dict, workspace_id: str | None = None):
346
319
  GUARD_DIR.mkdir(parents=True, exist_ok=True)
347
- ws_id = workspace_id or data.get("workspace_id") or _active_workspace_id()
348
- if ws_id:
349
- p = _ws_config_path(ws_id)
350
- p.parent.mkdir(parents=True, exist_ok=True)
351
- p.write_text(json.dumps(data, indent=2))
352
- p.chmod(0o600)
353
- # always write legacy path so old hook versions keep working
354
320
  CONFIG_PATH.write_text(json.dumps(data, indent=2))
355
321
  CONFIG_PATH.chmod(0o600)
356
322
 
@@ -612,20 +578,11 @@ def _req(method: str, url: str, body=None, token: str = None, api_key: str = Non
612
578
 
613
579
  def _save_policy(policy: dict, workspace_id: str | None = None):
614
580
  GUARD_DIR.mkdir(parents=True, exist_ok=True)
615
- ws_id = workspace_id or _active_workspace_id()
616
- if ws_id:
617
- _ws_policy_path(ws_id).write_text(json.dumps(policy, indent=2))
618
- # always write legacy path for old hook versions
619
581
  POLICY_PATH.write_text(json.dumps(policy, indent=2))
620
582
 
621
583
 
622
584
  def _load_policy(workspace_id: str | None = None) -> dict:
623
585
  try:
624
- ws_id = workspace_id or _active_workspace_id()
625
- if ws_id:
626
- p = _ws_policy_path(ws_id)
627
- if p.exists():
628
- return json.loads(p.read_text())
629
586
  return json.loads(POLICY_PATH.read_text())
630
587
  except Exception:
631
588
  return {"rules": []}
@@ -24,28 +24,9 @@ import urllib.request
24
24
  import urllib.error
25
25
  from pathlib import Path
26
26
 
27
- GUARD_DIR = Path.home() / ".conductguard"
28
- POLICY_PATH = GUARD_DIR / "policy.json"
29
- CONFIG_PATH = GUARD_DIR / "config.json"
30
- _CONDUCT_CFG = Path.home() / ".conduct" / "config.json"
31
-
32
-
33
- def _active_workspace_id() -> str | None:
34
- try:
35
- if _CONDUCT_CFG.exists():
36
- return json.loads(_CONDUCT_CFG.read_text()).get("workspace")
37
- except Exception:
38
- pass
39
- return None
40
-
41
-
42
- def _active_policy_path() -> Path:
43
- ws_id = _active_workspace_id()
44
- if ws_id:
45
- p = GUARD_DIR / f"policy_{ws_id}.json"
46
- if p.exists():
47
- return p
48
- return POLICY_PATH
27
+ GUARD_DIR = Path.home() / ".conductguard"
28
+ POLICY_PATH = GUARD_DIR / "policy.json"
29
+ CONFIG_PATH = GUARD_DIR / "config.json"
49
30
 
50
31
  PROTOCOL_VERSION = "2024-11-05"
51
32
 
@@ -141,26 +122,20 @@ _TOOLS = [
141
122
  # ── Helpers ───────────────────────────────────────────────────────────────────
142
123
 
143
124
  def _load_policy() -> dict:
144
- p = _active_policy_path()
145
- if p.exists():
125
+ if POLICY_PATH.exists():
146
126
  try:
147
- return json.loads(p.read_text())
127
+ return json.loads(POLICY_PATH.read_text())
148
128
  except Exception:
149
129
  pass
150
130
  return {"rules": []}
151
131
 
152
132
 
153
133
  def _load_config() -> dict:
154
- try:
155
- ws_id = _active_workspace_id()
156
- if ws_id:
157
- p = GUARD_DIR / "workspaces" / f"{ws_id}.json"
158
- if p.exists():
159
- return json.loads(p.read_text())
160
- if CONFIG_PATH.exists():
134
+ if CONFIG_PATH.exists():
135
+ try:
161
136
  return json.loads(CONFIG_PATH.read_text())
162
- except Exception:
163
- pass
137
+ except Exception:
138
+ pass
164
139
  return {}
165
140
 
166
141
 
@@ -185,7 +160,7 @@ def _maybe_sync() -> None:
185
160
  ws_id = cfg.get("workspace_id", "")
186
161
  if ws_id:
187
162
  with urllib.request.urlopen(f"{_DAEMON_URL}/policy?workspace_id={ws_id}", timeout=1) as r:
188
- _active_policy_path().write_text(r.read().decode())
163
+ POLICY_PATH.write_text(r.read().decode())
189
164
  return
190
165
  except Exception:
191
166
  pass # fall through to CLI sync
@@ -362,7 +337,7 @@ def _handle_guard_sync(workspace_id: str, token: str) -> str:
362
337
  with urllib.request.urlopen(req, timeout=10) as resp:
363
338
  policy = json.loads(resp.read())
364
339
  GUARD_DIR.mkdir(parents=True, exist_ok=True)
365
- _active_policy_path().write_text(json.dumps(policy, indent=2))
340
+ POLICY_PATH.write_text(json.dumps(policy, indent=2))
366
341
  rule_count = len(policy.get("rules", []))
367
342
  return f"Policy synced — {rule_count} rule(s) active (version: {policy.get('version', 'unknown')})."
368
343
  except urllib.error.HTTPError as e:
@@ -17,9 +17,8 @@ from typing import Literal, Optional
17
17
  # ── Canonical paths ───────────────────────────────────────────────────────────
18
18
 
19
19
  GUARD_DIR = Path.home() / ".conductguard"
20
- CONFIG_PATH = GUARD_DIR / "config.json" # legacy / fallback
21
- POLICY_PATH = GUARD_DIR / "policy.json" # legacy / fallback
22
- _CONDUCT_CFG = Path.home() / ".conduct" / "config.json"
20
+ CONFIG_PATH = GUARD_DIR / "config.json"
21
+ POLICY_PATH = GUARD_DIR / "policy.json"
23
22
  BUDGET_CACHE_PATH = GUARD_DIR / "budget_cache.json"
24
23
  BUDGET_CACHE_TTL = 300 # seconds
25
24
  VERSION_CACHE_PATH = GUARD_DIR / "version_cache.json"
@@ -43,23 +42,9 @@ class HookResult:
43
42
 
44
43
  # ── Config loading ────────────────────────────────────────────────────────────
45
44
 
46
- def _active_workspace_id() -> str | None:
47
- try:
48
- if _CONDUCT_CFG.exists():
49
- return json.loads(_CONDUCT_CFG.read_text()).get("workspace")
50
- except Exception:
51
- pass
52
- return None
53
-
54
-
55
45
  def load_config() -> dict:
56
- """Load Guard config for the active workspace, falling back to legacy single config."""
46
+ """Load Guard config from ~/.conductguard/config.json."""
57
47
  try:
58
- ws_id = _active_workspace_id()
59
- if ws_id:
60
- p = GUARD_DIR / "workspaces" / f"{ws_id}.json"
61
- if p.exists():
62
- return json.loads(p.read_text())
63
48
  if CONFIG_PATH.exists():
64
49
  return json.loads(CONFIG_PATH.read_text())
65
50
  except Exception:
@@ -68,12 +53,7 @@ def load_config() -> dict:
68
53
 
69
54
 
70
55
  def active_policy_path() -> Path:
71
- """Return per-workspace policy path, or legacy fallback."""
72
- ws_id = _active_workspace_id()
73
- if ws_id:
74
- p = GUARD_DIR / f"policy_{ws_id}.json"
75
- if p.exists():
76
- return p
56
+ """Return policy path: ~/.conductguard/policy.json."""
77
57
  return POLICY_PATH
78
58
 
79
59
 
@@ -97,24 +97,10 @@ def _load_conduct_config() -> dict:
97
97
  return {}
98
98
 
99
99
 
100
- def _active_guard_policy_path() -> Path:
101
- try:
102
- cfg = json.loads(CONDUCT_CONFIG_PATH.read_text()) if CONDUCT_CONFIG_PATH.exists() else {}
103
- ws_id = cfg.get("workspace")
104
- if ws_id:
105
- p = GUARD_DIR / f"policy_{ws_id}.json"
106
- if p.exists():
107
- return p
108
- except Exception:
109
- pass
110
- return GUARD_POLICY_PATH
111
-
112
-
113
100
  def _load_guard_policy() -> dict:
114
- p = _active_guard_policy_path()
115
- if p.exists():
101
+ if GUARD_POLICY_PATH.exists():
116
102
  try:
117
- return json.loads(p.read_text())
103
+ return json.loads(GUARD_POLICY_PATH.read_text())
118
104
  except Exception:
119
105
  pass
120
106
  return {"rules": []}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: conduct-cli
3
- Version: 0.6.34
3
+ Version: 0.6.35
4
4
  Summary: CLI for Conduct AI — secure, govern, 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