echoact 0.1.0__py3-none-any.whl

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.
Files changed (80) hide show
  1. echoact/__init__.py +3 -0
  2. echoact/__main__.py +117 -0
  3. echoact/app.py +315 -0
  4. echoact/audio/__init__.py +0 -0
  5. echoact/audio/devices.py +192 -0
  6. echoact/audio/player.py +611 -0
  7. echoact/audio/wav.py +854 -0
  8. echoact/config/__init__.py +0 -0
  9. echoact/config/budget.py +370 -0
  10. echoact/config/settings.py +1244 -0
  11. echoact/db/__init__.py +0 -0
  12. echoact/db/backup.py +2429 -0
  13. echoact/db/migrations.py +434 -0
  14. echoact/db/schema.sql +214 -0
  15. echoact/db/store.py +2062 -0
  16. echoact/diagnostics.py +902 -0
  17. echoact/domain.py +487 -0
  18. echoact/engine/__init__.py +0 -0
  19. echoact/engine/container.py +843 -0
  20. echoact/engine/protocol.py +241 -0
  21. echoact/engine/runtime.py +324 -0
  22. echoact/engine/supervisor.py +961 -0
  23. echoact/engine/worker.py +659 -0
  24. echoact/errors.py +281 -0
  25. echoact/instance.py +172 -0
  26. echoact/jobs/__init__.py +0 -0
  27. echoact/jobs/engine.py +776 -0
  28. echoact/jobs/request.py +300 -0
  29. echoact/mcp/__init__.py +0 -0
  30. echoact/mcp/__main__.py +50 -0
  31. echoact/mcp/client.py +202 -0
  32. echoact/mcp/config.py +112 -0
  33. echoact/mcp/server.py +340 -0
  34. echoact/models/__init__.py +0 -0
  35. echoact/models/catalog.py +273 -0
  36. echoact/models/manifest.py +278 -0
  37. echoact/models/registry.py +1551 -0
  38. echoact/paths.py +93 -0
  39. echoact/policy.py +189 -0
  40. echoact/security/__init__.py +0 -0
  41. echoact/security/credentials.py +930 -0
  42. echoact/security/ratelimit.py +534 -0
  43. echoact/service/__init__.py +20 -0
  44. echoact/service/app.py +182 -0
  45. echoact/service/deps.py +563 -0
  46. echoact/service/errors.py +241 -0
  47. echoact/service/routes.py +1125 -0
  48. echoact/service/schemas.py +509 -0
  49. echoact/service/server.py +270 -0
  50. echoact/text/__init__.py +0 -0
  51. echoact/text/language.py +44 -0
  52. echoact/text/loader.py +577 -0
  53. echoact/text/normalize.py +924 -0
  54. echoact/text/segment.py +499 -0
  55. echoact/text/sniff.py +1202 -0
  56. echoact/ui/__init__.py +0 -0
  57. echoact/ui/bridge.py +50 -0
  58. echoact/ui/controls.py +360 -0
  59. echoact/ui/credential_dialog.py +131 -0
  60. echoact/ui/fonts.py +94 -0
  61. echoact/ui/i18n.py +260 -0
  62. echoact/ui/icons.py +440 -0
  63. echoact/ui/library.py +1642 -0
  64. echoact/ui/licence.py +162 -0
  65. echoact/ui/main_window.py +1202 -0
  66. echoact/ui/mcp_setup.py +494 -0
  67. echoact/ui/models_view.py +1142 -0
  68. echoact/ui/notifications.py +202 -0
  69. echoact/ui/reading.py +494 -0
  70. echoact/ui/settings_view.py +2258 -0
  71. echoact/ui/status_view.py +1193 -0
  72. echoact/ui/theme.py +579 -0
  73. echoact/util/__init__.py +0 -0
  74. echoact/util/ids.py +62 -0
  75. echoact/util/logging.py +127 -0
  76. echoact-0.1.0.dist-info/METADATA +162 -0
  77. echoact-0.1.0.dist-info/RECORD +80 -0
  78. echoact-0.1.0.dist-info/WHEEL +4 -0
  79. echoact-0.1.0.dist-info/entry_points.txt +3 -0
  80. echoact-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,127 @@
