agentguardp 0.5.0__py3-none-any.whl

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.
agentguard/__init__.py ADDED
@@ -0,0 +1,9 @@
1
+ """
2
+ AgentGuard v0.1 — AI Agent Code Review & Security Compliance Platform
3
+ =====================================================================
4
+
5
+ Powered by XHLS security modules: model_guard + security_hardening + binary_sanitizer.
6
+ """
7
+
8
+ __version__ = "0.5.0"
9
+ __author__ = "XHLS / Xiaohei"
agentguard/cli.py ADDED
@@ -0,0 +1,222 @@
1
+ """
2
+ AgentGuard CLI v0.3.0
3
+ ==========================
4
+ Command-line interface for AgentGuard 鈥?Scanner + Auto-Fixer.
5
+
6
+ Usage:
7
+ agentguard scan ./my-project
8
+ agentguard fix findings.json --mode fix
9
+ agentguard activate AGENTGUARD-xxxx.yyyy
10
+ agentguard status
11
+ agentguard deactivate
12
+ """
13
+
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ # Fix Unicode emoji output on Windows GBK terminals
18
+ if sys.platform == "win32":
19
+ for stream in ("stdout", "stderr"):
20
+ try:
21
+ s = getattr(sys, stream)
22
+ if s is not None and hasattr(s, "reconfigure"):
23
+ s.reconfigure(encoding="utf-8", errors="replace")
24
+ except Exception:
25
+ pass
26
+
27
+ from .scanner.code_scanner import CodeScanner
28
+ from .reporter.reporter import terminal_report, json_report, sarif_report, markdown_report
29
+ from .license_verify import (
30
+ activate as license_activate,
31
+ get_active_tier,
32
+ deactivate as license_deactivate,
33
+ get_machine_hash,
34
+ )
35
+ from fixer.code_fixer import run as fixer_run
36
+ from .pipeline import cmd_pipeline
37
+ from .desktop import serve as desktop_serve
38
+
39
+
40
+ def scan(
41
+ path: str = ".",
42
+ *,
43
+ format: str = "terminal",
44
+ output: str = "",
45
+ verbose: bool = False,
46
+ tier: str = None,
47
+ files: list = None,
48
+ labs: bool = False,
49
+ bandit: bool = False,
50
+ ):
51
+ """Scan a directory or files for security issues."""
52
+ if tier is None:
53
+ tier = get_active_tier()
54
+
55
+ target = Path(path)
56
+ if not target.exists():
57
+ print(f"Error: Path not found: {path}", file=sys.stderr)
58
+ sys.exit(1)
59
+
60
+ scanner = CodeScanner(tier=tier)
61
+ result = scanner.scan_directory(str(target), files=files)
62
+
63
+ if bandit:
64
+ try:
65
+ from .scanner.bandit_adapter import run_bandit
66
+ bandit_findings = run_bandit(str(target))
67
+ result.findings.extend(bandit_findings)
68
+ except Exception as e:
69
+ print(f"[Bandit] Engine not available: {e}", file=sys.stderr)
70
+
71
+ reporters = {
72
+ "terminal": terminal_report,
73
+ "json": json_report,
74
+ "sarif": sarif_report,
75
+ "markdown": markdown_report,
76
+ }
77
+
78
+ reporter = reporters.get(format, terminal_report)
79
+ if format == "terminal":
80
+ report_text = reporter(result, verbose=verbose)
81
+ else:
82
+ report_text = reporter(result)
83
+
84
+ if output:
85
+ Path(output).write_text(report_text, encoding="utf-8")
86
+ print(f"Report saved to: {output}")
87
+ else:
88
+ print(report_text)
89
+
90
+ # [Labs] LLM heuristic discovery
91
+ if labs:
92
+ print("\n─── [Labs] LLM Heuristic Discovery ───")
93
+ print("Running LLM heuristic review (this may take a while)...")
94
+ try:
95
+ from .scanner.llm_heuristic import heuristic_scan_files
96
+ file_map = {f: Path(f).read_text(encoding='utf-8', errors='replace') for f in result.findings[:1]}
97
+ # Collect unique scanned files
98
+ scanned_files = list(set(str(f.file) if hasattr(f, 'file') else str(f) for f in result.findings))
99
+ # To scan all python files, not just files with findings:
100
+ py_files = list(Path(path).rglob('*.py')) if Path(path).is_dir() else ([Path(path)] if Path(path).suffix == '.py' else [])
101
+ file_map = {}
102
+ for pf in py_files:
103
+ try:
104
+ file_map[str(pf)] = pf.read_text(encoding='utf-8', errors='replace')
105
+ except Exception:
106
+ pass
107
+ # Build existing findings map
108
+ existing_by_file = {}
109
+ for f in result.findings:
110
+ fpath = f.file if hasattr(f, 'file') else str(f)
111
+ existing_by_file.setdefault(fpath, []).append({
112
+ 'rule_id': f.rule_id if hasattr(f, 'rule_id') else '?',
113
+ 'line': f.line if hasattr(f, 'line') else 0,
114
+ 'message': f.message if hasattr(f, 'message') else ''
115
+ })
116
+ heuristic_findings = heuristic_scan_files(file_map, existing_by_file)
117
+ if heuristic_findings:
118
+ from .scanner.llm_heuristic import format_heuristic_results
119
+ print(format_heuristic_results(heuristic_findings))
120
+ else:
121
+ print(" No new risks found by LLM heuristic.")
122
+ except Exception as e:
123
+ print(f" Labs heuristic unavailable: {e}")
124
+
125
+ if not result.passed:
126
+ sys.exit(1)
127
+
128
+
129
+ def main():
130
+ """Entry point for console_scripts."""
131
+ import argparse
132
+
133
+ parser = argparse.ArgumentParser(
134
+ prog="agentguard",
135
+ description="AgentGuard 鈥?AI Code Security Scanner + Auto-Fixer",
136
+ )
137
+ subparsers = parser.add_subparsers(dest="command")
138
+
139
+ # ---- scan ----
140
+ scan_parser = subparsers.add_parser("scan", help="Scan code for security issues")
141
+ scan_parser.add_argument("path", default=".", nargs="?", help="Directory or file to scan")
142
+ scan_parser.add_argument("--format", "-f", default="terminal",
143
+ choices=["terminal", "json", "sarif", "markdown"],
144
+ help="Output format (default: terminal)")
145
+ scan_parser.add_argument("--output", "-o", default="", help="Output file path")
146
+ scan_parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
147
+ scan_parser.add_argument("--tier", default=None, choices=["free", "pro"],
148
+ help="License tier (default: auto-detect from activated license)")
149
+ scan_parser.add_argument("--version", action="version", version="AgentGuard v0.5.0")
150
+ scan_parser.add_argument("--bandit", action="store_true", help="Use Bandit engine (100+ rules) in addition to built-in")
151
+ scan_parser.add_argument("--labs", action="store_true", help="Enable [Labs] LLM heuristic discovery (experimental)")
152
+
153
+ # ---- activate ----
154
+ activate_parser = subparsers.add_parser("activate", help="Activate a license key")
155
+ activate_parser.add_argument("key", help="License key (e.g., AGENTGUARD-xxxx.yyyy)")
156
+
157
+ # ---- status ----
158
+ subparsers.add_parser("status", help="Show license status and machine hash")
159
+ subparsers.add_parser("serve", help="Start desktop web GUI")
160
+
161
+ # ---- deactivate ----
162
+ subparsers.add_parser("deactivate", help="Deactivate license on this machine")
163
+
164
+ # ---- fix ----
165
+ fix_parser = subparsers.add_parser("fix", help="Auto-fix findings from a previous scan")
166
+ fix_parser.add_argument("findings", help="Path to findings JSON file from agentguard scan --format json")
167
+ fix_parser.add_argument("--mode", "-m", default="dry-run",
168
+ choices=["safe", "fix", "dry-run"],
169
+ help="Fix mode: safe, fix, dry-run (default)")
170
+ fix_parser.add_argument("--write", "-w", action="store_true", help="Write fixes to disk")
171
+
172
+ # ---- pipeline (scan + filter + fix) ----
173
+ pipe_parser = subparsers.add_parser("pipeline", aliases=["auto-fix"], help="Full pipeline: scan -> filter -> fix")
174
+ pipe_parser.add_argument("path", default=".", nargs="?", help="Directory or file to scan")
175
+ pipe_parser.add_argument("--mode", "-m", default="dry-run",
176
+ choices=["safe", "fix", "dry-run"],
177
+ help="Fix mode (default: dry-run)")
178
+ pipe_parser.add_argument("--ds", action="store_true", help="Enable DeepSeek secondary review")
179
+ pipe_parser.add_argument("--write", "-w", action="store_true", help="Write fixes to disk")
180
+ pipe_parser.add_argument("--bandit", action="store_true", help="Use Bandit engine (100+ rules) instead of built-in")
181
+ pipe_parser.add_argument("--labs", action="store_true", help="Enable [Labs] LLM heuristic discovery (experimental)")
182
+
183
+ args = parser.parse_args()
184
+
185
+ if args.command == "scan":
186
+ scan(
187
+ path=args.path,
188
+ format=args.format,
189
+ output=args.output,
190
+ verbose=args.verbose,
191
+ tier=args.tier,
192
+ labs=getattr(args, 'labs', False),
193
+ bandit=getattr(args, 'bandit', False),
194
+ )
195
+ elif args.command == "activate":
196
+ license_activate(args.key)
197
+ elif args.command == "status":
198
+ tier = get_active_tier()
199
+ print(f"Tier : {tier}")
200
+ print(f"Machine ID: {get_machine_hash()}")
201
+ print(f"Status : {'Activated' if tier != 'free' else 'Free (no license)'}")
202
+ elif args.command == "deactivate":
203
+ license_deactivate()
204
+ print("License deactivated. Back to free tier.")
205
+ elif args.command == "fix":
206
+ result = fixer_run(args.findings, mode=args.mode, write=args.write)
207
+ print(f"\nFixed: {result['fixed_count']} | Manual: {result['manual_count']} | Files: {result['files_changed']} | Mode: {result['mode']}")
208
+ if result.get('diff'):
209
+ print(result['diff'])
210
+ for r in result.get('results', []):
211
+ print(f" [{r['rule_id']}] L{r['line']}: {r.get('reason', r.get('fixed', ''))}")
212
+ elif args.command in ("pipeline", "auto-fix"):
213
+ cmd_pipeline(args)
214
+ elif args.command == "serve":
215
+ desktop_serve()
216
+ else:
217
+ from .gui import main as gui_main; gui_main()
218
+
219
+
220
+ if __name__ == "__main__":
221
+ main()
222
+