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.
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/PKG-INFO +1 -1
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/pyproject.toml +1 -1
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/guard.py +6 -49
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/guardmcp.py +11 -36
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/hooks/base.py +4 -24
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/mcp_server.py +2 -16
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli.egg-info/PKG-INFO +1 -1
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/README.md +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/setup.cfg +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/setup.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/__init__.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/api.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/hooks/__init__.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/hooks/posttooluse.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/hooks/precompact.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/hooks/pretooluse.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/hooks/session_parser.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/hooks/session_start.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/hooks/stop.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/log_util.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/main.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/memory.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli/paxel.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli.egg-info/SOURCES.txt +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli.egg-info/entry_points.txt +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli.egg-info/requires.txt +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/src/conduct_cli.egg-info/top_level.txt +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/tests/test_bash_operator_signature.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/tests/test_command_word_matcher.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/tests/test_guard_policy.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/tests/test_guard_savings.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/tests/test_hook_syntax.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/tests/test_journal_drain.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/tests/test_log_util.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/tests/test_proxy_env.py +0 -0
- {conduct_cli-0.6.34 → conduct_cli-0.6.35}/tests/test_signed_policy.py +0 -0
- {conduct_cli-0.6.34 → 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" }
|
|
@@ -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"
|
|
22
|
-
POLICY_PATH = GUARD_DIR / "policy.json"
|
|
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
|
-
|
|
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
|
|
28
|
-
POLICY_PATH
|
|
29
|
-
CONFIG_PATH
|
|
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
|
-
|
|
145
|
-
if p.exists():
|
|
125
|
+
if POLICY_PATH.exists():
|
|
146
126
|
try:
|
|
147
|
-
return json.loads(
|
|
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
|
-
|
|
155
|
-
|
|
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
|
-
|
|
163
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
21
|
-
POLICY_PATH = GUARD_DIR / "policy.json"
|
|
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
|
|
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
|
|
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
|
-
|
|
115
|
-
if p.exists():
|
|
101
|
+
if GUARD_POLICY_PATH.exists():
|
|
116
102
|
try:
|
|
117
|
-
return json.loads(
|
|
103
|
+
return json.loads(GUARD_POLICY_PATH.read_text())
|
|
118
104
|
except Exception:
|
|
119
105
|
pass
|
|
120
106
|
return {"rules": []}
|
|
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
|