1
+ """Logging that cannot leak what N-20 forbids.
2
+
3
+ Default logs carry no body text, no audio, no credential, and no home path.
4
+ That is not a review rule here but a filter: ``SensitiveFilter`` rejects a
5
+ record whose message or arguments look like a credential, and every helper
6
+ in this module takes identifiers and lengths rather than content.
7
+
8
+ Retention follows 4.1: 7 days, 100 MB, whichever is reached first.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import logging
14
+ import logging.handlers
15
+ import re
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+ from ..paths import log_dir, redact
20
+ from ..policy import LOG_MAX_BYTES, LOG_RETENTION_DAYS
21
+
22
+ _CONFIGURED = False
23
+
24
+ # Anything that looks like one of our credentials, in case a caller ever
25
+ # formats one into a message by accident.
26
+ _SECRET = re.compile(r"\b(eak_[A-Za-z0-9_\-]{8,}|[A-Fa-f0-9]{40,})\b")
27
+
28
+
29
+ class SensitiveFilter(logging.Filter):
30
+ """Redacts credential-shaped text rather than dropping the record.
31
+
32
+ Dropping would hide a diagnostic; N-25 wants the problem traceable.
33
+ Redacting keeps the stage, the code, and the job id, which is what
34
+ tracing actually needs.
35
+ """
36
+
37
+ def filter(self, record: logging.LogRecord) -> bool:
38
+ try:
39
+ msg = record.getMessage()
40
+ except Exception:
41
+ return True
42
+ if _SECRET.search(msg):
43
+ record.msg = _SECRET.sub("<redacted>", msg)
44
+ record.args = ()
45
+ return True
46
+
47
+
48
+ def configure(level: int = logging.INFO, *, to_file: bool = True) -> None:
49
+ """Install handlers once. Safe to call from any entry point."""
50
+ global _CONFIGURED
51
+ if _CONFIGURED:
52
+ return
53
+ _CONFIGURED = True
54
+
55
+ root = logging.getLogger("echoact")
56
+ root.setLevel(level)
57
+ root.propagate = False
58
+
59
+ fmt = logging.Formatter(
60
+ "%(asctime)s %(levelname)-7s %(name)s %(message)s", datefmt="%Y-%m-%dT%H:%M:%S"
61
+ )
62
+ sensitive = SensitiveFilter()
63
+
64
+ stderr = logging.StreamHandler()
65
+ stderr.setFormatter(fmt)
66
+ stderr.addFilter(sensitive)
67
+ root.addHandler(stderr)
68
+
69
+ if to_file:
70
+ try:
71
+ d = log_dir()
72
+ d.mkdir(parents=True, exist_ok=True)
73
+ # Size-based rotation bounds the 100 MB limit; the age limit is
74
+ # applied by ``prune_old_logs`` at start-up, because a rotating
75
+ # handler alone cannot express "whichever is reached first".
76
+ fh = logging.handlers.RotatingFileHandler(
77
+ d / "echoact.log",
78
+ maxBytes=LOG_MAX_BYTES // 5,
79
+ backupCount=4,
80
+ encoding="utf-8",
81
+ )
82
+ fh.setFormatter(fmt)
83
+ fh.addFilter(sensitive)
84
+ root.addHandler(fh)
85
+ except OSError:
86
+ # A log we cannot write is never a reason not to start; F-79's
87
+ # spirit applies to every ancillary facility.
88
+ root.warning("log file unavailable; logging to stderr only")
89
+
90
+
91
+ def prune_old_logs(now_s: float) -> int:
92
+ """Delete log files older than 4.1's retention window. Returns the count."""
93
+ cutoff = now_s - LOG_RETENTION_DAYS * 86400
94
+ removed = 0
95
+ try:
96
+ for p in log_dir().glob("echoact.log*"):
97
+ try:
98
+ if p.stat().st_mtime < cutoff:
99
+ p.unlink()
100
+ removed += 1
101
+ except OSError:
102
+ continue
103
+ except OSError:
104
+ pass
105
+ return removed
106
+
107
+
108
+ def get_logger(name: str) -> logging.Logger:
109
+ return logging.getLogger(f"echoact.{name}")
110
+
111
+
112
+ def job_context(job_id: str, stage: str, **extra: Any) -> str:
113
+ """N-25's traceable line: job id, stage, code, budget -- never text.
114
+
115
+ Values are rendered with ``repr`` for strings only after checking they
116
+ are short; anything long is reported by length, because the one long
117
+ string in this system is the user's document.
118
+ """
119
+ parts = [f"job={job_id}", f"stage={stage}"]
120
+ for k, v in extra.items():
121
+ if isinstance(v, str) and len(v) > 64:
122
+ parts.append(f"{k}=<{len(v)} chars>")
123
+ elif isinstance(v, Path):
124
+ parts.append(f"{k}={redact(v)}")
125
+ else:
126
+ parts.append(f"{k}={v}")
127
+ return " ".join(parts)
@@ -0,0 +1,162 @@
1
+ Metadata-Version: 2.5
2
+ Name: echoact
3
+ Version: 0.1.0
4
+ Summary: Local Korean/English speech generation for the desktop
5
+ Project-URL: Homepage, https://github.com/ZeroAct/echo-act
6
+ Project-URL: Issues, https://github.com/ZeroAct/echo-act/issues
7
+ Author-email: "Huijae Lee (ZeroAct)" <lhj56822@gmail.com>
8
+ License: MIT License
9
+
10
+ Copyright (c) 2026 Huijae Lee
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE
30
+ Keywords: english,korean,local,offline,speech,text-to-speech,tts
31
+ Classifier: Development Status :: 4 - Beta
32
+ Classifier: Environment :: MacOS X
33
+ Classifier: Environment :: Win32 (MS Windows)
34
+ Classifier: Topic :: Desktop Environment
35
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
36
+ Requires-Python: <3.13,>=3.12
37
+ Requires-Dist: fastapi>=0.115
38
+ Requires-Dist: fastmcp>=2.0
39
+ Requires-Dist: httpx>=0.27
40
+ Requires-Dist: numpy>=1.26
41
+ Requires-Dist: onnxruntime>=1.20
42
+ Requires-Dist: psutil>=6.0
43
+ Requires-Dist: pydantic>=2.9
44
+ Requires-Dist: pyside6>=6.8
45
+ Requires-Dist: python-multipart>=0.0.12
46
+ Requires-Dist: sounddevice>=0.5
47
+ Requires-Dist: soundfile>=0.12
48
+ Requires-Dist: supertonic==1.3.1
49
+ Requires-Dist: uvicorn>=0.32
50
+ Description-Content-Type: text/markdown
51
+
52
+ # EchoAct
53
+
54
+ EchoAct reads Korean and English text aloud on your own computer. Nothing you
55
+ type is sent anywhere: the model runs on your CPU, and once it is downloaded
56
+ the app works with no network connection at all.
57
+
58
+ - **Read a document, follow along.** The sentence being played is marked on
59
+ screen. The mark is drawn as an outline around the letters rather than as a
60
+ heavier weight, so the text never shifts under you while you read.
61
+ - **Playback starts before generation finishes.** The first sentence is spoken
62
+ as soon as it is ready, and the rest follows.
63
+ - **It stays out of the way.** You choose how much processor and memory the app
64
+ may use, and it stays inside that while you work on something else.
65
+ - **Automations can use it too.** A local HTTP service on the loopback address,
66
+ and an MCP server for tools that speak that protocol. Both are the same
67
+ engine, the same limits, and the same one job at a time.
68
+
69
+ Windows 11 and macOS 14 (Apple Silicon). No Python, Node, or database server
70
+ to install.
71
+
72
+ ## Run it with one command
73
+
74
+ With [uv](https://docs.astral.sh/uv/) installed, no checkout or venv needed:
75
+
76
+ ```
77
+ uvx echoact
78
+ ```
79
+
80
+ uv fetches EchoAct into an isolated environment and starts the app. The MCP
81
+ server is the same package: `uvx --from echoact echoact-mcp`.
82
+
83
+ ## Running it from a checkout
84
+
85
+ ```
86
+ uv sync
87
+ uv run python -m echoact
88
+ ```
89
+
90
+ The first launch offers to download the speech model (about 385 MB) and shows
91
+ its licence terms before it does. Generation works offline afterwards.
92
+
93
+ ## Local service
94
+
95
+ The service is on by default and listens only on `127.0.0.1:8765`. It always
96
+ requires a credential; one is created for you on first launch and is shown in
97
+ the app under Settings. It cannot be reached from another machine, and there
98
+ is no way to configure it to be.
99
+
100
+ Turn it off in Settings if you do not want it. The app, generation, playback
101
+ and the library all work exactly the same with it off.
102
+
103
+ | | |
104
+ | --- | --- |
105
+ | Base | `http://127.0.0.1:8765/api/v1` |
106
+ | Auth | `Authorization: Bearer <credential>` |
107
+ | Discovery | `GET /status`, `GET /models` |
108
+ | Before committing | `POST /estimate` — validates, counts segments, estimates length, says whether the slot is free |
109
+ | Generate | `POST /jobs` — needs a duplicate-prevention key; may ask to wait up to 10 s |
110
+ | Follow | `GET /jobs/{id}`, `GET /jobs/{id}/segments`, `POST /jobs/{id}/cancel` |
111
+ | Collect | `GET /jobs/{id}/audio`, `GET /jobs/{id}/result` |
112
+
113
+ One generation runs at a time across the app and every client, so a second
114
+ request is refused with a retry-after hint rather than queued.
115
+
116
+ ## MCP
117
+
118
+ Off by default; enable it in Settings. The MCP server is a small process your
119
+ MCP client starts, which talks to the local service over loopback with a
120
+ credential you issue for it. It adds nothing the HTTP service does not already
121
+ do, and its permissions are exactly that credential's.
122
+
123
+ **Connect an app** in the header writes the configuration for you: a tab each
124
+ for Claude Desktop, Claude Code, Codex CLI, VS Code and Cursor, with the right
125
+ key name and file path for each, the command this particular installation
126
+ actually uses, and a Copy button. Issue a credential from that screen and it
127
+ goes straight into the snippet — the only moment it can, since the app keeps
128
+ only a verifier.
129
+
130
+ Tools: `list_models`, `estimate_speech`, `create_speech`, `get_speech_job`,
131
+ `cancel_speech_job`, `list_speech_segments`, `get_speech_result`,
132
+ `list_speech_history`.
133
+
134
+ ## Your data
135
+
136
+ Documents and generation history are saved only when you ask. Results from a
137
+ one-off job are cleaned up when they are replaced or when the app exits, and a
138
+ result produced for an integration stays retrievable for an hour. Logs contain
139
+ no text and no audio. Nothing is uploaded, and there is no telemetry.
140
+
141
+ ## Repository
142
+
143
+ ```
144
+ docs/design.md The requirements baseline. Every behaviour traces to an
145
+ identifier in it; Appendix A records what was measured.
146
+ echoact/ The application.
147
+ spikes/ Standalone measurements that settled open questions.
148
+ tests/ pytest. `-m engine` needs the model; the rest does not.
149
+ ```
150
+
151
+ `CLAUDE.md` is the contributor's short version.
152
+
153
+ ## Author
154
+
155
+ Huijae Lee (ZeroAct) — lhj56822@gmail.com — [github.com/ZeroAct](https://github.com/ZeroAct)
156
+
157
+ ## Licence
158
+
159
+ The application code is MIT (see `LICENSE`). The speech model is downloaded
160
+ separately and carries its own licence with use restrictions, which the app
161
+ presents for acceptance before it prepares the model for the first time. Being
162
+ able to run it locally is not a right to redistribute it.
@@ -0,0 +1,80 @@
1
+ echoact/__init__.py,sha256=8qUrSmwzVjcvt2KajlLIR435aN5TqUl3yqo81qEZ020,84
2
+ echoact/__main__.py,sha256=pCvuTvG4DrM3o9l7qeJJzmjCyU5BuiO6WxIqITX-ePk,4163
3
+ echoact/app.py,sha256=MD6_uQWeDCVODScdw_m65Lbrg_7HinE9BaSsieUlH2E,13220
4
+ echoact/diagnostics.py,sha256=frn5M0Zeip9QKf2lBEXY5bbKC9HCWcgBz0gtxrW4g3M,31934
5
+ echoact/domain.py,sha256=q36vTBMA-qJd6jUe9Av53Q1RI1pMG2GqWqod6np1hVk,15414
6
+ echoact/errors.py,sha256=374Nl-N5jVQRgQ_GZ6lZfmnqkiCfFJ1eO0zuMBGSw40,13288
7
+ echoact/instance.py,sha256=l8FzvdFm7-TPRvdo0IPmAVeSGy_iqvF40E5StzQVDcQ,5942
8
+ echoact/paths.py,sha256=cwu8cpfyrEe86MlDZXKcJ3tzhYcPScItLPCnlesouuM,2912
9
+ echoact/policy.py,sha256=cXHWM2oq1j7FLP7g9XhYkQe9YyDwTLDjzP5-gsNAU1M,8092
10
+ echoact/audio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ echoact/audio/devices.py,sha256=bAF2z72Qw0Z5eY7tuHsbK24eS0HdjbqS7vTo4mWRrGs,6182
12
+ echoact/audio/player.py,sha256=z_gtMjHdrvsOyWRFffVdrQN0pdaSkR7TWNDWpjqp_b8,21648
13
+ echoact/audio/wav.py,sha256=uigBkm69qQnCwWkOsuRJoLdK9A5kw4FqWO1B7fVIQCY,31768
14
+ echoact/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ echoact/config/budget.py,sha256=Jxvzef_msK-PDTIgGQwPT0pvG_BAPfQPdIHlbODYIYk,14461
16
+ echoact/config/settings.py,sha256=eX-tsD4EF5GYydNYxuT25oZUAOSIrDKhG1QtOaEafcc,50796
17
+ echoact/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ echoact/db/backup.py,sha256=sCmD2GCkHqqyVpplvqOlV98iECIi6SCemBvCDWNCN50,97023
19
+ echoact/db/migrations.py,sha256=MXZO27ZDBDGMsUh-l9KY1Ojv9CV35Fpk3u2kQe4M4X0,15353
20
+ echoact/db/schema.sql,sha256=_3n1yaDAMpGWI5IuFI9Vu10tR3m1-NarQoVllFKGFWs,9762
21
+ echoact/db/store.py,sha256=jpFWxWw1dZtu1jHzpiT-_nhbXuGYp7y4jgs5ma71j7I,80461
22
+ echoact/engine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ echoact/engine/container.py,sha256=_yLWtvMzmtz619XFGr1a-gZOjrO0dFADMsLUt_o-vWA,32883
24
+ echoact/engine/protocol.py,sha256=pMAOQCD4SUJbNfn_WUa0pUSKFae3cgGSKvE6rxJS5Ac,6664
25
+ echoact/engine/runtime.py,sha256=49SuiDRlw9pdI9bYuAbclhl1_1WgT_DlnMDfnu5jFrE,12388
26
+ echoact/engine/supervisor.py,sha256=CilavGXiKl2nNRqVC2UmXAGYBsietmymg4T4Q9-b2FE,37891
27
+ echoact/engine/worker.py,sha256=Ejno2bWzbAZRFcvwGKxn1cyUjG7_9BKCtDhlYxco-0k,26280
28
+ echoact/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
+ echoact/jobs/engine.py,sha256=IKT1fG6b9r7oyLjBZpXbiM4KCVHbe4ohFvUA6DJ5MjA,31152
30
+ echoact/jobs/request.py,sha256=pL-5L97faUWwgtaCx8GcEs3oM8Uwefc5y8Li-ouSNfY,10455
31
+ echoact/mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
+ echoact/mcp/__main__.py,sha256=6oMi5egdeeOMPQyyz-wrx_0k-LVR85ZZrGmQw1iZYDs,1518
33
+ echoact/mcp/client.py,sha256=FkRnGg8DMfuKoJ93R6oITl-Ou-G7GHFnOnySeGl3wus,7683
34
+ echoact/mcp/config.py,sha256=J3riFfz4qCc71Ro5bLedJKpT5s4dtJQXaIUnpVe9-WQ,3828
35
+ echoact/mcp/server.py,sha256=-Zu14W353wYZKa-Iirpq3aFatJCTVfioU-uCJk8OExI,14177
36
+ echoact/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
+ echoact/models/catalog.py,sha256=8gYYH_n_5zqwFJCx1uErZ8nQpSf2nTxz3X2ZfXE9s0Q,11826
38
+ echoact/models/manifest.py,sha256=L94h-EqDphTzLEKfmNj93uaUcy2YH_tZ35BI0u_Q66Q,10801
39
+ echoact/models/registry.py,sha256=q6rudkg7jnADP2ExVJFq2Fl0YS9EGI1lpDUUJ98BK7E,60757
40
+ echoact/security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
+ echoact/security/credentials.py,sha256=M9eclu7tF1869I78Hk-Tu-eb-PuV9l7bfvoUpT5Q5b0,39226
42
+ echoact/security/ratelimit.py,sha256=OAhjwzZbYIg4T2HHUyklVWWyJixtdqkgW8OjgYSV9fs,21284
43
+ echoact/service/__init__.py,sha256=q30gb9KnFGTC9Z_BfP7afcFoDMcdS20eDW28IxbQILc,571
44
+ echoact/service/app.py,sha256=3M9Eoq3m7ptmy4PT2lzKH7fTFjLuapjm0jE7p3R1Uwk,8387
45
+ echoact/service/deps.py,sha256=9_pkPQV8RweCrbTZk4MA7Zs1LLNJMP6XfL-4ogJ6ZPI,21968
46
+ echoact/service/errors.py,sha256=QIpNCvdeZ5dXXK-EIUT3LtPSH72M22avRJd4CC1dLgo,10903
47
+ echoact/service/routes.py,sha256=aBTzvdGreE-5SdAE586ECp6CuEZKA_4E94fOdDMvCao,46611
48
+ echoact/service/schemas.py,sha256=rTg5y044mEFZlwQiPhJ3two8uN_DnKq3DAfFTIDIWJA,14532
49
+ echoact/service/server.py,sha256=5lWu72wAcpnBMSPUpwL0KCuGERHf256oAkb257kUOcY,11238
50
+ echoact/text/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
+ echoact/text/language.py,sha256=PlHiTWArdcANoip1rP7AMApNP9qSVzuFIoOryo0YFQg,1536
52
+ echoact/text/loader.py,sha256=6tUZvKf7uie5_E3t7bwYunFnXX72nDhTQ7JgfV7qFfY,22060
53
+ echoact/text/normalize.py,sha256=sds9MoFUj3jFGMspUhjawyuIyGYMgl_fS76WMUxyUsQ,35821
54
+ echoact/text/segment.py,sha256=SCdBIEu0oJ8Oeff49zBCBCAeT_poM6kf1wcDwqSGB8o,19804
55
+ echoact/text/sniff.py,sha256=zKa0TzMNdqcGbd08ewi96vuVPgtKyWdsPHkLvHe_8bk,46436
56
+ echoact/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
+ echoact/ui/bridge.py,sha256=sxgJm0Og9A0GMVFmWVkV36GDHxafi3_2JjGqQ3BCgkI,1852
58
+ echoact/ui/controls.py,sha256=_FYJ7hzlfFy69DhUBTK2rioYbQfCNZuK4Z3wipEhrs8,13627
59
+ echoact/ui/credential_dialog.py,sha256=OWs0bVR7B9zMVE6InAncN6ujBJIAWpUfqXv95MX_UD0,5032
60
+ echoact/ui/fonts.py,sha256=udRR-Di3F0I2XeG1gyFe1vaR3FoVdzC9VwT5FuC5IQM,3369
61
+ echoact/ui/i18n.py,sha256=2ul8NnQ964dGvafD3nIf5dCZgp4-qB4Aj0CvoNFhXSs,8727
62
+ echoact/ui/icons.py,sha256=EAFwW436Tmx9AWcxqtD52tTwWfpk7d2hdmUvEwSq5_k,12929
63
+ echoact/ui/library.py,sha256=AkJ3nro4Z4yW5W4RllGKlpIIkxAzt5CbhrLIe357I6U,66429
64
+ echoact/ui/licence.py,sha256=8XGQw6-JjW57FdclCRlBPt2zBpd65EsH6ufIdXZiNyQ,6112
65
+ echoact/ui/main_window.py,sha256=e8fktLe-SJRR_hHy1EjD7Aqy5N95hxHs4GOx3OlRc14,50828
66
+ echoact/ui/mcp_setup.py,sha256=kKgb2LUqFRhQU3vY80iX_yxc5n8_K7IQEglCTxjIKM0,19705
67
+ echoact/ui/models_view.py,sha256=O6llsbO3XKeghmGj2OUJuUKTZSsSLJbxIgYT4PlwGrs,49287
68
+ echoact/ui/notifications.py,sha256=Fp7DFLb4YitEscADf0hv3RY6xPJltW63GkqCeyytMZM,6870
69
+ echoact/ui/reading.py,sha256=K8YvHPit9EZpKj4Zik7w7Rku8dRcnM1BLk4uENZjMAw,19887
70
+ echoact/ui/settings_view.py,sha256=HFBwVHjT6s4dKEgoNMbrotOYiKqDt0VzHUi_9OLCB1o,96948
71
+ echoact/ui/status_view.py,sha256=2dhfCeYjPDmOqxalTGsLYuuqgKitFsZ4sdnw4EAkGbc,49010
72
+ echoact/ui/theme.py,sha256=Mw6o0VmNE6BO3jU3dxrW5EgL-EvsnT_koTeZxj7M8S8,20590
73
+ echoact/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
74
+ echoact/util/ids.py,sha256=0HZNFB6o2isUs6o6s5yvIFI1PRMhrvn-_h_TG-WlHGw,1506
75
+ echoact/util/logging.py,sha256=9ugCCk7PtMb66vuWtxyADTEy0sqhayoSyEebGE6dlWc,4167
76
+ echoact-0.1.0.dist-info/METADATA,sha256=m8v2MziRArSm1Tm0YgWeCmC36T1wHSE8lwi7yRClqA4,6882
77
+ echoact-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
78
+ echoact-0.1.0.dist-info/entry_points.txt,sha256=L201Tu6ziAqXH2XLuOZ_Kz52ssBhs6puEKj_GIjfiEw,90
79
+ echoact-0.1.0.dist-info/licenses/LICENSE,sha256=15qRkrCBfdMk5HUojB2dbIVtI_lJtHLbSveH4zpZefU,1088
80
+ echoact-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ echoact = echoact.__main__:main
3
+ echoact-mcp = echoact.mcp.__main__:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Huijae Lee
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.