Skip to content

Commit 05b417e

Browse files
committed
Deduplicate argv JSON sniffing and centralize API key resolution
Two cleanups from a codebase-wide simplification review: - Extract the raw-argv --json/-o json detection duplicated between main._command_line_requests_json and telemetry._notice_suppressed into a new rich-free, cycle-free aai_cli/argscan.py shared by both (telemetry could import neither main nor output), and register the module in the import-linter contracts. - Add AppState.resolve_api_key() alongside the existing resolve_profile/ resolve_environment/resolve_session methods, and route the 12 config.resolve_api_key(profile=state.profile) call sites across the command layer through it, so key resolution joins the other precedence rules in the one documented place. https://claude.ai/code/session_011nWq87bwQmisipjUNhjX99
1 parent 908ddb5 commit 05b417e

15 files changed

Lines changed: 64 additions & 54 deletions

.importlinter

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ name = Core modules do not import command modules
77
type = forbidden
88
source_modules =
99
aai_cli.agent
10+
aai_cli.argscan
1011
aai_cli.auth
1112
aai_cli.client
1213
aai_cli.code_gen
@@ -67,6 +68,7 @@ modules =
6768
name = Library layers do not depend on Rich rendering
6869
type = forbidden
6970
source_modules =
71+
aai_cli.argscan
7072
aai_cli.client
7173
aai_cli.config
7274
aai_cli.config_builder

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ Lessons that cost iterations getting the patch-coverage and mutation tail gates
9393
patch it must accept it or the call `TypeError`s.
9494
- **`--json` / `-j` is a per-command flag, not a root flag**: `assembly --json transcribe …` fails
9595
with "No such option"; it's `assembly transcribe … --json`. (The root callback still sniffs the
96-
whole token list via `_command_line_requests_json`, so a callback-level failure like a bad
96+
whole token list via `argscan.requests_json`, so a callback-level failure like a bad
9797
`--env` keeps the JSON error shape — but the flag itself lives on the subcommand.)
9898

9999
### Manual QA / running the CLI in sandboxed sessions

