agent_governance_toolkit 3.1.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.
- agent_compliance/__init__.py +44 -0
- agent_compliance/cli/__init__.py +2 -0
- agent_compliance/cli/agt.py +448 -0
- agent_compliance/cli/main.py +200 -0
- agent_compliance/governance/__init__.py +29 -0
- agent_compliance/governance/attestation_validator.py +143 -0
- agent_compliance/integrity.py +392 -0
- agent_compliance/lint_policy.py +346 -0
- agent_compliance/promotion.py +410 -0
- agent_compliance/prompt_defense.py +588 -0
- agent_compliance/py.typed +0 -0
- agent_compliance/security/__init__.py +33 -0
- agent_compliance/security/scanner.py +908 -0
- agent_compliance/supply_chain.py +463 -0
- agent_compliance/verify.py +354 -0
- agent_governance_toolkit-3.1.0.dist-info/METADATA +325 -0
- agent_governance_toolkit-3.1.0.dist-info/RECORD +21 -0
- agent_governance_toolkit-3.1.0.dist-info/WHEEL +5 -0
- agent_governance_toolkit-3.1.0.dist-info/entry_points.txt +5 -0
- agent_governance_toolkit-3.1.0.dist-info/licenses/LICENSE +21 -0
- agent_governance_toolkit-3.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Agent Governance - Unified installer and runtime policy enforcement.
|
|
5
|
+
|
|
6
|
+
Install the full stack:
|
|
7
|
+
pip install agent-governance-toolkit[full]
|
|
8
|
+
|
|
9
|
+
Note: The package was previously published as ``ai-agent-compliance``.
|
|
10
|
+
That name is deprecated and will redirect here for 6 months.
|
|
11
|
+
|
|
12
|
+
Components:
|
|
13
|
+
- agent-os-kernel: Governance kernel with policy enforcement
|
|
14
|
+
- agentmesh-platform: Zero-trust agent communication (SSL for AI Agents)
|
|
15
|
+
- agentmesh-runtime: Runtime supervisor with execution rings
|
|
16
|
+
- agent-sre: Site reliability engineering for AI agents
|
|
17
|
+
- agentmesh-marketplace: Plugin lifecycle management
|
|
18
|
+
- agent-lightning: RL training governance
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
__version__ = "3.1.0"
|
|
22
|
+
|
|
23
|
+
# Re-export core components for convenience
|
|
24
|
+
try:
|
|
25
|
+
from agent_os import StatelessKernel, ExecutionContext # noqa: F401
|
|
26
|
+
except ImportError:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
from agentmesh import TrustManager # noqa: F401
|
|
31
|
+
except ImportError:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
from agent_compliance.supply_chain import ( # noqa: F401
|
|
35
|
+
SupplyChainGuard,
|
|
36
|
+
SupplyChainFinding,
|
|
37
|
+
SupplyChainConfig,
|
|
38
|
+
)
|
|
39
|
+
from agent_compliance.prompt_defense import ( # noqa: F401
|
|
40
|
+
PromptDefenseEvaluator,
|
|
41
|
+
PromptDefenseConfig,
|
|
42
|
+
PromptDefenseFinding,
|
|
43
|
+
PromptDefenseReport,
|
|
44
|
+
)
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
AGT ΓÇö Unified CLI for the Agent Governance Toolkit.
|
|
5
|
+
|
|
6
|
+
Single entry point that namespaces all governance commands:
|
|
7
|
+
agt verify OWASP ASI compliance verification
|
|
8
|
+
agt integrity Module integrity checks
|
|
9
|
+
agt lint-policy Policy file linting
|
|
10
|
+
agt doctor Diagnose installation health
|
|
11
|
+
agt version Show installed package versions
|
|
12
|
+
|
|
13
|
+
Plugin subcommands from other AGT packages are discovered
|
|
14
|
+
via the ``agt.commands`` entry-point group.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import sys
|
|
20
|
+
from typing import Any, Dict, Optional
|
|
21
|
+
|
|
22
|
+
import click
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# Optional rich ΓÇö degrade gracefully if not installed
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
from rich.console import Console as _RichConsole
|
|
30
|
+
from rich.table import Table as _RichTable
|
|
31
|
+
from rich import box as _rich_box
|
|
32
|
+
|
|
33
|
+
_console = _RichConsole()
|
|
34
|
+
_console_err = _RichConsole(stderr=True)
|
|
35
|
+
_HAS_RICH = True
|
|
36
|
+
except ImportError: # pragma: no cover
|
|
37
|
+
_HAS_RICH = False
|
|
38
|
+
_console = None # type: ignore[assignment]
|
|
39
|
+
_console_err = None # type: ignore[assignment]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _print(msg: str, *, style: str = "", err: bool = False) -> None:
|
|
43
|
+
"""Print with optional rich styling; falls back to plain print."""
|
|
44
|
+
if _HAS_RICH and style:
|
|
45
|
+
target = _console_err if err else _console
|
|
46
|
+
target.print(msg, style=style) # type: ignore[union-attr]
|
|
47
|
+
else:
|
|
48
|
+
print(msg, file=sys.stderr if err else sys.stdout)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
# Helpers
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _get_package_version(package_name: str) -> Optional[str]:
|
|
57
|
+
"""Return installed version via importlib.metadata, or None."""
|
|
58
|
+
try:
|
|
59
|
+
from importlib.metadata import version
|
|
60
|
+
|
|
61
|
+
return version(package_name)
|
|
62
|
+
except Exception:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _discover_plugins() -> Dict[str, click.Command]:
|
|
67
|
+
"""Discover plugin commands from the ``agt.commands`` entry-point group."""
|
|
68
|
+
plugins: Dict[str, click.Command] = {}
|
|
69
|
+
try:
|
|
70
|
+
if sys.version_info >= (3, 10):
|
|
71
|
+
from importlib.metadata import entry_points
|
|
72
|
+
|
|
73
|
+
eps = entry_points(group="agt.commands")
|
|
74
|
+
else:
|
|
75
|
+
from importlib.metadata import entry_points
|
|
76
|
+
|
|
77
|
+
all_eps = entry_points()
|
|
78
|
+
eps = all_eps.get("agt.commands", [])
|
|
79
|
+
|
|
80
|
+
for ep in eps:
|
|
81
|
+
try:
|
|
82
|
+
obj = ep.load()
|
|
83
|
+
if isinstance(obj, click.Command):
|
|
84
|
+
plugins[ep.name] = obj
|
|
85
|
+
elif callable(obj):
|
|
86
|
+
# Adapter: callable that returns a click command
|
|
87
|
+
result = obj()
|
|
88
|
+
if isinstance(result, click.Command):
|
|
89
|
+
plugins[ep.name] = result
|
|
90
|
+
except Exception:
|
|
91
|
+
# Plugin failed to load ΓÇö silently skip; `doctor` will report it
|
|
92
|
+
pass
|
|
93
|
+
except Exception:
|
|
94
|
+
pass
|
|
95
|
+
return plugins
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
# Global context object
|
|
100
|
+
# ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class AgtContext:
|
|
104
|
+
"""Shared context passed to all subcommands via ``click.Context.obj``."""
|
|
105
|
+
|
|
106
|
+
def __init__(
|
|
107
|
+
self,
|
|
108
|
+
output_json: bool = False,
|
|
109
|
+
verbose: bool = False,
|
|
110
|
+
quiet: bool = False,
|
|
111
|
+
no_color: bool = False,
|
|
112
|
+
):
|
|
113
|
+
self.output_json = output_json
|
|
114
|
+
self.verbose = verbose
|
|
115
|
+
self.quiet = quiet
|
|
116
|
+
self.no_color = no_color
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
# Root group
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class AgtGroup(click.Group):
|
|
125
|
+
"""Custom group that merges built-in and plugin commands."""
|
|
126
|
+
|
|
127
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
128
|
+
super().__init__(*args, **kwargs)
|
|
129
|
+
self._plugins_loaded = False
|
|
130
|
+
|
|
131
|
+
def _ensure_plugins(self) -> None:
|
|
132
|
+
if self._plugins_loaded:
|
|
133
|
+
return
|
|
134
|
+
self._plugins_loaded = True
|
|
135
|
+
for name, cmd in _discover_plugins().items():
|
|
136
|
+
if name not in self.commands:
|
|
137
|
+
self.add_command(cmd, name)
|
|
138
|
+
|
|
139
|
+
def list_commands(self, ctx: click.Context) -> list[str]:
|
|
140
|
+
self._ensure_plugins()
|
|
141
|
+
return sorted(super().list_commands(ctx))
|
|
142
|
+
|
|
143
|
+
def get_command(self, ctx: click.Context, cmd_name: str) -> Optional[click.Command]:
|
|
144
|
+
self._ensure_plugins()
|
|
145
|
+
return super().get_command(ctx, cmd_name)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@click.group(cls=AgtGroup)
|
|
149
|
+
@click.option("--json", "output_json", is_flag=True, default=False, help="Output in JSON format.")
|
|
150
|
+
@click.option("--verbose", "-v", is_flag=True, default=False, help="Increase output verbosity.")
|
|
151
|
+
@click.option("--quiet", "-q", is_flag=True, default=False, help="Suppress non-essential output.")
|
|
152
|
+
@click.option("--no-color", is_flag=True, default=False, help="Disable colored output.")
|
|
153
|
+
@click.version_option(
|
|
154
|
+
version=_get_package_version("agent_governance_toolkit") or "unknown",
|
|
155
|
+
prog_name="agt",
|
|
156
|
+
)
|
|
157
|
+
@click.pass_context
|
|
158
|
+
def cli(ctx: click.Context, output_json: bool, verbose: bool, quiet: bool, no_color: bool) -> None:
|
|
159
|
+
"""
|
|
160
|
+
AGT ΓÇö Agent Governance Toolkit CLI.
|
|
161
|
+
|
|
162
|
+
Unified command-line interface for governing AI agents.
|
|
163
|
+
|
|
164
|
+
\b
|
|
165
|
+
Quick start:
|
|
166
|
+
agt verify Check OWASP ASI compliance
|
|
167
|
+
agt doctor Diagnose installation health
|
|
168
|
+
agt lint-policy ./dir Lint policy files
|
|
169
|
+
agt integrity Verify module integrity
|
|
170
|
+
|
|
171
|
+
\b
|
|
172
|
+
Plugin commands from installed AGT packages (mesh, sre, etc.)
|
|
173
|
+
are auto-discovered and appear below when installed.
|
|
174
|
+
"""
|
|
175
|
+
ctx.ensure_object(dict)
|
|
176
|
+
ctx.obj = AgtContext(
|
|
177
|
+
output_json=output_json,
|
|
178
|
+
verbose=verbose,
|
|
179
|
+
quiet=quiet,
|
|
180
|
+
no_color=no_color,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# ---------------------------------------------------------------------------
|
|
185
|
+
# Built-in commands
|
|
186
|
+
# ---------------------------------------------------------------------------
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@cli.command()
|
|
190
|
+
@click.option("--badge", is_flag=True, default=False, help="Output markdown badge only.")
|
|
191
|
+
@click.pass_obj
|
|
192
|
+
def verify(ctx_obj: AgtContext, badge: bool) -> None:
|
|
193
|
+
"""Run OWASP ASI 2026 governance verification."""
|
|
194
|
+
try:
|
|
195
|
+
from agent_compliance.verify import GovernanceVerifier
|
|
196
|
+
|
|
197
|
+
verifier = GovernanceVerifier()
|
|
198
|
+
attestation = verifier.verify()
|
|
199
|
+
|
|
200
|
+
if ctx_obj.output_json:
|
|
201
|
+
click.echo(attestation.to_json())
|
|
202
|
+
elif badge:
|
|
203
|
+
click.echo(attestation.badge_markdown())
|
|
204
|
+
else:
|
|
205
|
+
click.echo(attestation.summary())
|
|
206
|
+
|
|
207
|
+
if not attestation.passed:
|
|
208
|
+
raise SystemExit(1)
|
|
209
|
+
except SystemExit:
|
|
210
|
+
raise
|
|
211
|
+
except Exception as e:
|
|
212
|
+
_handle_error(e, ctx_obj.output_json)
|
|
213
|
+
raise SystemExit(1)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@cli.command()
|
|
217
|
+
@click.option("--manifest", type=click.Path(), default=None, help="Path to integrity.json manifest.")
|
|
218
|
+
@click.option("--generate", type=click.Path(), default=None, metavar="OUTPUT_PATH", help="Generate manifest at path.")
|
|
219
|
+
@click.pass_obj
|
|
220
|
+
def integrity(ctx_obj: AgtContext, manifest: Optional[str], generate: Optional[str]) -> None:
|
|
221
|
+
"""Verify or generate module integrity manifest."""
|
|
222
|
+
import json as json_mod
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
if generate and manifest:
|
|
226
|
+
_print("Error: --manifest and --generate are mutually exclusive", style="red", err=True)
|
|
227
|
+
raise SystemExit(1)
|
|
228
|
+
|
|
229
|
+
from agent_compliance.integrity import IntegrityVerifier
|
|
230
|
+
|
|
231
|
+
if generate:
|
|
232
|
+
verifier = IntegrityVerifier()
|
|
233
|
+
result = verifier.generate_manifest(generate)
|
|
234
|
+
if ctx_obj.output_json:
|
|
235
|
+
click.echo(json_mod.dumps({"status": "ok", "path": generate, "files": len(result["files"]), "functions": len(result["functions"])}, indent=2))
|
|
236
|
+
else:
|
|
237
|
+
click.echo(f"Manifest written to {generate}")
|
|
238
|
+
click.echo(f" Files hashed: {len(result['files'])}")
|
|
239
|
+
click.echo(f" Functions hashed: {len(result['functions'])}")
|
|
240
|
+
return
|
|
241
|
+
|
|
242
|
+
import os
|
|
243
|
+
|
|
244
|
+
if manifest and not os.path.exists(manifest):
|
|
245
|
+
_print(f"Error: manifest file not found: {manifest}", style="red", err=True)
|
|
246
|
+
raise SystemExit(1)
|
|
247
|
+
|
|
248
|
+
verifier = IntegrityVerifier(manifest_path=manifest)
|
|
249
|
+
report = verifier.verify()
|
|
250
|
+
|
|
251
|
+
if ctx_obj.output_json:
|
|
252
|
+
click.echo(json_mod.dumps(report.to_dict(), indent=2))
|
|
253
|
+
else:
|
|
254
|
+
click.echo(report.summary())
|
|
255
|
+
|
|
256
|
+
if not report.passed:
|
|
257
|
+
raise SystemExit(1)
|
|
258
|
+
except SystemExit:
|
|
259
|
+
raise
|
|
260
|
+
except Exception as e:
|
|
261
|
+
_handle_error(e, ctx_obj.output_json)
|
|
262
|
+
raise SystemExit(1)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@cli.command("lint-policy")
|
|
266
|
+
@click.argument("path", type=click.Path(exists=True))
|
|
267
|
+
@click.option("--strict", is_flag=True, default=False, help="Treat warnings as errors.")
|
|
268
|
+
@click.pass_obj
|
|
269
|
+
def lint_policy(ctx_obj: AgtContext, path: str, strict: bool) -> None:
|
|
270
|
+
"""Lint YAML policy files for common mistakes."""
|
|
271
|
+
import json as json_mod
|
|
272
|
+
|
|
273
|
+
try:
|
|
274
|
+
from agent_compliance.lint_policy import lint_path
|
|
275
|
+
|
|
276
|
+
result = lint_path(path)
|
|
277
|
+
|
|
278
|
+
if ctx_obj.output_json:
|
|
279
|
+
click.echo(json_mod.dumps(result.to_dict(), indent=2))
|
|
280
|
+
else:
|
|
281
|
+
for msg in result.messages:
|
|
282
|
+
click.echo(msg)
|
|
283
|
+
if result.messages:
|
|
284
|
+
click.echo()
|
|
285
|
+
click.echo(result.summary())
|
|
286
|
+
|
|
287
|
+
if strict and result.warnings:
|
|
288
|
+
raise SystemExit(1)
|
|
289
|
+
if not result.passed:
|
|
290
|
+
raise SystemExit(1)
|
|
291
|
+
except SystemExit:
|
|
292
|
+
raise
|
|
293
|
+
except Exception as e:
|
|
294
|
+
_handle_error(e, ctx_obj.output_json)
|
|
295
|
+
raise SystemExit(1)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
# ---------------------------------------------------------------------------
|
|
299
|
+
# Doctor command
|
|
300
|
+
# ---------------------------------------------------------------------------
|
|
301
|
+
|
|
302
|
+
_AGT_PACKAGES = [
|
|
303
|
+
("agent_governance_toolkit", "Agent Governance Toolkit", "Meta-package & compliance CLI"),
|
|
304
|
+
("agent_os_kernel", "Agent OS Kernel", "Policy engine & framework integrations"),
|
|
305
|
+
("agentmesh_platform", "AgentMesh Platform", "Zero-trust identity & trust scoring"),
|
|
306
|
+
("agentmesh_runtime", "AgentMesh Runtime", "Execution supervisor & privilege rings"),
|
|
307
|
+
("agent_sre", "Agent SRE", "SLOs, error budgets & chaos testing"),
|
|
308
|
+
("agentmesh_marketplace", "AgentMesh Marketplace", "Plugin lifecycle management"),
|
|
309
|
+
("agentmesh_lightning", "AgentMesh Lightning", "RL training governance"),
|
|
310
|
+
("agent_hypervisor", "Agent Hypervisor", "Session management & kill switch"),
|
|
311
|
+
]
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@cli.command()
|
|
315
|
+
@click.pass_obj
|
|
316
|
+
def doctor(ctx_obj: AgtContext) -> None:
|
|
317
|
+
"""Diagnose AGT installation health.
|
|
318
|
+
|
|
319
|
+
Checks installed packages, versions, Python compatibility,
|
|
320
|
+
and plugin registration status.
|
|
321
|
+
"""
|
|
322
|
+
import json as json_mod
|
|
323
|
+
import platform
|
|
324
|
+
|
|
325
|
+
py_version = platform.python_version()
|
|
326
|
+
results: list[Dict[str, Any]] = []
|
|
327
|
+
|
|
328
|
+
for pkg_name, display_name, description in _AGT_PACKAGES:
|
|
329
|
+
ver = _get_package_version(pkg_name)
|
|
330
|
+
results.append({
|
|
331
|
+
"package": pkg_name,
|
|
332
|
+
"name": display_name,
|
|
333
|
+
"description": description,
|
|
334
|
+
"installed": ver is not None,
|
|
335
|
+
"version": ver,
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
# Check for plugin registrations
|
|
339
|
+
plugins = _discover_plugins()
|
|
340
|
+
|
|
341
|
+
# Check config files
|
|
342
|
+
from pathlib import Path
|
|
343
|
+
|
|
344
|
+
config_locations = [
|
|
345
|
+
Path.cwd() / "agentmesh.yaml",
|
|
346
|
+
Path.cwd() / "policies",
|
|
347
|
+
Path.cwd() / "integrity.json",
|
|
348
|
+
]
|
|
349
|
+
config_found = {str(p): p.exists() for p in config_locations}
|
|
350
|
+
|
|
351
|
+
if ctx_obj.output_json:
|
|
352
|
+
report = {
|
|
353
|
+
"python_version": py_version,
|
|
354
|
+
"packages": results,
|
|
355
|
+
"plugins": list(plugins.keys()),
|
|
356
|
+
"config_files": config_found,
|
|
357
|
+
}
|
|
358
|
+
click.echo(json_mod.dumps(report, indent=2))
|
|
359
|
+
return
|
|
360
|
+
|
|
361
|
+
# Rich table output
|
|
362
|
+
_print(f"\n🩺 AGT Doctor — Python {py_version}", style="bold blue")
|
|
363
|
+
_print("")
|
|
364
|
+
|
|
365
|
+
installed_count = sum(1 for r in results if r["installed"])
|
|
366
|
+
total_count = len(results)
|
|
367
|
+
|
|
368
|
+
if _HAS_RICH and _console is not None and not ctx_obj.no_color:
|
|
369
|
+
table = _RichTable(
|
|
370
|
+
title="Installed Packages",
|
|
371
|
+
box=_rich_box.ROUNDED,
|
|
372
|
+
show_lines=False,
|
|
373
|
+
)
|
|
374
|
+
table.add_column("Package", style="cyan", no_wrap=True)
|
|
375
|
+
table.add_column("Version", style="green")
|
|
376
|
+
table.add_column("Status")
|
|
377
|
+
table.add_column("Description", style="dim")
|
|
378
|
+
|
|
379
|
+
for r in results:
|
|
380
|
+
status = "[green]Γ£ô installed[/green]" if r["installed"] else "[dim]┬╖ not installed[/dim]"
|
|
381
|
+
ver = r["version"] or "ΓÇö"
|
|
382
|
+
table.add_row(r["package"], ver, status, r["description"])
|
|
383
|
+
|
|
384
|
+
_console.print(table)
|
|
385
|
+
else:
|
|
386
|
+
click.echo("Installed Packages:")
|
|
387
|
+
click.echo("-" * 70)
|
|
388
|
+
for r in results:
|
|
389
|
+
status = "Γ£ô" if r["installed"] else "┬╖"
|
|
390
|
+
ver = r["version"] or "ΓÇö"
|
|
391
|
+
click.echo(f" {status} {r['package']:30s} {ver:12s} {r['description']}")
|
|
392
|
+
|
|
393
|
+
_print(f"\n {installed_count}/{total_count} packages installed", style="bold")
|
|
394
|
+
|
|
395
|
+
# Plugins
|
|
396
|
+
if plugins:
|
|
397
|
+
_print(f"\n Plugin commands: {', '.join(sorted(plugins.keys()))}", style="green")
|
|
398
|
+
else:
|
|
399
|
+
_print("\n No plugin commands registered (install AGT packages with [full] extras)", style="dim")
|
|
400
|
+
|
|
401
|
+
# Config files
|
|
402
|
+
_print("\n Config files:", style="bold")
|
|
403
|
+
for path_str, exists in config_found.items():
|
|
404
|
+
icon = "Γ£ô" if exists else "┬╖"
|
|
405
|
+
_print(f" {icon} {path_str}")
|
|
406
|
+
|
|
407
|
+
_print("")
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
# ---------------------------------------------------------------------------
|
|
411
|
+
# Error handling
|
|
412
|
+
# ---------------------------------------------------------------------------
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _handle_error(e: Exception, output_json: bool = False) -> None:
|
|
416
|
+
"""Centralized error handler."""
|
|
417
|
+
import json as json_mod
|
|
418
|
+
|
|
419
|
+
is_known = isinstance(
|
|
420
|
+
e, (IOError, ValueError, KeyError, PermissionError, FileNotFoundError)
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
if output_json:
|
|
424
|
+
err_type = "ValidationError" if is_known else "InternalError"
|
|
425
|
+
err_msg = str(e) if is_known else "An internal error occurred"
|
|
426
|
+
click.echo(json_mod.dumps({"status": "error", "message": err_msg, "type": err_type}, indent=2))
|
|
427
|
+
else:
|
|
428
|
+
if is_known:
|
|
429
|
+
_print(f"Error: {e}", style="red", err=True)
|
|
430
|
+
else:
|
|
431
|
+
_print("Error: An internal error occurred", style="red", err=True)
|
|
432
|
+
import os
|
|
433
|
+
if os.environ.get("AGENTOS_DEBUG"):
|
|
434
|
+
_print(f" {e}", style="dim", err=True)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
# ---------------------------------------------------------------------------
|
|
438
|
+
# Entry point
|
|
439
|
+
# ---------------------------------------------------------------------------
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def main() -> None:
|
|
443
|
+
"""Console-script entry point."""
|
|
444
|
+
cli(standalone_mode=True)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
if __name__ == "__main__":
|
|
448
|
+
main()
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
#!/usr/bin/env python3
|
|
4
|
+
# Copyright (c) Microsoft Corporation.
|
|
5
|
+
# Licensed under the MIT License.
|
|
6
|
+
"""
|
|
7
|
+
Agent Governance Toolkit CLI.
|
|
8
|
+
|
|
9
|
+
Commands:
|
|
10
|
+
verify Run OWASP ASI 2026 governance verification
|
|
11
|
+
integrity Verify or generate module integrity manifest
|
|
12
|
+
lint-policy Lint YAML policy files for common mistakes
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
from typing import Optional
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
import json
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def handle_error(e: Exception, output_json: bool = False, custom_msg: Optional[str] = None):
|
|
25
|
+
"""Centralized error handler for compliance CLI."""
|
|
26
|
+
is_known = isinstance(e, (IOError, ValueError, KeyError, PermissionError, FileNotFoundError))
|
|
27
|
+
|
|
28
|
+
if custom_msg:
|
|
29
|
+
err_msg = custom_msg
|
|
30
|
+
elif is_known:
|
|
31
|
+
err_msg = "A validation or file access error occurred."
|
|
32
|
+
else:
|
|
33
|
+
err_msg = "A governance processing error occurred."
|
|
34
|
+
|
|
35
|
+
if output_json:
|
|
36
|
+
print(json.dumps({"status": "fail" if not is_known else "error", "message": err_msg, "type": "ValidationError" if is_known else "InternalError"}, indent=2))
|
|
37
|
+
else:
|
|
38
|
+
print(f"Error: {err_msg}", file=sys.stderr)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def cmd_verify(args: argparse.Namespace) -> int:
|
|
42
|
+
"""Run governance verification."""
|
|
43
|
+
from agent_compliance.verify import GovernanceVerifier
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
verifier = GovernanceVerifier()
|
|
47
|
+
attestation = verifier.verify()
|
|
48
|
+
|
|
49
|
+
if args.json:
|
|
50
|
+
print(attestation.to_json())
|
|
51
|
+
elif args.badge:
|
|
52
|
+
print(attestation.badge_markdown())
|
|
53
|
+
else:
|
|
54
|
+
print(attestation.summary())
|
|
55
|
+
|
|
56
|
+
return 0 if attestation.passed else 1
|
|
57
|
+
except Exception as e:
|
|
58
|
+
handle_error(e, args.json)
|
|
59
|
+
return 1
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def cmd_integrity(args: argparse.Namespace) -> int:
|
|
63
|
+
"""Run integrity verification or generate manifest."""
|
|
64
|
+
from agent_compliance.integrity import IntegrityVerifier
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
if args.generate and args.manifest:
|
|
68
|
+
print(
|
|
69
|
+
"Error: --manifest and --generate are mutually exclusive",
|
|
70
|
+
file=sys.stderr,
|
|
71
|
+
)
|
|
72
|
+
return 1
|
|
73
|
+
|
|
74
|
+
if args.generate:
|
|
75
|
+
verifier = IntegrityVerifier()
|
|
76
|
+
manifest = verifier.generate_manifest(args.generate)
|
|
77
|
+
print(f"Manifest written to {args.generate}")
|
|
78
|
+
print(f" Files hashed: {len(manifest['files'])}")
|
|
79
|
+
print(f" Functions hashed: {len(manifest['functions'])}")
|
|
80
|
+
return 0
|
|
81
|
+
|
|
82
|
+
if args.manifest and not os.path.exists(args.manifest):
|
|
83
|
+
print(
|
|
84
|
+
f"Error: manifest file not found: {args.manifest}",
|
|
85
|
+
file=sys.stderr,
|
|
86
|
+
)
|
|
87
|
+
return 1
|
|
88
|
+
|
|
89
|
+
verifier = IntegrityVerifier(manifest_path=args.manifest)
|
|
90
|
+
report = verifier.verify()
|
|
91
|
+
|
|
92
|
+
if args.json:
|
|
93
|
+
import json
|
|
94
|
+
|
|
95
|
+
print(json.dumps(report.to_dict(), indent=2))
|
|
96
|
+
else:
|
|
97
|
+
print(report.summary())
|
|
98
|
+
|
|
99
|
+
return 0 if report.passed else 1
|
|
100
|
+
except Exception as e:
|
|
101
|
+
handle_error(e, args.json)
|
|
102
|
+
return 1
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def cmd_lint_policy(args: argparse.Namespace) -> int:
|
|
106
|
+
"""Lint YAML policy files for common mistakes."""
|
|
107
|
+
from agent_compliance.lint_policy import lint_path
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
result = lint_path(args.path)
|
|
111
|
+
|
|
112
|
+
if args.json:
|
|
113
|
+
import json
|
|
114
|
+
|
|
115
|
+
print(json.dumps(result.to_dict(), indent=2))
|
|
116
|
+
else:
|
|
117
|
+
for msg in result.messages:
|
|
118
|
+
print(msg)
|
|
119
|
+
if result.messages:
|
|
120
|
+
print()
|
|
121
|
+
print(result.summary())
|
|
122
|
+
|
|
123
|
+
if args.strict and result.warnings:
|
|
124
|
+
return 1
|
|
125
|
+
return 0 if result.passed else 1
|
|
126
|
+
except Exception as e:
|
|
127
|
+
handle_error(e, args.json)
|
|
128
|
+
return 1
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def main() -> int:
|
|
132
|
+
"""CLI entry point."""
|
|
133
|
+
parser = argparse.ArgumentParser(
|
|
134
|
+
prog="agent-compliance",
|
|
135
|
+
description="Agent Governance Toolkit — Compliance & Verification CLI",
|
|
136
|
+
)
|
|
137
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
138
|
+
|
|
139
|
+
# verify command
|
|
140
|
+
verify_parser = subparsers.add_parser(
|
|
141
|
+
"verify",
|
|
142
|
+
help="Run OWASP ASI 2026 governance verification",
|
|
143
|
+
)
|
|
144
|
+
verify_parser.add_argument(
|
|
145
|
+
"--json", action="store_true", help="Output JSON attestation"
|
|
146
|
+
)
|
|
147
|
+
verify_parser.add_argument(
|
|
148
|
+
"--badge", action="store_true", help="Output markdown badge only"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# integrity command
|
|
152
|
+
integrity_parser = subparsers.add_parser(
|
|
153
|
+
"integrity",
|
|
154
|
+
help="Verify or generate module integrity manifest",
|
|
155
|
+
)
|
|
156
|
+
integrity_parser.add_argument(
|
|
157
|
+
"--manifest", type=str, help="Path to integrity.json manifest to verify against"
|
|
158
|
+
)
|
|
159
|
+
integrity_parser.add_argument(
|
|
160
|
+
"--generate",
|
|
161
|
+
type=str,
|
|
162
|
+
metavar="OUTPUT_PATH",
|
|
163
|
+
help="Generate integrity manifest at the given path",
|
|
164
|
+
)
|
|
165
|
+
integrity_parser.add_argument(
|
|
166
|
+
"--json", action="store_true", help="Output JSON report"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# lint-policy command
|
|
170
|
+
lint_parser = subparsers.add_parser(
|
|
171
|
+
"lint-policy",
|
|
172
|
+
help="Lint YAML policy files for common mistakes",
|
|
173
|
+
)
|
|
174
|
+
lint_parser.add_argument(
|
|
175
|
+
"path", type=str, help="Path to a YAML policy file or directory"
|
|
176
|
+
)
|
|
177
|
+
lint_parser.add_argument(
|
|
178
|
+
"--json", action="store_true", help="Output JSON report"
|
|
179
|
+
)
|
|
180
|
+
lint_parser.add_argument(
|
|
181
|
+
"--strict",
|
|
182
|
+
action="store_true",
|
|
183
|
+
help="Treat warnings as errors (exit 1 if any warnings)",
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
args = parser.parse_args()
|
|
187
|
+
|
|
188
|
+
if args.command == "verify":
|
|
189
|
+
return cmd_verify(args)
|
|
190
|
+
elif args.command == "integrity":
|
|
191
|
+
return cmd_integrity(args)
|
|
192
|
+
elif args.command == "lint-policy":
|
|
193
|
+
return cmd_lint_policy(args)
|
|
194
|
+
else:
|
|
195
|
+
parser.print_help()
|
|
196
|
+
return 0
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
if __name__ == "__main__":
|
|
200
|
+
sys.exit(main())
|