conduct-cli 0.6.33__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.
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/PKG-INFO +1 -1
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/pyproject.toml +1 -1
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli/guard.py +148 -17
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli/hooks/base.py +6 -1
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli/hooks/pretooluse.py +12 -8
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli/main.py +5 -7
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli.egg-info/PKG-INFO +1 -1
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/README.md +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/setup.cfg +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/setup.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli/__init__.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli/api.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli/guardmcp.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli/hooks/__init__.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli/hooks/posttooluse.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli/hooks/precompact.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli/hooks/session_parser.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli/hooks/session_start.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli/hooks/stop.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli/log_util.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli/mcp_server.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli/memory.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli/paxel.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli.egg-info/SOURCES.txt +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli.egg-info/entry_points.txt +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli.egg-info/requires.txt +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/src/conduct_cli.egg-info/top_level.txt +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/tests/test_bash_operator_signature.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/tests/test_command_word_matcher.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/tests/test_guard_policy.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/tests/test_guard_savings.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/tests/test_hook_syntax.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/tests/test_journal_drain.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/tests/test_log_util.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/tests/test_proxy_env.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/tests/test_signed_policy.py +0 -0
- {conduct_cli-0.6.33 → conduct_cli-0.6.35}/tests/test_switch.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "conduct-cli"
|
|
7
|
-
version = "0.6.
|
|
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" }
|
|
@@ -21,6 +21,10 @@ GUARD_DIR = Path.home() / ".conductguard"
|
|
|
21
21
|
CONFIG_PATH = GUARD_DIR / "config.json"
|
|
22
22
|
POLICY_PATH = GUARD_DIR / "policy.json"
|
|
23
23
|
|
|
24
|
+
|
|
25
|
+
def active_policy_path() -> Path:
|
|
26
|
+
return POLICY_PATH
|
|
27
|
+
|
|
24
28
|
# ── Hook templates — loaded from real .py files (no string embedding) ─────────
|
|
25
29
|
|
|
26
30
|
# ── Thin launcher content ─────────────────────────────────────────────────────
|
|
@@ -102,11 +106,8 @@ def _bash_command_words(cmd: str) -> list[str]:
|
|
|
102
106
|
|
|
103
107
|
def _check_policy(tool_name, tool_input, tokens_before=0):
|
|
104
108
|
"""Return (matched_rule, action, rule_id, message) or (None, 'allow', None, None)."""
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
try:
|
|
108
|
-
policy = _json.loads(POLICY_PATH.read_text())
|
|
109
|
-
except Exception:
|
|
109
|
+
policy = _load_policy()
|
|
110
|
+
if not policy.get("rules"):
|
|
110
111
|
return None, "allow", None, None
|
|
111
112
|
|
|
112
113
|
rules = policy.get("rules", [])
|
|
@@ -305,13 +306,16 @@ def _ensure_persona(workspace_id: str, api_key: str, base_url: str) -> str:
|
|
|
305
306
|
return chosen
|
|
306
307
|
|
|
307
308
|
|
|
308
|
-
def _load_guard_config() -> dict:
|
|
309
|
+
def _load_guard_config(workspace_id: str | None = None) -> dict:
|
|
309
310
|
if CONFIG_PATH.exists():
|
|
310
|
-
|
|
311
|
+
try:
|
|
312
|
+
return json.loads(CONFIG_PATH.read_text())
|
|
313
|
+
except Exception:
|
|
314
|
+
pass
|
|
311
315
|
return {}
|
|
312
316
|
|
|
313
317
|
|
|
314
|
-
def _save_guard_config(data: dict):
|
|
318
|
+
def _save_guard_config(data: dict, workspace_id: str | None = None):
|
|
315
319
|
GUARD_DIR.mkdir(parents=True, exist_ok=True)
|
|
316
320
|
CONFIG_PATH.write_text(json.dumps(data, indent=2))
|
|
317
321
|
CONFIG_PATH.chmod(0o600)
|
|
@@ -572,12 +576,12 @@ def _req(method: str, url: str, body=None, token: str = None, api_key: str = Non
|
|
|
572
576
|
|
|
573
577
|
|
|
574
578
|
|
|
575
|
-
def _save_policy(policy: dict):
|
|
579
|
+
def _save_policy(policy: dict, workspace_id: str | None = None):
|
|
576
580
|
GUARD_DIR.mkdir(parents=True, exist_ok=True)
|
|
577
581
|
POLICY_PATH.write_text(json.dumps(policy, indent=2))
|
|
578
582
|
|
|
579
583
|
|
|
580
|
-
def _load_policy() -> dict:
|
|
584
|
+
def _load_policy(workspace_id: str | None = None) -> dict:
|
|
581
585
|
try:
|
|
582
586
|
return json.loads(POLICY_PATH.read_text())
|
|
583
587
|
except Exception:
|
|
@@ -774,6 +778,26 @@ def cmd_guard_install(args):
|
|
|
774
778
|
if signing_key_hex:
|
|
775
779
|
_write_signing_key(signing_key_hex)
|
|
776
780
|
|
|
781
|
+
# Start watch daemon — auto-discovers agents every 15 min, no user action needed.
|
|
782
|
+
try:
|
|
783
|
+
import subprocess as _sp, sys as _sys
|
|
784
|
+
running, _ = _is_watch_running()
|
|
785
|
+
if not running:
|
|
786
|
+
log_file = open(_WATCH_LOG, "a")
|
|
787
|
+
proc = _sp.Popen(
|
|
788
|
+
[_sys.executable, "-c",
|
|
789
|
+
"from conduct_cli.guard import _watch_loop; _watch_loop()"],
|
|
790
|
+
start_new_session=True,
|
|
791
|
+
stdout=log_file,
|
|
792
|
+
stderr=log_file,
|
|
793
|
+
)
|
|
794
|
+
_WATCH_PID.write_text(str(proc.pid))
|
|
795
|
+
print(f" {GREEN}Guard watch:{RESET} background discovery daemon started (pid {proc.pid})")
|
|
796
|
+
else:
|
|
797
|
+
print(f" {GREEN}Guard watch:{RESET} already running")
|
|
798
|
+
except Exception:
|
|
799
|
+
pass # non-fatal
|
|
800
|
+
|
|
777
801
|
|
|
778
802
|
def _write_signing_key(hex_key: str) -> None:
|
|
779
803
|
"""Write a hex-encoded signing key to ~/.conductguard/signing.key.
|
|
@@ -1634,13 +1658,7 @@ def cmd_guard_status(args):
|
|
|
1634
1658
|
pass
|
|
1635
1659
|
|
|
1636
1660
|
# Load local policy for rule count
|
|
1637
|
-
rule_count =
|
|
1638
|
-
if POLICY_PATH.exists():
|
|
1639
|
-
try:
|
|
1640
|
-
policy = json.loads(POLICY_PATH.read_text())
|
|
1641
|
-
rule_count = len(policy.get("rules", []))
|
|
1642
|
-
except Exception:
|
|
1643
|
-
pass
|
|
1661
|
+
rule_count = len(_load_policy().get("rules", []))
|
|
1644
1662
|
|
|
1645
1663
|
# Fetch today's spend
|
|
1646
1664
|
spend = {}
|
|
@@ -1884,6 +1902,11 @@ def register_guard_parser(sub):
|
|
|
1884
1902
|
discover_p.add_argument("--config-only", action="store_true", help="Skip process scan, config files only")
|
|
1885
1903
|
discover_p.add_argument("--report", default=None, metavar="FILE", help="Write full JSON report to file")
|
|
1886
1904
|
|
|
1905
|
+
# conduct guard watch
|
|
1906
|
+
watch_p = guard_sub.add_parser("watch", help="Start background daemon — scans every 15 min, auto-pushes to Guard")
|
|
1907
|
+
watch_p.add_argument("--stop", action="store_true", help="Stop the running watch daemon")
|
|
1908
|
+
watch_p.add_argument("--status", action="store_true", help="Show whether the watch daemon is running")
|
|
1909
|
+
|
|
1887
1910
|
# conduct guard lint [--file PATH]
|
|
1888
1911
|
lint_p = guard_sub.add_parser("lint", help="Validate local policy — show errors and warnings")
|
|
1889
1912
|
lint_p.add_argument("--file", default=None, metavar="FILE",
|
|
@@ -2011,6 +2034,112 @@ def _scan_processes() -> list[dict]:
|
|
|
2011
2034
|
return found
|
|
2012
2035
|
|
|
2013
2036
|
|
|
2037
|
+
_WATCH_PID = Path.home() / ".conductguard" / "watch.pid"
|
|
2038
|
+
_WATCH_LOG = Path.home() / ".conductguard" / "watch.log"
|
|
2039
|
+
_WATCH_INTERVAL = 15 * 60 # seconds
|
|
2040
|
+
|
|
2041
|
+
|
|
2042
|
+
def _is_watch_running():
|
|
2043
|
+
"""Returns (is_running: bool, pid: int | None)."""
|
|
2044
|
+
if not _WATCH_PID.exists():
|
|
2045
|
+
return False, None
|
|
2046
|
+
try:
|
|
2047
|
+
pid = int(_WATCH_PID.read_text().strip())
|
|
2048
|
+
os.kill(pid, 0)
|
|
2049
|
+
return True, pid
|
|
2050
|
+
except (ValueError, ProcessLookupError):
|
|
2051
|
+
_WATCH_PID.unlink(missing_ok=True)
|
|
2052
|
+
return False, None
|
|
2053
|
+
|
|
2054
|
+
|
|
2055
|
+
def _watch_loop():
|
|
2056
|
+
"""Background loop — runs discover+push every 15 min. Invoked as subprocess."""
|
|
2057
|
+
import time, json as _json
|
|
2058
|
+
# resolve config at each iteration so workspace switches are picked up
|
|
2059
|
+
def _cfg():
|
|
2060
|
+
return _load_guard_config()
|
|
2061
|
+
|
|
2062
|
+
while True:
|
|
2063
|
+
try:
|
|
2064
|
+
c = _cfg()
|
|
2065
|
+
api_url = _api_url(c)
|
|
2066
|
+
token = c.get("member_token", "")
|
|
2067
|
+
api_key = c.get("api_key", "")
|
|
2068
|
+
agents = []
|
|
2069
|
+
for name, config_path, mcp_key in _discover_config_agents():
|
|
2070
|
+
agents.append({
|
|
2071
|
+
"name": name, "framework": name, "source": "config",
|
|
2072
|
+
"location": str(config_path),
|
|
2073
|
+
"evidence": {"config_path": str(config_path), "under_guard": mcp_key},
|
|
2074
|
+
"risk_score": 20 if mcp_key else 70,
|
|
2075
|
+
"under_guard": mcp_key,
|
|
2076
|
+
})
|
|
2077
|
+
try:
|
|
2078
|
+
process_agents = _scan_processes()
|
|
2079
|
+
registered = {a["framework"] for a in agents if a["under_guard"]}
|
|
2080
|
+
for a in process_agents:
|
|
2081
|
+
a["under_guard"] = a["framework"] in registered
|
|
2082
|
+
agents += process_agents
|
|
2083
|
+
except Exception:
|
|
2084
|
+
pass
|
|
2085
|
+
_req("POST", f"{api_url}/guard/discover/scan",
|
|
2086
|
+
body={"triggered_by": "watch", "agents": agents},
|
|
2087
|
+
token=token, api_key=api_key)
|
|
2088
|
+
except Exception:
|
|
2089
|
+
pass
|
|
2090
|
+
time.sleep(_WATCH_INTERVAL)
|
|
2091
|
+
|
|
2092
|
+
|
|
2093
|
+
def cmd_guard_watch(args):
|
|
2094
|
+
"""Start/stop/status the background discovery daemon."""
|
|
2095
|
+
import signal as _signal
|
|
2096
|
+
|
|
2097
|
+
if getattr(args, "status", False):
|
|
2098
|
+
running, pid = _is_watch_running()
|
|
2099
|
+
if running:
|
|
2100
|
+
print(f" Guard watch daemon running (pid {pid})")
|
|
2101
|
+
log_tail = ""
|
|
2102
|
+
if _WATCH_LOG.exists():
|
|
2103
|
+
lines = _WATCH_LOG.read_text().splitlines()
|
|
2104
|
+
log_tail = "\n".join(lines[-5:])
|
|
2105
|
+
if log_tail:
|
|
2106
|
+
print(f" Last log:\n{log_tail}")
|
|
2107
|
+
else:
|
|
2108
|
+
print(" Guard watch daemon not running. Start with: conduct guard watch")
|
|
2109
|
+
return
|
|
2110
|
+
|
|
2111
|
+
if getattr(args, "stop", False):
|
|
2112
|
+
running, pid = _is_watch_running()
|
|
2113
|
+
if not running:
|
|
2114
|
+
print(" Guard watch daemon is not running.")
|
|
2115
|
+
return
|
|
2116
|
+
os.kill(pid, _signal.SIGTERM)
|
|
2117
|
+
_WATCH_PID.unlink(missing_ok=True)
|
|
2118
|
+
print(f" Guard watch daemon stopped (pid {pid}).")
|
|
2119
|
+
return
|
|
2120
|
+
|
|
2121
|
+
# Start
|
|
2122
|
+
running, pid = _is_watch_running()
|
|
2123
|
+
if running:
|
|
2124
|
+
print(f" Guard watch daemon already running (pid {pid}). Stop with: conduct guard watch --stop")
|
|
2125
|
+
return
|
|
2126
|
+
|
|
2127
|
+
_WATCH_PID.parent.mkdir(parents=True, exist_ok=True)
|
|
2128
|
+
log_file = open(_WATCH_LOG, "a")
|
|
2129
|
+
import subprocess, sys as _sys
|
|
2130
|
+
proc = subprocess.Popen(
|
|
2131
|
+
[_sys.executable, "-c",
|
|
2132
|
+
"from conduct_cli.guard import _watch_loop; _watch_loop()"],
|
|
2133
|
+
start_new_session=True,
|
|
2134
|
+
stdout=log_file,
|
|
2135
|
+
stderr=log_file,
|
|
2136
|
+
)
|
|
2137
|
+
_WATCH_PID.write_text(str(proc.pid))
|
|
2138
|
+
print(f" Guard watch daemon started (pid {proc.pid}).")
|
|
2139
|
+
print(f" Scans every 15 min — results visible at conductai.ai/guard/discovery")
|
|
2140
|
+
print(f" Stop with: conduct guard watch --stop")
|
|
2141
|
+
|
|
2142
|
+
|
|
2014
2143
|
def cmd_guard_lint(args):
|
|
2015
2144
|
"""Validate the local policy file and report errors / warnings."""
|
|
2016
2145
|
import json as _json, re as _re
|
|
@@ -2329,6 +2458,8 @@ def dispatch_guard(args, guard_p):
|
|
|
2329
2458
|
cmd_guard_install(args)
|
|
2330
2459
|
elif guard_command == "discover":
|
|
2331
2460
|
cmd_guard_discover(args)
|
|
2461
|
+
elif guard_command == "watch":
|
|
2462
|
+
cmd_guard_watch(args)
|
|
2332
2463
|
elif guard_command == "lint":
|
|
2333
2464
|
cmd_guard_lint(args)
|
|
2334
2465
|
elif guard_command == "booster-status":
|
|
@@ -43,7 +43,7 @@ class HookResult:
|
|
|
43
43
|
# ── Config loading ────────────────────────────────────────────────────────────
|
|
44
44
|
|
|
45
45
|
def load_config() -> dict:
|
|
46
|
-
"""
|
|
46
|
+
"""Load Guard config from ~/.conductguard/config.json."""
|
|
47
47
|
try:
|
|
48
48
|
if CONFIG_PATH.exists():
|
|
49
49
|
return json.loads(CONFIG_PATH.read_text())
|
|
@@ -52,6 +52,11 @@ def load_config() -> dict:
|
|
|
52
52
|
return {}
|
|
53
53
|
|
|
54
54
|
|
|
55
|
+
def active_policy_path() -> Path:
|
|
56
|
+
"""Return policy path: ~/.conductguard/policy.json."""
|
|
57
|
+
return POLICY_PATH
|
|
58
|
+
|
|
59
|
+
|
|
55
60
|
# ── Repo / tool detection ─────────────────────────────────────────────────────
|
|
56
61
|
|
|
57
62
|
def detect_repo() -> Optional[str]:
|
|
@@ -18,6 +18,7 @@ from conduct_cli.hooks.base import (
|
|
|
18
18
|
JOURNAL_DIR,
|
|
19
19
|
JOURNAL_PID_PATH,
|
|
20
20
|
POLICY_PATH,
|
|
21
|
+
active_policy_path,
|
|
21
22
|
SIGNING_KEY_PATH,
|
|
22
23
|
VERSION_CACHE_PATH,
|
|
23
24
|
VERSION_CACHE_TTL,
|
|
@@ -152,18 +153,20 @@ def _post_signature_invalid_event(expected_sig, computed_sig, policy_version, ho
|
|
|
152
153
|
def _maybe_sync_policy() -> None:
|
|
153
154
|
"""Sync policy from daemon (instant) or remote API (once per minute). Never raises."""
|
|
154
155
|
try:
|
|
155
|
-
cfg =
|
|
156
|
+
cfg = load_config()
|
|
156
157
|
workspace_id = cfg.get("workspace_id")
|
|
157
158
|
api_key = cfg.get("api_key", "")
|
|
158
159
|
api_url = cfg.get("api_url", "https://api.conductai.ai").rstrip("/")
|
|
159
160
|
if not workspace_id:
|
|
160
161
|
return
|
|
161
162
|
|
|
163
|
+
pol_path = active_policy_path()
|
|
164
|
+
|
|
162
165
|
if _daemon_alive():
|
|
163
166
|
url = f"{_DAEMON_URL}/policy?workspace_id={workspace_id}"
|
|
164
167
|
with urllib.request.urlopen(url, timeout=1) as resp:
|
|
165
168
|
remote = json.loads(resp.read())
|
|
166
|
-
|
|
169
|
+
pol_path.write_text(json.dumps(remote, indent=2))
|
|
167
170
|
return
|
|
168
171
|
|
|
169
172
|
if VERSION_CACHE_PATH.exists():
|
|
@@ -188,8 +191,8 @@ def _maybe_sync_policy() -> None:
|
|
|
188
191
|
remote_version = remote.get("version", "")
|
|
189
192
|
local_version = ""
|
|
190
193
|
local_policy: dict = {}
|
|
191
|
-
if
|
|
192
|
-
local_policy = json.loads(
|
|
194
|
+
if pol_path.exists():
|
|
195
|
+
local_policy = json.loads(pol_path.read_text())
|
|
193
196
|
local_version = local_policy.get("version", "")
|
|
194
197
|
|
|
195
198
|
old_mode = local_policy.get("fail_mode", "fail_open")
|
|
@@ -207,7 +210,7 @@ def _maybe_sync_policy() -> None:
|
|
|
207
210
|
return
|
|
208
211
|
|
|
209
212
|
if remote_version != local_version:
|
|
210
|
-
|
|
213
|
+
pol_path.write_text(json.dumps(remote, indent=2))
|
|
211
214
|
VERSION_CACHE_PATH.write_text(json.dumps({"ts": time.time(), "version": remote_version}))
|
|
212
215
|
except Exception:
|
|
213
216
|
pass
|
|
@@ -292,10 +295,11 @@ def _bash_operator_signature(command: str) -> str:
|
|
|
292
295
|
|
|
293
296
|
def check_policy(tool_name: str, tool_input: dict, tokens_before: int = 0):
|
|
294
297
|
"""Return (matched_rule, action, rule_id, message) or (None, 'allow', None, None)."""
|
|
295
|
-
|
|
298
|
+
pol_path = active_policy_path()
|
|
299
|
+
if not pol_path.exists():
|
|
296
300
|
return None, "allow", None, None
|
|
297
301
|
try:
|
|
298
|
-
policy = json.loads(
|
|
302
|
+
policy = json.loads(pol_path.read_text())
|
|
299
303
|
except Exception:
|
|
300
304
|
return None, "allow", None, None
|
|
301
305
|
|
|
@@ -409,7 +413,7 @@ def main() -> None:
|
|
|
409
413
|
_this_file = Path(__file__).resolve()
|
|
410
414
|
|
|
411
415
|
# Fail-closed gate
|
|
412
|
-
if _get_fail_mode() == "fail_closed" and not
|
|
416
|
+
if _get_fail_mode() == "fail_closed" and not active_policy_path().exists():
|
|
413
417
|
tool_name = (data.get("tool_name") or "").lower()
|
|
414
418
|
tool_input = data.get("tool_input") or {}
|
|
415
419
|
session_id = data.get("session_id")
|
|
@@ -2736,19 +2736,17 @@ def cmd_test_guard(args):
|
|
|
2736
2736
|
"""Test each guard policy rule with a matching synthetic tool call."""
|
|
2737
2737
|
import json as _json
|
|
2738
2738
|
import re as _re
|
|
2739
|
-
from conduct_cli.guard import
|
|
2739
|
+
from conduct_cli.guard import _load_guard_config, _load_policy, active_policy_path
|
|
2740
2740
|
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
except Exception:
|
|
2744
|
-
cfg = {}
|
|
2741
|
+
cfg = _load_guard_config()
|
|
2742
|
+
pol_path = active_policy_path() # workspace-scoped
|
|
2745
2743
|
|
|
2746
|
-
if not
|
|
2744
|
+
if not pol_path.exists():
|
|
2747
2745
|
print(f"{RED}No policy file found. Run: conduct guard sync{RESET}")
|
|
2748
2746
|
sys.exit(1)
|
|
2749
2747
|
|
|
2750
2748
|
try:
|
|
2751
|
-
policy =
|
|
2749
|
+
policy = _load_policy()
|
|
2752
2750
|
except Exception as e:
|
|
2753
2751
|
print(f"{RED}Could not load policy: {e}{RESET}")
|
|
2754
2752
|
sys.exit(1)
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|