@@ -44,80 +44,84 @@ def query_devices(self) -> Sequence[Mapping[str, object]]: ...
4444}
4545
4646
47+ def _check (
48+ name : str ,
49+ status : str ,
50+ detail : str ,
51+ * ,
52+ fix : str | None = None ,
53+ affects : list [str ] | None = None ,
54+ ) -> Check :
55+ """Assemble a Check. ``affects`` defaults to empty — an 'ok' check blocks nothing."""
56+ return {"name" : name , "status" : status , "affects" : affects or [], "detail" : detail , "fix" : fix }
57+
58+
4759def check_python () -> Check :
4860 v = sys .version_info
4961 version = f"{ v .major } .{ v .minor } .{ v .micro } "
5062 if v >= (3 , 12 ):
51- return { "name" : " python" , "status" : " ok" , "affects" : [], "detail" : version , "fix" : None }
52- return {
53- "name" : " python" ,
54- "status" : " fail" ,
55- "affects" : [ "everything" ] ,
56- "detail" : f" Python { version } is too old; the CLI needs 3.12+ " ,
57- "fix" : "Install Python 3.12 or newer, then reinstall the CLI." ,
58- }
63+ return _check ( " python" , "ok" , version )
64+ return _check (
65+ "python" ,
66+ "fail" ,
67+ f"Python { version } is too old; the CLI needs 3.12+" ,
68+ fix = "Install Python 3.12 or newer, then reinstall the CLI. " ,
69+ affects = [ "everything" ] ,
70+ )
5971
6072
6173def _check_api_key (profile : str ) -> Check :
62- affects = ["everything" ]
6374 try :
6475 key = config .resolve_api_key (profile = profile )
6576 except NotAuthenticated :
66- return {
67- "name" : " api-key" ,
68- "status" : " fail" ,
69- "affects" : affects ,
70- "detail" : "No API key found ." ,
71- "fix" : "Run 'aai login' (or set ASSEMBLYAI_API_KEY)." ,
72- }
77+ return _check (
78+ "api-key" ,
79+ "fail" ,
80+ "No API key found." ,
81+ fix = "Run 'aai login' (or set ASSEMBLYAI_API_KEY) ." ,
82+ affects = [ "everything" ] ,
83+ )
7384 # validate_key doubles as the connectivity probe: it makes one cheap authed call,
7485 # so a pass means the key is valid AND api.assemblyai.com is reachable.
7586 try :
7687 valid = client .validate_key (key )
7788 except CLIError as exc :
78- return {
79- "name" : " api-key" ,
80- "status" : " fail" ,
81- "affects" : affects ,
82- "detail" : f"Could not reach AssemblyAI: { exc . message } " ,
83- "fix" : "Check your network/proxy and that api.assemblyai.com is reachable." ,
84- }
89+ return _check (
90+ "api-key" ,
91+ "fail" ,
92+ f"Could not reach AssemblyAI: { exc . message } " ,
93+ fix = "Check your network/proxy and that api.assemblyai.com is reachable. " ,
94+ affects = [ "everything" ] ,
95+ )
8596 if valid :
86- return {
87- "name" : "api-key" ,
88- "status" : "ok" ,
89- "affects" : [],
90- "detail" : "API key is valid and AssemblyAI is reachable." ,
91- "fix" : None ,
92- }
93- return {
94- "name" : "api-key" ,
95- "status" : "fail" ,
96- "affects" : affects ,
97- "detail" : "API key was rejected (HTTP 401)." ,
98- "fix" : "Run 'aai login' with a valid key." ,
99- }
97+ return _check ("api-key" , "ok" , "API key is valid and AssemblyAI is reachable." )
98+ return _check (
99+ "api-key" ,
100+ "fail" ,
101+ "API key was rejected (HTTP 401)." ,
102+ fix = "Run 'aai login' with a valid key." ,
103+ affects = ["everything" ],
104+ )
100105
101106
102107def check_ffmpeg () -> Check :
103108 # ffmpeg is ONLY used to stream non-WAV files or URLs (stream/agent), where it
104109 # decodes them to 16 kHz mono PCM on the fly. Plain `transcribe` (including
105110 # YouTube URLs) uploads the file to AssemblyAI and never invokes ffmpeg, so it is
106111 # not required for transcription.
107- affects = ["stream/agent (non-WAV file or URL input)" ]
108112 if shutil .which ("ffmpeg" ):
109- return {"name" : "ffmpeg" , "status" : "ok" , "affects" : [], "detail" : "found" , "fix" : None }
110- return {
111- "name" : "ffmpeg" ,
112- "status" : "warn" ,
113- "affects" : affects ,
114- "detail" : (
113+ return _check ("ffmpeg" , "ok" , "found" )
114+ return _check (
115+ "ffmpeg" ,
116+ "warn" ,
117+ (
115118 "ffmpeg not found. Only needed to stream non-WAV files or URLs; "
116119 "transcription (including YouTube) works without it, as does streaming a "
117120 "16 kHz mono WAV."
118121 ),
119- "fix" : "Install ffmpeg (macOS: brew install ffmpeg; Debian/Ubuntu: apt-get install ffmpeg)." ,
120- }
122+ fix = "Install ffmpeg (macOS: brew install ffmpeg; Debian/Ubuntu: apt-get install ffmpeg)." ,
123+ affects = ["stream/agent (non-WAV file or URL input)" ],
124+ )
121125
122126
123127def _probe_input_devices () -> int :
@@ -144,59 +148,50 @@ def check_audio() -> Check:
144148 try :
145149 inputs = _probe_input_devices ()
146150 except ImportError :
147- return {
148- "name" : " audio" ,
149- "status" : " warn" ,
150- "affects" : affects ,
151- "detail" : "sounddevice is not importable; the microphone can't be used. " ,
152- "fix" : "pip install --force-reinstall sounddevice" ,
153- }
151+ return _check (
152+ "audio" ,
153+ "warn" ,
154+ "sounddevice is not importable; the microphone can't be used." ,
155+ fix = "pip install --force-reinstall sounddevice " ,
156+ affects = affects ,
157+ )
154158 except Exception as exc : # noqa: BLE001 - any PortAudio/device failure is a soft warning
155- return {
156- "name" : " audio" ,
157- "status" : " warn" ,
158- "affects" : affects ,
159- "detail" : f"audio system unavailable: { exc } " ,
160- "fix" : "On Linux install PortAudio: sudo apt-get install libportaudio2" ,
161- }
159+ return _check (
160+ "audio" ,
161+ "warn" ,
162+ f"audio system unavailable: { exc } " ,
163+ fix = "On Linux install PortAudio: sudo apt-get install libportaudio2 " ,
164+ affects = affects ,
165+ )
162166 if inputs == 0 :
163- return {
164- "name" : "audio" ,
165- "status" : "warn" ,
166- "affects" : affects ,
167- "detail" : "No microphone (input device) found." ,
168- "fix" : "Connect a microphone; live mic input is needed for stream/agent." ,
169- }
170- return {
171- "name" : "audio" ,
172- "status" : "ok" ,
173- "affects" : [],
174- "detail" : f"{ inputs } microphone input device(s) available." ,
175- "fix" : None ,
176- }
167+ return _check (
168+ "audio" ,
169+ "warn" ,
170+ "No microphone (input device) found." ,
171+ fix = "Connect a microphone; live mic input is needed for stream/agent." ,
172+ affects = affects ,
173+ )
174+ return _check ("audio" , "ok" , f"{ inputs } microphone input device(s) available." )
177175
178176
179177def _check_coding_agent () -> Check :
180- affects = ["aai setup install" ]
181178 missing = [tool for tool in ("claude" , "npx" ) if shutil .which (tool ) is None ]
182179 if not missing :
183- return {
184- "name" : "coding-agent" ,
185- "status" : "ok" ,
186- "affects" : [],
187- "detail" : "claude and npx found; run 'aai setup install' to wire up the docs MCP + skills." ,
188- "fix" : None ,
189- }
190- return {
191- "name" : "coding-agent" ,
192- "status" : "warn" ,
193- "affects" : affects ,
194- "detail" : f"not found: { ', ' .join (missing )} ." ,
195- "fix" : (
180+ return _check (
181+ "coding-agent" ,
182+ "ok" ,
183+ "claude and npx found; run 'aai setup install' to wire up the docs MCP + skills." ,
184+ )
185+ return _check (
186+ "coding-agent" ,
187+ "warn" ,
188+ f"not found: { ', ' .join (missing )} ." ,
189+ fix = (
196190 "Install Claude Code (https://claude.com/claude-code) and Node.js, "
197191 "then run 'aai setup install'."
198192 ),
199- }
193+ affects = ["aai setup install" ],
194+ )
200195
201196
202197def render (data : DoctorResult ) -> str :
0 commit comments