conduct-cli 0.4.38__tar.gz → 0.4.40__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.38 → conduct_cli-0.4.40}/PKG-INFO +1 -1
- {conduct_cli-0.4.38 → conduct_cli-0.4.40}/pyproject.toml +1 -1
- {conduct_cli-0.4.38 → conduct_cli-0.4.40}/src/conduct_cli/guard.py +184 -0
- {conduct_cli-0.4.38 → conduct_cli-0.4.40}/src/conduct_cli/main.py +9 -5
- {conduct_cli-0.4.38 → conduct_cli-0.4.40}/src/conduct_cli.egg-info/PKG-INFO +1 -1
- {conduct_cli-0.4.38 → conduct_cli-0.4.40}/README.md +0 -0
- {conduct_cli-0.4.38 → conduct_cli-0.4.40}/setup.cfg +0 -0
- {conduct_cli-0.4.38 → conduct_cli-0.4.40}/setup.py +0 -0
- {conduct_cli-0.4.38 → conduct_cli-0.4.40}/src/conduct_cli/__init__.py +0 -0
- {conduct_cli-0.4.38 → conduct_cli-0.4.40}/src/conduct_cli/api.py +0 -0
- {conduct_cli-0.4.38 → conduct_cli-0.4.40}/src/conduct_cli/guardmcp.py +0 -0
- {conduct_cli-0.4.38 → conduct_cli-0.4.40}/src/conduct_cli/mcp_server.py +0 -0
- {conduct_cli-0.4.38 → conduct_cli-0.4.40}/src/conduct_cli.egg-info/SOURCES.txt +0 -0
- {conduct_cli-0.4.38 → conduct_cli-0.4.40}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
- {conduct_cli-0.4.38 → conduct_cli-0.4.40}/src/conduct_cli.egg-info/entry_points.txt +0 -0
- {conduct_cli-0.4.38 → conduct_cli-0.4.40}/src/conduct_cli.egg-info/requires.txt +0 -0
- {conduct_cli-0.4.38 → conduct_cli-0.4.40}/src/conduct_cli.egg-info/top_level.txt +0 -0
|
@@ -446,6 +446,144 @@ if __name__ == "__main__":
|
|
|
446
446
|
main()
|
|
447
447
|
'''
|
|
448
448
|
|
|
449
|
+
_PRECOMPACT_HOOK_SCRIPT = '''\
|
|
450
|
+
#!/usr/bin/env python3
|
|
451
|
+
"""ConductGuard PreCompact hook — persists session context before compaction."""
|
|
452
|
+
import json
|
|
453
|
+
import os
|
|
454
|
+
import subprocess
|
|
455
|
+
import sys
|
|
456
|
+
from datetime import datetime, timezone
|
|
457
|
+
from pathlib import Path
|
|
458
|
+
|
|
459
|
+
GUARD_DIR = Path.home() / ".conductguard"
|
|
460
|
+
SNAPSHOT_PATH = GUARD_DIR / "session_snapshot.json"
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def _git(cmd):
|
|
464
|
+
try:
|
|
465
|
+
return subprocess.check_output(
|
|
466
|
+
["git"] + cmd, stderr=subprocess.DEVNULL, text=True, timeout=3
|
|
467
|
+
).strip()
|
|
468
|
+
except Exception:
|
|
469
|
+
return ""
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _guard_status():
|
|
473
|
+
try:
|
|
474
|
+
out = subprocess.check_output(
|
|
475
|
+
["conductguard", "status", "--json"],
|
|
476
|
+
stderr=subprocess.DEVNULL, text=True, timeout=3,
|
|
477
|
+
)
|
|
478
|
+
return json.loads(out.strip())
|
|
479
|
+
except Exception:
|
|
480
|
+
return None
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def _memory_headline():
|
|
484
|
+
try:
|
|
485
|
+
root = Path.cwd()
|
|
486
|
+
mem_key = str(root).replace("/", "-").lstrip("-")
|
|
487
|
+
mem_path = Path.home() / ".claude" / "projects" / mem_key / "memory" / "MEMORY.md"
|
|
488
|
+
if mem_path.exists():
|
|
489
|
+
return "\\n".join(mem_path.read_text().splitlines()[:10])
|
|
490
|
+
except Exception:
|
|
491
|
+
pass
|
|
492
|
+
return ""
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def main():
|
|
496
|
+
try:
|
|
497
|
+
sys.stdin.read()
|
|
498
|
+
except Exception:
|
|
499
|
+
pass
|
|
500
|
+
|
|
501
|
+
try:
|
|
502
|
+
GUARD_DIR.mkdir(parents=True, exist_ok=True)
|
|
503
|
+
snapshot = {
|
|
504
|
+
"compacted_at": datetime.now(timezone.utc).isoformat(),
|
|
505
|
+
"tier1": {
|
|
506
|
+
"git_branch": _git(["branch", "--show-current"]),
|
|
507
|
+
"recent_commits": _git(["log", "--oneline", "-3"]),
|
|
508
|
+
"memory_headline": _memory_headline(),
|
|
509
|
+
},
|
|
510
|
+
"tier2": {"guard_status": _guard_status()},
|
|
511
|
+
"tier3": {"cwd": str(Path.cwd()), "python": sys.version.split()[0]},
|
|
512
|
+
}
|
|
513
|
+
tmp = GUARD_DIR / "session_snapshot.tmp"
|
|
514
|
+
tmp.write_text(json.dumps(snapshot, indent=2))
|
|
515
|
+
tmp.rename(SNAPSHOT_PATH)
|
|
516
|
+
except Exception:
|
|
517
|
+
pass
|
|
518
|
+
|
|
519
|
+
sys.exit(0)
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
if __name__ == "__main__":
|
|
523
|
+
main()
|
|
524
|
+
'''
|
|
525
|
+
|
|
526
|
+
_SESSION_START_HOOK_SCRIPT = '''\
|
|
527
|
+
#!/usr/bin/env python3
|
|
528
|
+
"""ConductGuard SessionStart hook — prints context after compaction."""
|
|
529
|
+
import json
|
|
530
|
+
import sys
|
|
531
|
+
from datetime import datetime, timezone
|
|
532
|
+
from pathlib import Path
|
|
533
|
+
|
|
534
|
+
SNAPSHOT_PATH = Path.home() / ".conductguard" / "session_snapshot.json"
|
|
535
|
+
MAX_AGE_HOURS = 2
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def main():
|
|
539
|
+
try:
|
|
540
|
+
sys.stdin.read()
|
|
541
|
+
except Exception:
|
|
542
|
+
pass
|
|
543
|
+
|
|
544
|
+
if not SNAPSHOT_PATH.exists():
|
|
545
|
+
sys.exit(0)
|
|
546
|
+
|
|
547
|
+
try:
|
|
548
|
+
snapshot = json.loads(SNAPSHOT_PATH.read_text())
|
|
549
|
+
compacted_at = datetime.fromisoformat(snapshot.get("compacted_at", ""))
|
|
550
|
+
age_hours = (datetime.now(timezone.utc) - compacted_at).total_seconds() / 3600
|
|
551
|
+
if age_hours > MAX_AGE_HOURS:
|
|
552
|
+
sys.exit(0)
|
|
553
|
+
|
|
554
|
+
t1 = snapshot.get("tier1", {})
|
|
555
|
+
branch = t1.get("git_branch", "")
|
|
556
|
+
commits = t1.get("recent_commits", "")
|
|
557
|
+
headline = t1.get("memory_headline", "")
|
|
558
|
+
t2 = snapshot.get("tier2", {})
|
|
559
|
+
guard = t2.get("guard_status") or {}
|
|
560
|
+
|
|
561
|
+
lines = [f"## Session resumed (snapshot from {compacted_at.strftime(\'%Y-%m-%d %H:%M\')} UTC)"]
|
|
562
|
+
if branch:
|
|
563
|
+
last = commits.splitlines()[0] if commits else ""
|
|
564
|
+
lines.append(f"- Branch: {branch}" + (f" | Last: {last}" if last else ""))
|
|
565
|
+
budget = guard.get("budget_pct")
|
|
566
|
+
if budget is not None:
|
|
567
|
+
lines.append(f"- Guard: {budget}% budget used")
|
|
568
|
+
else:
|
|
569
|
+
lines.append("- Guard: state unavailable")
|
|
570
|
+
if headline:
|
|
571
|
+
lines.append(f"- Memory index:\\n {headline}")
|
|
572
|
+
else:
|
|
573
|
+
lines.append("- Memory index:\\n (none)")
|
|
574
|
+
|
|
575
|
+
print("\\n".join(lines))
|
|
576
|
+
except Exception:
|
|
577
|
+
pass
|
|
578
|
+
|
|
579
|
+
sys.exit(0)
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
if __name__ == "__main__":
|
|
583
|
+
main()
|
|
584
|
+
'''
|
|
585
|
+
|
|
586
|
+
|
|
449
587
|
# ── Python interpreter selection ─────────────────────────────────────────────
|
|
450
588
|
|
|
451
589
|
def _best_python() -> str:
|
|
@@ -478,6 +616,42 @@ def _write_hook(path: Path) -> None:
|
|
|
478
616
|
) from exc
|
|
479
617
|
|
|
480
618
|
|
|
619
|
+
def _install_session_hooks() -> None:
|
|
620
|
+
"""Write PreCompact + SessionStart hook scripts and register them in ~/.claude/settings.json."""
|
|
621
|
+
python = _best_python()
|
|
622
|
+
|
|
623
|
+
precompact_path = GUARD_DIR / "guard-precompact.py"
|
|
624
|
+
session_start_path = GUARD_DIR / "guard-session-start.py"
|
|
625
|
+
|
|
626
|
+
precompact_path.write_text(_PRECOMPACT_HOOK_SCRIPT)
|
|
627
|
+
precompact_path.chmod(0o755)
|
|
628
|
+
session_start_path.write_text(_SESSION_START_HOOK_SCRIPT)
|
|
629
|
+
session_start_path.chmod(0o755)
|
|
630
|
+
|
|
631
|
+
claude_settings = Path.home() / ".claude" / "settings.json"
|
|
632
|
+
settings: dict = {}
|
|
633
|
+
if claude_settings.exists():
|
|
634
|
+
try:
|
|
635
|
+
settings = json.loads(claude_settings.read_text())
|
|
636
|
+
except Exception:
|
|
637
|
+
pass
|
|
638
|
+
|
|
639
|
+
hooks = settings.setdefault("hooks", {})
|
|
640
|
+
|
|
641
|
+
pre_cmd = f"{python} {precompact_path}"
|
|
642
|
+
compact_hooks = hooks.setdefault("PreCompact", [])
|
|
643
|
+
if not any(pre_cmd in str(e) for h in compact_hooks for e in h.get("hooks", [])):
|
|
644
|
+
compact_hooks.append({"hooks": [{"type": "command", "command": pre_cmd}]})
|
|
645
|
+
|
|
646
|
+
start_cmd = f"{python} {session_start_path}"
|
|
647
|
+
start_hooks = hooks.setdefault("SessionStart", [])
|
|
648
|
+
if not any(start_cmd in str(e) for h in start_hooks for e in h.get("hooks", [])):
|
|
649
|
+
start_hooks.append({"hooks": [{"type": "command", "command": start_cmd}]})
|
|
650
|
+
|
|
651
|
+
claude_settings.parent.mkdir(parents=True, exist_ok=True)
|
|
652
|
+
claude_settings.write_text(json.dumps(settings, indent=2) + "\n")
|
|
653
|
+
|
|
654
|
+
|
|
481
655
|
# ── Guard config helpers ──────────────────────────────────────────────────────
|
|
482
656
|
|
|
483
657
|
def _load_guard_config() -> dict:
|
|
@@ -815,6 +989,12 @@ def cmd_guard_install(args):
|
|
|
815
989
|
# Register MCP in all found AI tools — Cursor/Windsurf (advisory)
|
|
816
990
|
_register_mcp(workspace_id, member_token or "", server)
|
|
817
991
|
|
|
992
|
+
# Install session persistence hooks (PreCompact + SessionStart)
|
|
993
|
+
try:
|
|
994
|
+
_install_session_hooks()
|
|
995
|
+
except Exception:
|
|
996
|
+
pass
|
|
997
|
+
|
|
818
998
|
|
|
819
999
|
def cmd_guard_join(args):
|
|
820
1000
|
invite_code = args.invite_code
|
|
@@ -1020,6 +1200,10 @@ def cmd_guard_sync(args):
|
|
|
1020
1200
|
_install_codex_hook(hook_path)
|
|
1021
1201
|
cfg2 = _load_guard_config()
|
|
1022
1202
|
_register_mcp(workspace_id, cfg2.get("member_token", ""), base_url)
|
|
1203
|
+
try:
|
|
1204
|
+
_install_session_hooks()
|
|
1205
|
+
except Exception:
|
|
1206
|
+
pass
|
|
1023
1207
|
print(f" {GREEN}Hook script updated{RESET}")
|
|
1024
1208
|
|
|
1025
1209
|
# Capture savings from RTK and Agent Booster
|
|
@@ -1327,10 +1327,13 @@ def cmd_run(args):
|
|
|
1327
1327
|
print(f" {GRAY}{k}={v}{RESET}")
|
|
1328
1328
|
print()
|
|
1329
1329
|
|
|
1330
|
-
|
|
1330
|
+
body: dict = {
|
|
1331
1331
|
"triggered_by": "cli",
|
|
1332
1332
|
"initial_state": {"__manual": True, "inputs": initial_state},
|
|
1333
|
-
}
|
|
1333
|
+
}
|
|
1334
|
+
if getattr(args, "max_turns", None):
|
|
1335
|
+
body["max_turns"] = args.max_turns
|
|
1336
|
+
run = api.req("POST", f"{server}/workflows/{workflow_id}/runs", json_h, body)
|
|
1334
1337
|
_stream_run(server, workflow_id, run["id"], workspace_id, token, api_key)
|
|
1335
1338
|
|
|
1336
1339
|
|
|
@@ -1435,9 +1438,10 @@ def main():
|
|
|
1435
1438
|
|
|
1436
1439
|
# conduct run (existing)
|
|
1437
1440
|
run_p = sub.add_parser("run", help="Run an installed agent by name")
|
|
1438
|
-
run_p.add_argument("agent",
|
|
1439
|
-
run_p.add_argument("--project",
|
|
1440
|
-
run_p.add_argument("--input",
|
|
1441
|
+
run_p.add_argument("agent", help="Agent name (e.g. 'security_autopilot_fix')")
|
|
1442
|
+
run_p.add_argument("--project", metavar="name", help="Narrow to a specific project")
|
|
1443
|
+
run_p.add_argument("--input", action="append", metavar="key=value", help="Runtime input (repeatable)")
|
|
1444
|
+
run_p.add_argument("--max-turns", dest="max_turns", type=int, metavar="N", help="Max agentic turns (default: auto)")
|
|
1441
1445
|
|
|
1442
1446
|
# conduct guard
|
|
1443
1447
|
guard_p, _guard_sub = _guard.register_guard_parser(sub)
|
|
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
|