conduct-cli 0.4.31__tar.gz → 0.4.33__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.
- {conduct_cli-0.4.31 → conduct_cli-0.4.33}/PKG-INFO +1 -1
- {conduct_cli-0.4.31 → conduct_cli-0.4.33}/pyproject.toml +1 -1
- {conduct_cli-0.4.31 → conduct_cli-0.4.33}/src/conduct_cli/guard.py +56 -10
- {conduct_cli-0.4.31 → conduct_cli-0.4.33}/src/conduct_cli/main.py +7 -6
- {conduct_cli-0.4.31 → conduct_cli-0.4.33}/src/conduct_cli.egg-info/PKG-INFO +1 -1
- {conduct_cli-0.4.31 → conduct_cli-0.4.33}/README.md +0 -0
- {conduct_cli-0.4.31 → conduct_cli-0.4.33}/setup.cfg +0 -0
- {conduct_cli-0.4.31 → conduct_cli-0.4.33}/setup.py +0 -0
- {conduct_cli-0.4.31 → conduct_cli-0.4.33}/src/conduct_cli/__init__.py +0 -0
- {conduct_cli-0.4.31 → conduct_cli-0.4.33}/src/conduct_cli/api.py +0 -0
- {conduct_cli-0.4.31 → conduct_cli-0.4.33}/src/conduct_cli/guardmcp.py +0 -0
- {conduct_cli-0.4.31 → conduct_cli-0.4.33}/src/conduct_cli/mcp_server.py +0 -0
- {conduct_cli-0.4.31 → conduct_cli-0.4.33}/src/conduct_cli.egg-info/SOURCES.txt +0 -0
- {conduct_cli-0.4.31 → conduct_cli-0.4.33}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
- {conduct_cli-0.4.31 → conduct_cli-0.4.33}/src/conduct_cli.egg-info/entry_points.txt +0 -0
- {conduct_cli-0.4.31 → conduct_cli-0.4.33}/src/conduct_cli.egg-info/requires.txt +0 -0
- {conduct_cli-0.4.31 → conduct_cli-0.4.33}/src/conduct_cli.egg-info/top_level.txt +0 -0
|
@@ -31,11 +31,44 @@ import time
|
|
|
31
31
|
import urllib.request
|
|
32
32
|
from pathlib import Path
|
|
33
33
|
|
|
34
|
-
GUARD_DIR
|
|
35
|
-
POLICY_PATH
|
|
36
|
-
CONFIG_PATH
|
|
37
|
-
BUDGET_CACHE_PATH
|
|
38
|
-
BUDGET_CACHE_TTL
|
|
34
|
+
GUARD_DIR = Path.home() / ".conductguard"
|
|
35
|
+
POLICY_PATH = GUARD_DIR / "policy.json"
|
|
36
|
+
CONFIG_PATH = GUARD_DIR / "config.json"
|
|
37
|
+
BUDGET_CACHE_PATH = GUARD_DIR / "budget_cache.json"
|
|
38
|
+
BUDGET_CACHE_TTL = 300 # 5 minutes
|
|
39
|
+
VERSION_CACHE_PATH = GUARD_DIR / "version_cache.json"
|
|
40
|
+
VERSION_CACHE_TTL = 60 # 1 minute — matches server poll window
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _maybe_sync_policy():
|
|
44
|
+
"""Check server policy version once per minute; re-download if stale. Never raises."""
|
|
45
|
+
try:
|
|
46
|
+
cfg = json.loads(CONFIG_PATH.read_text()) if CONFIG_PATH.exists() else {}
|
|
47
|
+
workspace_id = cfg.get("workspace_id")
|
|
48
|
+
api_key = cfg.get("api_key", "")
|
|
49
|
+
api_url = cfg.get("api_url", "https://api.conductai.ai").rstrip("/")
|
|
50
|
+
if not workspace_id:
|
|
51
|
+
return
|
|
52
|
+
# Check cache TTL
|
|
53
|
+
if VERSION_CACHE_PATH.exists():
|
|
54
|
+
cache = json.loads(VERSION_CACHE_PATH.read_text())
|
|
55
|
+
if time.time() - cache.get("ts", 0) < VERSION_CACHE_TTL:
|
|
56
|
+
return
|
|
57
|
+
# Fetch current version from server
|
|
58
|
+
url = f"{api_url}/guard/policies/sync?workspace_id={workspace_id}"
|
|
59
|
+
req = urllib.request.Request(url, headers={"Authorization": f"Bearer {api_key}"} if api_key else {})
|
|
60
|
+
with urllib.request.urlopen(req, timeout=2) as resp:
|
|
61
|
+
remote = json.loads(resp.read())
|
|
62
|
+
remote_version = remote.get("version", "")
|
|
63
|
+
# Compare to local
|
|
64
|
+
local_version = ""
|
|
65
|
+
if POLICY_PATH.exists():
|
|
66
|
+
local_version = json.loads(POLICY_PATH.read_text()).get("version", "")
|
|
67
|
+
if remote_version != local_version:
|
|
68
|
+
POLICY_PATH.write_text(json.dumps(remote, indent=2))
|
|
69
|
+
VERSION_CACHE_PATH.write_text(json.dumps({"ts": time.time(), "version": remote_version}))
|
|
70
|
+
except Exception:
|
|
71
|
+
pass # Never block a tool call due to sync failure
|
|
39
72
|
|
|
40
73
|
|
|
41
74
|
def _load_budget_cache():
|
|
@@ -366,6 +399,9 @@ def main():
|
|
|
366
399
|
except Exception:
|
|
367
400
|
sys.exit(0)
|
|
368
401
|
|
|
402
|
+
# Policy version check (cached 60s) — auto-syncs if server version differs
|
|
403
|
+
_maybe_sync_policy()
|
|
404
|
+
|
|
369
405
|
# Hard budget cap (cached 5 min)
|
|
370
406
|
hard_blocked, reason = _load_budget_cache()
|
|
371
407
|
if hard_blocked is None:
|
|
@@ -934,15 +970,25 @@ def _report_tools_to_server() -> None:
|
|
|
934
970
|
if not email:
|
|
935
971
|
return
|
|
936
972
|
|
|
973
|
+
# Also pull conduct API key for X-Api-Key auth (member_token is not accepted by this endpoint)
|
|
974
|
+
conduct_cfg_path = Path.home() / ".conduct" / "config.json"
|
|
975
|
+
conduct_api_key = ""
|
|
976
|
+
if conduct_cfg_path.exists():
|
|
977
|
+
try:
|
|
978
|
+
conduct_api_key = json.loads(conduct_cfg_path.read_text()).get("api_key", "")
|
|
979
|
+
except Exception:
|
|
980
|
+
pass
|
|
981
|
+
|
|
937
982
|
payload = json.dumps({"email": email, "tools": tools}).encode()
|
|
938
|
-
|
|
983
|
+
headers = {"Content-Type": "application/json"}
|
|
984
|
+
if conduct_api_key and conduct_api_key.startswith("cond_live_"):
|
|
985
|
+
headers["X-Api-Key"] = conduct_api_key
|
|
986
|
+
elif token:
|
|
987
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
939
988
|
req = urllib.request.Request(
|
|
940
989
|
f"{base_url}/guard/developer-tools",
|
|
941
990
|
data=payload,
|
|
942
|
-
headers=
|
|
943
|
-
"Content-Type": "application/json",
|
|
944
|
-
"Authorization": f"Bearer {auth}",
|
|
945
|
-
},
|
|
991
|
+
headers=headers,
|
|
946
992
|
method="POST",
|
|
947
993
|
)
|
|
948
994
|
urllib.request.urlopen(req, timeout=8)
|
|
@@ -218,7 +218,7 @@ def _write_codex_mcp_config() -> bool:
|
|
|
218
218
|
content = config_path.read_text() if config_path.exists() else ""
|
|
219
219
|
if "conduct-mcp" in content:
|
|
220
220
|
return True
|
|
221
|
-
mcp_block = '\n[
|
|
221
|
+
mcp_block = '\n[mcp_servers.conduct]\ncommand = "conduct-mcp"\nargs = []\n'
|
|
222
222
|
config_path.write_text(content + mcp_block)
|
|
223
223
|
return True
|
|
224
224
|
except Exception:
|
|
@@ -400,14 +400,15 @@ def _report_tool_coverage() -> None:
|
|
|
400
400
|
return
|
|
401
401
|
|
|
402
402
|
payload = json.dumps({"email": email, "tools": tools}).encode()
|
|
403
|
-
|
|
403
|
+
headers = {"Content-Type": "application/json"}
|
|
404
|
+
if api_key and api_key.startswith("cond_live_"):
|
|
405
|
+
headers["X-Api-Key"] = api_key
|
|
406
|
+
elif token:
|
|
407
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
404
408
|
req = urllib.request.Request(
|
|
405
409
|
f"{server}/guard/developer-tools",
|
|
406
410
|
data=payload,
|
|
407
|
-
headers=
|
|
408
|
-
"Content-Type": "application/json",
|
|
409
|
-
"Authorization": f"Bearer {auth}",
|
|
410
|
-
},
|
|
411
|
+
headers=headers,
|
|
411
412
|
method="POST",
|
|
412
413
|
)
|
|
413
414
|
urllib.request.urlopen(req, timeout=8)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|