cloak-api / copilot /cli.py
n1th1sh's picture
Upload 35 files
375880c verified
Raw History Blame Contribute Delete
4.81 kB
"""Try the copilot in a terminal.
python -m copilot.cli # interactive, typical test tenant
python -m copilot.cli --context my_tenant.json # your own context file
python -m copilot.cli "block aadhaar in chatgpt" # one request, then exit
python -m copilot.cli --json "block usb for interns" # raw response JSON
Questions are asked one by one; your answers are sent back exactly as the dashboard would.
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from .pipeline import parse
DEFAULT_CONTEXT = Path(__file__).parent.parent / 'corpus' / 'contexts' / 'typical.json'
def load_context(path: str | None) -> dict:
if not path and not DEFAULT_CONTEXT.exists():
return {} # installed without the test tenants: an empty tenant (built-in rules only)
p = Path(path) if path else DEFAULT_CONTEXT
try:
return json.loads(p.read_text(encoding='utf-8'))
except (OSError, json.JSONDecodeError) as e:
print(f'Could not read context {p}: {e}. Using an empty tenant.', file=sys.stderr)
return {}
def show(r: dict, full: bool) -> None:
print(f'\n[{r["status"]}] {r["message"]}')
for i in r.get('issues', []):
print(f' ({i["code"]}) {i["message"]}')
for n, d in enumerate(r['drafts']):
print(f'\n Draft {n}: {d["policyType"] or "?"} - {d["status"]}')
print(f' {d["summary"]}')
if d['policyBody']:
print(f' name: {d["policyBody"]["name"]}')
rd = d['ruleDecision']
if rd['mode'] != 'none':
print(f' detection rule: {rd["mode"]} {rd["ruleIds"]}')
for i in d['issues']:
if i['code'] != 'CREATED_INACTIVE':
print(f' {i["level"]:>8}: {i["message"]}')
if full and d['policyBody']:
for step in d['saveSteps']:
print(f' {step["method"]} {step["path"]}')
print(' ' + json.dumps(step['body'], indent=2).replace('\n', '\n '))
if r.get('examples') and not r['drafts']:
print('\n Try for example:')
for e in r['examples'][:4]:
print(f' - {e}')
def ask(r: dict, answers: dict) -> bool:
"""Ask every open question. Returns False when there was nothing to ask."""
asked = False
for n, d in enumerate(r['drafts']):
for q in d['questions']:
key = f'{q["slot"]}@{n}'
if key in answers:
continue
asked = True
print(f'\n Draft {n} asks: {q["text"]}' + ('' if q['blocking'] else ' (optional, Enter to skip)'))
for k, o in enumerate(q['options'][:12]):
print(f' {k + 1}. {o["label"]}')
raw = input(' > ').strip()
if not raw:
answers[key] = q.get('default')
continue
if raw.isdigit() and 1 <= int(raw) <= len(q['options']):
answers[key] = q['options'][int(raw) - 1]['value']
elif q['slot'] in ('data', 'connector', 'mcp', 'sites'):
answers[key] = [x.strip() for x in raw.split(',') if x.strip()]
else:
answers[key] = raw
return asked
def run_one(text: str, ctx: dict, as_json: bool, interactive: bool) -> None:
answers: dict = {}
r = parse(text, ctx, answers)
if as_json:
print(json.dumps(r, indent=2, ensure_ascii=False))
return
if not interactive:
show(r, full=True)
return
show(r, full=False)
while r['status'] == 'needs_input' and ask(r, answers):
r = parse(text, ctx, answers)
show(r, full=False)
if r['drafts'] and any(d['policyBody'] for d in r['drafts']):
if input('\nShow the exact request bodies? [y/N] ').strip().lower() == 'y':
show(r, full=True)
def main() -> None:
ap = argparse.ArgumentParser(description='Policy Copilot in the terminal')
ap.add_argument('text', nargs='*')
ap.add_argument('--context')
ap.add_argument('--json', action='store_true')
args = ap.parse_args()
for stream in (sys.stdout, sys.stderr):
try:
stream.reconfigure(encoding='utf-8', errors='replace')
except (AttributeError, ValueError):
pass
ctx = load_context(args.context)
if args.text:
run_one(' '.join(args.text), ctx, args.json, interactive=False)
return
print('Policy Copilot. Describe a policy (empty line to quit).')
while True:
try:
text = input('\n> ').strip()
except (EOFError, KeyboardInterrupt):
break
if not text:
break
run_one(text, ctx, False, interactive=True)
if __name__ == '__main__':
main()