claude-code-tools 1.19.7__tar.gz → 1.19.8__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.7 → claude_code_tools-1.19.8}/PKG-INFO +1 -1
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/__init__.py +1 -1
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/voice_type/cli.py +25 -0
- claude_code_tools-1.19.8/claude_code_tools/voice_type/setup_wizard.py +357 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/pyproject.toml +2 -2
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/.gitignore +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/LICENSE +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/README.md +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/action_rpc.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/__init__.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/backends.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/cli.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/config.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/convert.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/discord_bot.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/locking.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/paths.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/registry.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/session.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/store.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/tmux.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/trust.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/aichat.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/claude_continue.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_continue.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_cli.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_fingerprint.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_generation.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_legacy.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_models.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_process.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_reservation.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_retry.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_reuse.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_state.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_supervisor.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_worker.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/config.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/csv2gsheet.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/delete_session.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/dotenv_vault.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/env_safe.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/export_all.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/export_claude_session.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/export_codex_session.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/export_session.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/find_claude_session.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/find_codex_session.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/find_original_session.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/find_session.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/find_trimmed_sessions.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/fix_session.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/gdoc2docx.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/gdoc2md.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/gsheet2csv.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/md2gdoc.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/msg/__init__.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/msg/cli.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/msg/hooks.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/msg/models.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/msg/prompt_detect.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/msg/store.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/msg/watcher.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/node_menu_ui.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/port_claude_noise.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/port_claude_to_codex.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/port_codex_flatten.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/port_codex_to_claude.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/port_service.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/resolve_session.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/resolve_session_names.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/resolve_session_render.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/search_index.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/session_lineage.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/session_menu.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/session_menu_cli.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/session_utils.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/smart_trim.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/smart_trim_core.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/tmux_cli_controller.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/tmux_execution_helpers.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/tmux_remote_controller.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/trim_in_place.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/trim_session.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/trim_session_claude.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/trim_session_codex.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/voice_type/__init__.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/voice_type/app.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/voice_type/config.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/voice_type/engine_parakeet.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/voice_type/engine_parakeet_mlx.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/voice_type/engines.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/voice_type/hotkey.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/voice_type/inject.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/voice_type/logic.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/voice_type/overlay.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_cli.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_cli_contract.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_cli_formatting.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_cli_identity_policy.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_cli_manifest.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_cli_projection.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_cli_rendering.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_cli_snapshots.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_cli_store_backends.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_processes.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_runs.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_store_io.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_validation.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/docs/agent-tunnel-spec.md +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/docs/claude-code-tmux-tutorials.md +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/docs/dot-zshrc.md +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/docs/find-claude-session.md +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/docs/intercom-spec.md +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/docs/lmsh.md +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/docs/local-llm-setup.md +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/docs/msg-phase-a-plan.md +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/docs/msg-plan-v2.md +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/docs/tmux-cli-instructions.md +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/docs/vault-documentation.md +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/hatch_build.py +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/node_ui/action_config.js +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/node_ui/menu.js +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/node_ui/package-lock.json +0 -0
- {claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/node_ui/package.json +0 -0
|
@@ -157,6 +157,12 @@ def _cmd_hotkey() -> int:
|
|
|
157
157
|
return 0
|
|
158
158
|
|
|
159
159
|
|
|
160
|
+
def _cmd_setup(args: argparse.Namespace) -> int:
|
|
161
|
+
from .setup_wizard import run_setup
|
|
162
|
+
|
|
163
|
+
return run_setup(config_path=args.config, force=args.force)
|
|
164
|
+
|
|
165
|
+
|
|
160
166
|
def _cmd_init(args: argparse.Namespace) -> int:
|
|
161
167
|
try:
|
|
162
168
|
path = write_sample_config(args.config, force=args.force)
|
|
@@ -164,6 +170,7 @@ def _cmd_init(args: argparse.Namespace) -> int:
|
|
|
164
170
|
print(f"voice-type: {e}", file=sys.stderr)
|
|
165
171
|
return 1
|
|
166
172
|
print(f"wrote {path}")
|
|
173
|
+
print("(tip: `voice-type setup` walks you through it interactively)")
|
|
167
174
|
return 0
|
|
168
175
|
|
|
169
176
|
|
|
@@ -179,6 +186,7 @@ examples:
|
|
|
179
186
|
voice-type --engine parakeet-mlx --segmentation hold
|
|
180
187
|
# best accuracy: whole-take dictation
|
|
181
188
|
voice-type --mode wake # hands-free with wake word "claude"
|
|
189
|
+
voice-type setup # interactive walkthrough -> config
|
|
182
190
|
voice-type init # write a commented sample config
|
|
183
191
|
voice-type hotkey # record a chord, print the config line
|
|
184
192
|
|
|
@@ -218,12 +226,29 @@ Accessibility (to type), Input Monitoring (global hotkeys).
|
|
|
218
226
|
"hotkey",
|
|
219
227
|
help="press a key combo and print the matching config line",
|
|
220
228
|
)
|
|
229
|
+
p_setup = sub.add_parser(
|
|
230
|
+
"setup",
|
|
231
|
+
help="interactive walkthrough that writes your config",
|
|
232
|
+
)
|
|
233
|
+
p_setup.add_argument(
|
|
234
|
+
"--config",
|
|
235
|
+
type=Path,
|
|
236
|
+
# SUPPRESS so a --config given BEFORE the subcommand is not
|
|
237
|
+
# clobbered by the subparser's default (see the init parser).
|
|
238
|
+
default=argparse.SUPPRESS,
|
|
239
|
+
help="destination path",
|
|
240
|
+
)
|
|
241
|
+
p_setup.add_argument(
|
|
242
|
+
"--force", action="store_true", help="overwrite existing config"
|
|
243
|
+
)
|
|
221
244
|
|
|
222
245
|
args = parser.parse_args()
|
|
223
246
|
if args.command == "init":
|
|
224
247
|
return _cmd_init(args)
|
|
225
248
|
if args.command == "hotkey":
|
|
226
249
|
return _cmd_hotkey()
|
|
250
|
+
if args.command == "setup":
|
|
251
|
+
return _cmd_setup(args)
|
|
227
252
|
return _cmd_run(args)
|
|
228
253
|
|
|
229
254
|
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"""Interactive setup wizard for voice-type (``voice-type setup``).
|
|
2
|
+
|
|
3
|
+
Walks a new user through the handful of choices that matter — engine,
|
|
4
|
+
activation mode, segmentation, hotkey, wake word, and a few niceties —
|
|
5
|
+
explaining each, then writes a valid ``config.toml`` and prints the
|
|
6
|
+
command to run. Unlike ``voice-type init`` (which drops a fully
|
|
7
|
+
commented sample file to edit by hand), this asks questions.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import tempfile
|
|
14
|
+
import tomllib
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from .config import (
|
|
18
|
+
DEFAULT_CONFIG_PATH,
|
|
19
|
+
VALID_MODEL_ARCHS,
|
|
20
|
+
Config,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class _Cancelled(Exception):
|
|
25
|
+
"""Raised when the user aborts a prompt (Ctrl-C).
|
|
26
|
+
|
|
27
|
+
questionary 2.x turns Ctrl-C (and Ctrl-Q) into the ``None`` answer
|
|
28
|
+
that ``_ask`` maps to this exception; Escape is not a cancellation
|
|
29
|
+
key inside questionary prompts, so it is not advertised here.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _ask(prompt): # noqa: ANN001, ANN202
|
|
34
|
+
"""Return a prompt's answer, or raise ``_Cancelled`` if aborted.
|
|
35
|
+
|
|
36
|
+
questionary returns ``None`` when a prompt is cancelled (Ctrl-C);
|
|
37
|
+
every answer flows through here so an abort — at a required OR an
|
|
38
|
+
optional prompt — always cancels the whole wizard instead of being
|
|
39
|
+
mistaken for a "no"/default and writing a config anyway.
|
|
40
|
+
"""
|
|
41
|
+
value = prompt.ask()
|
|
42
|
+
if value is None:
|
|
43
|
+
raise _Cancelled
|
|
44
|
+
return value
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# TOML basic-string escapes required by the spec; every other control
|
|
48
|
+
# character (U+0000–U+001F and U+007F) is emitted as a \uXXXX escape so
|
|
49
|
+
# a prompt answer with a newline/tab/etc. never produces invalid TOML.
|
|
50
|
+
_TOML_STR_ESCAPES = {
|
|
51
|
+
"\\": "\\\\",
|
|
52
|
+
'"': '\\"',
|
|
53
|
+
"\b": "\\b",
|
|
54
|
+
"\t": "\\t",
|
|
55
|
+
"\n": "\\n",
|
|
56
|
+
"\f": "\\f",
|
|
57
|
+
"\r": "\\r",
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _toml_string(s: str) -> str:
|
|
62
|
+
out: list[str] = []
|
|
63
|
+
for ch in s:
|
|
64
|
+
escaped = _TOML_STR_ESCAPES.get(ch)
|
|
65
|
+
if escaped is not None:
|
|
66
|
+
out.append(escaped)
|
|
67
|
+
elif ord(ch) < 0x20 or ord(ch) == 0x7F:
|
|
68
|
+
out.append(f"\\u{ord(ch):04X}")
|
|
69
|
+
else:
|
|
70
|
+
out.append(ch)
|
|
71
|
+
return '"' + "".join(out) + '"'
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _toml_value(v: object) -> str:
|
|
75
|
+
if isinstance(v, bool):
|
|
76
|
+
return "true" if v else "false"
|
|
77
|
+
if isinstance(v, str):
|
|
78
|
+
return _toml_string(v)
|
|
79
|
+
if isinstance(v, (int, float)):
|
|
80
|
+
return repr(v)
|
|
81
|
+
if isinstance(v, list):
|
|
82
|
+
return "[" + ", ".join(_toml_value(x) for x in v) + "]"
|
|
83
|
+
raise TypeError(f"cannot serialize {v!r} to TOML")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _atomic_write(path: Path, text: str, overwrite: bool) -> None:
|
|
87
|
+
"""Write ``text`` to ``path`` atomically.
|
|
88
|
+
|
|
89
|
+
Writes to a temporary file in the destination directory, flushes and
|
|
90
|
+
fsyncs it, then publishes it over the destination so an I/O failure or
|
|
91
|
+
interruption can never truncate or half-write an existing config. The
|
|
92
|
+
temporary file is removed on any failure.
|
|
93
|
+
|
|
94
|
+
When ``overwrite`` is true the publish is an unconditional
|
|
95
|
+
``os.replace``. When it is false — the caller never authorized
|
|
96
|
+
clobbering an existing config — the publish is an atomic no-clobber
|
|
97
|
+
``os.link``, which raises ``FileExistsError`` if the destination
|
|
98
|
+
appeared (e.g. another process created it) after the wizard's initial
|
|
99
|
+
existence check, so a config is never silently destroyed.
|
|
100
|
+
"""
|
|
101
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
102
|
+
fd, tmp = tempfile.mkstemp(
|
|
103
|
+
dir=str(path.parent), prefix=".config-", suffix=".toml.tmp"
|
|
104
|
+
)
|
|
105
|
+
try:
|
|
106
|
+
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
|
107
|
+
f.write(text)
|
|
108
|
+
f.flush()
|
|
109
|
+
os.fsync(f.fileno())
|
|
110
|
+
if overwrite:
|
|
111
|
+
os.replace(tmp, path)
|
|
112
|
+
else:
|
|
113
|
+
# Atomic create-or-fail: link succeeds only if path is absent.
|
|
114
|
+
os.link(tmp, path)
|
|
115
|
+
try:
|
|
116
|
+
os.unlink(tmp)
|
|
117
|
+
except OSError:
|
|
118
|
+
pass
|
|
119
|
+
except BaseException:
|
|
120
|
+
try:
|
|
121
|
+
os.unlink(tmp)
|
|
122
|
+
except OSError:
|
|
123
|
+
pass
|
|
124
|
+
raise
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _write_config(path: Path, answers: dict, overwrite: bool) -> None:
|
|
128
|
+
lines = [
|
|
129
|
+
"# voice-type configuration — written by `voice-type setup`.",
|
|
130
|
+
"# Run `voice-type init --force` for a fully commented sample.",
|
|
131
|
+
"",
|
|
132
|
+
]
|
|
133
|
+
for key, value in answers.items():
|
|
134
|
+
lines.append(f"{key} = {_toml_value(value)}")
|
|
135
|
+
text = "\n".join(lines) + "\n"
|
|
136
|
+
# Guard: never replace the destination with a document tomllib can't
|
|
137
|
+
# read back (the loader that voice-type itself uses).
|
|
138
|
+
tomllib.loads(text)
|
|
139
|
+
_atomic_write(path, text, overwrite)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _manual_hotkey(q, default: str): # noqa: ANN001, ANN202
|
|
143
|
+
"""Prompt for a hotkey typed by hand, validated by ``parse_hotkey``."""
|
|
144
|
+
from .hotkey import parse_hotkey
|
|
145
|
+
|
|
146
|
+
def _valid(text: str): # noqa: ANN202
|
|
147
|
+
try:
|
|
148
|
+
parse_hotkey(text)
|
|
149
|
+
return True
|
|
150
|
+
except ValueError as e:
|
|
151
|
+
return str(e)
|
|
152
|
+
|
|
153
|
+
return _ask(q.text(
|
|
154
|
+
'Hotkey (e.g. "<ctrl>+;" or "ctrl+;"):',
|
|
155
|
+
default=default,
|
|
156
|
+
validate=_valid,
|
|
157
|
+
))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _ask_hotkey(q, label: str, default: str): # noqa: ANN001, ANN202
|
|
161
|
+
"""Return a hotkey string via keep-default / record / type."""
|
|
162
|
+
how = _ask(q.select(
|
|
163
|
+
f"{label} hotkey:",
|
|
164
|
+
choices=[
|
|
165
|
+
f"Keep default ({default})",
|
|
166
|
+
"Record one now (press the combo)",
|
|
167
|
+
"Type it manually",
|
|
168
|
+
],
|
|
169
|
+
))
|
|
170
|
+
if how.startswith("Keep"):
|
|
171
|
+
return default
|
|
172
|
+
if how.startswith("Record"):
|
|
173
|
+
try:
|
|
174
|
+
from .hotkey import record_hotkey
|
|
175
|
+
|
|
176
|
+
print(" Press the key combo now (15s)...")
|
|
177
|
+
chord = record_hotkey()
|
|
178
|
+
except ImportError:
|
|
179
|
+
# Recording needs the optional voice dependency (pynput). Say
|
|
180
|
+
# so — rather than the ambiguous "nothing recorded" — and fall
|
|
181
|
+
# through to manual entry instead of silently keeping default.
|
|
182
|
+
print(
|
|
183
|
+
" recording needs the voice dependency (pynput); "
|
|
184
|
+
"type the hotkey manually instead."
|
|
185
|
+
)
|
|
186
|
+
return _manual_hotkey(q, default)
|
|
187
|
+
except KeyboardInterrupt:
|
|
188
|
+
# Ctrl-C during the record window cancels the whole wizard,
|
|
189
|
+
# matching every other prompt — nothing gets written.
|
|
190
|
+
raise _Cancelled
|
|
191
|
+
if not chord:
|
|
192
|
+
print(" nothing recorded; keeping default.")
|
|
193
|
+
return default
|
|
194
|
+
# A recorded chord can contain keys the hotkey listener can't
|
|
195
|
+
# bind (e.g. <caps_lock>, media keys). Validate it the same way
|
|
196
|
+
# a manually typed one is validated, so the wizard never writes
|
|
197
|
+
# a config that later fails when the listeners start.
|
|
198
|
+
from .hotkey import parse_hotkey
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
parse_hotkey(chord)
|
|
202
|
+
except ValueError as e:
|
|
203
|
+
print(
|
|
204
|
+
f" recorded {chord!r} can't be used as a hotkey ({e}); "
|
|
205
|
+
"type one manually instead."
|
|
206
|
+
)
|
|
207
|
+
else:
|
|
208
|
+
print(f" recorded: {chord}")
|
|
209
|
+
return chord
|
|
210
|
+
# fall through to manual entry (which retries / keeps the default)
|
|
211
|
+
return _manual_hotkey(q, default)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def run_setup(config_path: Path | None = None, force: bool = False) -> int:
|
|
215
|
+
"""Run the interactive wizard; returns a process exit code."""
|
|
216
|
+
try:
|
|
217
|
+
import questionary as q
|
|
218
|
+
except ImportError as e: # pragma: no cover - questionary is a base dep
|
|
219
|
+
print(f"voice-type: {e}; setup needs questionary")
|
|
220
|
+
return 1
|
|
221
|
+
|
|
222
|
+
path = config_path or DEFAULT_CONFIG_PATH
|
|
223
|
+
ans: dict = {}
|
|
224
|
+
# Whether clobbering an existing config was authorized: --force, or the
|
|
225
|
+
# user confirming the overwrite prompt below. If neither, the final
|
|
226
|
+
# write uses an atomic no-clobber publish so a config created by another
|
|
227
|
+
# process while the wizard runs is never silently overwritten.
|
|
228
|
+
overwrite_ok = force
|
|
229
|
+
try:
|
|
230
|
+
if path.exists() and not force:
|
|
231
|
+
if not _ask(
|
|
232
|
+
q.confirm(f"{path} exists — overwrite it?", default=False)
|
|
233
|
+
):
|
|
234
|
+
print("setup cancelled.")
|
|
235
|
+
return 1
|
|
236
|
+
overwrite_ok = True
|
|
237
|
+
|
|
238
|
+
print("voice-type setup — a few questions, then you're ready.\n")
|
|
239
|
+
|
|
240
|
+
engine = _ask(q.select(
|
|
241
|
+
"Transcription engine:",
|
|
242
|
+
choices=[
|
|
243
|
+
q.Choice(
|
|
244
|
+
"parakeet-mlx — best accuracy + speed (Apple GPU; "
|
|
245
|
+
"needs the voice-mlx extra)",
|
|
246
|
+
value="parakeet-mlx",
|
|
247
|
+
),
|
|
248
|
+
q.Choice(
|
|
249
|
+
"parakeet — Parakeet on CPU (needs voice-parakeet)",
|
|
250
|
+
value="parakeet",
|
|
251
|
+
),
|
|
252
|
+
q.Choice(
|
|
253
|
+
"moonshine — small streaming models (needs voice)",
|
|
254
|
+
value="moonshine",
|
|
255
|
+
),
|
|
256
|
+
],
|
|
257
|
+
))
|
|
258
|
+
ans["engine"] = engine
|
|
259
|
+
is_parakeet = engine in ("parakeet", "parakeet-mlx")
|
|
260
|
+
|
|
261
|
+
if engine == "parakeet":
|
|
262
|
+
ans["parakeet_model"] = _ask(q.select(
|
|
263
|
+
"Parakeet build:",
|
|
264
|
+
choices=[
|
|
265
|
+
q.Choice("v3-int8 — multilingual, ~490 MB", "v3-int8"),
|
|
266
|
+
q.Choice(
|
|
267
|
+
"v2-fp16 — English, higher precision", "v2-fp16"
|
|
268
|
+
),
|
|
269
|
+
],
|
|
270
|
+
))
|
|
271
|
+
elif engine == "moonshine":
|
|
272
|
+
ans["model_arch"] = _ask(q.select(
|
|
273
|
+
"Moonshine model:",
|
|
274
|
+
choices=list(VALID_MODEL_ARCHS),
|
|
275
|
+
default="medium-streaming",
|
|
276
|
+
))
|
|
277
|
+
|
|
278
|
+
mode = _ask(q.select(
|
|
279
|
+
"How should dictation activate?",
|
|
280
|
+
choices=[
|
|
281
|
+
q.Choice("toggle — a hotkey starts/stops", "toggle"),
|
|
282
|
+
q.Choice("wake — say a wake word (hands-free)", "wake"),
|
|
283
|
+
q.Choice("vad — always on while running", "vad"),
|
|
284
|
+
],
|
|
285
|
+
))
|
|
286
|
+
ans["mode"] = mode
|
|
287
|
+
|
|
288
|
+
if mode == "toggle" and is_parakeet:
|
|
289
|
+
ans["segmentation"] = _ask(q.select(
|
|
290
|
+
"When should the text appear?",
|
|
291
|
+
choices=[
|
|
292
|
+
q.Choice(
|
|
293
|
+
"hold — whole take on toggle-off (most accurate)",
|
|
294
|
+
"hold",
|
|
295
|
+
),
|
|
296
|
+
q.Choice("vad — each utterance when you pause", "vad"),
|
|
297
|
+
],
|
|
298
|
+
))
|
|
299
|
+
|
|
300
|
+
ans["hotkey"] = _ask_hotkey(q, "Toggle-recording", "<ctrl>+;")
|
|
301
|
+
|
|
302
|
+
if mode == "wake":
|
|
303
|
+
word = _ask(q.text("Wake word:", default="claude"))
|
|
304
|
+
ans["wake_word"] = word.strip() or "claude"
|
|
305
|
+
aliases = _ask(q.text(
|
|
306
|
+
"Wake-word aliases the model mishears "
|
|
307
|
+
"(comma-separated, optional):",
|
|
308
|
+
default="",
|
|
309
|
+
))
|
|
310
|
+
if aliases.strip():
|
|
311
|
+
ans["wake_word_aliases"] = [
|
|
312
|
+
a.strip() for a in aliases.split(",") if a.strip()
|
|
313
|
+
]
|
|
314
|
+
|
|
315
|
+
if _ask(q.confirm(
|
|
316
|
+
"Configure extras (sounds, clipboard rescue, ghost)?",
|
|
317
|
+
default=False,
|
|
318
|
+
)):
|
|
319
|
+
ans["sounds"] = _ask(
|
|
320
|
+
q.confirm("Play start/stop chimes?", default=True)
|
|
321
|
+
)
|
|
322
|
+
if _ask(q.confirm(
|
|
323
|
+
"Add a hotkey to re-type the last transcript "
|
|
324
|
+
"(wrong-window rescue)?",
|
|
325
|
+
default=False,
|
|
326
|
+
)):
|
|
327
|
+
ans["paste_hotkey"] = _ask_hotkey(
|
|
328
|
+
q, "Paste-again", "<cmd>+<ctrl>+v"
|
|
329
|
+
)
|
|
330
|
+
ans["copy_to_clipboard"] = True
|
|
331
|
+
ans["overlay"] = _ask(q.confirm(
|
|
332
|
+
"Show the floating ghost while recording?", default=True
|
|
333
|
+
))
|
|
334
|
+
except _Cancelled:
|
|
335
|
+
print("\nsetup cancelled — nothing written.")
|
|
336
|
+
return 1
|
|
337
|
+
|
|
338
|
+
# Validate the combination before writing.
|
|
339
|
+
try:
|
|
340
|
+
Config(**ans).validate()
|
|
341
|
+
except (ValueError, TypeError) as e:
|
|
342
|
+
print(f"\nvoice-type: those choices don't combine ({e}).")
|
|
343
|
+
return 1
|
|
344
|
+
|
|
345
|
+
try:
|
|
346
|
+
_write_config(path, ans, overwrite_ok)
|
|
347
|
+
except FileExistsError:
|
|
348
|
+
# A config appeared after the initial existence check and the user
|
|
349
|
+
# never authorized an overwrite; refuse rather than clobber it.
|
|
350
|
+
print(
|
|
351
|
+
f"\nvoice-type: {path} was created while setup was running; "
|
|
352
|
+
"not overwriting it. Re-run with --force to replace it."
|
|
353
|
+
)
|
|
354
|
+
return 1
|
|
355
|
+
print(f"\n✓ wrote {path}")
|
|
356
|
+
print("\nStart dictating with:\n voice-type")
|
|
357
|
+
return 0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "claude-code-tools"
|
|
3
|
-
version = "1.19.
|
|
3
|
+
version = "1.19.8"
|
|
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.8"
|
|
135
135
|
tag_format = "v$version"
|
|
136
136
|
version_files = [
|
|
137
137
|
"pyproject.toml:version",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/__init__.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/backends.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/config.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/convert.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/discord_bot.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/locking.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/paths.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/registry.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/session.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/store.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/agent_tunnel/tmux.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/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.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_fingerprint.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_generation.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_legacy.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_models.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_process.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_reservation.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_retry.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_reuse.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_state.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/codex_server_supervisor.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/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.7 → claude_code_tools-1.19.8}/claude_code_tools/export_claude_session.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/export_codex_session.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/find_claude_session.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/find_codex_session.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/find_original_session.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/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.7 → claude_code_tools-1.19.8}/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.7 → claude_code_tools-1.19.8}/claude_code_tools/port_claude_noise.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/port_claude_to_codex.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/port_codex_flatten.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/port_codex_to_claude.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/resolve_session_names.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/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.7 → claude_code_tools-1.19.8}/claude_code_tools/tmux_cli_controller.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/tmux_execution_helpers.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/tmux_remote_controller.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/trim_session_claude.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/trim_session_codex.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/voice_type/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/voice_type/config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/voice_type/engines.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/voice_type/hotkey.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/voice_type/inject.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/voice_type/overlay.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_cli_contract.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_cli_formatting.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_cli_manifest.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_cli_projection.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_cli_rendering.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_cli_snapshots.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_processes.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/claude_code_tools/workflow_store_io.py
RENAMED
|
File without changes
|
{claude_code_tools-1.19.7 → claude_code_tools-1.19.8}/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
|