claude-code-tools 1.19.4__tar.gz → 1.19.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/PKG-INFO +1 -1
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/__init__.py +1 -1
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/voice_type/app.py +5 -2
- claude_code_tools-1.19.5/claude_code_tools/voice_type/inject.py +167 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/voice_type/overlay.py +80 -43
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/pyproject.toml +2 -2
- claude_code_tools-1.19.4/claude_code_tools/voice_type/inject.py +0 -69
- claude_code_tools-1.19.4/docs/ai-agent-writing-skills.md +0 -500
- claude_code_tools-1.19.4/docs/cc-codex-instructions.md +0 -37
- claude_code_tools-1.19.4/docs/claude-code-chutes.md +0 -138
- claude_code_tools-1.19.4/docs/ensemble-ideas.md +0 -56
- claude_code_tools-1.19.4/docs/example-with-images.md +0 -366
- claude_code_tools-1.19.4/docs/linked-in-20260102.md +0 -33
- claude_code_tools-1.19.4/docs/paper-writing-style-transfer-tools.md +0 -397
- claude_code_tools-1.19.4/docs/reddit-aichat-resume-v2.md +0 -80
- claude_code_tools-1.19.4/docs/reddit-aichat-resume.md +0 -29
- claude_code_tools-1.19.4/docs/reddit-aichat.md +0 -79
- claude_code_tools-1.19.4/docs/reddit-post.md +0 -55
- claude_code_tools-1.19.4/docs/rollover-details.md +0 -67
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/.gitignore +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/LICENSE +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/README.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/action_rpc.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/agent_tunnel/__init__.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/agent_tunnel/backends.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/agent_tunnel/cli.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/agent_tunnel/config.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/agent_tunnel/convert.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/agent_tunnel/discord_bot.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/agent_tunnel/locking.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/agent_tunnel/paths.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/agent_tunnel/registry.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/agent_tunnel/session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/agent_tunnel/store.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/agent_tunnel/tmux.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/agent_tunnel/trust.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/aichat.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/claude_continue.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/codex_continue.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/codex_server.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/codex_server_cli.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/codex_server_fingerprint.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/codex_server_generation.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/codex_server_legacy.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/codex_server_models.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/codex_server_process.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/codex_server_reservation.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/codex_server_retry.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/codex_server_reuse.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/codex_server_state.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/codex_server_supervisor.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/codex_server_worker.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/config.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/csv2gsheet.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/delete_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/dotenv_vault.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/env_safe.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/export_all.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/export_claude_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/export_codex_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/export_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/find_claude_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/find_codex_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/find_original_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/find_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/find_trimmed_sessions.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/fix_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/gdoc2docx.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/gdoc2md.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/gsheet2csv.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/md2gdoc.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/msg/__init__.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/msg/cli.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/msg/hooks.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/msg/models.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/msg/prompt_detect.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/msg/store.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/msg/watcher.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/node_menu_ui.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/port_claude_noise.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/port_claude_to_codex.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/port_codex_flatten.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/port_codex_to_claude.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/port_service.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/resolve_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/resolve_session_names.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/resolve_session_render.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/search_index.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/session_lineage.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/session_menu.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/session_menu_cli.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/session_utils.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/smart_trim.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/smart_trim_core.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/tmux_cli_controller.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/tmux_execution_helpers.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/tmux_remote_controller.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/trim_in_place.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/trim_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/trim_session_claude.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/trim_session_codex.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/voice_type/__init__.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/voice_type/cli.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/voice_type/config.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/voice_type/engine_parakeet.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/voice_type/engine_parakeet_mlx.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/voice_type/engines.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/voice_type/hotkey.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/voice_type/logic.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/workflow_cli.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/workflow_cli_contract.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/workflow_cli_formatting.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/workflow_cli_identity_policy.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/workflow_cli_manifest.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/workflow_cli_projection.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/workflow_cli_rendering.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/workflow_cli_snapshots.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/workflow_cli_store_backends.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/workflow_processes.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/workflow_runs.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/workflow_store_io.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/workflow_validation.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/docs/agent-tunnel-spec.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/docs/claude-code-tmux-tutorials.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/docs/dot-zshrc.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/docs/find-claude-session.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/docs/intercom-spec.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/docs/lmsh.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/docs/local-llm-setup.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/docs/msg-phase-a-plan.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/docs/msg-plan-v2.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/docs/tmux-cli-instructions.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/docs/vault-documentation.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/hatch_build.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/node_ui/action_config.js +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/node_ui/menu.js +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/node_ui/package-lock.json +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/node_ui/package.json +0 -0
|
@@ -15,7 +15,7 @@ from enum import Enum
|
|
|
15
15
|
from typing import Callable
|
|
16
16
|
|
|
17
17
|
from .config import Config
|
|
18
|
-
from .inject import Typist, copy_to_clipboard
|
|
18
|
+
from .inject import SoundPlayer, Typist, copy_to_clipboard
|
|
19
19
|
from .logic import (
|
|
20
20
|
collapse_repeats,
|
|
21
21
|
contains_phrase,
|
|
@@ -44,6 +44,9 @@ class VoiceTypeApp:
|
|
|
44
44
|
def __init__(self, cfg: Config) -> None:
|
|
45
45
|
self.cfg = cfg
|
|
46
46
|
self.typist = Typist()
|
|
47
|
+
# Preload the chimes so the first one is instant and in sync
|
|
48
|
+
# with the overlay (afplay would lag ~100 ms behind the pill).
|
|
49
|
+
self._sound = SoundPlayer(cfg.sound_start, cfg.sound_stop)
|
|
47
50
|
self._lock = threading.Lock()
|
|
48
51
|
self._state = (
|
|
49
52
|
State.ACTIVE
|
|
@@ -140,7 +143,7 @@ class VoiceTypeApp:
|
|
|
140
143
|
if pre_msg is not None:
|
|
141
144
|
self._status(pre_msg)
|
|
142
145
|
if self.cfg.sounds and State.ACTIVE in (old, new):
|
|
143
|
-
|
|
146
|
+
self._sound.play(
|
|
144
147
|
self.cfg.sound_start
|
|
145
148
|
if new == State.ACTIVE
|
|
146
149
|
else self.cfg.sound_stop
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Keystroke injection and audio feedback.
|
|
2
|
+
|
|
3
|
+
Typing goes through pynput's ``keyboard.Controller``, which synthesizes
|
|
4
|
+
key events into whatever application currently has focus. On macOS the
|
|
5
|
+
process running voice-type (your terminal) needs Accessibility permission
|
|
6
|
+
(System Settings > Privacy & Security > Accessibility).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
_SYSTEM_SOUNDS = Path("/System/Library/Sounds")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Typist:
|
|
19
|
+
"""Types text into the focused application via synthetic key events."""
|
|
20
|
+
|
|
21
|
+
def __init__(self) -> None:
|
|
22
|
+
from pynput.keyboard import Controller
|
|
23
|
+
|
|
24
|
+
self._keyboard = Controller()
|
|
25
|
+
|
|
26
|
+
def type_text(self, text: str) -> None:
|
|
27
|
+
"""Type ``text`` at the current cursor position."""
|
|
28
|
+
self._keyboard.type(text)
|
|
29
|
+
|
|
30
|
+
def press_enter(self) -> None:
|
|
31
|
+
"""Press Enter in the focused application."""
|
|
32
|
+
from pynput.keyboard import Key
|
|
33
|
+
|
|
34
|
+
self._keyboard.tap(Key.enter)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _resolve_sound(name: str) -> Path | None:
|
|
38
|
+
"""Map a sound name or path to an existing file, or None."""
|
|
39
|
+
if not name:
|
|
40
|
+
return None
|
|
41
|
+
p = Path(name) if "/" in name else _SYSTEM_SOUNDS / f"{name}.aiff"
|
|
42
|
+
return p if p.exists() else None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def play_sound(name: str) -> None:
|
|
46
|
+
"""Play a sound by spawning ``afplay`` (the fallback path).
|
|
47
|
+
|
|
48
|
+
Higher-latency than :class:`SoundPlayer` because afplay's own
|
|
49
|
+
CoreAudio startup delays audio-out ~100 ms; used only when the
|
|
50
|
+
preloaded AudioServices path is unavailable. No-ops off macOS / on
|
|
51
|
+
missing files.
|
|
52
|
+
"""
|
|
53
|
+
sound = _resolve_sound(name) if sys.platform == "darwin" else None
|
|
54
|
+
if sound is None:
|
|
55
|
+
return
|
|
56
|
+
subprocess.Popen(
|
|
57
|
+
["afplay", str(sound)],
|
|
58
|
+
stdout=subprocess.DEVNULL,
|
|
59
|
+
stderr=subprocess.DEVNULL,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class SoundPlayer:
|
|
64
|
+
"""Instant start/stop sounds via CoreAudio ``AudioServices``.
|
|
65
|
+
|
|
66
|
+
Spawning ``afplay`` per sound delays audio-out ~100 ms behind the
|
|
67
|
+
in-process overlay, so the chime lands visibly after the waveform
|
|
68
|
+
pill. AudioServices plays a *preloaded* system-sound id in well
|
|
69
|
+
under a millisecond with no run loop, keeping sound and pill in
|
|
70
|
+
sync. Sounds are registered once at construction; playback is
|
|
71
|
+
thread-safe and asynchronous. Falls back to ``afplay`` when the
|
|
72
|
+
framework can't be loaded, and no-ops off macOS.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(self, *names: str) -> None:
|
|
76
|
+
self._ids: dict[str, int] = {}
|
|
77
|
+
self._at = None
|
|
78
|
+
self._cf = None
|
|
79
|
+
self._ctypes = None
|
|
80
|
+
if sys.platform == "darwin":
|
|
81
|
+
try:
|
|
82
|
+
import ctypes
|
|
83
|
+
import ctypes.util
|
|
84
|
+
|
|
85
|
+
self._ctypes = ctypes
|
|
86
|
+
at = ctypes.CDLL(ctypes.util.find_library("AudioToolbox"))
|
|
87
|
+
cf = ctypes.CDLL(
|
|
88
|
+
ctypes.util.find_library("CoreFoundation")
|
|
89
|
+
)
|
|
90
|
+
cf.CFURLCreateFromFileSystemRepresentation.restype = (
|
|
91
|
+
ctypes.c_void_p
|
|
92
|
+
)
|
|
93
|
+
cf.CFURLCreateFromFileSystemRepresentation.argtypes = [
|
|
94
|
+
ctypes.c_void_p,
|
|
95
|
+
ctypes.c_char_p,
|
|
96
|
+
ctypes.c_long,
|
|
97
|
+
ctypes.c_bool,
|
|
98
|
+
]
|
|
99
|
+
cf.CFRelease.argtypes = [ctypes.c_void_p]
|
|
100
|
+
at.AudioServicesCreateSystemSoundID.restype = (
|
|
101
|
+
ctypes.c_int32
|
|
102
|
+
)
|
|
103
|
+
at.AudioServicesCreateSystemSoundID.argtypes = [
|
|
104
|
+
ctypes.c_void_p,
|
|
105
|
+
ctypes.POINTER(ctypes.c_uint32),
|
|
106
|
+
]
|
|
107
|
+
at.AudioServicesPlaySystemSound.argtypes = [
|
|
108
|
+
ctypes.c_uint32
|
|
109
|
+
]
|
|
110
|
+
self._at, self._cf = at, cf
|
|
111
|
+
except Exception:
|
|
112
|
+
self._at = None
|
|
113
|
+
for name in names:
|
|
114
|
+
self._sid(name) # preload so the first play is instant
|
|
115
|
+
|
|
116
|
+
def _sid(self, name: str) -> int | None:
|
|
117
|
+
"""Return the (cached) system-sound id for ``name``, or None."""
|
|
118
|
+
if not name or self._at is None:
|
|
119
|
+
return None
|
|
120
|
+
if name in self._ids:
|
|
121
|
+
return self._ids[name]
|
|
122
|
+
path = _resolve_sound(name)
|
|
123
|
+
if path is None:
|
|
124
|
+
return None
|
|
125
|
+
try:
|
|
126
|
+
raw = str(path).encode()
|
|
127
|
+
url = self._cf.CFURLCreateFromFileSystemRepresentation(
|
|
128
|
+
None, raw, len(raw), False
|
|
129
|
+
)
|
|
130
|
+
if not url:
|
|
131
|
+
return None
|
|
132
|
+
sid = self._ctypes.c_uint32(0)
|
|
133
|
+
err = self._at.AudioServicesCreateSystemSoundID(
|
|
134
|
+
url, self._ctypes.byref(sid)
|
|
135
|
+
)
|
|
136
|
+
self._cf.CFRelease(url)
|
|
137
|
+
if err != 0 or sid.value == 0:
|
|
138
|
+
return None
|
|
139
|
+
self._ids[name] = sid.value
|
|
140
|
+
return sid.value
|
|
141
|
+
except Exception:
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
def play(self, name: str) -> None:
|
|
145
|
+
"""Play ``name`` instantly; fall back to afplay if needed."""
|
|
146
|
+
if not name or sys.platform != "darwin":
|
|
147
|
+
return
|
|
148
|
+
sid = self._sid(name)
|
|
149
|
+
if sid is not None:
|
|
150
|
+
try:
|
|
151
|
+
self._at.AudioServicesPlaySystemSound(sid)
|
|
152
|
+
return
|
|
153
|
+
except Exception:
|
|
154
|
+
pass
|
|
155
|
+
play_sound(name)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def copy_to_clipboard(text: str) -> None:
|
|
159
|
+
"""Put ``text`` on the macOS clipboard (no-op elsewhere/on failure)."""
|
|
160
|
+
if sys.platform != "darwin" or not text:
|
|
161
|
+
return
|
|
162
|
+
try:
|
|
163
|
+
subprocess.run(
|
|
164
|
+
["pbcopy"], input=text.encode(), check=False, timeout=5
|
|
165
|
+
)
|
|
166
|
+
except Exception:
|
|
167
|
+
pass
|
{claude_code_tools-1.19.4 → claude_code_tools-1.19.5}/claude_code_tools/voice_type/overlay.py
RENAMED
|
@@ -21,11 +21,14 @@ from typing import Callable
|
|
|
21
21
|
SampleFn = Callable[[], tuple[str, bool, float]]
|
|
22
22
|
TickFn = Callable[[], None]
|
|
23
23
|
|
|
24
|
-
_WIDTH =
|
|
25
|
-
_HEIGHT =
|
|
26
|
-
_MARGIN_BOTTOM =
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
_WIDTH = 460.0
|
|
25
|
+
_HEIGHT = 92.0
|
|
26
|
+
_MARGIN_BOTTOM = 110.0
|
|
27
|
+
# Recent audio-level history (drives the local vibration envelope) and
|
|
28
|
+
# the number of points the string is rendered with (smoothness).
|
|
29
|
+
_HISTORY = 96
|
|
30
|
+
_RENDER = 190
|
|
31
|
+
_TICK_SECONDS = 0.033 # ~30 fps, smooth string motion
|
|
29
32
|
|
|
30
33
|
|
|
31
34
|
def overlay_available() -> bool:
|
|
@@ -66,60 +69,94 @@ def run_overlay(sample: SampleFn, tick: TickFn, stopped: Callable[[], bool]) ->
|
|
|
66
69
|
)
|
|
67
70
|
|
|
68
71
|
class WaveView(AppKit.NSView): # noqa: D401
|
|
69
|
-
"""
|
|
72
|
+
"""A translucent string that lies flat and vibrates with the voice.
|
|
73
|
+
|
|
74
|
+
The line is pinned at both ends (like a plucked string) and its
|
|
75
|
+
oscillation amplitude tracks the live, gain-adjusted mic level:
|
|
76
|
+
silence reads as a near-flat line, speech makes it ripple. The
|
|
77
|
+
recent-level history modulates the amplitude ALONG the string
|
|
78
|
+
(and scrolls), so the shape reflects the actual audio rather
|
|
79
|
+
than a fixed animation.
|
|
80
|
+
"""
|
|
70
81
|
|
|
71
82
|
def initWithFrame_(self, frame): # noqa: ANN001, ANN201, N802
|
|
72
83
|
self = objc.super(WaveView, self).initWithFrame_(frame)
|
|
73
84
|
if self is None:
|
|
74
85
|
return None
|
|
75
|
-
self.levels = [0.0] *
|
|
86
|
+
self.levels = [0.0] * _HISTORY
|
|
87
|
+
self.phase = 0.0
|
|
76
88
|
self.recording = False
|
|
77
|
-
self.label = ""
|
|
78
89
|
return self
|
|
79
90
|
|
|
80
91
|
def push_(self, sample): # noqa: ANN001, ANN201, N802
|
|
81
|
-
|
|
82
|
-
|
|
92
|
+
_label, recording, level = sample
|
|
93
|
+
level = max(0.0, min(1.0, float(level)))
|
|
94
|
+
# Mild smoothing so the string settles rather than jitters.
|
|
95
|
+
prev = self.levels[-1]
|
|
96
|
+
smoothed = prev + 0.5 * (level - prev)
|
|
97
|
+
self.levels = self.levels[1:] + [smoothed]
|
|
98
|
+
self.phase += 0.42 # advance the travelling wave each frame
|
|
83
99
|
self.recording = bool(recording)
|
|
84
|
-
self.label = str(label)
|
|
85
100
|
self.setNeedsDisplay_(True)
|
|
86
101
|
|
|
102
|
+
def _string_path(self, width, mid, max_amp): # noqa: ANN001, ANN202
|
|
103
|
+
import math
|
|
104
|
+
|
|
105
|
+
path = NSBezierPath.bezierPath()
|
|
106
|
+
path.setLineJoinStyle_(AppKit.NSLineJoinStyleRound)
|
|
107
|
+
path.setLineCapStyle_(AppKit.NSLineCapStyleRound)
|
|
108
|
+
n = len(self.levels)
|
|
109
|
+
for i in range(_RENDER + 1):
|
|
110
|
+
u = i / _RENDER # 0..1 along the string
|
|
111
|
+
# local amplitude from the level history at this point
|
|
112
|
+
fpos = u * (n - 1)
|
|
113
|
+
lo = int(fpos)
|
|
114
|
+
frac = fpos - lo
|
|
115
|
+
lvl = self.levels[lo]
|
|
116
|
+
if lo + 1 < n:
|
|
117
|
+
lvl += frac * (self.levels[lo + 1] - lvl)
|
|
118
|
+
window = math.sin(math.pi * u) # pin both ends to flat
|
|
119
|
+
amp = lvl * max_amp * window
|
|
120
|
+
disp = amp * (
|
|
121
|
+
math.sin(u * 7.0 * math.pi + self.phase)
|
|
122
|
+
+ 0.4 * math.sin(u * 13.0 * math.pi - 1.7 * self.phase)
|
|
123
|
+
) / 1.4
|
|
124
|
+
x = u * width
|
|
125
|
+
y = mid + disp
|
|
126
|
+
if i == 0:
|
|
127
|
+
path.moveToPoint_((x, y))
|
|
128
|
+
else:
|
|
129
|
+
path.lineToPoint_((x, y))
|
|
130
|
+
return path
|
|
131
|
+
|
|
87
132
|
def drawRect_(self, rect): # noqa: ANN001, ANN201, N802
|
|
88
133
|
b = self.bounds()
|
|
89
|
-
|
|
90
|
-
|
|
134
|
+
# Ultra-faint rounded backdrop: just enough to read the
|
|
135
|
+
# string over any wallpaper, without looking like a panel.
|
|
136
|
+
pad = 6.0
|
|
137
|
+
back = NSMakeRect(
|
|
138
|
+
pad, pad, b.size.width - 2 * pad, b.size.height - 2 * pad
|
|
91
139
|
)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
else:
|
|
99
|
-
bar_color = NSColor.colorWithCalibratedWhite_alpha_(
|
|
100
|
-
0.62, 0.9
|
|
101
|
-
)
|
|
102
|
-
bar_color.setFill()
|
|
103
|
-
# state dot on the left
|
|
104
|
-
dot = 8.0
|
|
105
|
-
dot_rect = NSMakeRect(
|
|
106
|
-
14.0, (b.size.height - dot) / 2, dot, dot
|
|
107
|
-
)
|
|
108
|
-
NSBezierPath.bezierPathWithOvalInRect_(dot_rect).fill()
|
|
109
|
-
# scrolling waveform bars, mirrored around the centerline
|
|
110
|
-
left = 30.0
|
|
111
|
-
right = 14.0
|
|
112
|
-
span = b.size.width - left - right
|
|
113
|
-
bw = span / _BARS
|
|
140
|
+
r = back.size.height / 2
|
|
141
|
+
NSColor.colorWithCalibratedWhite_alpha_(0.0, 0.16).setFill()
|
|
142
|
+
NSBezierPath.bezierPathWithRoundedRect_xRadius_yRadius_(
|
|
143
|
+
back, r, r
|
|
144
|
+
).fill()
|
|
145
|
+
|
|
114
146
|
mid = b.size.height / 2
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
147
|
+
max_amp = b.size.height / 2 - 16.0
|
|
148
|
+
path = self._string_path(b.size.width, mid, max_amp)
|
|
149
|
+
# soft glow behind, then the crisp translucent string
|
|
150
|
+
NSColor.colorWithCalibratedRed_green_blue_alpha_(
|
|
151
|
+
1.0, 0.30, 0.26, 0.16
|
|
152
|
+
).setStroke()
|
|
153
|
+
path.setLineWidth_(7.0)
|
|
154
|
+
path.stroke()
|
|
155
|
+
NSColor.colorWithCalibratedRed_green_blue_alpha_(
|
|
156
|
+
1.0, 0.42, 0.38, 0.55
|
|
157
|
+
).setStroke()
|
|
158
|
+
path.setLineWidth_(2.0)
|
|
159
|
+
path.stroke()
|
|
123
160
|
|
|
124
161
|
class Driver(AppKit.NSObject):
|
|
125
162
|
"""NSTimer target: samples audio, ticks the app, ends the loop.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "claude-code-tools"
|
|
3
|
-
version = "1.19.
|
|
3
|
+
version = "1.19.5"
|
|
4
4
|
description = "Collection of tools for working with Claude Code"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -131,7 +131,7 @@ markers = [
|
|
|
131
131
|
|
|
132
132
|
[tool.commitizen]
|
|
133
133
|
name = "cz_conventional_commits"
|
|
134
|
-
version = "1.19.
|
|
134
|
+
version = "1.19.5"
|
|
135
135
|
tag_format = "v$version"
|
|
136
136
|
version_files = [
|
|
137
137
|
"pyproject.toml:version",
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
"""Keystroke injection and audio feedback.
|
|
2
|
-
|
|
3
|
-
Typing goes through pynput's ``keyboard.Controller``, which synthesizes
|
|
4
|
-
key events into whatever application currently has focus. On macOS the
|
|
5
|
-
process running voice-type (your terminal) needs Accessibility permission
|
|
6
|
-
(System Settings > Privacy & Security > Accessibility).
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
import subprocess
|
|
12
|
-
import sys
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
|
|
15
|
-
_SYSTEM_SOUNDS = Path("/System/Library/Sounds")
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class Typist:
|
|
19
|
-
"""Types text into the focused application via synthetic key events."""
|
|
20
|
-
|
|
21
|
-
def __init__(self) -> None:
|
|
22
|
-
from pynput.keyboard import Controller
|
|
23
|
-
|
|
24
|
-
self._keyboard = Controller()
|
|
25
|
-
|
|
26
|
-
def type_text(self, text: str) -> None:
|
|
27
|
-
"""Type ``text`` at the current cursor position."""
|
|
28
|
-
self._keyboard.type(text)
|
|
29
|
-
|
|
30
|
-
def press_enter(self) -> None:
|
|
31
|
-
"""Press Enter in the focused application."""
|
|
32
|
-
from pynput.keyboard import Key
|
|
33
|
-
|
|
34
|
-
self._keyboard.tap(Key.enter)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def play_sound(name: str) -> None:
|
|
38
|
-
"""Play a named macOS system sound or a sound file path.
|
|
39
|
-
|
|
40
|
-
``name`` is either a system sound name (e.g. "Glass", "Bottle" —
|
|
41
|
-
see /System/Library/Sounds) or an absolute path to an audio file.
|
|
42
|
-
Silently no-ops off macOS, on empty names, and on missing files.
|
|
43
|
-
"""
|
|
44
|
-
if sys.platform != "darwin" or not name:
|
|
45
|
-
return
|
|
46
|
-
sound = (
|
|
47
|
-
Path(name)
|
|
48
|
-
if "/" in name
|
|
49
|
-
else _SYSTEM_SOUNDS / f"{name}.aiff"
|
|
50
|
-
)
|
|
51
|
-
if not sound.exists():
|
|
52
|
-
return
|
|
53
|
-
subprocess.Popen(
|
|
54
|
-
["afplay", str(sound)],
|
|
55
|
-
stdout=subprocess.DEVNULL,
|
|
56
|
-
stderr=subprocess.DEVNULL,
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def copy_to_clipboard(text: str) -> None:
|
|
61
|
-
"""Put ``text`` on the macOS clipboard (no-op elsewhere/on failure)."""
|
|
62
|
-
if sys.platform != "darwin" or not text:
|
|
63
|
-
return
|
|
64
|
-
try:
|
|
65
|
-
subprocess.run(
|
|
66
|
-
["pbcopy"], input=text.encode(), check=False, timeout=5
|
|
67
|
-
)
|
|
68
|
-
except Exception:
|
|
69
|
-
pass
|