aai_cli/argscan.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Sniffing the raw, not-yet-parsed command line for output-mode flags.
2+
3+
Both the root callback (`main`) and telemetry's first-run notice run before any
4+
subcommand parses its own ``--json``, so honoring a pipeline's request for
5+
machine-readable output at that point means scanning the raw token list. The
6+
shared definition lives here — free of Rich and import cycles — so the two
7+
callers can't drift on which flag forms count.
8+
"""
9+
10+
from __future__ import annotations
11+
12+
13+
def requests_json(raw_args: list[str]) -> bool:
14+
"""Whether the token list opts into JSON output: ``--json``, ``-j``,
15+
``-o json``, ``--output json``, or their glued forms (``--output=json``,
16+
``-ojson``)."""
17+
for index, token in enumerate(raw_args):
18+
if token in ("--json", "-j", "--output=json", "-ojson"):
19+
return True
20+
if token in ("-o", "--output") and raw_args[index + 1 : index + 2] == ["json"]:
21+
return True
22+
return False

aai_cli/commands/agent.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import typer
88

9-
from aai_cli import choices, client, code_gen, config, help_panels, options, output
9+
from aai_cli import choices, client, code_gen, help_panels, options, output
1010
from aai_cli.agent.audio import SAMPLE_RATE, DuplexAudio, NullPlayer
1111
from aai_cli.agent.render import AgentRenderer
1212
from aai_cli.agent.session import (
@@ -182,7 +182,7 @@ def body(state: AppState, json_mode: bool) -> None:
182182
# Existence-check the clip before credentials, so a typo'd path reads as
183183
# "file not found" instead of triggering a login.
184184
client.resolve_audio_source(source, sample=sample)
185-
api_key = config.resolve_api_key(profile=state.profile)
185+
api_key = state.resolve_api_key()
186186

187187
renderer = AgentRenderer(
188188
json_mode=json_mode,

aai_cli/commands/evaluate.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import typer
1717
from rich.console import RenderableType
1818

19-
from aai_cli import client, config, der, eval_data, help_panels, jsonshape, options, output, wer
19+
from aai_cli import client, der, eval_data, help_panels, jsonshape, options, output, wer
2020
from aai_cli.context import AppState, run_command
2121
from aai_cli.errors import CLIError, NotAuthenticated, UsageError
2222
from aai_cli.help_text import examples_epilog
@@ -345,7 +345,7 @@ def body(state: AppState, json_mode: bool) -> None:
345345
)
346346
# Resolve credentials before any dataset download: a signed-out user must
347347
# not pull the whole dataset only to fail at the first transcription.
348-
api_key = config.resolve_api_key(profile=state.profile)
348+
api_key = state.resolve_api_key()
349349
data = eval_data.load(
350350
dataset,
351351
split=split,

aai_cli/commands/llm.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import typer
66
from rich.markup import escape
77

8-
from aai_cli import choices, client, config, help_panels, options, output, stdio
8+
from aai_cli import choices, client, help_panels, options, output, stdio
99
from aai_cli import llm as gateway
1010
from aai_cli.context import AppState, run_command
1111
from aai_cli.errors import UsageError
@@ -151,7 +151,7 @@ def llm(
151151

152152
def follow_body(state: AppState, json_mode: bool) -> None:
153153
prompt_text = _validate_follow_args(prompt, output_field, transcript_id)
154-
api_key = config.resolve_api_key(profile=state.profile)
154+
api_key = state.resolve_api_key()
155155

156156
def ask(transcript_text: str) -> str:
157157
messages = gateway.build_messages(
@@ -185,7 +185,7 @@ def body(state: AppState, json_mode: bool) -> None:
185185
)
186186
prompt_text = prompt
187187
stdin_text = _stdin_transcript_text(state, json_mode, transcript_id)
188-
api_key = config.resolve_api_key(profile=state.profile)
188+
api_key = state.resolve_api_key()
189189
messages = gateway.build_messages(
190190
prompt_text, system=system, transcript_id=transcript_id, transcript_text=stdin_text
191191
)

aai_cli/commands/login.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ def body(state: AppState, json_mode: bool) -> None:
162162
profile = resolve_profile(state)
163163
# The full env -> keyring chain (raises NotAuthenticated when empty), so a CI
164164
# box authenticated via ASSEMBLYAI_API_KEY can use whoami as a preflight check.
165-
key = config.resolve_api_key(profile=state.profile)
165+
key = state.resolve_api_key()
166166
masked = output.mask_secret(key)
167167
env = environments.active().name
168168
# A network failure must not suppress the local table: profile, env, masked

aai_cli/commands/speak.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import typer
77

8-
from aai_cli import config, help_panels, options, output
8+
from aai_cli import help_panels, options, output
99
from aai_cli.context import AppState, run_command
1010
from aai_cli.errors import CLIError, UsageError
1111
from aai_cli.help_text import examples_epilog
@@ -231,7 +231,7 @@ def body(state: AppState, json_mode: bool) -> None:
231231
"(--sandbox goes before the command; or use --env sandbox000).",
232232
)
233233
spoken = _read_text(text)
234-
api_key = config.resolve_api_key(profile=state.profile)
234+
api_key = state.resolve_api_key()
235235
bare_voice, overrides = dialogue.parse_voice_overrides(voice)
236236
if dialogue.looks_like_speaker_labeled(spoken):
237237
_speak_dialogue(

aai_cli/commands/stream.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
choices,
1111
client,
1212
code_gen,
13-
config,
1413
config_builder,
1514
help_panels,
1615
llm,
@@ -430,7 +429,7 @@ def body(state: AppState, json_mode: bool) -> None:
430429
validate_sources(opts, has_llm=bool(llm_prompt), text_mode=text_mode)
431430
if opts.from_file and not opts.from_stdin:
432431
client.resolve_audio_source(opts.source, sample=opts.sample)
433-
api_key = config.resolve_api_key(profile=state.profile)
432+
api_key = state.resolve_api_key()
434433

435434
llm_prompts = list(llm_prompt or [])
436435
session = StreamSession(

aai_cli/commands/transcribe.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
choices,
1010
client,
1111
code_gen,
12-
config,
1312
config_builder,
1413
help_panels,
1514
llm,
@@ -429,7 +428,7 @@ def body(state: AppState, json_mode: bool) -> None:
429428
out=out, output_field=output_field, llm_prompt=llm_prompt, show_code=show_code
430429
)
431430
transcribe_batch.run_batch(
432-
config.resolve_api_key(profile=state.profile),
431+
state.resolve_api_key(),
433432
sources,
434433
transcription_config=config_builder.construct_transcription_config(merged),
435434
concurrency=concurrency,
@@ -466,7 +465,7 @@ def body(state: AppState, json_mode: bool) -> None:
466465
transcribe_exec.check_source_exists(source, sample=sample)
467466
transcribe_exec.warn_unrecognized_extension(source, json_mode=json_mode, quiet=state.quiet)
468467

469-
api_key = config.resolve_api_key(profile=state.profile)
468+
api_key = state.resolve_api_key()
470469
with output.status("Transcribing…", json_mode=json_mode, quiet=state.quiet):
471470
transcript = transcribe_exec.run_transcription(
472471
api_key,

0 commit comments

Comments
 (0)