Files
vm-cloudflare/mcp/oracle_answer/cli.py
2025-12-17 00:02:39 +00:00

172 lines
4.8 KiB
Python

"""
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()