""" Command-line interface for oracle_answer tool. Uses NVIDIA's free API (build.nvidia.com) for actual LLM responses. NOTE FOR AUTOMATION: - All CLI arguments must be defined ONLY in build_parser(). - When changing CLI flags, rewrite build_parser() entirely. - Do not define duplicate flags like --question in other functions. """ import argparse import asyncio import json import sys from typing import List, Optional from layer0 import layer0_entry from layer0.shadow_classifier import ShadowEvalResult from .tool import OracleAnswerTool def build_parser() -> argparse.ArgumentParser: """ Build argument parser. RULE: This function is the single source of truth for CLI args. Never append args elsewhere. """ parser = argparse.ArgumentParser( prog="oracle-answer", description="Sovereign compliance oracle powered by NVIDIA AI.", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: oracle-answer --question "Are we GDPR compliant?" --frameworks GDPR ISO-27001 oracle-answer --question "Incident response time SLA?" --mode advisory oracle-answer --question "Test?" --local-only (skip NVIDIA API) """, ) parser.add_argument( "--question", required=True, type=str, help="Compliance / security question to answer.", ) parser.add_argument( "--frameworks", nargs="*", default=["NIST-CSF", "ISO-27001"], type=str, help="Frameworks to reference (space-separated).", ) parser.add_argument( "--mode", default="strict", choices=["strict", "advisory"], help="strict = conservative, advisory = more exploratory.", ) parser.add_argument( "--json", action="store_true", help="Output ToolResponse as JSON instead of pretty text.", ) parser.add_argument( "--local-only", action="store_true", help="Skip NVIDIA API calls (for testing).", ) return parser async def main_async(args: Optional[List[str]] = None) -> int: """Async main entry point.""" parser = build_parser() ns = parser.parse_args(args=args) # Layer 0: pre-boot Shadow Eval gate before any processing. routing_action, shadow = layer0_entry(ns.question) if routing_action != "HANDOFF_TO_LAYER1": _render_layer0_block(routing_action, shadow) return 1 tool = OracleAnswerTool( default_frameworks=ns.frameworks, use_local_only=ns.local_only, ) resp = await tool.answer( question=ns.question, frameworks=ns.frameworks, mode=ns.mode, ) if ns.json: print( json.dumps( { "answer": resp.answer, "framework_hits": resp.framework_hits, "reasoning": resp.reasoning, "model": resp.model, }, indent=2, ) ) else: print("\n" + "=" * 80) print("ORACLE ANSWER (Powered by NVIDIA AI)") print("=" * 80 + "\n") print(resp.answer) if resp.reasoning: print("\n--- Reasoning ---\n") print(resp.reasoning) if resp.framework_hits: print("\n--- Framework Hits ---\n") for framework, hits in resp.framework_hits.items(): if hits: print(f"{framework}:") for hit in hits: print(f" • {hit}") print(f"\n[Model: {resp.model}]") print() return 0 def main() -> None: """Sync wrapper for CLI entry point.""" try: sys.exit(asyncio.run(main_async())) except KeyboardInterrupt: sys.exit(1) def _render_layer0_block(routing_action: str, shadow: ShadowEvalResult) -> None: """ Minimal user-facing responses for Layer 0 decisions. - Catastrophic: fail closed, no details beyond refusal. - Forbidden: governance violation noted. - Ambiguous: ask for clarification. """ if routing_action == "FAIL_CLOSED": print("Layer 0: cannot comply with this request.", file=sys.stderr) return if routing_action == "HANDOFF_TO_GUARDRAILS": print( "Layer 0: governance violation detected (e.g., GitOps bypass or dashboard request).", file=sys.stderr, ) if shadow.reason: print(f"Reason: {shadow.reason}", file=sys.stderr) return if routing_action == "PROMPT_FOR_CLARIFICATION": print( "Layer 0: request is ambiguous. Please add specifics before rerunning.", file=sys.stderr, ) return # Unexpected action; default to refusal. print("Layer 0: unrecognized routing action; refusing request.", file=sys.stderr) if __name__ == "__main__": main()