22from __future__ import annotations
33
44import argparse
5+ import getpass
56import json
67import os
78import re
@@ -27,6 +28,25 @@ def _normalize_base_url(url: str) -> str:
2728 return clean or DEFAULT_BASE_URL
2829
2930
31+ def _is_interactive_terminal () -> bool :
32+ try :
33+ return bool (sys .stdin .isatty () and sys .stdout .isatty ())
34+ except Exception :
35+ return False
36+
37+
38+ def _confirm_llm_usage () -> bool :
39+ answer = input ("Use LLM to generate narrative summaries for this run? [y/N]: " ).strip ().lower ()
40+ return answer in {"y" , "yes" }
41+
42+
43+ def _prompt_api_key () -> str :
44+ try :
45+ return getpass .getpass ("Enter LLM API key (input hidden): " ).strip ()
46+ except Exception :
47+ return ""
48+
49+
3050def _post_json (url : str , api_key : str , payload : Dict [str , Any ], timeout : int = 90 ) -> Tuple [int , str ]:
3151 data = json .dumps (payload ).encode ("utf-8" )
3252 req = urllib .request .Request (
@@ -161,6 +181,8 @@ def _default_llm_payload(enabled: bool, model: str) -> Dict[str, Any]:
161181 "generated_at" : common .now_iso (),
162182 "enabled" : enabled ,
163183 "used" : False ,
184+ "asked_before_use" : False ,
185+ "prompted_for_key" : False ,
164186 "provider" : "openai_compatible" ,
165187 "model" : model ,
166188 "repo_summary_paragraph" : "" ,
@@ -184,17 +206,41 @@ def generate_llm_descriptions(
184206 docs_payload : Dict [str , Any ],
185207 out_dir : Path ,
186208 enabled : bool = True ,
209+ ask_before_use : bool = False ,
210+ prompt_for_key : bool = False ,
187211) -> Dict [str , Any ]:
188212 model = os .environ .get ("CODE_EXPLAINER_LLM_MODEL" , DEFAULT_MODEL ).strip () or DEFAULT_MODEL
189213 payload = _default_llm_payload (enabled = enabled , model = model )
190214 if not enabled :
191215 common .write_json (out_dir / "llm_summary.json" , payload )
192216 return payload
193217
218+ interactive = _is_interactive_terminal ()
219+ if ask_before_use :
220+ payload ["asked_before_use" ] = True
221+ if not interactive :
222+ payload ["enabled" ] = False
223+ payload ["error" ] = "LLM ask-before-use requested but terminal is non-interactive; skipped."
224+ common .write_json (out_dir / "llm_summary.json" , payload )
225+ return payload
226+ if not _confirm_llm_usage ():
227+ payload ["enabled" ] = False
228+ payload ["error" ] = "User declined LLM narrative generation for this run."
229+ common .write_json (out_dir / "llm_summary.json" , payload )
230+ return payload
231+
194232 api_key = (
195233 os .environ .get ("CODE_EXPLAINER_LLM_API_KEY" , "" ).strip ()
196234 or os .environ .get ("OPENAI_API_KEY" , "" ).strip ()
197235 )
236+ if not api_key and prompt_for_key :
237+ payload ["prompted_for_key" ] = True
238+ if not interactive :
239+ payload ["error" ] = "Prompt-for-key requested but terminal is non-interactive and no key was found."
240+ common .write_json (out_dir / "llm_summary.json" , payload )
241+ return payload
242+ api_key = _prompt_api_key ()
243+
198244 if not api_key :
199245 payload ["error" ] = "No API key found (set CODE_EXPLAINER_LLM_API_KEY or OPENAI_API_KEY)."
200246 common .write_json (out_dir / "llm_summary.json" , payload )
@@ -286,6 +332,8 @@ def main() -> int:
286332 parser .add_argument ("--coverage" , required = True )
287333 parser .add_argument ("--output" , required = True )
288334 parser .add_argument ("--enabled" , default = "true" )
335+ parser .add_argument ("--ask-before-use" , default = "false" )
336+ parser .add_argument ("--prompt-for-key" , default = "false" )
289337 args = parser .parse_args ()
290338
291339 payload = generate_llm_descriptions (
@@ -301,6 +349,8 @@ def main() -> int:
301349 docs_payload = common .read_json (Path (args .coverage ), default = {}),
302350 out_dir = Path (args .output ).resolve (),
303351 enabled = common .bool_from_string (args .enabled ),
352+ ask_before_use = common .bool_from_string (args .ask_before_use ),
353+ prompt_for_key = common .bool_from_string (args .prompt_for_key ),
304354 )
305355 print (json .dumps ({"used" : payload .get ("used" , False ), "error" : payload .get ("error" , "" )}, indent = 2 ))
306356 return 0
0 commit comments