conduct-cli 0.4.63__tar.gz → 0.4.64__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.
Files changed (24) hide show
  1. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/PKG-INFO +1 -1
  2. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/pyproject.toml +1 -1
  3. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/src/conduct_cli/main.py +159 -3
  4. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/src/conduct_cli.egg-info/PKG-INFO +1 -1
  5. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/README.md +0 -0
  6. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/setup.cfg +0 -0
  7. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/setup.py +0 -0
  8. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/src/conduct_cli/__init__.py +0 -0
  9. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/src/conduct_cli/api.py +0 -0
  10. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/src/conduct_cli/guard.py +0 -0
  11. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/src/conduct_cli/guardmcp.py +0 -0
  12. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/src/conduct_cli/hook_precompact_template.py +0 -0
  13. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/src/conduct_cli/hook_session_start_template.py +0 -0
  14. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/src/conduct_cli/hook_template.py +0 -0
  15. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/src/conduct_cli/mcp_server.py +0 -0
  16. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/src/conduct_cli.egg-info/SOURCES.txt +0 -0
  17. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
  18. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/src/conduct_cli.egg-info/entry_points.txt +0 -0
  19. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/src/conduct_cli.egg-info/requires.txt +0 -0
  20. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/src/conduct_cli.egg-info/top_level.txt +0 -0
  21. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/tests/test_guard_policy.py +0 -0
  22. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/tests/test_guard_savings.py +0 -0
  23. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/tests/test_hook_syntax.py +0 -0
  24. {conduct_cli-0.4.63 → conduct_cli-0.4.64}/tests/test_switch.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: conduct-cli
3
- Version: 0.4.63
3
+ Version: 0.4.64
4
4
  Summary: CLI for Conduct AI — install agents, manage projects, run tests
5
5
  Author-email: Conduct AI <hello@conductai.ai>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "conduct-cli"
7
- version = "0.4.63"
7
+ version = "0.4.64"
8
8
  description = "CLI for Conduct AI — install agents, manage projects, run tests"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -2319,6 +2319,159 @@ def cmd_test_security(args):
2319
2319
  print(f"\n {CYAN}→ View findings: {api_url.replace('api.', 'app.')}/secure/activity{RESET}\n")
2320
2320
 
2321
2321
 
