claude-code-tools 1.19.4__tar.gz → 1.19.6__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.6}/PKG-INFO +1 -1
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/__init__.py +1 -1
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/voice_type/app.py +5 -2
- claude_code_tools-1.19.6/claude_code_tools/voice_type/inject.py +167 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/voice_type/overlay.py +81 -43
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/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.6}/.gitignore +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/LICENSE +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/README.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/action_rpc.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/agent_tunnel/__init__.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/agent_tunnel/backends.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/agent_tunnel/cli.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/agent_tunnel/config.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/agent_tunnel/convert.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/agent_tunnel/discord_bot.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/agent_tunnel/locking.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/agent_tunnel/paths.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/agent_tunnel/registry.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/agent_tunnel/session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/agent_tunnel/store.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/agent_tunnel/tmux.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/agent_tunnel/trust.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/aichat.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/claude_continue.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/codex_continue.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/codex_server.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/codex_server_cli.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/codex_server_fingerprint.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/codex_server_generation.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/codex_server_legacy.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/codex_server_models.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/codex_server_process.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/codex_server_reservation.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/codex_server_retry.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/codex_server_reuse.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/codex_server_state.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/codex_server_supervisor.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/codex_server_worker.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/config.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/csv2gsheet.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/delete_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/dotenv_vault.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/env_safe.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/export_all.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/export_claude_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/export_codex_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/export_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/find_claude_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/find_codex_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/find_original_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/find_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/find_trimmed_sessions.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/fix_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/gdoc2docx.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/gdoc2md.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/gsheet2csv.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/md2gdoc.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/msg/__init__.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/msg/cli.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/msg/hooks.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/msg/models.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/msg/prompt_detect.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/msg/store.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/msg/watcher.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/node_menu_ui.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/port_claude_noise.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/port_claude_to_codex.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/port_codex_flatten.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/port_codex_to_claude.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/port_service.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/resolve_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/resolve_session_names.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/resolve_session_render.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/search_index.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/session_lineage.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/session_menu.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/session_menu_cli.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/session_utils.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/smart_trim.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/smart_trim_core.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/tmux_cli_controller.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/tmux_execution_helpers.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/tmux_remote_controller.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/trim_in_place.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/trim_session.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/trim_session_claude.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/trim_session_codex.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/voice_type/__init__.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/voice_type/cli.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/voice_type/config.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/voice_type/engine_parakeet.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/voice_type/engine_parakeet_mlx.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/voice_type/engines.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/voice_type/hotkey.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/voice_type/logic.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/workflow_cli.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/workflow_cli_contract.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/workflow_cli_formatting.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/workflow_cli_identity_policy.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/workflow_cli_manifest.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/workflow_cli_projection.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/workflow_cli_rendering.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/workflow_cli_snapshots.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/workflow_cli_store_backends.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/workflow_processes.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/workflow_runs.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/workflow_store_io.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/claude_code_tools/workflow_validation.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/docs/agent-tunnel-spec.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/docs/claude-code-tmux-tutorials.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/docs/dot-zshrc.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/docs/find-claude-session.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/docs/intercom-spec.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/docs/lmsh.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/docs/local-llm-setup.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/docs/msg-phase-a-plan.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/docs/msg-plan-v2.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/docs/tmux-cli-instructions.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/docs/vault-documentation.md +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/hatch_build.py +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/node_ui/action_config.js +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/node_ui/menu.js +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/node_ui/package-lock.json +0 -0
- {claude_code_tools-1.19.4 → claude_code_tools-1.19.6}/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.6}/claude_code_tools/voice_type/overlay.py
RENAMED
|
@@ -21,11 +21,12 @@ 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 = 200.0
|
|
25
|
+
_HEIGHT = 150.0
|
|
26
|
+
_MARGIN_BOTTOM = 120.0
|
|
27
|
+
# Points around the blob's rim (smoothness) and frame rate.
|
|
28
|
+
_BLOB_POINTS = 108
|
|
29
|
+
_TICK_SECONDS = 0.033 # ~30 fps, smooth wobble
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
def overlay_available() -> bool:
|
|
@@ -59,6 +60,7 @@ def run_overlay(sample: SampleFn, tick: TickFn, stopped: Callable[[], bool]) ->
|
|
|
59
60
|
NSBackingStoreBuffered,
|
|
60
61
|
NSBezierPath,
|
|
61
62
|
NSColor,
|
|
63
|
+
NSGradient,
|
|
62
64
|
NSMakeRect,
|
|
63
65
|
NSPanel,
|
|
64
66
|
NSScreen,
|
|
@@ -66,60 +68,96 @@ def run_overlay(sample: SampleFn, tick: TickFn, stopped: Callable[[], bool]) ->
|
|
|
66
68
|
)
|
|
67
69
|
|
|
68
70
|
class WaveView(AppKit.NSView): # noqa: D401
|
|
69
|
-
"""
|
|
71
|
+
"""A glowing blue blob that wobbles and swells with the voice.
|
|
72
|
+
|
|
73
|
+
A soft orb sits at the bottom of the screen: it breathes gently
|
|
74
|
+
in silence and, as the live gain-adjusted mic level rises, its
|
|
75
|
+
rim deforms (organic multi-harmonic wobble) and the whole blob
|
|
76
|
+
swells — so its shape reflects the actual audio. Bluish, with a
|
|
77
|
+
radial gradient core, a rim highlight, and an outer halo for
|
|
78
|
+
presence.
|
|
79
|
+
"""
|
|
70
80
|
|
|
71
81
|
def initWithFrame_(self, frame): # noqa: ANN001, ANN201, N802
|
|
72
82
|
self = objc.super(WaveView, self).initWithFrame_(frame)
|
|
73
83
|
if self is None:
|
|
74
84
|
return None
|
|
75
|
-
self.
|
|
85
|
+
self.amp = 0.0 # smoothed audio level driving the wobble
|
|
86
|
+
self.phase = 0.0
|
|
76
87
|
self.recording = False
|
|
77
|
-
self.label = ""
|
|
78
88
|
return self
|
|
79
89
|
|
|
80
90
|
def push_(self, sample): # noqa: ANN001, ANN201, N802
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
_label, recording, level = sample
|
|
92
|
+
level = max(0.0, min(1.0, float(level)))
|
|
93
|
+
# Fast attack, slow release: pops on speech, settles gently.
|
|
94
|
+
k = 0.55 if level > self.amp else 0.15
|
|
95
|
+
self.amp += k * (level - self.amp)
|
|
96
|
+
self.phase += 0.16
|
|
83
97
|
self.recording = bool(recording)
|
|
84
|
-
self.label = str(label)
|
|
85
98
|
self.setNeedsDisplay_(True)
|
|
86
99
|
|
|
100
|
+
def _blob_path(self, cx, cy, base_r): # noqa: ANN001, ANN202
|
|
101
|
+
import math
|
|
102
|
+
|
|
103
|
+
path = NSBezierPath.bezierPath()
|
|
104
|
+
wob = 0.09 + 0.42 * self.amp # rim deformation depth
|
|
105
|
+
breathe = 0.05 * math.sin(self.phase * 0.5)
|
|
106
|
+
swell = 1.0 + 0.24 * self.amp # overall growth with volume
|
|
107
|
+
for i in range(_BLOB_POINTS + 1):
|
|
108
|
+
a = 2.0 * math.pi * i / _BLOB_POINTS
|
|
109
|
+
shape = (
|
|
110
|
+
math.sin(3 * a + self.phase)
|
|
111
|
+
+ 0.6 * math.sin(5 * a - 1.3 * self.phase)
|
|
112
|
+
+ 0.3 * math.sin(7 * a + 1.7 * self.phase)
|
|
113
|
+
) / 1.9
|
|
114
|
+
r = base_r * swell * (1.0 + breathe + wob * shape)
|
|
115
|
+
x = cx + r * math.cos(a)
|
|
116
|
+
y = cy + r * math.sin(a)
|
|
117
|
+
if i == 0:
|
|
118
|
+
path.moveToPoint_((x, y))
|
|
119
|
+
else:
|
|
120
|
+
path.lineToPoint_((x, y))
|
|
121
|
+
path.closePath()
|
|
122
|
+
return path
|
|
123
|
+
|
|
87
124
|
def drawRect_(self, rect): # noqa: ANN001, ANN201, N802
|
|
88
125
|
b = self.bounds()
|
|
89
|
-
|
|
90
|
-
|
|
126
|
+
cx, cy = b.size.width / 2, b.size.height / 2
|
|
127
|
+
base_r = min(b.size.width, b.size.height) * 0.26
|
|
128
|
+
|
|
129
|
+
# outer halo for presence
|
|
130
|
+
halo = self._blob_path(cx, cy, base_r * 1.28)
|
|
131
|
+
NSColor.colorWithCalibratedRed_green_blue_alpha_(
|
|
132
|
+
0.30, 0.62, 1.0, 0.20
|
|
133
|
+
).setFill()
|
|
134
|
+
halo.fill()
|
|
135
|
+
|
|
136
|
+
# the blob: radial gradient core -> deeper blue rim
|
|
137
|
+
blob = self._blob_path(cx, cy, base_r)
|
|
138
|
+
core = NSColor.colorWithCalibratedRed_green_blue_alpha_(
|
|
139
|
+
0.60, 0.85, 1.0, 0.85
|
|
91
140
|
)
|
|
92
|
-
NSColor.
|
|
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
|
|
141
|
+
edge = NSColor.colorWithCalibratedRed_green_blue_alpha_(
|
|
142
|
+
0.16, 0.44, 0.98, 0.70
|
|
143
|
+
)
|
|
144
|
+
grad = NSGradient.alloc().initWithStartingColor_endingColor_(
|
|
145
|
+
core, edge
|
|
107
146
|
)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
).fill()
|
|
147
|
+
if grad is not None:
|
|
148
|
+
grad.drawInBezierPath_relativeCenterPosition_(
|
|
149
|
+
blob, (0.0, 0.28)
|
|
150
|
+
)
|
|
151
|
+
else: # pragma: no cover - gradient unavailable
|
|
152
|
+
edge.setFill()
|
|
153
|
+
blob.fill()
|
|
154
|
+
|
|
155
|
+
# bright rim
|
|
156
|
+
NSColor.colorWithCalibratedRed_green_blue_alpha_(
|
|
157
|
+
0.75, 0.92, 1.0, 0.9
|
|
158
|
+
).setStroke()
|
|
159
|
+
blob.setLineWidth_(2.0)
|
|
160
|
+
blob.stroke()
|
|
123
161
|
|
|
124
162
|
class Driver(AppKit.NSObject):
|
|
125
163
|
"""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.6"
|
|
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.6"
|
|
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
|