talktype 0.1.0__tar.gz → 0.2.0__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.
- {talktype-0.1.0 → talktype-0.2.0}/.env.example +2 -2
- {talktype-0.1.0 → talktype-0.2.0}/.gitignore +3 -0
- {talktype-0.1.0 → talktype-0.2.0}/PKG-INFO +8 -7
- {talktype-0.1.0 → talktype-0.2.0}/README.md +6 -6
- talktype-0.2.0/Talk.spec +98 -0
- talktype-0.2.0/assets/Talk.icns +0 -0
- talktype-0.2.0/assets/Talk.iconset/icon_128x128.png +0 -0
- talktype-0.2.0/assets/Talk.iconset/icon_128x128@2x.png +0 -0
- talktype-0.2.0/assets/Talk.iconset/icon_16x16.png +0 -0
- talktype-0.2.0/assets/Talk.iconset/icon_16x16@2x.png +0 -0
- talktype-0.2.0/assets/Talk.iconset/icon_256x256.png +0 -0
- talktype-0.2.0/assets/Talk.iconset/icon_256x256@2x.png +0 -0
- talktype-0.2.0/assets/Talk.iconset/icon_32x32.png +0 -0
- talktype-0.2.0/assets/Talk.iconset/icon_32x32@2x.png +0 -0
- talktype-0.2.0/assets/Talk.iconset/icon_512x512.png +0 -0
- talktype-0.2.0/assets/Talk.iconset/icon_512x512@2x.png +0 -0
- talktype-0.2.0/assets/Talk.iconset/icon_64x64.png +0 -0
- talktype-0.2.0/assets/Talk.iconset/icon_64x64@2x.png +0 -0
- talktype-0.2.0/assets/Talk_icon_512.png +0 -0
- talktype-0.2.0/assets/mic.png +0 -0
- talktype-0.2.0/assets/mic_rec.png +0 -0
- talktype-0.2.0/assets/mic_wait.png +0 -0
- talktype-0.2.0/entitlements.plist +16 -0
- {talktype-0.1.0 → talktype-0.2.0}/pyproject.toml +7 -1
- talktype-0.2.0/scripts/build_dmg.sh +109 -0
- {talktype-0.1.0 → talktype-0.2.0}/scripts/install_macos.sh +2 -2
- {talktype-0.1.0 → talktype-0.2.0}/src/talk/__init__.py +1 -1
- {talktype-0.1.0 → talktype-0.2.0}/src/talk/app.py +45 -8
- {talktype-0.1.0 → talktype-0.2.0}/src/talk/config.py +2 -2
- talktype-0.2.0/src/talk/menubar.py +79 -0
- {talktype-0.1.0 → talktype-0.2.0}/uv.lock +109 -1
- {talktype-0.1.0 → talktype-0.2.0}/PLAN.md +0 -0
- {talktype-0.1.0 → talktype-0.2.0}/RESEARCH.md +0 -0
- {talktype-0.1.0 → talktype-0.2.0}/scripts/doctor_macos.sh +0 -0
- {talktype-0.1.0 → talktype-0.2.0}/scripts/run_macos.sh +0 -0
- {talktype-0.1.0 → talktype-0.2.0}/src/talk/__main__.py +0 -0
- {talktype-0.1.0 → talktype-0.2.0}/src/talk/audio.py +0 -0
- {talktype-0.1.0 → talktype-0.2.0}/src/talk/backends/__init__.py +0 -0
- {talktype-0.1.0 → talktype-0.2.0}/src/talk/backends/base.py +0 -0
- {talktype-0.1.0 → talktype-0.2.0}/src/talk/backends/factory.py +0 -0
- {talktype-0.1.0 → talktype-0.2.0}/src/talk/backends/openai_backend.py +0 -0
- {talktype-0.1.0 → talktype-0.2.0}/src/talk/backends/parakeet_backend.py +0 -0
- {talktype-0.1.0 → talktype-0.2.0}/src/talk/doctor.py +0 -0
- {talktype-0.1.0 → talktype-0.2.0}/src/talk/paste.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: talktype
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Local-first dictation for macOS — press a hotkey, talk, text appears at your cursor
|
|
5
5
|
Project-URL: Homepage, https://github.com/strangeloopcanon/talk
|
|
6
6
|
Project-URL: Repository, https://github.com/strangeloopcanon/talk
|
|
@@ -23,6 +23,7 @@ Requires-Dist: openai>=1.108.0
|
|
|
23
23
|
Requires-Dist: parakeet-mlx>=0.4.2
|
|
24
24
|
Requires-Dist: pynput>=1.8.1
|
|
25
25
|
Requires-Dist: python-dotenv>=1.1.1
|
|
26
|
+
Requires-Dist: rumps>=0.4.0
|
|
26
27
|
Requires-Dist: sounddevice>=0.5.2
|
|
27
28
|
Description-Content-Type: text/markdown
|
|
28
29
|
|
|
@@ -40,7 +41,7 @@ Press a hotkey, talk, text appears at your cursor.
|
|
|
40
41
|
## Install
|
|
41
42
|
|
|
42
43
|
```bash
|
|
43
|
-
pip install
|
|
44
|
+
pip install talktype
|
|
44
45
|
```
|
|
45
46
|
|
|
46
47
|
That's it. On first run, Parakeet model weights (~1.2 GB) download automatically.
|
|
@@ -61,9 +62,9 @@ cd talk
|
|
|
61
62
|
talk run
|
|
62
63
|
```
|
|
63
64
|
|
|
64
|
-
- Press `
|
|
65
|
-
- Press `
|
|
66
|
-
- Press `
|
|
65
|
+
- Press `Ctrl+Option+D` to start recording.
|
|
66
|
+
- Press `Ctrl+Option+D` again to stop, transcribe, and paste.
|
|
67
|
+
- Press `Ctrl+Option+Q` to quit.
|
|
67
68
|
|
|
68
69
|
### Preflight checks
|
|
69
70
|
|
|
@@ -100,8 +101,8 @@ Key options (all have sensible defaults):
|
|
|
100
101
|
| Variable | Default | Notes |
|
|
101
102
|
|----------|---------|-------|
|
|
102
103
|
| `DICTATE_BACKEND` | `parakeet` | `parakeet` (local) or `openai` (cloud) |
|
|
103
|
-
| `DICTATE_HOTKEY` | `<
|
|
104
|
-
| `DICTATE_QUIT_HOTKEY` | `<
|
|
104
|
+
| `DICTATE_HOTKEY` | `<ctrl>+<alt>+d` | Toggle recording |
|
|
105
|
+
| `DICTATE_QUIT_HOTKEY` | `<ctrl>+<alt>+q` | Quit the app |
|
|
105
106
|
| `DICTATE_AUTOPASTE` | `true` | Paste transcription at cursor |
|
|
106
107
|
| `PARAKEET_MODEL` | `mlx-community/parakeet-tdt-0.6b-v3` | Local model |
|
|
107
108
|
| `OPENAI_API_KEY` | *(empty)* | Required if backend=openai |
|
|
@@ -12,7 +12,7 @@ Press a hotkey, talk, text appears at your cursor.
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
pip install
|
|
15
|
+
pip install talktype
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
That's it. On first run, Parakeet model weights (~1.2 GB) download automatically.
|
|
@@ -33,9 +33,9 @@ cd talk
|
|
|
33
33
|
talk run
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
- Press `
|
|
37
|
-
- Press `
|
|
38
|
-
- Press `
|
|
36
|
+
- Press `Ctrl+Option+D` to start recording.
|
|
37
|
+
- Press `Ctrl+Option+D` again to stop, transcribe, and paste.
|
|
38
|
+
- Press `Ctrl+Option+Q` to quit.
|
|
39
39
|
|
|
40
40
|
### Preflight checks
|
|
41
41
|
|
|
@@ -72,8 +72,8 @@ Key options (all have sensible defaults):
|
|
|
72
72
|
| Variable | Default | Notes |
|
|
73
73
|
|----------|---------|-------|
|
|
74
74
|
| `DICTATE_BACKEND` | `parakeet` | `parakeet` (local) or `openai` (cloud) |
|
|
75
|
-
| `DICTATE_HOTKEY` | `<
|
|
76
|
-
| `DICTATE_QUIT_HOTKEY` | `<
|
|
75
|
+
| `DICTATE_HOTKEY` | `<ctrl>+<alt>+d` | Toggle recording |
|
|
76
|
+
| `DICTATE_QUIT_HOTKEY` | `<ctrl>+<alt>+q` | Quit the app |
|
|
77
77
|
| `DICTATE_AUTOPASTE` | `true` | Paste transcription at cursor |
|
|
78
78
|
| `PARAKEET_MODEL` | `mlx-community/parakeet-tdt-0.6b-v3` | Local model |
|
|
79
79
|
| `OPENAI_API_KEY` | *(empty)* | Required if backend=openai |
|
talktype-0.2.0/Talk.spec
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# -*- mode: python ; coding: utf-8 -*-
|
|
2
|
+
"""PyInstaller spec for Talk.app — macOS menu bar dictation."""
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from PyInstaller.utils.hooks import collect_all, collect_submodules
|
|
9
|
+
|
|
10
|
+
block_cipher = None
|
|
11
|
+
|
|
12
|
+
ROOT = os.path.dirname(os.path.abspath(SPEC)) # noqa: F821
|
|
13
|
+
|
|
14
|
+
# Heavy native packages that need special collection
|
|
15
|
+
mlx_datas, mlx_binaries, mlx_hiddenimports = collect_all("mlx")
|
|
16
|
+
parakeet_datas, parakeet_binaries, parakeet_hiddenimports = collect_all("parakeet_mlx")
|
|
17
|
+
numpy_datas, numpy_binaries, numpy_hiddenimports = collect_all("numpy")
|
|
18
|
+
|
|
19
|
+
extra_hiddenimports = (
|
|
20
|
+
collect_submodules("talk")
|
|
21
|
+
+ collect_submodules("rumps")
|
|
22
|
+
+ collect_submodules("pynput")
|
|
23
|
+
+ collect_submodules("sounddevice")
|
|
24
|
+
+ collect_submodules("dotenv")
|
|
25
|
+
+ collect_submodules("openai")
|
|
26
|
+
+ collect_submodules("huggingface_hub")
|
|
27
|
+
+ mlx_hiddenimports
|
|
28
|
+
+ parakeet_hiddenimports
|
|
29
|
+
+ numpy_hiddenimports
|
|
30
|
+
+ [
|
|
31
|
+
"pyobjc",
|
|
32
|
+
"AppKit",
|
|
33
|
+
"Foundation",
|
|
34
|
+
"objc",
|
|
35
|
+
"PyObjCTools",
|
|
36
|
+
]
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
a = Analysis(
|
|
40
|
+
[os.path.join(ROOT, "src", "talk", "menubar.py")],
|
|
41
|
+
pathex=[os.path.join(ROOT, "src")],
|
|
42
|
+
binaries=mlx_binaries + parakeet_binaries + numpy_binaries,
|
|
43
|
+
datas=(
|
|
44
|
+
[(os.path.join(ROOT, "assets", f), ".") for f in ("mic.png", "mic_rec.png", "mic_wait.png")]
|
|
45
|
+
+ mlx_datas
|
|
46
|
+
+ parakeet_datas
|
|
47
|
+
+ numpy_datas
|
|
48
|
+
),
|
|
49
|
+
hiddenimports=extra_hiddenimports,
|
|
50
|
+
hookspath=[],
|
|
51
|
+
hooksconfig={},
|
|
52
|
+
runtime_hooks=[],
|
|
53
|
+
excludes=[],
|
|
54
|
+
win_no_prefer_redirects=False,
|
|
55
|
+
win_private_assemblies=False,
|
|
56
|
+
cipher=block_cipher,
|
|
57
|
+
noarchive=False,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) # noqa: F821
|
|
61
|
+
|
|
62
|
+
exe = EXE( # noqa: F821
|
|
63
|
+
pyz,
|
|
64
|
+
a.scripts,
|
|
65
|
+
[],
|
|
66
|
+
exclude_binaries=True,
|
|
67
|
+
name="Talk",
|
|
68
|
+
debug=False,
|
|
69
|
+
bootloader_ignore_signals=False,
|
|
70
|
+
strip=False,
|
|
71
|
+
upx=False,
|
|
72
|
+
console=False,
|
|
73
|
+
target_arch="arm64",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
coll = COLLECT( # noqa: F821
|
|
77
|
+
exe,
|
|
78
|
+
a.binaries,
|
|
79
|
+
a.zipfiles,
|
|
80
|
+
a.datas,
|
|
81
|
+
strip=False,
|
|
82
|
+
upx=False,
|
|
83
|
+
name="Talk",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
app = BUNDLE( # noqa: F821
|
|
87
|
+
coll,
|
|
88
|
+
name="Talk.app",
|
|
89
|
+
icon=os.path.join(ROOT, "assets", "Talk.icns"),
|
|
90
|
+
bundle_identifier="com.powrofyou.talk",
|
|
91
|
+
info_plist={
|
|
92
|
+
"LSUIElement": True,
|
|
93
|
+
"NSMicrophoneUsageDescription": "Talk needs microphone access for voice dictation.",
|
|
94
|
+
"CFBundleShortVersionString": "0.2.0",
|
|
95
|
+
"CFBundleName": "Talk",
|
|
96
|
+
"NSHighResolutionCapable": True,
|
|
97
|
+
},
|
|
98
|
+
)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>com.apple.security.cs.allow-jit</key>
|
|
6
|
+
<true/>
|
|
7
|
+
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
|
8
|
+
<true/>
|
|
9
|
+
<key>com.apple.security.cs.disable-library-validation</key>
|
|
10
|
+
<true/>
|
|
11
|
+
<key>com.apple.security.device.audio-input</key>
|
|
12
|
+
<true/>
|
|
13
|
+
<key>com.apple.security.automation.apple-events</key>
|
|
14
|
+
<true/>
|
|
15
|
+
</dict>
|
|
16
|
+
</plist>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "talktype"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "Local-first dictation for macOS — press a hotkey, talk, text appears at your cursor"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -24,6 +24,7 @@ dependencies = [
|
|
|
24
24
|
"parakeet-mlx>=0.4.2",
|
|
25
25
|
"pynput>=1.8.1",
|
|
26
26
|
"python-dotenv>=1.1.1",
|
|
27
|
+
"rumps>=0.4.0",
|
|
27
28
|
"sounddevice>=0.5.2",
|
|
28
29
|
]
|
|
29
30
|
|
|
@@ -44,3 +45,8 @@ packages = ["src/talk"]
|
|
|
44
45
|
|
|
45
46
|
[tool.uv]
|
|
46
47
|
package = true
|
|
48
|
+
|
|
49
|
+
[dependency-groups]
|
|
50
|
+
dev = [
|
|
51
|
+
"pyinstaller>=6.19.0",
|
|
52
|
+
]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Build Talk.app, code-sign it, package as DMG, notarize, and staple.
|
|
3
|
+
# Prerequisites:
|
|
4
|
+
# uv add --dev pyinstaller
|
|
5
|
+
# xcrun notarytool store-credentials "talk-notarize" --apple-id <email> --team-id 7NV8TSX4VN
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
10
|
+
|
|
11
|
+
IDENTITY="Developer ID Application: Powr of You Ltd. (7NV8TSX4VN)"
|
|
12
|
+
KEYCHAIN_PROFILE="talk-notarize"
|
|
13
|
+
APP_NAME="Talk"
|
|
14
|
+
BUNDLE_ID="com.powrofyou.talk"
|
|
15
|
+
|
|
16
|
+
DIST="$ROOT/dist"
|
|
17
|
+
APP="$DIST/$APP_NAME.app"
|
|
18
|
+
DMG="$DIST/$APP_NAME.dmg"
|
|
19
|
+
ENTITLEMENTS="$ROOT/entitlements.plist"
|
|
20
|
+
SPEC="$ROOT/Talk.spec"
|
|
21
|
+
|
|
22
|
+
# ---- helpers ----------------------------------------------------------------
|
|
23
|
+
info() { printf '\033[1;34m→ %s\033[0m\n' "$*"; }
|
|
24
|
+
ok() { printf '\033[1;32m✓ %s\033[0m\n' "$*"; }
|
|
25
|
+
die() { printf '\033[1;31m✗ %s\033[0m\n' "$*" >&2; exit 1; }
|
|
26
|
+
|
|
27
|
+
# ---- pre-flight checks -----------------------------------------------------
|
|
28
|
+
command -v pyinstaller >/dev/null 2>&1 || command -v uv >/dev/null 2>&1 || die "pyinstaller not found"
|
|
29
|
+
command -v hdiutil >/dev/null 2>&1 || die "hdiutil not found (expected on macOS)"
|
|
30
|
+
command -v codesign >/dev/null 2>&1 || die "codesign not found"
|
|
31
|
+
[[ -f "$SPEC" ]] || die "Talk.spec not found at $SPEC"
|
|
32
|
+
[[ -f "$ENTITLEMENTS" ]] || die "entitlements.plist not found at $ENTITLEMENTS"
|
|
33
|
+
|
|
34
|
+
# ---- 1. build .app ---------------------------------------------------------
|
|
35
|
+
info "Building $APP_NAME.app with PyInstaller"
|
|
36
|
+
cd "$ROOT"
|
|
37
|
+
uv run pyinstaller "$SPEC" --clean --noconfirm
|
|
38
|
+
[[ -d "$APP" ]] || die "PyInstaller build did not produce $APP"
|
|
39
|
+
ok "Build complete ($(du -sh "$APP" | cut -f1))"
|
|
40
|
+
|
|
41
|
+
# ---- 2. code-sign (inside-out) ----------------------------------------------
|
|
42
|
+
info "Signing binaries inside the bundle"
|
|
43
|
+
|
|
44
|
+
# Sign all .dylib and .so files first
|
|
45
|
+
find "$APP" -type f \( -name "*.dylib" -o -name "*.so" \) -print0 | \
|
|
46
|
+
while IFS= read -r -d '' lib; do
|
|
47
|
+
codesign --sign "$IDENTITY" --timestamp --force --options runtime \
|
|
48
|
+
--entitlements "$ENTITLEMENTS" "$lib" 2>/dev/null || true
|
|
49
|
+
done
|
|
50
|
+
|
|
51
|
+
# Sign all embedded frameworks
|
|
52
|
+
find "$APP/Contents/Frameworks" -type f -perm +111 -print0 2>/dev/null | \
|
|
53
|
+
while IFS= read -r -d '' bin; do
|
|
54
|
+
codesign --sign "$IDENTITY" --timestamp --force --options runtime \
|
|
55
|
+
--entitlements "$ENTITLEMENTS" "$bin" 2>/dev/null || true
|
|
56
|
+
done
|
|
57
|
+
|
|
58
|
+
# Sign the main executable
|
|
59
|
+
codesign --sign "$IDENTITY" --timestamp --force --options runtime \
|
|
60
|
+
--entitlements "$ENTITLEMENTS" "$APP/Contents/MacOS/$APP_NAME"
|
|
61
|
+
|
|
62
|
+
# Sign the whole .app bundle
|
|
63
|
+
codesign --sign "$IDENTITY" --deep --force --options runtime \
|
|
64
|
+
--timestamp --entitlements "$ENTITLEMENTS" "$APP"
|
|
65
|
+
|
|
66
|
+
ok "Code signing complete"
|
|
67
|
+
|
|
68
|
+
# Verify
|
|
69
|
+
codesign --verify --deep --strict "$APP" || die "Signature verification failed"
|
|
70
|
+
ok "Signature verified"
|
|
71
|
+
|
|
72
|
+
# ---- 3. create DMG ---------------------------------------------------------
|
|
73
|
+
info "Creating DMG"
|
|
74
|
+
rm -f "$DMG"
|
|
75
|
+
|
|
76
|
+
STAGING="$(mktemp -d)"
|
|
77
|
+
trap 'rm -rf "$STAGING"' EXIT
|
|
78
|
+
cp -R "$APP" "$STAGING/"
|
|
79
|
+
ln -s /Applications "$STAGING/Applications"
|
|
80
|
+
|
|
81
|
+
hdiutil create -volname "$APP_NAME" -srcfolder "$STAGING" -ov -format UDZO "$DMG"
|
|
82
|
+
rm -rf "$STAGING"
|
|
83
|
+
trap - EXIT
|
|
84
|
+
|
|
85
|
+
ok "DMG created at $DMG ($(du -sh "$DMG" | cut -f1))"
|
|
86
|
+
|
|
87
|
+
# ---- 4. sign DMG -----------------------------------------------------------
|
|
88
|
+
info "Signing DMG"
|
|
89
|
+
codesign --sign "$IDENTITY" --timestamp "$DMG"
|
|
90
|
+
ok "DMG signed"
|
|
91
|
+
|
|
92
|
+
# ---- 5. notarize ------------------------------------------------------------
|
|
93
|
+
info "Submitting for notarization (this may take a few minutes)"
|
|
94
|
+
xcrun notarytool submit "$DMG" \
|
|
95
|
+
--keychain-profile "$KEYCHAIN_PROFILE" \
|
|
96
|
+
--wait
|
|
97
|
+
|
|
98
|
+
ok "Notarization complete"
|
|
99
|
+
|
|
100
|
+
# ---- 6. staple --------------------------------------------------------------
|
|
101
|
+
info "Stapling notarization ticket"
|
|
102
|
+
xcrun stapler staple "$DMG"
|
|
103
|
+
ok "Stapled"
|
|
104
|
+
|
|
105
|
+
# ---- done -------------------------------------------------------------------
|
|
106
|
+
echo ""
|
|
107
|
+
ok "Done! Distributable DMG: $DMG"
|
|
108
|
+
echo " Size: $(du -sh "$DMG" | cut -f1)"
|
|
109
|
+
echo " Verify: spctl --assess --type open --context context:primary-signature \"$DMG\""
|
|
@@ -48,8 +48,8 @@ ensure_key_default() {
|
|
|
48
48
|
|
|
49
49
|
# Keep Parakeet as the default for fresh installs.
|
|
50
50
|
ensure_key_default "DICTATE_BACKEND" "parakeet"
|
|
51
|
-
ensure_key_default "DICTATE_HOTKEY" "<
|
|
52
|
-
ensure_key_default "DICTATE_QUIT_HOTKEY" "<
|
|
51
|
+
ensure_key_default "DICTATE_HOTKEY" "<ctrl>+<alt>+d"
|
|
52
|
+
ensure_key_default "DICTATE_QUIT_HOTKEY" "<ctrl>+<alt>+q"
|
|
53
53
|
ensure_key_default "DICTATE_AUTOPASTE" "true"
|
|
54
54
|
ensure_key_default "PARAKEET_MODEL" "mlx-community/parakeet-tdt-0.6b-v3"
|
|
55
55
|
|
|
@@ -5,6 +5,7 @@ import subprocess
|
|
|
5
5
|
import threading
|
|
6
6
|
import time
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from typing import Callable
|
|
8
9
|
|
|
9
10
|
from pynput import keyboard
|
|
10
11
|
|
|
@@ -13,6 +14,8 @@ from talk.backends.factory import build_backend
|
|
|
13
14
|
from talk.config import Settings
|
|
14
15
|
from talk.paste import paste_text
|
|
15
16
|
|
|
17
|
+
StateCallback = Callable[[str], None]
|
|
18
|
+
|
|
16
19
|
|
|
17
20
|
def _play_sound(filename: str) -> None:
|
|
18
21
|
sound_path = Path("/System/Library/Sounds") / filename
|
|
@@ -26,7 +29,11 @@ def _play_sound(filename: str) -> None:
|
|
|
26
29
|
|
|
27
30
|
|
|
28
31
|
class DictationApp:
|
|
29
|
-
def __init__(
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
settings: Settings,
|
|
35
|
+
on_state_change: StateCallback | None = None,
|
|
36
|
+
) -> None:
|
|
30
37
|
self.settings = settings
|
|
31
38
|
self.backend = build_backend(settings)
|
|
32
39
|
self.recorder = MicRecorder(
|
|
@@ -34,11 +41,20 @@ class DictationApp:
|
|
|
34
41
|
channels=settings.channels,
|
|
35
42
|
)
|
|
36
43
|
|
|
44
|
+
self._on_state_change = on_state_change
|
|
37
45
|
self._lock = threading.Lock()
|
|
38
46
|
self._stop_event = threading.Event()
|
|
47
|
+
self._listener: keyboard.GlobalHotKeys | None = None
|
|
39
48
|
self._is_recording = False
|
|
40
49
|
self._is_transcribing = False
|
|
41
50
|
|
|
51
|
+
def _emit_state(self, state: str) -> None:
|
|
52
|
+
if self._on_state_change is not None:
|
|
53
|
+
try:
|
|
54
|
+
self._on_state_change(state)
|
|
55
|
+
except Exception: # noqa: BLE001
|
|
56
|
+
pass
|
|
57
|
+
|
|
42
58
|
def _toggle_recording(self) -> None:
|
|
43
59
|
chunk: AudioChunk | None = None
|
|
44
60
|
|
|
@@ -55,6 +71,7 @@ class DictationApp:
|
|
|
55
71
|
return
|
|
56
72
|
|
|
57
73
|
self._is_recording = True
|
|
74
|
+
self._emit_state("recording")
|
|
58
75
|
_play_sound("Glass.aiff")
|
|
59
76
|
print("[rec] Recording... press hotkey again to stop.")
|
|
60
77
|
return
|
|
@@ -64,10 +81,12 @@ class DictationApp:
|
|
|
64
81
|
except RecorderError as exc:
|
|
65
82
|
print(f"[error] Could not stop recording cleanly: {exc}")
|
|
66
83
|
self._is_recording = False
|
|
84
|
+
self._emit_state("idle")
|
|
67
85
|
return
|
|
68
86
|
|
|
69
87
|
self._is_recording = False
|
|
70
88
|
self._is_transcribing = True
|
|
89
|
+
self._emit_state("transcribing")
|
|
71
90
|
|
|
72
91
|
_play_sound("Pop.aiff")
|
|
73
92
|
worker = threading.Thread(
|
|
@@ -101,6 +120,7 @@ class DictationApp:
|
|
|
101
120
|
finally:
|
|
102
121
|
with self._lock:
|
|
103
122
|
self._is_transcribing = False
|
|
123
|
+
self._emit_state("idle")
|
|
104
124
|
|
|
105
125
|
def _request_shutdown(self) -> None:
|
|
106
126
|
with self._lock:
|
|
@@ -113,25 +133,42 @@ class DictationApp:
|
|
|
113
133
|
print("[exit] Shutting down dictation app.")
|
|
114
134
|
self._stop_event.set()
|
|
115
135
|
|
|
116
|
-
def
|
|
117
|
-
|
|
118
|
-
print(f"[ready] Toggle dictation: {self.settings.hotkey}")
|
|
119
|
-
print(f"[ready] Quit app: {self.settings.quit_hotkey}")
|
|
136
|
+
def start_hotkey_listener(self) -> None:
|
|
137
|
+
"""Start the global hotkey listener without blocking.
|
|
120
138
|
|
|
139
|
+
Use this when another event loop (e.g. rumps) owns the main thread.
|
|
140
|
+
Call stop() to tear down.
|
|
141
|
+
"""
|
|
121
142
|
keymap = {
|
|
122
143
|
self.settings.hotkey: self._toggle_recording,
|
|
123
144
|
self.settings.quit_hotkey: self._request_shutdown,
|
|
124
145
|
}
|
|
146
|
+
self._listener = keyboard.GlobalHotKeys(keymap)
|
|
147
|
+
self._listener.start()
|
|
148
|
+
|
|
149
|
+
def stop(self) -> None:
|
|
150
|
+
"""Stop the hotkey listener and clean up."""
|
|
151
|
+
self._request_shutdown()
|
|
152
|
+
if self._listener is not None:
|
|
153
|
+
self._listener.stop()
|
|
154
|
+
self._listener = None
|
|
155
|
+
|
|
156
|
+
def run(self) -> None:
|
|
157
|
+
"""Blocking run loop for CLI mode."""
|
|
158
|
+
print(f"[ready] Backend: {self.backend.name}")
|
|
159
|
+
print(f"[ready] Toggle dictation: {self.settings.hotkey}")
|
|
160
|
+
print(f"[ready] Quit app: {self.settings.quit_hotkey}")
|
|
125
161
|
|
|
126
162
|
signal.signal(signal.SIGTERM, lambda *_: self._request_shutdown())
|
|
127
163
|
|
|
128
|
-
|
|
129
|
-
listener.start()
|
|
164
|
+
self.start_hotkey_listener()
|
|
130
165
|
try:
|
|
131
166
|
while not self._stop_event.is_set():
|
|
132
167
|
time.sleep(0.15)
|
|
133
168
|
except KeyboardInterrupt:
|
|
134
169
|
self._request_shutdown()
|
|
135
170
|
finally:
|
|
136
|
-
|
|
171
|
+
if self._listener is not None:
|
|
172
|
+
self._listener.stop()
|
|
173
|
+
self._listener = None
|
|
137
174
|
|
|
@@ -76,8 +76,8 @@ def load_settings() -> Settings:
|
|
|
76
76
|
load_dotenv(CONFIG_DIR / ".env")
|
|
77
77
|
|
|
78
78
|
backend = os.getenv("DICTATE_BACKEND", "parakeet").strip().lower()
|
|
79
|
-
hotkey = normalize_hotkey(os.getenv("DICTATE_HOTKEY", "<
|
|
80
|
-
quit_hotkey = normalize_hotkey(os.getenv("DICTATE_QUIT_HOTKEY", "<
|
|
79
|
+
hotkey = normalize_hotkey(os.getenv("DICTATE_HOTKEY", "<ctrl>+<alt>+d"))
|
|
80
|
+
quit_hotkey = normalize_hotkey(os.getenv("DICTATE_QUIT_HOTKEY", "<ctrl>+<alt>+q"))
|
|
81
81
|
|
|
82
82
|
return Settings(
|
|
83
83
|
backend=backend,
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Menu bar app entry point for Talk.app (macOS)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _resource_path(filename: str) -> str:
|
|
11
|
+
"""Resolve asset paths for both dev and PyInstaller bundles."""
|
|
12
|
+
if getattr(sys, "_MEIPASS", None):
|
|
13
|
+
return str(Path(sys._MEIPASS) / filename) # type: ignore[attr-defined]
|
|
14
|
+
return str(Path(__file__).resolve().parent.parent.parent / "assets" / filename)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main() -> None:
|
|
18
|
+
import rumps
|
|
19
|
+
|
|
20
|
+
from talk.app import DictationApp
|
|
21
|
+
from talk.config import load_settings
|
|
22
|
+
|
|
23
|
+
ICON_IDLE = _resource_path("mic.png")
|
|
24
|
+
ICON_RECORDING = _resource_path("mic_rec.png")
|
|
25
|
+
ICON_TRANSCRIBING = _resource_path("mic_wait.png")
|
|
26
|
+
|
|
27
|
+
class TalkApp(rumps.App):
|
|
28
|
+
def __init__(self) -> None:
|
|
29
|
+
super().__init__(
|
|
30
|
+
"Talk",
|
|
31
|
+
icon=ICON_IDLE if os.path.exists(ICON_IDLE) else None,
|
|
32
|
+
quit_button=None,
|
|
33
|
+
)
|
|
34
|
+
self._status_item = rumps.MenuItem("Starting...")
|
|
35
|
+
self.menu = [
|
|
36
|
+
self._status_item,
|
|
37
|
+
None,
|
|
38
|
+
rumps.MenuItem("Quit Talk", callback=self._quit),
|
|
39
|
+
]
|
|
40
|
+
self._dictation: DictationApp | None = None
|
|
41
|
+
|
|
42
|
+
@rumps.timer(1)
|
|
43
|
+
def _deferred_init(self, timer: rumps.Timer) -> None:
|
|
44
|
+
"""Load model after the menu bar icon is visible."""
|
|
45
|
+
timer.stop()
|
|
46
|
+
settings = load_settings()
|
|
47
|
+
self._dictation = DictationApp(
|
|
48
|
+
settings, on_state_change=self._on_state,
|
|
49
|
+
)
|
|
50
|
+
self._dictation.start_hotkey_listener()
|
|
51
|
+
self._status_item.title = f"Ready \u2014 {settings.hotkey} to dictate"
|
|
52
|
+
|
|
53
|
+
def _on_state(self, state: str) -> None:
|
|
54
|
+
icons = {
|
|
55
|
+
"idle": ICON_IDLE,
|
|
56
|
+
"recording": ICON_RECORDING,
|
|
57
|
+
"transcribing": ICON_TRANSCRIBING,
|
|
58
|
+
}
|
|
59
|
+
icon_path = icons.get(state, ICON_IDLE)
|
|
60
|
+
if os.path.exists(icon_path):
|
|
61
|
+
self.icon = icon_path
|
|
62
|
+
|
|
63
|
+
labels = {
|
|
64
|
+
"idle": "Ready",
|
|
65
|
+
"recording": "Recording...",
|
|
66
|
+
"transcribing": "Transcribing...",
|
|
67
|
+
}
|
|
68
|
+
self._status_item.title = labels.get(state, "Ready")
|
|
69
|
+
|
|
70
|
+
def _quit(self, _sender: rumps.MenuItem) -> None:
|
|
71
|
+
if self._dictation is not None:
|
|
72
|
+
self._dictation.stop()
|
|
73
|
+
rumps.quit_application()
|
|
74
|
+
|
|
75
|
+
TalkApp().run()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
main()
|
|
@@ -6,6 +6,15 @@ resolution-markers = [
|
|
|
6
6
|
"python_full_version < '3.13'",
|
|
7
7
|
]
|
|
8
8
|
|
|
9
|
+
[[package]]
|
|
10
|
+
name = "altgraph"
|
|
11
|
+
version = "0.17.5"
|
|
12
|
+
source = { registry = "https://pypi.org/simple" }
|
|
13
|
+
sdist = { url = "https://files.pythonhosted.org/packages/7e/f8/97fdf103f38fed6792a1601dbc16cc8aac56e7459a9fff08c812d8ae177a/altgraph-0.17.5.tar.gz", hash = "sha256:c87b395dd12fabde9c99573a9749d67da8d29ef9de0125c7f536699b4a9bc9e7", size = 48428, upload-time = "2025-11-21T20:35:50.583Z" }
|
|
14
|
+
wheels = [
|
|
15
|
+
{ url = "https://files.pythonhosted.org/packages/a9/ba/000a1996d4308bc65120167c21241a3b205464a2e0b58deda26ae8ac21d1/altgraph-0.17.5-py2.py3-none-any.whl", hash = "sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597", size = 21228, upload-time = "2025-11-21T20:35:49.444Z" },
|
|
16
|
+
]
|
|
17
|
+
|
|
9
18
|
[[package]]
|
|
10
19
|
name = "annotated-doc"
|
|
11
20
|
version = "0.0.4"
|
|
@@ -585,6 +594,18 @@ wheels = [
|
|
|
585
594
|
{ url = "https://files.pythonhosted.org/packages/30/a8/e61a8c2b3cc7a597073d9cde1fcbb567e9d827f1db30c93cf80422eac70d/llvmlite-0.46.0-cp314-cp314-win_amd64.whl", hash = "sha256:7821eda3ec1f18050f981819756631d60b6d7ab1a6cf806d9efefbe3f4082d61", size = 39153056, upload-time = "2025-12-08T18:15:33.938Z" },
|
|
586
595
|
]
|
|
587
596
|
|
|
597
|
+
[[package]]
|
|
598
|
+
name = "macholib"
|
|
599
|
+
version = "1.16.4"
|
|
600
|
+
source = { registry = "https://pypi.org/simple" }
|
|
601
|
+
dependencies = [
|
|
602
|
+
{ name = "altgraph" },
|
|
603
|
+
]
|
|
604
|
+
sdist = { url = "https://files.pythonhosted.org/packages/10/2f/97589876ea967487978071c9042518d28b958d87b17dceb7cdc1d881f963/macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362", size = 59427, upload-time = "2025-11-22T08:28:38.373Z" }
|
|
605
|
+
wheels = [
|
|
606
|
+
{ url = "https://files.pythonhosted.org/packages/c7/d1/a9f36f8ecdf0fb7c9b1e78c8d7af12b8c8754e74851ac7b94a8305540fc7/macholib-1.16.4-py2.py3-none-any.whl", hash = "sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea", size = 38117, upload-time = "2025-11-22T08:28:36.939Z" },
|
|
607
|
+
]
|
|
608
|
+
|
|
588
609
|
[[package]]
|
|
589
610
|
name = "markdown-it-py"
|
|
590
611
|
version = "4.0.0"
|
|
@@ -851,6 +872,15 @@ wheels = [
|
|
|
851
872
|
{ url = "https://files.pythonhosted.org/packages/76/38/2ea2d9dfc35295b600e86583baf6cbb9f3242a52cbea98625ad788fb9afb/parakeet_mlx-0.5.1-py3-none-any.whl", hash = "sha256:869cfe8d7a16b00f4a49d2f42a76210aca638681bd972911f4ab4d7bf9ca36b6", size = 37205, upload-time = "2026-02-21T04:18:14.899Z" },
|
|
852
873
|
]
|
|
853
874
|
|
|
875
|
+
[[package]]
|
|
876
|
+
name = "pefile"
|
|
877
|
+
version = "2024.8.26"
|
|
878
|
+
source = { registry = "https://pypi.org/simple" }
|
|
879
|
+
sdist = { url = "https://files.pythonhosted.org/packages/03/4f/2750f7f6f025a1507cd3b7218691671eecfd0bbebebe8b39aa0fe1d360b8/pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632", size = 76008, upload-time = "2024-08-26T20:58:38.155Z" }
|
|
880
|
+
wheels = [
|
|
881
|
+
{ url = "https://files.pythonhosted.org/packages/54/16/12b82f791c7f50ddec566873d5bdd245baa1491bac11d15ffb98aecc8f8b/pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f", size = 74766, upload-time = "2024-08-26T21:01:02.632Z" },
|
|
882
|
+
]
|
|
883
|
+
|
|
854
884
|
[[package]]
|
|
855
885
|
name = "platformdirs"
|
|
856
886
|
version = "4.9.2"
|
|
@@ -1004,6 +1034,47 @@ wheels = [
|
|
|
1004
1034
|
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
|
1005
1035
|
]
|
|
1006
1036
|
|
|
1037
|
+
[[package]]
|
|
1038
|
+
name = "pyinstaller"
|
|
1039
|
+
version = "6.19.0"
|
|
1040
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1041
|
+
dependencies = [
|
|
1042
|
+
{ name = "altgraph" },
|
|
1043
|
+
{ name = "macholib", marker = "sys_platform == 'darwin'" },
|
|
1044
|
+
{ name = "packaging" },
|
|
1045
|
+
{ name = "pefile", marker = "sys_platform == 'win32'" },
|
|
1046
|
+
{ name = "pyinstaller-hooks-contrib" },
|
|
1047
|
+
{ name = "pywin32-ctypes", marker = "sys_platform == 'win32'" },
|
|
1048
|
+
{ name = "setuptools" },
|
|
1049
|
+
]
|
|
1050
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c8/63/fd62472b6371d89dc138d40c36d87a50dc2de18a035803bbdc376b4ffac4/pyinstaller-6.19.0.tar.gz", hash = "sha256:ec73aeb8bd9b7f2f1240d328a4542e90b3c6e6fbc106014778431c616592a865", size = 4036072, upload-time = "2026-02-14T18:06:28.718Z" }
|
|
1051
|
+
wheels = [
|
|
1052
|
+
{ url = "https://files.pythonhosted.org/packages/e3/eb/23374721fecfa72677e79800921cb6aceefa6ba48574dc404f3f6c6c3be7/pyinstaller-6.19.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:4190e76b74f0c4b5c5f11ac360928cd2e36ec8e3194d437bf6b8648c7bc0c134", size = 1040563, upload-time = "2026-02-14T18:05:22.436Z" },
|
|
1053
|
+
{ url = "https://files.pythonhosted.org/packages/cd/7e/dfd724b0b533f5aaec0ee5df406fe2319987ed6964480a706f85478b12ea/pyinstaller-6.19.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8bd68abd812d8a6ba33b9f1810e91fee0f325969733721b78151f0065319ca11", size = 735477, upload-time = "2026-02-14T18:05:27.143Z" },
|
|
1054
|
+
{ url = "https://files.pythonhosted.org/packages/88/c9/ee3a4101c31f26344e66896c73c1fd6ed8282bf871473365b7f8674af406/pyinstaller-6.19.0-py3-none-manylinux2014_i686.whl", hash = "sha256:1ec54ef967996ca61dacba676227e2b23219878ccce5ee9d6f3aada7b8ed8abf", size = 747143, upload-time = "2026-02-14T18:05:31.488Z" },
|
|
1055
|
+
{ url = "https://files.pythonhosted.org/packages/da/0a/fc77e9f861be8cf300ac37155f59cc92aff99b29f2ddd78546f563a5b5a6/pyinstaller-6.19.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:4ab2bb52e58448e14ddf9450601bdedd66800465043501c1d8f1cab87b60b122", size = 744849, upload-time = "2026-02-14T18:05:35.492Z" },
|
|
1056
|
+
{ url = "https://files.pythonhosted.org/packages/6d/e3/6872e020ee758afe0b821663858492c10745608b07150e5e2c824a5b3e1c/pyinstaller-6.19.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:da6d5c6391ccefe73554b9fa29b86001c8e378e0f20c2a4004f836ba537eff63", size = 741590, upload-time = "2026-02-14T18:05:39.59Z" },
|
|
1057
|
+
{ url = "https://files.pythonhosted.org/packages/53/60/b8db5f1a4b0fb228175f2ea0aa33f949adcc097fbe981cc524f9faf85777/pyinstaller-6.19.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a0fc5f6b3c55aa54353f0c74ffa59b1115433c1850c6f655d62b461a2ed6cbbe", size = 741448, upload-time = "2026-02-14T18:05:45.636Z" },
|
|
1058
|
+
{ url = "https://files.pythonhosted.org/packages/6f/4d/63b0600f2694e9141b83129fbc1c488ec84d5a0770b1448ec154dcd0fee9/pyinstaller-6.19.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:e649ba6bd1b0b89b210ad92adb5fbdc8a42dd2c5ca4f72ef3a0bfec83a424b83", size = 740613, upload-time = "2026-02-14T18:05:49.726Z" },
|
|
1059
|
+
{ url = "https://files.pythonhosted.org/packages/01/d4/e812ad36178093a0e9fd4b8127577748dd85b0cb71de912229dca21fd741/pyinstaller-6.19.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:481a909c8e60c8692fc60fcb1344d984b44b943f8bc9682f2fcdae305ad297e6", size = 740350, upload-time = "2026-02-14T18:05:54.093Z" },
|
|
1060
|
+
{ url = "https://files.pythonhosted.org/packages/52/03/b2c2ee41fb8e10fd2a45d21f5ec2ef25852cfb978dbf762972eed59e3d63/pyinstaller-6.19.0-py3-none-win32.whl", hash = "sha256:3c5c251054fe4cfaa04c34a363dcfbf811545438cb7198304cd444756bc2edd2", size = 1324317, upload-time = "2026-02-14T18:06:00.085Z" },
|
|
1061
|
+
{ url = "https://files.pythonhosted.org/packages/9c/d3/6d5e62b8270e2b53a6065e281b3a7785079b00e9019c8019952828dd1669/pyinstaller-6.19.0-py3-none-win_amd64.whl", hash = "sha256:b5bb6536c6560330d364d91522250f254b107cf69129d9cbcd0e6727c570be33", size = 1384894, upload-time = "2026-02-14T18:06:06.425Z" },
|
|
1062
|
+
{ url = "https://files.pythonhosted.org/packages/81/65/458cd523308a101a22fd2742893405030cc24994cc74b1b767cecf137160/pyinstaller-6.19.0-py3-none-win_arm64.whl", hash = "sha256:c2d5a539b0bfe6159d5522c8c70e1c0e487f22c2badae0f97d45246223b798ea", size = 1325374, upload-time = "2026-02-14T18:06:12.804Z" },
|
|
1063
|
+
]
|
|
1064
|
+
|
|
1065
|
+
[[package]]
|
|
1066
|
+
name = "pyinstaller-hooks-contrib"
|
|
1067
|
+
version = "2026.1"
|
|
1068
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1069
|
+
dependencies = [
|
|
1070
|
+
{ name = "packaging" },
|
|
1071
|
+
{ name = "setuptools" },
|
|
1072
|
+
]
|
|
1073
|
+
sdist = { url = "https://files.pythonhosted.org/packages/95/eb/e1dd9a5348e4cf348471c0e5fd617d948779bc3199cf4edb134d8fceca91/pyinstaller_hooks_contrib-2026.1.tar.gz", hash = "sha256:a5f0891a1e81e92406ab917d9e76adfd7a2b68415ee2e35c950a7b3910bc361b", size = 171504, upload-time = "2026-02-18T13:01:15.711Z" }
|
|
1074
|
+
wheels = [
|
|
1075
|
+
{ url = "https://files.pythonhosted.org/packages/7f/69/12bafee3cc485d977f596e0d803d7c6fb147430fc35dfe505730aa3a28dd/pyinstaller_hooks_contrib-2026.1-py3-none-any.whl", hash = "sha256:66ad4888ba67de6f3cfd7ef554f9dd1a4389e2eb19f84d7129a5a6818e3f2180", size = 452841, upload-time = "2026-02-18T13:01:14.471Z" },
|
|
1076
|
+
]
|
|
1077
|
+
|
|
1007
1078
|
[[package]]
|
|
1008
1079
|
name = "pynput"
|
|
1009
1080
|
version = "1.8.1"
|
|
@@ -1129,6 +1200,15 @@ wheels = [
|
|
|
1129
1200
|
{ url = "https://files.pythonhosted.org/packages/fc/b8/ff33610932e0ee81ae7f1269c890f697d56ff74b9f5b2ee5d9b7fa2c5355/python_xlib-0.33-py2.py3-none-any.whl", hash = "sha256:c3534038d42e0df2f1392a1b30a15a4ff5fdc2b86cfa94f072bf11b10a164398", size = 182185, upload-time = "2022-12-25T18:52:58.662Z" },
|
|
1130
1201
|
]
|
|
1131
1202
|
|
|
1203
|
+
[[package]]
|
|
1204
|
+
name = "pywin32-ctypes"
|
|
1205
|
+
version = "0.2.3"
|
|
1206
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1207
|
+
sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" }
|
|
1208
|
+
wheels = [
|
|
1209
|
+
{ url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" },
|
|
1210
|
+
]
|
|
1211
|
+
|
|
1132
1212
|
[[package]]
|
|
1133
1213
|
name = "pyyaml"
|
|
1134
1214
|
version = "6.0.3"
|
|
@@ -1212,6 +1292,15 @@ wheels = [
|
|
|
1212
1292
|
{ url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" },
|
|
1213
1293
|
]
|
|
1214
1294
|
|
|
1295
|
+
[[package]]
|
|
1296
|
+
name = "rumps"
|
|
1297
|
+
version = "0.4.0"
|
|
1298
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1299
|
+
dependencies = [
|
|
1300
|
+
{ name = "pyobjc-framework-cocoa" },
|
|
1301
|
+
]
|
|
1302
|
+
sdist = { url = "https://files.pythonhosted.org/packages/b2/e2/2e6a47951290bd1a2831dcc50aec4b25d104c0cf00e8b7868cbd29cf3bfe/rumps-0.4.0.tar.gz", hash = "sha256:17fb33c21b54b1e25db0d71d1d793dc19dc3c0b7d8c79dc6d833d0cffc8b1596", size = 39257, upload-time = "2022-10-15T05:15:10.386Z" }
|
|
1303
|
+
|
|
1215
1304
|
[[package]]
|
|
1216
1305
|
name = "scikit-learn"
|
|
1217
1306
|
version = "1.8.0"
|
|
@@ -1333,6 +1422,15 @@ wheels = [
|
|
|
1333
1422
|
{ url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" },
|
|
1334
1423
|
]
|
|
1335
1424
|
|
|
1425
|
+
[[package]]
|
|
1426
|
+
name = "setuptools"
|
|
1427
|
+
version = "82.0.0"
|
|
1428
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1429
|
+
sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" }
|
|
1430
|
+
wheels = [
|
|
1431
|
+
{ url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" },
|
|
1432
|
+
]
|
|
1433
|
+
|
|
1336
1434
|
[[package]]
|
|
1337
1435
|
name = "shellingham"
|
|
1338
1436
|
version = "1.5.4"
|
|
@@ -1456,7 +1554,7 @@ wheels = [
|
|
|
1456
1554
|
]
|
|
1457
1555
|
|
|
1458
1556
|
[[package]]
|
|
1459
|
-
name = "
|
|
1557
|
+
name = "talktype"
|
|
1460
1558
|
version = "0.1.0"
|
|
1461
1559
|
source = { editable = "." }
|
|
1462
1560
|
dependencies = [
|
|
@@ -1465,9 +1563,15 @@ dependencies = [
|
|
|
1465
1563
|
{ name = "parakeet-mlx" },
|
|
1466
1564
|
{ name = "pynput" },
|
|
1467
1565
|
{ name = "python-dotenv" },
|
|
1566
|
+
{ name = "rumps" },
|
|
1468
1567
|
{ name = "sounddevice" },
|
|
1469
1568
|
]
|
|
1470
1569
|
|
|
1570
|
+
[package.dev-dependencies]
|
|
1571
|
+
dev = [
|
|
1572
|
+
{ name = "pyinstaller" },
|
|
1573
|
+
]
|
|
1574
|
+
|
|
1471
1575
|
[package.metadata]
|
|
1472
1576
|
requires-dist = [
|
|
1473
1577
|
{ name = "numpy", specifier = ">=1.26" },
|
|
@@ -1475,9 +1579,13 @@ requires-dist = [
|
|
|
1475
1579
|
{ name = "parakeet-mlx", specifier = ">=0.4.2" },
|
|
1476
1580
|
{ name = "pynput", specifier = ">=1.8.1" },
|
|
1477
1581
|
{ name = "python-dotenv", specifier = ">=1.1.1" },
|
|
1582
|
+
{ name = "rumps", specifier = ">=0.4.0" },
|
|
1478
1583
|
{ name = "sounddevice", specifier = ">=0.5.2" },
|
|
1479
1584
|
]
|
|
1480
1585
|
|
|
1586
|
+
[package.metadata.requires-dev]
|
|
1587
|
+
dev = [{ name = "pyinstaller", specifier = ">=6.19.0" }]
|
|
1588
|
+
|
|
1481
1589
|
[[package]]
|
|
1482
1590
|
name = "threadpoolctl"
|
|
1483
1591
|
version = "3.6.0"
|
|
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
|