claude-code-tools 1.19.6__tar.gz → 1.19.7__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.6 → claude_code_tools-1.19.7}/PKG-INFO +1 -1
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/__init__.py +1 -1
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/voice_type/app.py +5 -1
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/voice_type/config.py +25 -5
- claude_code_tools-1.19.7/claude_code_tools/voice_type/overlay.py +402 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/pyproject.toml +2 -2
- claude_code_tools-1.19.6/claude_code_tools/voice_type/overlay.py +0 -273
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/.gitignore +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/LICENSE +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/README.md +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/action_rpc.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/__init__.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/backends.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/cli.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/config.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/convert.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/discord_bot.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/locking.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/paths.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/registry.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/session.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/store.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/tmux.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/trust.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/aichat.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/claude_continue.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_continue.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_cli.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_fingerprint.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_generation.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_legacy.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_models.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_process.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_reservation.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_retry.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_reuse.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_state.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_supervisor.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_worker.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/config.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/csv2gsheet.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/delete_session.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/dotenv_vault.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/env_safe.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/export_all.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/export_claude_session.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/export_codex_session.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/export_session.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/find_claude_session.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/find_codex_session.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/find_original_session.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/find_session.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/find_trimmed_sessions.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/fix_session.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/gdoc2docx.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/gdoc2md.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/gsheet2csv.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/md2gdoc.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/msg/__init__.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/msg/cli.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/msg/hooks.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/msg/models.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/msg/prompt_detect.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/msg/store.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/msg/watcher.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/node_menu_ui.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/port_claude_noise.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/port_claude_to_codex.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/port_codex_flatten.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/port_codex_to_claude.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/port_service.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/resolve_session.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/resolve_session_names.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/resolve_session_render.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/search_index.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/session_lineage.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/session_menu.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/session_menu_cli.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/session_utils.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/smart_trim.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/smart_trim_core.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/tmux_cli_controller.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/tmux_execution_helpers.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/tmux_remote_controller.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/trim_in_place.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/trim_session.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/trim_session_claude.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/trim_session_codex.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/voice_type/__init__.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/voice_type/cli.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/voice_type/engine_parakeet.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/voice_type/engine_parakeet_mlx.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/voice_type/engines.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/voice_type/hotkey.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/voice_type/inject.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/voice_type/logic.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_cli.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_cli_contract.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_cli_formatting.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_cli_identity_policy.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_cli_manifest.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_cli_projection.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_cli_rendering.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_cli_snapshots.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_cli_store_backends.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_processes.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_runs.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_store_io.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_validation.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/docs/agent-tunnel-spec.md +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/docs/claude-code-tmux-tutorials.md +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/docs/dot-zshrc.md +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/docs/find-claude-session.md +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/docs/intercom-spec.md +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/docs/lmsh.md +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/docs/local-llm-setup.md +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/docs/msg-phase-a-plan.md +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/docs/msg-plan-v2.md +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/docs/tmux-cli-instructions.md +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/docs/vault-documentation.md +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/hatch_build.py +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/node_ui/action_config.js +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/node_ui/menu.js +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/node_ui/package-lock.json +0 -0
- {claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/node_ui/package.json +0 -0
|
@@ -536,7 +536,11 @@ class VoiceTypeApp:
|
|
|
536
536
|
return state.value, state is State.ACTIVE, level
|
|
537
537
|
|
|
538
538
|
overlay.run_overlay(
|
|
539
|
-
sample,
|
|
539
|
+
sample,
|
|
540
|
+
tick,
|
|
541
|
+
self._stop.is_set,
|
|
542
|
+
flex=self.cfg.overlay_flex,
|
|
543
|
+
speed=self.cfg.overlay_speed,
|
|
540
544
|
)
|
|
541
545
|
return result["code"]
|
|
542
546
|
self._status("overlay unavailable; running without it")
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/voice_type/config.py
RENAMED
|
@@ -55,9 +55,13 @@ class Config:
|
|
|
55
55
|
fastest for v3-int8 on Apple Silicon; 8 helps v2-fp16).
|
|
56
56
|
strip_fillers: Drop standalone filler words (uh, um, ...) from
|
|
57
57
|
typed text.
|
|
58
|
-
overlay: Show the floating
|
|
59
|
-
(macOS only; ignored elsewhere). The
|
|
58
|
+
overlay: Show the floating ghost indicator while recording
|
|
59
|
+
(macOS only; ignored elsewhere). The ghost is hidden while
|
|
60
60
|
paused/passive — its presence IS the recording indicator.
|
|
61
|
+
overlay_flex: How much the ghost's face flexes/wobbles its
|
|
62
|
+
shape (1.0 = default; higher = more flexible, calmer < 1.0).
|
|
63
|
+
overlay_speed: Overall animation speed multiplier (1.0 =
|
|
64
|
+
default; lower = slower/gentler motion).
|
|
61
65
|
model_arch: Moonshine model architecture name (moonshine engine
|
|
62
66
|
only).
|
|
63
67
|
language: Language tag understood by Moonshine (e.g. "en").
|
|
@@ -98,6 +102,8 @@ class Config:
|
|
|
98
102
|
mlx_model: str = "mlx-community/parakeet-tdt-0.6b-v3"
|
|
99
103
|
strip_fillers: bool = True
|
|
100
104
|
overlay: bool = True
|
|
105
|
+
overlay_flex: float = 1.0
|
|
106
|
+
overlay_speed: float = 1.0
|
|
101
107
|
model_arch: str = "medium-streaming"
|
|
102
108
|
language: str = "en"
|
|
103
109
|
hotkey: str = "<ctrl>+;"
|
|
@@ -181,6 +187,16 @@ class Config:
|
|
|
181
187
|
raise ValueError(
|
|
182
188
|
f"{name} must be a string, got {value!r}"
|
|
183
189
|
)
|
|
190
|
+
for name in ("overlay_flex", "overlay_speed"):
|
|
191
|
+
value = getattr(self, name)
|
|
192
|
+
if isinstance(value, bool) or not isinstance(
|
|
193
|
+
value, (int, float)
|
|
194
|
+
):
|
|
195
|
+
raise ValueError(f"{name} must be a number, got {value!r}")
|
|
196
|
+
if not 0.0 <= value <= 5.0:
|
|
197
|
+
raise ValueError(
|
|
198
|
+
f"{name} must be between 0 and 5, got {value!r}"
|
|
199
|
+
)
|
|
184
200
|
if self.mode == "wake" and not self.wake_word.strip():
|
|
185
201
|
raise ValueError('mode "wake" requires a non-empty wake_word')
|
|
186
202
|
if isinstance(self.idle_timeout, bool) or not isinstance(
|
|
@@ -304,10 +320,14 @@ parakeet_model = "v3-int8"
|
|
|
304
320
|
# Apple Silicon (~32x realtime); v2-fp16 benefits from 8 (~16x).
|
|
305
321
|
parakeet_threads = 4
|
|
306
322
|
|
|
307
|
-
# Floating
|
|
308
|
-
#
|
|
309
|
-
# steals focus.
|
|
323
|
+
# Floating ghost indicator (macOS): shown ONLY while recording — a
|
|
324
|
+
# little blue ghost whose mouth opens with your voice; hidden when
|
|
325
|
+
# paused/waiting. Click-through; never steals focus.
|
|
310
326
|
overlay = true
|
|
327
|
+
# How much the ghost's face flexes its shape (higher = more flexible).
|
|
328
|
+
overlay_flex = 1.0
|
|
329
|
+
# Overall animation speed (lower = slower, gentler motion).
|
|
330
|
+
overlay_speed = 1.0
|
|
311
331
|
|
|
312
332
|
# Remove standalone filler words (uh, um, ...) from typed text.
|
|
313
333
|
strip_fillers = true
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
"""Floating waveform pill: visible proof that voice-type is listening.
|
|
2
|
+
|
|
3
|
+
A small always-on-top, click-through, non-activating macOS panel at the
|
|
4
|
+
bottom-center of the screen, shown ONLY while recording. It scrolls a
|
|
5
|
+
live waveform of the mic level — red waves as you speak — and hides
|
|
6
|
+
when paused or waiting for the wake word. Because the panel is a
|
|
7
|
+
non-activating borderless NSPanel that ignores mouse events, it can
|
|
8
|
+
never steal keyboard focus from the app being dictated into.
|
|
9
|
+
|
|
10
|
+
Uses pyobjc (already a transitive dependency of pynput on macOS).
|
|
11
|
+
The overlay must run on the process's MAIN thread (AppKit rule); the
|
|
12
|
+
app hands its per-tick housekeeping in as a callback.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import signal
|
|
18
|
+
from typing import Callable
|
|
19
|
+
|
|
20
|
+
# (state_label, is_recording, level 0..1) sampled ~20x per second.
|
|
21
|
+
SampleFn = Callable[[], tuple[str, bool, float]]
|
|
22
|
+
TickFn = Callable[[], None]
|
|
23
|
+
|
|
24
|
+
_WIDTH = 210.0
|
|
25
|
+
_HEIGHT = 240.0 # generous headroom so the halo never clips when moving
|
|
26
|
+
_MARGIN_BOTTOM = 110.0
|
|
27
|
+
_TICK_SECONDS = 0.033 # ~30 fps
|
|
28
|
+
# Master animation speed: small = slow, gentle, gradual motion.
|
|
29
|
+
_PHASE_SPEED = 0.05
|
|
30
|
+
# Idle blink: every ~_BLINK_EVERY frames the eyes close for a few.
|
|
31
|
+
_BLINK_EVERY = 130
|
|
32
|
+
_BLINK_FRAMES = 6
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def overlay_available() -> bool:
|
|
36
|
+
"""True if the AppKit bridge is importable (macOS with pyobjc)."""
|
|
37
|
+
try:
|
|
38
|
+
import AppKit # noqa: F401
|
|
39
|
+
|
|
40
|
+
return True
|
|
41
|
+
except Exception:
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def run_overlay( # noqa: PLR0913
|
|
46
|
+
sample: SampleFn,
|
|
47
|
+
tick: TickFn,
|
|
48
|
+
stopped: Callable[[], bool],
|
|
49
|
+
flex: float = 1.0,
|
|
50
|
+
speed: float = 1.0,
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Show the ghost and block until ``stopped()`` returns True.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
sample: Returns (state_label, is_recording, level) each frame.
|
|
56
|
+
tick: App housekeeping to run each frame (idle timeout, fatal
|
|
57
|
+
checks); exceptions are swallowed so the UI never dies.
|
|
58
|
+
stopped: Polled each frame; True ends the loop and closes the
|
|
59
|
+
panel.
|
|
60
|
+
flex: Face shape-flex multiplier (config overlay_flex).
|
|
61
|
+
speed: Animation speed multiplier (config overlay_speed).
|
|
62
|
+
|
|
63
|
+
SIGINT is redirected to a flag-friendly handler while the loop
|
|
64
|
+
runs (AppKit's run loop would otherwise swallow Ctrl+C), and
|
|
65
|
+
restored afterwards.
|
|
66
|
+
"""
|
|
67
|
+
import AppKit
|
|
68
|
+
import objc
|
|
69
|
+
from AppKit import (
|
|
70
|
+
NSAffineTransform,
|
|
71
|
+
NSApplication,
|
|
72
|
+
NSBackingStoreBuffered,
|
|
73
|
+
NSBezierPath,
|
|
74
|
+
NSColor,
|
|
75
|
+
NSGradient,
|
|
76
|
+
NSGraphicsContext,
|
|
77
|
+
NSMakeRect,
|
|
78
|
+
NSPanel,
|
|
79
|
+
NSScreen,
|
|
80
|
+
NSTimer,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Plain helpers (NOT NSView methods — pyobjc would try to bridge
|
|
84
|
+
# underscored methods as selectors and reject the extra arguments).
|
|
85
|
+
def _mk_oval(cx, cy, w, h): # noqa: ANN001, ANN202
|
|
86
|
+
return NSBezierPath.bezierPathWithOvalInRect_(
|
|
87
|
+
NSMakeRect(cx - w / 2, cy - h / 2, w, h)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def _mk_ghost(cx, cy, gw, jelly, ph): # noqa: ANN001, ANN202
|
|
91
|
+
"""Ghost silhouette (domed head, wavy hem) with a soft jelly flex.
|
|
92
|
+
|
|
93
|
+
Built from points so a gentle, slow, spatially-coherent
|
|
94
|
+
displacement can wobble the whole outline — the head is
|
|
95
|
+
non-rigid (flexes shape), but the amplitude is small and smooth
|
|
96
|
+
so it never turns into a lumpy blob. ``jelly`` is the wobble
|
|
97
|
+
amplitude in points; ``ph`` the slow master phase.
|
|
98
|
+
"""
|
|
99
|
+
import math
|
|
100
|
+
|
|
101
|
+
dome_y = cy + gw * 0.55
|
|
102
|
+
hem_y = cy - gw * 0.95
|
|
103
|
+
pts = [(cx + gw, hem_y), (cx + gw, dome_y)]
|
|
104
|
+
nd = 26
|
|
105
|
+
for i in range(1, nd):
|
|
106
|
+
a = math.pi * i / nd
|
|
107
|
+
pts.append((cx + gw * math.cos(a), dome_y + gw * math.sin(a)))
|
|
108
|
+
pts.append((cx - gw, dome_y))
|
|
109
|
+
pts.append((cx - gw, hem_y))
|
|
110
|
+
segs, bumps, ha = 30, 3, gw * 0.16
|
|
111
|
+
for i in range(1, segs + 1):
|
|
112
|
+
u = i / segs
|
|
113
|
+
x = cx - gw + u * 2 * gw
|
|
114
|
+
pts.append((x, hem_y - ha * math.sin(u * math.pi * bumps)))
|
|
115
|
+
|
|
116
|
+
p = NSBezierPath.bezierPath()
|
|
117
|
+
for i, (x, y) in enumerate(pts):
|
|
118
|
+
dx = jelly * math.sin(0.028 * y + ph * 0.8)
|
|
119
|
+
dy = jelly * math.sin(0.028 * x - ph * 0.6)
|
|
120
|
+
pt = (x + dx, y + dy)
|
|
121
|
+
if i == 0:
|
|
122
|
+
p.moveToPoint_(pt)
|
|
123
|
+
else:
|
|
124
|
+
p.lineToPoint_(pt)
|
|
125
|
+
p.closePath()
|
|
126
|
+
return p
|
|
127
|
+
|
|
128
|
+
def _mk_mouth(mx, lip_y, gw, open_, ph): # noqa: ANN001, ANN202
|
|
129
|
+
"""An organic mouth-hole that opens downward (jaw) and morphs.
|
|
130
|
+
|
|
131
|
+
Not a scaling oval: the rim is perturbed by slow low harmonics
|
|
132
|
+
so it wobbles/changes shape, it opens mostly DOWNWARD from a
|
|
133
|
+
fixed upper-lip line (like a dropping jaw), and it stays a thin
|
|
134
|
+
slit at rest. ``open_`` is 0..1; ``ph`` is the slow master phase.
|
|
135
|
+
"""
|
|
136
|
+
import math
|
|
137
|
+
|
|
138
|
+
rx = gw * (0.36 + 0.08 * open_)
|
|
139
|
+
ry = gw * (0.05 + 0.34 * open_)
|
|
140
|
+
cy = lip_y - ry * 0.6 # grows downward from the lip line
|
|
141
|
+
# Constrained flex: two gentle low harmonics at small amplitude
|
|
142
|
+
# so the mouth has organic life without becoming a lumpy blob.
|
|
143
|
+
# (It is also clipped to the face by the caller, so it can never
|
|
144
|
+
# spill past the ghost's silhouette.)
|
|
145
|
+
deform = 0.04 + 0.06 * open_
|
|
146
|
+
n = 44
|
|
147
|
+
p = NSBezierPath.bezierPath()
|
|
148
|
+
for i in range(n + 1):
|
|
149
|
+
a = 2.0 * math.pi * i / n
|
|
150
|
+
wob = 1.0 + deform * (
|
|
151
|
+
math.sin(2 * a + ph * 0.5)
|
|
152
|
+
+ 0.4 * math.sin(3 * a - ph * 0.4)
|
|
153
|
+
)
|
|
154
|
+
x = mx + rx * wob * math.cos(a)
|
|
155
|
+
y = cy + ry * wob * math.sin(a)
|
|
156
|
+
if i == 0:
|
|
157
|
+
p.moveToPoint_((x, y))
|
|
158
|
+
else:
|
|
159
|
+
p.lineToPoint_((x, y))
|
|
160
|
+
p.closePath()
|
|
161
|
+
return p
|
|
162
|
+
|
|
163
|
+
class WaveView(AppKit.NSView): # noqa: D401
|
|
164
|
+
"""A little glowing-blue ghost whose mouth opens with your voice.
|
|
165
|
+
|
|
166
|
+
A friendly translucent ghost floats at the bottom of the screen:
|
|
167
|
+
it bobs and blinks on its own so it feels alive, and its mouth
|
|
168
|
+
opens wider the louder you speak (driven by the live,
|
|
169
|
+
gain-adjusted mic level) -- the Animoji-style talking face that
|
|
170
|
+
makes the tool fun to use.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
def initWithFrame_(self, frame): # noqa: ANN001, ANN201, N802
|
|
174
|
+
self = objc.super(WaveView, self).initWithFrame_(frame)
|
|
175
|
+
if self is None:
|
|
176
|
+
return None
|
|
177
|
+
self.amp = 0.0 # smoothed level -> mouth opening
|
|
178
|
+
self.phase = 0.0
|
|
179
|
+
self.frame_no = 0
|
|
180
|
+
self.recording = False
|
|
181
|
+
return self
|
|
182
|
+
|
|
183
|
+
def push_(self, sample): # noqa: ANN001, ANN201, N802
|
|
184
|
+
_label, recording, level = sample
|
|
185
|
+
level = max(0.0, min(1.0, float(level)))
|
|
186
|
+
# Gentle attack, slow release so the mouth and motion change
|
|
187
|
+
# gradually rather than twitching frame-to-frame.
|
|
188
|
+
k = 0.4 if level > self.amp else 0.10
|
|
189
|
+
self.amp += k * (level - self.amp)
|
|
190
|
+
self.phase += _PHASE_SPEED * speed
|
|
191
|
+
self.frame_no += 1
|
|
192
|
+
self.recording = bool(recording)
|
|
193
|
+
self.setNeedsDisplay_(True)
|
|
194
|
+
|
|
195
|
+
def drawRect_(self, rect): # noqa: ANN001, ANN201, N802
|
|
196
|
+
import math
|
|
197
|
+
|
|
198
|
+
b = self.bounds()
|
|
199
|
+
cx, cy = b.size.width / 2, b.size.height / 2
|
|
200
|
+
gw = min(b.size.width, b.size.height) * 0.22
|
|
201
|
+
amp = self.amp
|
|
202
|
+
ph = self.phase
|
|
203
|
+
|
|
204
|
+
# Slow, gentle motion — a gradual drift, not a vibration.
|
|
205
|
+
# Bob and sway run at DIFFERENT slow frequencies so the
|
|
206
|
+
# ghost describes a lazy figure-eight rather than a rigid
|
|
207
|
+
# vertical bounce; both grow only mildly with volume.
|
|
208
|
+
bob = (3.0 + 3.0 * amp) * math.sin(ph * 0.9)
|
|
209
|
+
sway = (3.0 + 4.5 * amp) * math.sin(ph * 0.6 + 0.8)
|
|
210
|
+
dcx, dcy = cx + sway, cy + bob
|
|
211
|
+
# The whole face gently breathes and EXPANDS with volume
|
|
212
|
+
# (uniform scale — grows/shrinks, no shape distortion), plus
|
|
213
|
+
# a soft left/right lean. That is the head's audio reaction.
|
|
214
|
+
scale = (1.0 + 0.03 * math.sin(ph * 0.7)) * (1.0 + 0.11 * amp)
|
|
215
|
+
sx = sy = scale
|
|
216
|
+
lean = (2.5 + 3.0 * amp) * math.sin(ph * 0.45) # degrees
|
|
217
|
+
|
|
218
|
+
NSGraphicsContext.currentContext().saveGraphicsState()
|
|
219
|
+
t = NSAffineTransform.transform()
|
|
220
|
+
t.translateXBy_yBy_(dcx, dcy)
|
|
221
|
+
t.rotateByDegrees_(lean)
|
|
222
|
+
t.scaleXBy_yBy_(sx, sy)
|
|
223
|
+
t.translateXBy_yBy_(-dcx, -dcy)
|
|
224
|
+
t.concat()
|
|
225
|
+
try:
|
|
226
|
+
# Slow, spatially-coherent jelly flex so the head is
|
|
227
|
+
# visibly non-rigid; more when talking, and scaled by the
|
|
228
|
+
# user's overlay_flex.
|
|
229
|
+
jelly = (2.6 + 5.0 * amp) * flex
|
|
230
|
+
|
|
231
|
+
# soft halo for presence (flexes WITH the body)
|
|
232
|
+
halo = _mk_ghost(dcx, dcy, gw * 1.10, jelly, ph)
|
|
233
|
+
NSColor.colorWithCalibratedRed_green_blue_alpha_(
|
|
234
|
+
0.30, 0.62, 1.0, 0.20
|
|
235
|
+
).setFill()
|
|
236
|
+
halo.fill()
|
|
237
|
+
|
|
238
|
+
# body: bluish radial gradient + bright rim
|
|
239
|
+
body = _mk_ghost(dcx, dcy, gw, jelly, ph)
|
|
240
|
+
core = NSColor.colorWithCalibratedRed_green_blue_alpha_(
|
|
241
|
+
0.62, 0.86, 1.0, 0.88
|
|
242
|
+
)
|
|
243
|
+
edge = NSColor.colorWithCalibratedRed_green_blue_alpha_(
|
|
244
|
+
0.16, 0.44, 0.98, 0.74
|
|
245
|
+
)
|
|
246
|
+
grad = (
|
|
247
|
+
NSGradient.alloc().initWithStartingColor_endingColor_(
|
|
248
|
+
core, edge
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
if grad is not None:
|
|
252
|
+
grad.drawInBezierPath_relativeCenterPosition_(
|
|
253
|
+
body, (0.0, 0.35)
|
|
254
|
+
)
|
|
255
|
+
else: # pragma: no cover
|
|
256
|
+
edge.setFill()
|
|
257
|
+
body.fill()
|
|
258
|
+
NSColor.colorWithCalibratedRed_green_blue_alpha_(
|
|
259
|
+
0.80, 0.94, 1.0, 0.9
|
|
260
|
+
).setStroke()
|
|
261
|
+
body.setLineWidth_(2.0)
|
|
262
|
+
body.stroke()
|
|
263
|
+
|
|
264
|
+
# --- face features, CLIPPED to the body so nothing (the
|
|
265
|
+
# mouth in particular) can ever spill past the silhouette
|
|
266
|
+
gc = NSGraphicsContext.currentContext()
|
|
267
|
+
gc.saveGraphicsState()
|
|
268
|
+
body.addClip()
|
|
269
|
+
dark = NSColor.colorWithCalibratedRed_green_blue_alpha_(
|
|
270
|
+
0.05, 0.10, 0.26, 0.92
|
|
271
|
+
)
|
|
272
|
+
dark.setFill()
|
|
273
|
+
eye_y = dcy + gw * 0.45
|
|
274
|
+
eye_dx = gw * 0.42
|
|
275
|
+
blinking = (
|
|
276
|
+
self.frame_no % _BLINK_EVERY
|
|
277
|
+
) < _BLINK_FRAMES
|
|
278
|
+
eye_w = gw * 0.26
|
|
279
|
+
eye_h = gw * 0.06 if blinking else gw * 0.36
|
|
280
|
+
for s in (-1, 1):
|
|
281
|
+
_mk_oval(
|
|
282
|
+
dcx + s * eye_dx, eye_y, eye_w, eye_h
|
|
283
|
+
).fill()
|
|
284
|
+
_mk_oval(dcx, dcy + gw * 0.05, gw * 0.10, gw * 0.12).fill()
|
|
285
|
+
lip_y = dcy - gw * 0.18
|
|
286
|
+
_mk_mouth(dcx, lip_y, gw, amp, ph).fill()
|
|
287
|
+
gc.restoreGraphicsState()
|
|
288
|
+
finally:
|
|
289
|
+
NSGraphicsContext.currentContext().restoreGraphicsState()
|
|
290
|
+
|
|
291
|
+
class Driver(AppKit.NSObject):
|
|
292
|
+
"""NSTimer target: samples audio, ticks the app, ends the loop.
|
|
293
|
+
|
|
294
|
+
The pill is shown ONLY while recording — its mere presence is
|
|
295
|
+
the "you left the mic on" indicator; absence means not
|
|
296
|
+
listening. (A color change alone proved too subtle.)
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
def initWithView_panel_(self, view, panel): # noqa: ANN001, ANN201, N802, E501
|
|
300
|
+
self = objc.super(Driver, self).init()
|
|
301
|
+
if self is None:
|
|
302
|
+
return None
|
|
303
|
+
self.view = view
|
|
304
|
+
self.panel = panel
|
|
305
|
+
self.shown = False
|
|
306
|
+
return self
|
|
307
|
+
|
|
308
|
+
def tick_(self, _timer): # noqa: ANN001, ANN201, N802
|
|
309
|
+
try:
|
|
310
|
+
tick()
|
|
311
|
+
except Exception:
|
|
312
|
+
pass
|
|
313
|
+
try:
|
|
314
|
+
data = sample()
|
|
315
|
+
recording = bool(data[1])
|
|
316
|
+
if recording != self.shown:
|
|
317
|
+
if recording:
|
|
318
|
+
self.panel.orderFrontRegardless()
|
|
319
|
+
else:
|
|
320
|
+
self.panel.orderOut_(None)
|
|
321
|
+
self.shown = recording
|
|
322
|
+
if recording:
|
|
323
|
+
self.view.push_(data)
|
|
324
|
+
except Exception:
|
|
325
|
+
pass
|
|
326
|
+
if stopped():
|
|
327
|
+
NSApplication.sharedApplication().stop_(None)
|
|
328
|
+
# stop_() only takes effect once an event is processed;
|
|
329
|
+
# post a no-op event so the run loop wakes immediately.
|
|
330
|
+
evt = AppKit.NSEvent.otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2_( # noqa: E501
|
|
331
|
+
AppKit.NSEventTypeApplicationDefined,
|
|
332
|
+
(0, 0), 0, 0.0, 0, None, 0, 0, 0,
|
|
333
|
+
)
|
|
334
|
+
NSApplication.sharedApplication().postEvent_atStart_(
|
|
335
|
+
evt, True
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
app = NSApplication.sharedApplication()
|
|
339
|
+
# Accessory: no Dock icon, no menu bar, but windows still display.
|
|
340
|
+
app.setActivationPolicy_(
|
|
341
|
+
AppKit.NSApplicationActivationPolicyAccessory
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
screen = NSScreen.mainScreen()
|
|
345
|
+
sframe = screen.frame() if screen is not None else NSMakeRect(
|
|
346
|
+
0, 0, 1440, 900
|
|
347
|
+
)
|
|
348
|
+
x = sframe.origin.x + (sframe.size.width - _WIDTH) / 2
|
|
349
|
+
y = sframe.origin.y + _MARGIN_BOTTOM
|
|
350
|
+
panel = NSPanel.alloc().initWithContentRect_styleMask_backing_defer_(
|
|
351
|
+
NSMakeRect(x, y, _WIDTH, _HEIGHT),
|
|
352
|
+
AppKit.NSWindowStyleMaskBorderless
|
|
353
|
+
| AppKit.NSWindowStyleMaskNonactivatingPanel,
|
|
354
|
+
NSBackingStoreBuffered,
|
|
355
|
+
False,
|
|
356
|
+
)
|
|
357
|
+
panel.setLevel_(AppKit.NSStatusWindowLevel)
|
|
358
|
+
panel.setOpaque_(False)
|
|
359
|
+
panel.setBackgroundColor_(NSColor.clearColor())
|
|
360
|
+
panel.setIgnoresMouseEvents_(True)
|
|
361
|
+
panel.setHasShadow_(True)
|
|
362
|
+
panel.setCollectionBehavior_(
|
|
363
|
+
AppKit.NSWindowCollectionBehaviorCanJoinAllSpaces
|
|
364
|
+
| AppKit.NSWindowCollectionBehaviorStationary
|
|
365
|
+
)
|
|
366
|
+
view = WaveView.alloc().initWithFrame_(
|
|
367
|
+
NSMakeRect(0, 0, _WIDTH, _HEIGHT)
|
|
368
|
+
)
|
|
369
|
+
panel.setContentView_(view)
|
|
370
|
+
# Hidden at startup; the driver shows it only while recording.
|
|
371
|
+
|
|
372
|
+
driver = Driver.alloc().initWithView_panel_(view, panel)
|
|
373
|
+
timer = NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_( # noqa: E501
|
|
374
|
+
_TICK_SECONDS, driver, b"tick:", None, True
|
|
375
|
+
)
|
|
376
|
+
AppKit.NSRunLoop.currentRunLoop().addTimer_forMode_(
|
|
377
|
+
timer, AppKit.NSRunLoopCommonModes
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
# Ctrl+C: AppKit's run loop doesn't deliver KeyboardInterrupt on
|
|
381
|
+
# its own. Record the signal in a flag the timer polls (the timer
|
|
382
|
+
# firing is what lets the Python-level handler run at all), end
|
|
383
|
+
# the loop, and re-raise KeyboardInterrupt for the caller.
|
|
384
|
+
sigint = {"hit": False}
|
|
385
|
+
caller_stopped = stopped
|
|
386
|
+
|
|
387
|
+
def stopped() -> bool: # noqa: ANN202
|
|
388
|
+
return sigint["hit"] or caller_stopped()
|
|
389
|
+
|
|
390
|
+
previous_handler = signal.getsignal(signal.SIGINT)
|
|
391
|
+
signal.signal(
|
|
392
|
+
signal.SIGINT,
|
|
393
|
+
lambda _sig, _frame: sigint.__setitem__("hit", True),
|
|
394
|
+
)
|
|
395
|
+
try:
|
|
396
|
+
app.run()
|
|
397
|
+
finally:
|
|
398
|
+
signal.signal(signal.SIGINT, previous_handler)
|
|
399
|
+
timer.invalidate()
|
|
400
|
+
panel.orderOut_(None)
|
|
401
|
+
if sigint["hit"]:
|
|
402
|
+
raise KeyboardInterrupt
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "claude-code-tools"
|
|
3
|
-
version = "1.19.
|
|
3
|
+
version = "1.19.7"
|
|
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.7"
|
|
135
135
|
tag_format = "v$version"
|
|
136
136
|
version_files = [
|
|
137
137
|
"pyproject.toml:version",
|
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
"""Floating waveform pill: visible proof that voice-type is listening.
|
|
2
|
-
|
|
3
|
-
A small always-on-top, click-through, non-activating macOS panel at the
|
|
4
|
-
bottom-center of the screen, shown ONLY while recording. It scrolls a
|
|
5
|
-
live waveform of the mic level — red waves as you speak — and hides
|
|
6
|
-
when paused or waiting for the wake word. Because the panel is a
|
|
7
|
-
non-activating borderless NSPanel that ignores mouse events, it can
|
|
8
|
-
never steal keyboard focus from the app being dictated into.
|
|
9
|
-
|
|
10
|
-
Uses pyobjc (already a transitive dependency of pynput on macOS).
|
|
11
|
-
The overlay must run on the process's MAIN thread (AppKit rule); the
|
|
12
|
-
app hands its per-tick housekeeping in as a callback.
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
from __future__ import annotations
|
|
16
|
-
|
|
17
|
-
import signal
|
|
18
|
-
from typing import Callable
|
|
19
|
-
|
|
20
|
-
# (state_label, is_recording, level 0..1) sampled ~20x per second.
|
|
21
|
-
SampleFn = Callable[[], tuple[str, bool, float]]
|
|
22
|
-
TickFn = Callable[[], None]
|
|
23
|
-
|
|
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
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def overlay_available() -> bool:
|
|
33
|
-
"""True if the AppKit bridge is importable (macOS with pyobjc)."""
|
|
34
|
-
try:
|
|
35
|
-
import AppKit # noqa: F401
|
|
36
|
-
|
|
37
|
-
return True
|
|
38
|
-
except Exception:
|
|
39
|
-
return False
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def run_overlay(sample: SampleFn, tick: TickFn, stopped: Callable[[], bool]) -> None:
|
|
43
|
-
"""Show the pill and block until ``stopped()`` returns True.
|
|
44
|
-
|
|
45
|
-
Args:
|
|
46
|
-
sample: Returns (state_label, is_recording, level) each frame.
|
|
47
|
-
tick: App housekeeping to run each frame (idle timeout, fatal
|
|
48
|
-
checks); exceptions are swallowed so the UI never dies.
|
|
49
|
-
stopped: Polled each frame; True ends the loop and closes the
|
|
50
|
-
panel.
|
|
51
|
-
|
|
52
|
-
SIGINT is redirected to a flag-friendly handler while the loop
|
|
53
|
-
runs (AppKit's run loop would otherwise swallow Ctrl+C), and
|
|
54
|
-
restored afterwards.
|
|
55
|
-
"""
|
|
56
|
-
import AppKit
|
|
57
|
-
import objc
|
|
58
|
-
from AppKit import (
|
|
59
|
-
NSApplication,
|
|
60
|
-
NSBackingStoreBuffered,
|
|
61
|
-
NSBezierPath,
|
|
62
|
-
NSColor,
|
|
63
|
-
NSGradient,
|
|
64
|
-
NSMakeRect,
|
|
65
|
-
NSPanel,
|
|
66
|
-
NSScreen,
|
|
67
|
-
NSTimer,
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
class WaveView(AppKit.NSView): # noqa: D401
|
|
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
|
-
"""
|
|
80
|
-
|
|
81
|
-
def initWithFrame_(self, frame): # noqa: ANN001, ANN201, N802
|
|
82
|
-
self = objc.super(WaveView, self).initWithFrame_(frame)
|
|
83
|
-
if self is None:
|
|
84
|
-
return None
|
|
85
|
-
self.amp = 0.0 # smoothed audio level driving the wobble
|
|
86
|
-
self.phase = 0.0
|
|
87
|
-
self.recording = False
|
|
88
|
-
return self
|
|
89
|
-
|
|
90
|
-
def push_(self, sample): # noqa: ANN001, ANN201, N802
|
|
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
|
|
97
|
-
self.recording = bool(recording)
|
|
98
|
-
self.setNeedsDisplay_(True)
|
|
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
|
-
|
|
124
|
-
def drawRect_(self, rect): # noqa: ANN001, ANN201, N802
|
|
125
|
-
b = self.bounds()
|
|
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
|
|
140
|
-
)
|
|
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
|
|
146
|
-
)
|
|
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()
|
|
161
|
-
|
|
162
|
-
class Driver(AppKit.NSObject):
|
|
163
|
-
"""NSTimer target: samples audio, ticks the app, ends the loop.
|
|
164
|
-
|
|
165
|
-
The pill is shown ONLY while recording — its mere presence is
|
|
166
|
-
the "you left the mic on" indicator; absence means not
|
|
167
|
-
listening. (A color change alone proved too subtle.)
|
|
168
|
-
"""
|
|
169
|
-
|
|
170
|
-
def initWithView_panel_(self, view, panel): # noqa: ANN001, ANN201, N802, E501
|
|
171
|
-
self = objc.super(Driver, self).init()
|
|
172
|
-
if self is None:
|
|
173
|
-
return None
|
|
174
|
-
self.view = view
|
|
175
|
-
self.panel = panel
|
|
176
|
-
self.shown = False
|
|
177
|
-
return self
|
|
178
|
-
|
|
179
|
-
def tick_(self, _timer): # noqa: ANN001, ANN201, N802
|
|
180
|
-
try:
|
|
181
|
-
tick()
|
|
182
|
-
except Exception:
|
|
183
|
-
pass
|
|
184
|
-
try:
|
|
185
|
-
data = sample()
|
|
186
|
-
recording = bool(data[1])
|
|
187
|
-
if recording != self.shown:
|
|
188
|
-
if recording:
|
|
189
|
-
self.panel.orderFrontRegardless()
|
|
190
|
-
else:
|
|
191
|
-
self.panel.orderOut_(None)
|
|
192
|
-
self.shown = recording
|
|
193
|
-
if recording:
|
|
194
|
-
self.view.push_(data)
|
|
195
|
-
except Exception:
|
|
196
|
-
pass
|
|
197
|
-
if stopped():
|
|
198
|
-
NSApplication.sharedApplication().stop_(None)
|
|
199
|
-
# stop_() only takes effect once an event is processed;
|
|
200
|
-
# post a no-op event so the run loop wakes immediately.
|
|
201
|
-
evt = AppKit.NSEvent.otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2_( # noqa: E501
|
|
202
|
-
AppKit.NSEventTypeApplicationDefined,
|
|
203
|
-
(0, 0), 0, 0.0, 0, None, 0, 0, 0,
|
|
204
|
-
)
|
|
205
|
-
NSApplication.sharedApplication().postEvent_atStart_(
|
|
206
|
-
evt, True
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
app = NSApplication.sharedApplication()
|
|
210
|
-
# Accessory: no Dock icon, no menu bar, but windows still display.
|
|
211
|
-
app.setActivationPolicy_(
|
|
212
|
-
AppKit.NSApplicationActivationPolicyAccessory
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
screen = NSScreen.mainScreen()
|
|
216
|
-
sframe = screen.frame() if screen is not None else NSMakeRect(
|
|
217
|
-
0, 0, 1440, 900
|
|
218
|
-
)
|
|
219
|
-
x = sframe.origin.x + (sframe.size.width - _WIDTH) / 2
|
|
220
|
-
y = sframe.origin.y + _MARGIN_BOTTOM
|
|
221
|
-
panel = NSPanel.alloc().initWithContentRect_styleMask_backing_defer_(
|
|
222
|
-
NSMakeRect(x, y, _WIDTH, _HEIGHT),
|
|
223
|
-
AppKit.NSWindowStyleMaskBorderless
|
|
224
|
-
| AppKit.NSWindowStyleMaskNonactivatingPanel,
|
|
225
|
-
NSBackingStoreBuffered,
|
|
226
|
-
False,
|
|
227
|
-
)
|
|
228
|
-
panel.setLevel_(AppKit.NSStatusWindowLevel)
|
|
229
|
-
panel.setOpaque_(False)
|
|
230
|
-
panel.setBackgroundColor_(NSColor.clearColor())
|
|
231
|
-
panel.setIgnoresMouseEvents_(True)
|
|
232
|
-
panel.setHasShadow_(True)
|
|
233
|
-
panel.setCollectionBehavior_(
|
|
234
|
-
AppKit.NSWindowCollectionBehaviorCanJoinAllSpaces
|
|
235
|
-
| AppKit.NSWindowCollectionBehaviorStationary
|
|
236
|
-
)
|
|
237
|
-
view = WaveView.alloc().initWithFrame_(
|
|
238
|
-
NSMakeRect(0, 0, _WIDTH, _HEIGHT)
|
|
239
|
-
)
|
|
240
|
-
panel.setContentView_(view)
|
|
241
|
-
# Hidden at startup; the driver shows it only while recording.
|
|
242
|
-
|
|
243
|
-
driver = Driver.alloc().initWithView_panel_(view, panel)
|
|
244
|
-
timer = NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_( # noqa: E501
|
|
245
|
-
_TICK_SECONDS, driver, b"tick:", None, True
|
|
246
|
-
)
|
|
247
|
-
AppKit.NSRunLoop.currentRunLoop().addTimer_forMode_(
|
|
248
|
-
timer, AppKit.NSRunLoopCommonModes
|
|
249
|
-
)
|
|
250
|
-
|
|
251
|
-
# Ctrl+C: AppKit's run loop doesn't deliver KeyboardInterrupt on
|
|
252
|
-
# its own. Record the signal in a flag the timer polls (the timer
|
|
253
|
-
# firing is what lets the Python-level handler run at all), end
|
|
254
|
-
# the loop, and re-raise KeyboardInterrupt for the caller.
|
|
255
|
-
sigint = {"hit": False}
|
|
256
|
-
caller_stopped = stopped
|
|
257
|
-
|
|
258
|
-
def stopped() -> bool: # noqa: ANN202
|
|
259
|
-
return sigint["hit"] or caller_stopped()
|
|
260
|
-
|
|
261
|
-
previous_handler = signal.getsignal(signal.SIGINT)
|
|
262
|
-
signal.signal(
|
|
263
|
-
signal.SIGINT,
|
|
264
|
-
lambda _sig, _frame: sigint.__setitem__("hit", True),
|
|
265
|
-
)
|
|
266
|
-
try:
|
|
267
|
-
app.run()
|
|
268
|
-
finally:
|
|
269
|
-
signal.signal(signal.SIGINT, previous_handler)
|
|
270
|
-
timer.invalidate()
|
|
271
|
-
panel.orderOut_(None)
|
|
272
|
-
if sigint["hit"]:
|
|
273
|
-
raise KeyboardInterrupt
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/__init__.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/backends.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/config.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/convert.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/discord_bot.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/locking.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/paths.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/registry.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/session.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/store.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/tmux.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/agent_tunnel/trust.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_fingerprint.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_generation.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_legacy.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_models.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_process.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_reservation.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_retry.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_reuse.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_state.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_supervisor.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/codex_server_worker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/export_claude_session.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/export_codex_session.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/find_claude_session.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/find_codex_session.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/find_original_session.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/find_trimmed_sessions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/msg/prompt_detect.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/port_claude_noise.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/port_claude_to_codex.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/port_codex_flatten.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/port_codex_to_claude.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/resolve_session_names.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/resolve_session_render.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/tmux_cli_controller.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/tmux_execution_helpers.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/tmux_remote_controller.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/trim_session_claude.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/trim_session_codex.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/voice_type/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/voice_type/engines.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/voice_type/hotkey.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/voice_type/inject.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_cli_contract.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_cli_formatting.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_cli_manifest.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_cli_projection.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_cli_rendering.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_cli_snapshots.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_processes.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_store_io.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.6 → claude_code_tools-1.19.7}/claude_code_tools/workflow_validation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|