2322
+ def cmd_test_security_verify(args):
2323
+ """Post test findings and verify the full triage pipeline end-to-end."""
2324
+ from conduct_cli.guard import CONFIG_PATH
2325
+ import json as _json
2326
+ import time as _time
2327
+
2328
+ try:
2329
+ cfg = _json.loads(CONFIG_PATH.read_text()) if CONFIG_PATH.exists() else {}
2330
+ except Exception:
2331
+ cfg = {}
2332
+
2333
+ workspace_id = cfg.get("workspace_id")
2334
+ api_key = cfg.get("api_key", "")
2335
+ user_email = cfg.get("user_email", "")
2336
+ api_url = cfg.get("api_url", "https://api.conductai.ai").rstrip("/")
2337
+
2338
+ if not workspace_id:
2339
+ print(f"{RED}Not configured. Run: conduct guard setup{RESET}")
2340
+ sys.exit(1)
2341
+
2342
+ import urllib.request
2343
+
2344
+ TIMEOUT = 120 # seconds to wait for all findings to leave "open"
2345
+ POLL_SECS = 5
2346
+
2347
+ # ── Step 1: post test findings ────────────────────────────────────────
2348
+ print(f"\n{BOLD}▶ conduct test-security-verify{RESET}")
2349
+ print(f" {GRAY}Step 1/3 — posting {len(_SECURITY_TEST_CASES)} test findings…{RESET}\n")
2350
+
2351
+ # Clean previous run
2352
+ try:
2353
+ req = urllib.request.Request(
2354
+ f"{api_url}/security-findings?workspace_id={workspace_id}&source_run_id=conduct-test-security",
2355
+ headers={"X-Api-Key": api_key},
2356
+ method="DELETE",
2357
+ )
2358
+ with urllib.request.urlopen(req, timeout=8) as resp:
2359
+ r = _json.loads(resp.read())
2360
+ n = r.get("deleted", 0)
2361
+ if n:
2362
+ print(f" {GRAY}↺ Cleaned {n} previous test finding{'s' if n != 1 else ''}{RESET}\n")
2363
+ except Exception:
2364
+ pass
2365
+
2366
+ finding_ids: list[str] = []
2367
+ for name, vtype, severity, description, test_file, test_line in _SECURITY_TEST_CASES:
2368
+ body: dict = {
2369
+ "tool": "claude-code",
2370
+ "severity": severity,
2371
+ "type": vtype,
2372
+ "description": f"[TEST] {description}",
2373
+ "reporter_email": user_email,
2374
+ "source_run_id": "conduct-test-security",
2375
+ }
2376
+ if test_file:
2377
+ body["file"] = test_file
2378
+ if test_line is not None:
2379
+ body["line"] = test_line
2380
+ payload = _json.dumps(body).encode()
2381
+ try:
2382
+ req = urllib.request.Request(
2383
+ f"{api_url}/security-findings?workspace_id={workspace_id}",
2384
+ data=payload,
2385
+ headers={"Content-Type": "application/json", "X-Api-Key": api_key},
2386
+ method="POST",
2387
+ )
2388
+ with urllib.request.urlopen(req, timeout=8) as resp:
2389
+ r = _json.loads(resp.read())
2390
+ fid = r.get("id", "")
2391
+ finding_ids.append(fid)
2392
+ sev_color = RED if severity == "critical" else (YELLOW if severity == "high" else CYAN)
2393
+ print(f" {GREEN}✓{RESET} {name:<22} {sev_color}{severity:<8}{RESET} {GRAY}{fid[:8]}{RESET}")
2394
+ except Exception as e:
2395
+ print(f" {RED}✕{RESET} {name:<22} {RED}FAILED — {e}{RESET}")
2396
+
2397
+ if not finding_ids:
2398
+ print(f"\n{RED}✗ No findings posted — aborting.{RESET}\n")
2399
+ sys.exit(1)
2400
+
2401
+ # ── Step 2: poll until all findings move off "open" ───────────────────
2402
+ print(f"\n {GRAY}Step 2/3 — waiting for triage pipeline (timeout {TIMEOUT}s)…{RESET}\n")
2403
+ deadline = _time.time() + TIMEOUT
2404
+ final_statuses: dict[str, str] = {}
2405
+
2406
+ while _time.time() < deadline:
2407
+ try:
2408
+ req = urllib.request.Request(
2409
+ f"{api_url}/security-findings?workspace_id={workspace_id}&source_run_id=conduct-test-security&limit=100",
2410
+ headers={"X-Api-Key": api_key},
2411
+ )
2412
+ with urllib.request.urlopen(req, timeout=10) as resp:
2413
+ findings = _json.loads(resp.read())
2414
+ except Exception as e:
2415
+ print(f" {YELLOW}⚠ poll error: {e}{RESET}")
2416
+ _time.sleep(POLL_SECS)
2417
+ continue
2418
+
2419
+ for f in findings:
2420
+ if f["id"] in finding_ids:
2421
+ final_statuses[f["id"]] = f["status"]
2422
+
2423
+ still_open = [fid for fid in finding_ids if final_statuses.get(fid) == "open"]
2424
+ done_count = len(finding_ids) - len(still_open)
2425
+ elapsed = int(_time.time() - (deadline - TIMEOUT))
2426
+ print(f" {GRAY}[{elapsed:>3}s] {done_count}/{len(finding_ids)} processed…{RESET}", end="\r")
2427
+
2428
+ if not still_open:
2429
+ print() # newline after \r
2430
+ break
2431
+ _time.sleep(POLL_SECS)
2432
+ else:
2433
+ print(f"\n\n {RED}✗ Timeout — {len(still_open)} finding(s) still 'open' after {TIMEOUT}s{RESET}\n")
2434
+
2435
+ # ── Step 3: report per-finding results ────────────────────────────────
2436
+ print(f"\n {GRAY}Step 3/3 — results{RESET}\n")
2437
+
2438
+ all_pass = True
2439
+ name_by_id = {}
2440
+ for i, (name, *_) in enumerate(_SECURITY_TEST_CASES):
2441
+ if i < len(finding_ids):
2442
+ name_by_id[finding_ids[i]] = name
2443
+
2444
+ for fid in finding_ids:
2445
+ status = final_statuses.get(fid, "open")
2446
+ name = name_by_id.get(fid, fid[:8])
2447
+ if status == "open":
2448
+ icon = f"{RED}✗{RESET}"
2449
+ note = f"{RED}still open — triage did not run{RESET}"
2450
+ all_pass = False
2451
+ elif status == "dismissed":
2452
+ icon = f"{CYAN}○{RESET}"
2453
+ note = f"{CYAN}dismissed (false positive){RESET}"
2454
+ elif status == "triaging":
2455
+ icon = f"{YELLOW}◑{RESET}"
2456
+ note = f"{YELLOW}triaging (real finding, no autopilot fix){RESET}"
2457
+ elif status == "fixed":
2458
+ icon = f"{GREEN}✓{RESET}"
2459
+ note = f"{GREEN}fixed{RESET}"
2460
+ else:
2461
+ icon = f"{GRAY}?{RESET}"
2462
+ note = f"{GRAY}{status}{RESET}"
2463
+ print(f" {icon} {name:<22} → {note}")
2464
+
2465
+ print()
2466
+ if all_pass:
2467
+ print(f" {GREEN}{BOLD}✓ All findings processed — triage pipeline OK{RESET}\n")
2468
+ else:
2469
+ print(f" {RED}{BOLD}✗ Some findings were not processed — check Security Automation project runs{RESET}")
2470
+ app_url = api_url.replace("api.", "app.")
2471
+ print(f" {CYAN}→ {app_url}/projects{RESET}\n")
2472
+ sys.exit(1)
2473
+
2474
+
2322
2475
  def cmd_test_guard(args):
