style: drop inline comments, trim docstring periods
remove inline comments (CLAUDE.md: docstrings only), strip trailing periods from single-line docstrings, and fix a PulseArmy->PulseAudio typo. no behavior change. Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
parent
bf516143b5
commit
7f4a6f6699
@ -1,3 +1,3 @@
|
||||
"""claudedo — voice-control daemon for claude code (local STT -> tmux send-keys)."""
|
||||
"""claudedo — voice-control daemon for claude code (local STT -> tmux send-keys)"""
|
||||
|
||||
__version__ = "0.1.0"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
"""claudedo CLI: start | stop | status | test-audio | install."""
|
||||
"""claudedo CLI: start | stop | status | test-audio | install"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -95,7 +95,7 @@ def cmd_test_audio(args: argparse.Namespace) -> int:
|
||||
config.samplerate, config.channels, device,
|
||||
held=_timed_hold(2.0), max_utterance=3.0, min_utterance=0.0,
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001 — surface any capture failure to the user
|
||||
except Exception as exc:
|
||||
print(f"\naudio capture FAILED: {exc}", file=sys.stderr)
|
||||
print("fix-chain: install.sh apt deps + ~/.asoundrc pulse shim + Windows mic permission",
|
||||
file=sys.stderr)
|
||||
|
||||
@ -6,7 +6,7 @@ a concrete sounddevice input device. two capture paths:
|
||||
utterance (no streaming STT; chunk-on-silence is enough for commands).
|
||||
- record_while(predicate): ptt mode — capture while predicate() is true (key held).
|
||||
|
||||
the WSLg/PulseArmy path is verified separately by `claudedo test-audio`; if capture
|
||||
the WSLg/PulseAudio path is verified separately by `claudedo test-audio`; if capture
|
||||
fails here the fix-chain is the apt deps + ~/.asoundrc + Windows mic permission.
|
||||
"""
|
||||
|
||||
@ -23,11 +23,11 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AudioError(Exception):
|
||||
"""raised when no usable input device is found or capture fails."""
|
||||
"""raised when no usable input device is found or capture fails"""
|
||||
|
||||
|
||||
def list_devices() -> list[dict]:
|
||||
"""return sounddevice's device table (for test-audio / debugging)."""
|
||||
"""return sounddevice's device table (for test-audio / debugging)"""
|
||||
import sounddevice as sd
|
||||
|
||||
return list(sd.query_devices())
|
||||
@ -121,7 +121,7 @@ def record_until_silence(samplerate: int, channels: int, device: int | None,
|
||||
def record_while(samplerate: int, channels: int, device: int | None,
|
||||
held: Callable[[], bool], max_utterance: float,
|
||||
min_utterance: float) -> np.ndarray | None:
|
||||
"""capture while held() is true (push-to-talk). returns mono float32 or None."""
|
||||
"""capture while held() is true (push-to-talk). returns mono float32 or None"""
|
||||
import sounddevice as sd
|
||||
|
||||
block_dur = 0.05
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
"""load and validate config.toml into a typed Config object with clear errors."""
|
||||
"""load and validate config.toml into a typed Config object with clear errors"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -10,7 +10,7 @@ from pathlib import Path
|
||||
try:
|
||||
import tomllib as _toml
|
||||
_TOML_BINARY = True
|
||||
except ModuleNotFoundError: # python < 3.11
|
||||
except ModuleNotFoundError:
|
||||
import tomli as _toml
|
||||
_TOML_BINARY = True
|
||||
|
||||
@ -27,12 +27,12 @@ DEFAULT_CONFIG_PATHS = (
|
||||
|
||||
|
||||
class ConfigError(Exception):
|
||||
"""raised on a missing or invalid configuration value."""
|
||||
"""raised on a missing or invalid configuration value"""
|
||||
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
"""validated claudedo configuration."""
|
||||
"""validated claudedo configuration"""
|
||||
|
||||
wake_phrases: list[str]
|
||||
mode: str
|
||||
@ -53,7 +53,7 @@ class Config:
|
||||
|
||||
|
||||
def find_config_path(explicit: str | os.PathLike | None = None) -> Path:
|
||||
"""resolve the config file path, raising ConfigError if none is found."""
|
||||
"""resolve the config file path, raising ConfigError if none is found"""
|
||||
candidates: list[Path] = []
|
||||
if explicit:
|
||||
candidates.append(Path(explicit))
|
||||
@ -79,7 +79,7 @@ def _require(table: dict, section: str, key: str, types: tuple, default=None):
|
||||
|
||||
|
||||
def load_config(explicit: str | os.PathLike | None = None) -> Config:
|
||||
"""load config.toml from the first existing default path (or an explicit one)."""
|
||||
"""load config.toml from the first existing default path (or an explicit one)"""
|
||||
path = find_config_path(explicit)
|
||||
try:
|
||||
with open(path, "rb") as fh:
|
||||
|
||||
@ -32,7 +32,7 @@ def _ensure_state_dir() -> None:
|
||||
|
||||
|
||||
def write_state(pid: int, mode: str, target_session: str | None) -> None:
|
||||
"""write the running daemon's status for `claudedo status` to read."""
|
||||
"""write the running daemon's status for `claudedo status` to read"""
|
||||
_ensure_state_dir()
|
||||
STATEFILE.write_text(json.dumps({
|
||||
"pid": pid,
|
||||
@ -43,7 +43,7 @@ def write_state(pid: int, mode: str, target_session: str | None) -> None:
|
||||
|
||||
|
||||
def read_state() -> dict | None:
|
||||
"""read the daemon status file, or None if absent/unreadable."""
|
||||
"""read the daemon status file, or None if absent/unreadable"""
|
||||
try:
|
||||
return json.loads(STATEFILE.read_text(encoding="utf-8"))
|
||||
except (FileNotFoundError, json.JSONDecodeError, OSError):
|
||||
@ -51,7 +51,7 @@ def read_state() -> dict | None:
|
||||
|
||||
|
||||
def read_pid() -> int | None:
|
||||
"""return the pid of a running daemon, or None (also clears stale pidfiles)."""
|
||||
"""return the pid of a running daemon, or None (also clears stale pidfiles)"""
|
||||
try:
|
||||
pid = int(PIDFILE.read_text(encoding="utf-8").strip())
|
||||
except (FileNotFoundError, ValueError, OSError):
|
||||
@ -67,7 +67,7 @@ def read_pid() -> int | None:
|
||||
|
||||
|
||||
def stop_running() -> bool:
|
||||
"""signal a running daemon to stop. returns whether one was found."""
|
||||
"""signal a running daemon to stop. returns whether one was found"""
|
||||
pid = read_pid()
|
||||
if pid is None:
|
||||
return False
|
||||
@ -105,7 +105,7 @@ class _PTTKey:
|
||||
|
||||
|
||||
class Daemon:
|
||||
"""owns the capture/transcribe/inject loop and runtime mode switching."""
|
||||
"""owns the capture/transcribe/inject loop and runtime mode switching"""
|
||||
|
||||
def __init__(self, config: Config) -> None:
|
||||
self.config = config
|
||||
@ -186,7 +186,7 @@ class Daemon:
|
||||
write_state(os.getpid(), self.mode, target.read_active())
|
||||
|
||||
def run(self) -> None:
|
||||
"""run the daemon loop until a stop signal arrives."""
|
||||
"""run the daemon loop until a stop signal arrives"""
|
||||
_ensure_state_dir()
|
||||
PIDFILE.write_text(str(os.getpid()), encoding="utf-8")
|
||||
self._install_signals()
|
||||
@ -213,7 +213,7 @@ class Daemon:
|
||||
|
||||
|
||||
def run_daemon(config: Config) -> None:
|
||||
"""entry point used by the CLI ``start`` command."""
|
||||
"""entry point used by the CLI ``start`` command"""
|
||||
if read_pid() is not None:
|
||||
raise RuntimeError("claudedo is already running (see `claudedo status`)")
|
||||
Daemon(config).run()
|
||||
|
||||
@ -42,7 +42,7 @@ class Action:
|
||||
|
||||
|
||||
def normalize(text: str) -> str:
|
||||
"""lowercase, strip punctuation, collapse whitespace, map number words to digits."""
|
||||
"""lowercase, strip punctuation, collapse whitespace, map number words to digits"""
|
||||
text = text.lower().strip()
|
||||
text = _PUNCT.sub(" ", text)
|
||||
text = _WS.sub(" ", text).strip()
|
||||
@ -57,7 +57,7 @@ def _ratio(a: str, b: str) -> float:
|
||||
|
||||
|
||||
def _wake_variants(phrase: str) -> set[str]:
|
||||
"""spaced and despaced forms of a wake phrase for lenient matching."""
|
||||
"""spaced and despaced forms of a wake phrase for lenient matching"""
|
||||
norm = normalize(phrase)
|
||||
return {norm, norm.replace(" ", "")}
|
||||
|
||||
@ -104,7 +104,7 @@ def _fuzzy_in(token: str, options: tuple[str, ...], threshold: float) -> bool:
|
||||
|
||||
|
||||
def match_command(remainder: str, threshold: float) -> Action | None:
|
||||
"""map a normalized command remainder to an Action, or None if unrecognized."""
|
||||
"""map a normalized command remainder to an Action, or None if unrecognized"""
|
||||
remainder = remainder.strip()
|
||||
if not remainder:
|
||||
return None
|
||||
@ -152,7 +152,7 @@ def match_command(remainder: str, threshold: float) -> Action | None:
|
||||
|
||||
def parse(transcript: str, wake_phrases: list[str], threshold: float,
|
||||
require_wake: bool) -> Action | None:
|
||||
"""full parse: wake gate then command match. None means discard."""
|
||||
"""full parse: wake gate then command match. None means discard"""
|
||||
remainder = strip_wake(transcript, wake_phrases, threshold, require_wake)
|
||||
if remainder is None:
|
||||
return None
|
||||
|
||||
@ -14,7 +14,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Transcriber:
|
||||
"""a loaded faster-whisper model that transcribes float32 mono audio chunks."""
|
||||
"""a loaded faster-whisper model that transcribes float32 mono audio chunks"""
|
||||
|
||||
def __init__(self, model: str = "small", language: str = "en", device: str = "auto",
|
||||
compute_type: str = "auto") -> None:
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
"""resolve the active claude code tmux session from ~/.claude-active."""
|
||||
"""resolve the active claude code tmux session from ~/.claude-active"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -26,7 +26,7 @@ def session_name(name: str) -> str:
|
||||
|
||||
|
||||
def read_active() -> str | None:
|
||||
"""return the target session name from ~/.claude-active, or None if unset."""
|
||||
"""return the target session name from ~/.claude-active, or None if unset"""
|
||||
try:
|
||||
name = ACTIVE_FILE.read_text(encoding="utf-8").strip()
|
||||
except FileNotFoundError:
|
||||
@ -38,7 +38,7 @@ def read_active() -> str | None:
|
||||
|
||||
|
||||
def write_active(name: str) -> None:
|
||||
"""overwrite ~/.claude-active with a session name (used by ``switch``)."""
|
||||
"""overwrite ~/.claude-active with a session name (used by ``switch``)"""
|
||||
ACTIVE_FILE.write_text(name + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
@ -51,7 +51,7 @@ def set_target(name: str) -> str:
|
||||
|
||||
|
||||
def session_exists(name: str) -> bool:
|
||||
"""true if a tmux session with this name currently exists."""
|
||||
"""true if a tmux session with this name currently exists"""
|
||||
if not name:
|
||||
return False
|
||||
result = subprocess.run(
|
||||
@ -67,6 +67,13 @@ def resolve_target() -> str | None:
|
||||
|
||||
never guesses a target: on a missing/empty ~/.claude-active or a stale session
|
||||
name, this logs a clear warning and returns None so the caller injects nothing.
|
||||
|
||||
TODO: most-recently-active targeting (preferred over attached). today the target
|
||||
is the project most recently ATTACHED to (the cc kit writes ~/.claude-active on
|
||||
attach); upgrade to the session claude most recently asked a question in, via
|
||||
tmux session_activity timestamps (list-sessions -F '#{session_name}
|
||||
#{session_activity}', pick the highest-activity claude-* session) or by scraping
|
||||
panes (capture-pane) for a waiting-prompt UI.
|
||||
"""
|
||||
name = read_active()
|
||||
if not name:
|
||||
@ -76,13 +83,3 @@ def resolve_target() -> str | None:
|
||||
log.warning("target session %r no longer exists — skipping injection", name)
|
||||
return None
|
||||
return name
|
||||
|
||||
|
||||
# TODO: most-recently-active targeting (preferred over attached). today the target
|
||||
# is "the project most recently ATTACHED to" (the cc kit writes ~/.claude-active on
|
||||
# attach). upgrade to "the session claude most recently asked a question / produced
|
||||
# output in" via tmux session_activity timestamps:
|
||||
# tmux list-sessions -F '#{session_name} #{session_activity}'
|
||||
# pick the highest-activity claude-* session; or scrape panes
|
||||
# (tmux capture-pane -p -t <s>) for a waiting-prompt UI and target the session whose
|
||||
# pane currently shows one.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user