conduct-cli 0.6.34__tar.gz → 0.6.36__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.36}/PKG-INFO +1 -1
  2. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/pyproject.toml +1 -1
  3. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli/guard.py +7 -49
  4. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli/guardmcp.py +11 -36
  5. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli/hooks/base.py +4 -24
  6. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli/main.py +1 -0
  7. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli/mcp_server.py +2 -16
  8. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli.egg-info/PKG-INFO +1 -1
  9. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/README.md +0 -0
  10. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/setup.cfg +0 -0
  11. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/setup.py +0 -0
  12. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli/__init__.py +0 -0
  13. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli/api.py +0 -0
  14. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli/hooks/__init__.py +0 -0
  15. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli/hooks/posttooluse.py +0 -0
  16. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli/hooks/precompact.py +0 -0
  17. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli/hooks/pretooluse.py +0 -0
  18. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli/hooks/session_parser.py +0 -0
  19. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli/hooks/session_start.py +0 -0
  20. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli/hooks/stop.py +0 -0
  21. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli/log_util.py +0 -0
  22. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli/memory.py +0 -0
  23. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli/paxel.py +0 -0
  24. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli.egg-info/SOURCES.txt +0 -0
  25. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
  26. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli.egg-info/entry_points.txt +0 -0
  27. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli.egg-info/requires.txt +0 -0
  28. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/src/conduct_cli.egg-info/top_level.txt +0 -0
  29. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/tests/test_bash_operator_signature.py +0 -0
  30. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/tests/test_command_word_matcher.py +0 -0
  31. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/tests/test_guard_policy.py +0 -0
  32. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/tests/test_guard_savings.py +0 -0
  33. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/tests/test_hook_syntax.py +0 -0
  34. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/tests/test_journal_drain.py +0 -0
  35. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/tests/test_log_util.py +0 -0
  36. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/tests/test_proxy_env.py +0 -0
  37. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/tests/test_signed_policy.py +0 -0
  38. {conduct_cli-0.6.34 → conduct_cli-0.6.36}/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.36
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.36"
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" }
@@ -1,3 +1,4 @@
1
+ from __future__ import annotations
1
2
  """conduct guard — team policy + MCP registration subcommand."""
2
3
 
3
4
  import json
@@ -18,35 +19,11 @@ CYAN = "\033[36m"
18
19
  YELLOW = "\033[33m"
19
20
 
20
21
  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"
22
+ CONFIG_PATH = GUARD_DIR / "config.json"
23
+ POLICY_PATH = GUARD_DIR / "policy.json"
42
24
 
43
25
 
44
26
  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
27
  return POLICY_PATH
51
28
 
52
29
  # ── Hook templates — loaded from real .py files (no string embedding) ─────────
@@ -331,26 +308,16 @@ def _ensure_persona(workspace_id: str, api_key: str, base_url: str) -> str:
331
308
 
332
309
 
333
310
  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
311
  if CONFIG_PATH.exists():
341
- return json.loads(CONFIG_PATH.read_text())
312
+ try:
313
+ return json.loads(CONFIG_PATH.read_text())
314
+ except Exception:
315
+ pass
342
316
  return {}
343
317
 
344
318
 
345
319
  def _save_guard_config(data: dict, workspace_id: str | None = None):
346
320
  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
321
  CONFIG_PATH.write_text(json.dumps(data, indent=2))
355
322
  CONFIG_PATH.chmod(0o600)
356
323
 
@@ -612,20 +579,11 @@ def _req(method: str, url: str, body=None, token: str = None, api_key: str = Non
612
579
 
613
580
  def _save_policy(policy: dict, workspace_id: str | None = None):
614
581
  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
582
  POLICY_PATH.write_text(json.dumps(policy, indent=2))
620
583
 
621
584
 
622
585
  def _load_policy(workspace_id: str | None = None) -> dict:
623
586
  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
587
  return json.loads(POLICY_PATH.read_text())
630
588
  except Exception:
631
589
  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
 
@@ -1,3 +1,4 @@
1
+ from __future__ import annotations
1
2
  import argparse
2
3
  import importlib.metadata
3
4
  import json
@@ -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.36
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