2323
2476
  """Test each guard policy rule with a matching synthetic tool call."""
2324
2477
  import json as _json
@@ -2556,9 +2709,10 @@ def main():
2556
2709
  # conduct sync
2557
2710
  sub.add_parser("sync", help="Sync Guard policies (and Security Loop policies if installed)")
2558
2711
 
2559
- # conduct test-guard / test-security
2560
- sub.add_parser("test-guard", help="Fire a synthetic event per guard policy rule and show decisions")
2561
- sub.add_parser("test-security", help="Post a synthetic finding per security classifier pattern")
2712
+ # conduct test-guard / test-security / test-security-verify
2713
+ sub.add_parser("test-guard", help="Fire a synthetic event per guard policy rule and show decisions")
2714
+ sub.add_parser("test-security", help="Post a synthetic finding per security classifier pattern")
2715
+ sub.add_parser("test-security-verify", help="Post test findings and verify full triage pipeline end-to-end")
2562
2716
 
2563
2717
  args = parser.parse_args()
2564
2718
 
@@ -2633,6 +2787,8 @@ def main():
2633
2787
  cmd_test_guard(args)
2634
2788
  elif args.command == "test-security":
2635
2789
  cmd_test_security(args)
2790
+ elif args.command == "test-security-verify":
2791
+ cmd_test_security_verify(args)
2636
2792
  else:
2637
2793
  parser.print_help()
2638
2794
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: conduct-cli
3
- Version: 0.4.63
3
+ Version: 0.4.64
4
4
  Summary: CLI for Conduct AI — install agents, manage projects, run tests
5
5
  Author-email: Conduct AI <hello@conductai.ai>
6
6
  License: MIT
File without changes
File without changes
File without changes