conduct-cli 0.5.3__tar.gz → 0.5.4__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.5.3 → conduct_cli-0.5.4}/PKG-INFO +1 -1
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/pyproject.toml +1 -1
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/src/conduct_cli/guard.py +73 -8
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/src/conduct_cli/main.py +20 -9
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/src/conduct_cli.egg-info/PKG-INFO +1 -1
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/README.md +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/setup.cfg +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/setup.py +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/src/conduct_cli/__init__.py +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/src/conduct_cli/api.py +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/src/conduct_cli/guardmcp.py +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/src/conduct_cli/hook_precompact_template.py +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/src/conduct_cli/hook_session_start_template.py +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/src/conduct_cli/hook_stop_template.py +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/src/conduct_cli/hook_template.py +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/src/conduct_cli/mcp_server.py +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/src/conduct_cli/memory.py +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/src/conduct_cli/paxel.py +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/src/conduct_cli.egg-info/SOURCES.txt +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/src/conduct_cli.egg-info/entry_points.txt +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/src/conduct_cli.egg-info/requires.txt +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/src/conduct_cli.egg-info/top_level.txt +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/tests/test_guard_policy.py +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/tests/test_guard_savings.py +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/tests/test_hook_syntax.py +0 -0
- {conduct_cli-0.5.3 → conduct_cli-0.5.4}/tests/test_switch.py +0 -0
|
@@ -159,6 +159,61 @@ def _install_session_hooks() -> None:
|
|
|
159
159
|
|
|
160
160
|
# ── Guard config helpers ──────────────────────────────────────────────────────
|
|
161
161
|
|
|
162
|
+
_PERSONA_LABELS = {
|
|
163
|
+
"conservative": "Conservative — production-safe, default deny",
|
|
164
|
+
"standard": "Standard — engineering teams, balanced",
|
|
165
|
+
"developer": "Developer — local dev, audit-first",
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _ensure_persona(workspace_id: str, api_key: str, base_url: str) -> str:
|
|
170
|
+
"""Prompt for persona if none is set yet. Saves choice to guard config and API.
|
|
171
|
+
|
|
172
|
+
Returns the active persona name. Skips prompt silently if already set.
|
|
173
|
+
"""
|
|
174
|
+
cfg = _load_guard_config()
|
|
175
|
+
if cfg.get("persona"):
|
|
176
|
+
return cfg["persona"]
|
|
177
|
+
|
|
178
|
+
print(f"\n{BOLD}Choose a policy persona for your agents:{RESET}")
|
|
179
|
+
choices = list(_PERSONA_LABELS.keys())
|
|
180
|
+
for i, key in enumerate(choices, 1):
|
|
181
|
+
print(f" {i}. {_PERSONA_LABELS[key]}")
|
|
182
|
+
|
|
183
|
+
while True:
|
|
184
|
+
try:
|
|
185
|
+
raw = input(f"\nEnter 1-{len(choices)} [default: 2 — Standard]: ").strip()
|
|
186
|
+
if raw == "":
|
|
187
|
+
raw = "2"
|
|
188
|
+
idx = int(raw) - 1
|
|
189
|
+
if 0 <= idx < len(choices):
|
|
190
|
+
chosen = choices[idx]
|
|
191
|
+
break
|
|
192
|
+
print(f" Enter a number between 1 and {len(choices)}")
|
|
193
|
+
except (ValueError, EOFError):
|
|
194
|
+
chosen = "standard"
|
|
195
|
+
break
|
|
196
|
+
|
|
197
|
+
# Push to API
|
|
198
|
+
try:
|
|
199
|
+
_req(
|
|
200
|
+
"PATCH",
|
|
201
|
+
f"{base_url}/guard/config/persona",
|
|
202
|
+
body={"persona": chosen},
|
|
203
|
+
api_key=api_key,
|
|
204
|
+
)
|
|
205
|
+
except Exception:
|
|
206
|
+
pass # non-fatal — local config still records the choice
|
|
207
|
+
|
|
208
|
+
# Persist locally so we skip the prompt on subsequent syncs
|
|
209
|
+
cfg = _load_guard_config()
|
|
210
|
+
cfg["persona"] = chosen
|
|
211
|
+
_save_guard_config(cfg)
|
|
212
|
+
|
|
213
|
+
print(f" {GREEN}Persona set:{RESET} {chosen.capitalize()}")
|
|
214
|
+
return chosen
|
|
215
|
+
|
|
216
|
+
|
|
162
217
|
def _load_guard_config() -> dict:
|
|
163
218
|
if CONFIG_PATH.exists():
|
|
164
219
|
return json.loads(CONFIG_PATH.read_text())
|
|
@@ -484,6 +539,9 @@ def cmd_guard_install(args):
|
|
|
484
539
|
except Exception:
|
|
485
540
|
pass
|
|
486
541
|
|
|
542
|
+
# Persona selection — prompt once, skip if already chosen
|
|
543
|
+
_ensure_persona(workspace_id, api_key, server)
|
|
544
|
+
|
|
487
545
|
# Persist guard config — include api_key so CLI commands can authenticate
|
|
488
546
|
import time as _time
|
|
489
547
|
_save_guard_config({
|
|
@@ -655,15 +713,19 @@ def _report_tools_to_server() -> None:
|
|
|
655
713
|
"hook_registered": False,
|
|
656
714
|
})
|
|
657
715
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
716
|
+
vscode_ext_dir = home / ".vscode" / "extensions"
|
|
717
|
+
copilot_installed = vscode_ext_dir.exists() and any(
|
|
718
|
+
p.name.startswith("github.copilot") for p in vscode_ext_dir.iterdir() if p.is_dir()
|
|
719
|
+
)
|
|
720
|
+
if copilot_installed:
|
|
721
|
+
vscode_candidates = [
|
|
722
|
+
home / "Library" / "Application Support" / "Code" / "User" / "settings.json",
|
|
723
|
+
home / ".config" / "Code" / "User" / "settings.json",
|
|
724
|
+
home / ".vscode" / "settings.json",
|
|
725
|
+
]
|
|
726
|
+
vscode_settings = next((p for p in vscode_candidates if p.exists()), None)
|
|
665
727
|
try:
|
|
666
|
-
d = json.loads(vscode_settings.read_text())
|
|
728
|
+
d = json.loads(vscode_settings.read_text()) if vscode_settings else {}
|
|
667
729
|
mcp_reg = "conduct" in d.get("mcp", {}).get("servers", {})
|
|
668
730
|
except Exception:
|
|
669
731
|
mcp_reg = False
|
|
@@ -718,6 +780,9 @@ def cmd_guard_sync(args):
|
|
|
718
780
|
api_key = cfg.get("api_key", "")
|
|
719
781
|
base_url = _api_url(cfg)
|
|
720
782
|
|
|
783
|
+
# Persona selection — prompt once, skip if already chosen
|
|
784
|
+
_ensure_persona(workspace_id, api_key, base_url)
|
|
785
|
+
|
|
721
786
|
print(f"Syncing policy…")
|
|
722
787
|
|
|
723
788
|
try:
|
|
@@ -302,16 +302,21 @@ def _detect_ai_tools() -> list:
|
|
|
302
302
|
"hook_registered": False, # Windsurf uses MCP only
|
|
303
303
|
})
|
|
304
304
|
|
|
305
|
-
# VS Code (Copilot)
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
305
|
+
# VS Code (Copilot) — only report if Copilot extension is actually installed
|
|
306
|
+
vscode_ext_dir = home / ".vscode" / "extensions"
|
|
307
|
+
copilot_installed = vscode_ext_dir.exists() and any(
|
|
308
|
+
p.name.startswith("github.copilot") for p in vscode_ext_dir.iterdir()
|
|
309
|
+
if p.is_dir()
|
|
310
|
+
)
|
|
311
|
+
if copilot_installed:
|
|
312
|
+
vscode_settings_candidates = [
|
|
313
|
+
home / "Library" / "Application Support" / "Code" / "User" / "settings.json",
|
|
314
|
+
home / ".config" / "Code" / "User" / "settings.json",
|
|
315
|
+
home / ".vscode" / "settings.json",
|
|
316
|
+
]
|
|
317
|
+
vscode_settings = next((p for p in vscode_settings_candidates if p.exists()), None)
|
|
313
318
|
try:
|
|
314
|
-
d = json.loads(vscode_settings.read_text())
|
|
319
|
+
d = json.loads(vscode_settings.read_text()) if vscode_settings else {}
|
|
315
320
|
mcp_reg = "conduct" in d.get("mcp", {}).get("servers", {})
|
|
316
321
|
except Exception:
|
|
317
322
|
mcp_reg = False
|
|
@@ -409,6 +414,12 @@ def cmd_mcp_install(args):
|
|
|
409
414
|
if uncovered:
|
|
410
415
|
print(f"{YELLOW} Not covered: {', '.join(uncovered)} — run: conduct mcp install{RESET}")
|
|
411
416
|
|
|
417
|
+
# Push updated coverage to Guard so the dashboard reflects the new state immediately
|
|
418
|
+
try:
|
|
419
|
+
_report_tool_coverage()
|
|
420
|
+
except Exception:
|
|
421
|
+
pass
|
|
422
|
+
|
|
412
423
|
|
|
413
424
|
def cmd_login(args):
|
|
414
425
|
server = args.server
|
|
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
|