litetui 0.22.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.
Files changed (223) hide show
  1. litetui-0.22.0/PKG-INFO +10 -0
  2. litetui-0.22.0/README.md +231 -0
  3. litetui-0.22.0/pyproject.toml +91 -0
  4. litetui-0.22.0/setup.cfg +4 -0
  5. litetui-0.22.0/src/litetui/__init__.py +5 -0
  6. litetui-0.22.0/src/litetui/app.py +4629 -0
  7. litetui-0.22.0/src/litetui/appsvc.py +157 -0
  8. litetui-0.22.0/src/litetui/ask_user_question.py +677 -0
  9. litetui-0.22.0/src/litetui/calendar_view.py +185 -0
  10. litetui-0.22.0/src/litetui/chrome_tool.py +237 -0
  11. litetui-0.22.0/src/litetui/cli.py +35 -0
  12. litetui-0.22.0/src/litetui/colorpicker.py +232 -0
  13. litetui-0.22.0/src/litetui/conversation.py +332 -0
  14. litetui-0.22.0/src/litetui/cron.py +223 -0
  15. litetui-0.22.0/src/litetui/dialog_demo.py +129 -0
  16. litetui-0.22.0/src/litetui/file_state.py +52 -0
  17. litetui-0.22.0/src/litetui/file_tools.py +181 -0
  18. litetui-0.22.0/src/litetui/fmt.py +16 -0
  19. litetui-0.22.0/src/litetui/goal_loop.py +464 -0
  20. litetui-0.22.0/src/litetui/harness.py +608 -0
  21. litetui-0.22.0/src/litetui/jobkill.py +182 -0
  22. litetui-0.22.0/src/litetui/listen_tool.py +416 -0
  23. litetui-0.22.0/src/litetui/llm_backend.py +1458 -0
  24. litetui-0.22.0/src/litetui/loop_list.py +171 -0
  25. litetui-0.22.0/src/litetui/mcp_client.py +411 -0
  26. litetui-0.22.0/src/litetui/paths.py +48 -0
  27. litetui-0.22.0/src/litetui/pccontrol_tool.py +176 -0
  28. litetui-0.22.0/src/litetui/picker.py +176 -0
  29. litetui-0.22.0/src/litetui/plugins/__init__.py +563 -0
  30. litetui-0.22.0/src/litetui/plugins/ask_user_question.py +25 -0
  31. litetui-0.22.0/src/litetui/plugins/chrome.py +19 -0
  32. litetui-0.22.0/src/litetui/plugins/convo.py +215 -0
  33. litetui-0.22.0/src/litetui/plugins/core_tools.py +376 -0
  34. litetui-0.22.0/src/litetui/plugins/file_tools.py +18 -0
  35. litetui-0.22.0/src/litetui/plugins/glassbox_plugin.py +267 -0
  36. litetui-0.22.0/src/litetui/plugins/goal_loop_plugin.py +36 -0
  37. litetui-0.22.0/src/litetui/plugins/harness_plugin.py +29 -0
  38. litetui-0.22.0/src/litetui/plugins/help_plugin.py +93 -0
  39. litetui-0.22.0/src/litetui/plugins/listen.py +31 -0
  40. litetui-0.22.0/src/litetui/plugins/mark_plugin.py +72 -0
  41. litetui-0.22.0/src/litetui/plugins/mcp_plugin.py +26 -0
  42. litetui-0.22.0/src/litetui/plugins/misc.py +202 -0
  43. litetui-0.22.0/src/litetui/plugins/model_switch.py +634 -0
  44. litetui-0.22.0/src/litetui/plugins/pccontrol.py +23 -0
  45. litetui-0.22.0/src/litetui/plugins/runtime_log_plugin.py +15 -0
  46. litetui-0.22.0/src/litetui/plugins/scheduler_plugin.py +63 -0
  47. litetui-0.22.0/src/litetui/plugins/scheduler_ui.py +791 -0
  48. litetui-0.22.0/src/litetui/plugins/settings_ui.py +67 -0
  49. litetui-0.22.0/src/litetui/plugins/skills_plugin.py +271 -0
  50. litetui-0.22.0/src/litetui/plugins/studio.py +31 -0
  51. litetui-0.22.0/src/litetui/plugins/themes_plugin.py +30 -0
  52. litetui-0.22.0/src/litetui/plugins/view_image.py +32 -0
  53. litetui-0.22.0/src/litetui/prompt_compiler.py +60 -0
  54. litetui-0.22.0/src/litetui/router_record.py +224 -0
  55. litetui-0.22.0/src/litetui/runtime_log.py +194 -0
  56. litetui-0.22.0/src/litetui/sanitize.py +93 -0
  57. litetui-0.22.0/src/litetui/schedule_builder.py +147 -0
  58. litetui-0.22.0/src/litetui/scheduler.py +422 -0
  59. litetui-0.22.0/src/litetui/seat_guard.py +210 -0
  60. litetui-0.22.0/src/litetui/settings.py +482 -0
  61. litetui-0.22.0/src/litetui/settings_screen.py +797 -0
  62. litetui-0.22.0/src/litetui/side_panel.py +711 -0
  63. litetui-0.22.0/src/litetui/skills.py +365 -0
  64. litetui-0.22.0/src/litetui/studio_tool.py +310 -0
  65. litetui-0.22.0/src/litetui/textfmt.py +365 -0
  66. litetui-0.22.0/src/litetui/themes.py +291 -0
  67. litetui-0.22.0/src/litetui/ticker.py +135 -0
  68. litetui-0.22.0/src/litetui/tool_approval.py +253 -0
  69. litetui-0.22.0/src/litetui/tool_context.py +161 -0
  70. litetui-0.22.0/src/litetui/tool_list.py +205 -0
  71. litetui-0.22.0/src/litetui/tool_policy.py +605 -0
  72. litetui-0.22.0/src/litetui/tool_schemas.py +91 -0
  73. litetui-0.22.0/src/litetui/ttyguard.py +238 -0
  74. litetui-0.22.0/src/litetui/turn_engine.py +231 -0
  75. litetui-0.22.0/src/litetui/turnstats.py +240 -0
  76. litetui-0.22.0/src/litetui/version.py +20 -0
  77. litetui-0.22.0/src/litetui/widgets.py +767 -0
  78. litetui-0.22.0/src/litetui.egg-info/PKG-INFO +10 -0
  79. litetui-0.22.0/src/litetui.egg-info/SOURCES.txt +221 -0
  80. litetui-0.22.0/src/litetui.egg-info/dependency_links.txt +1 -0
  81. litetui-0.22.0/src/litetui.egg-info/entry_points.txt +2 -0
  82. litetui-0.22.0/src/litetui.egg-info/requires.txt +5 -0
  83. litetui-0.22.0/src/litetui.egg-info/top_level.txt +2 -0
  84. litetui-0.22.0/tests/test_ask_user_question.py +255 -0
  85. litetui-0.22.0/tests/test_ask_user_question_sidebar.py +243 -0
  86. litetui-0.22.0/tests/test_authority_footer_and_key.py +451 -0
  87. litetui-0.22.0/tests/test_autocompact.py +131 -0
  88. litetui-0.22.0/tests/test_autonomous_profile.py +149 -0
  89. litetui-0.22.0/tests/test_autoscroll.py +316 -0
  90. litetui-0.22.0/tests/test_backend_switch.py +92 -0
  91. litetui-0.22.0/tests/test_calendar.py +362 -0
  92. litetui-0.22.0/tests/test_calendar_ui.py +506 -0
  93. litetui-0.22.0/tests/test_cancel_tool_placement.py +182 -0
  94. litetui-0.22.0/tests/test_chat_ready_call_sites.py +551 -0
  95. litetui-0.22.0/tests/test_chrome_tool.py +101 -0
  96. litetui-0.22.0/tests/test_cli.py +42 -0
  97. litetui-0.22.0/tests/test_collector_integrity.py +111 -0
  98. litetui-0.22.0/tests/test_colorpicker.py +100 -0
  99. litetui-0.22.0/tests/test_command_palette.py +315 -0
  100. litetui-0.22.0/tests/test_compaction_card_renders.py +135 -0
  101. litetui-0.22.0/tests/test_compaction_ui.py +346 -0
  102. litetui-0.22.0/tests/test_confirm_stop_sidebar.py +213 -0
  103. litetui-0.22.0/tests/test_connect_banner.py +193 -0
  104. litetui-0.22.0/tests/test_context_length.py +238 -0
  105. litetui-0.22.0/tests/test_convo_picked.py +68 -0
  106. litetui-0.22.0/tests/test_convo_rename.py +182 -0
  107. litetui-0.22.0/tests/test_convos.py +151 -0
  108. litetui-0.22.0/tests/test_convos_picker.py +117 -0
  109. litetui-0.22.0/tests/test_cron_wiring.py +294 -0
  110. litetui-0.22.0/tests/test_ctx_resync.py +132 -0
  111. litetui-0.22.0/tests/test_deny_stops_the_turn.py +263 -0
  112. litetui-0.22.0/tests/test_desktop_tools.py +110 -0
  113. litetui-0.22.0/tests/test_dialog_geometry.py +321 -0
  114. litetui-0.22.0/tests/test_elapsed.py +124 -0
  115. litetui-0.22.0/tests/test_eta.py +160 -0
  116. litetui-0.22.0/tests/test_file_tools.py +299 -0
  117. litetui-0.22.0/tests/test_first_boot.py +114 -0
  118. litetui-0.22.0/tests/test_fleet_identity.py +144 -0
  119. litetui-0.22.0/tests/test_footer.py +180 -0
  120. litetui-0.22.0/tests/test_footer_fields.py +127 -0
  121. litetui-0.22.0/tests/test_glassbox_emission.py +321 -0
  122. litetui-0.22.0/tests/test_glassbox_events.py +149 -0
  123. litetui-0.22.0/tests/test_glassbox_plugin.py +214 -0
  124. litetui-0.22.0/tests/test_goal_loop.py +348 -0
  125. litetui-0.22.0/tests/test_harness_tool.py +122 -0
  126. litetui-0.22.0/tests/test_input.py +138 -0
  127. litetui-0.22.0/tests/test_integrations.py +224 -0
  128. litetui-0.22.0/tests/test_job_builder_ui.py +259 -0
  129. litetui-0.22.0/tests/test_keys_toggle.py +72 -0
  130. litetui-0.22.0/tests/test_kill_tree_honesty.py +343 -0
  131. litetui-0.22.0/tests/test_lazy_convo.py +150 -0
  132. litetui-0.22.0/tests/test_live_state_isolation.py +99 -0
  133. litetui-0.22.0/tests/test_llama_chat_ready.py +345 -0
  134. litetui-0.22.0/tests/test_llama_discovery.py +130 -0
  135. litetui-0.22.0/tests/test_llama_flags.py +73 -0
  136. litetui-0.22.0/tests/test_llama_ini.py +101 -0
  137. litetui-0.22.0/tests/test_llama_router.py +294 -0
  138. litetui-0.22.0/tests/test_llama_single_model.py +242 -0
  139. litetui-0.22.0/tests/test_lms_backend.py +119 -0
  140. litetui-0.22.0/tests/test_loop_list.py +212 -0
  141. litetui-0.22.0/tests/test_mark.py +79 -0
  142. litetui-0.22.0/tests/test_markup_safety.py +94 -0
  143. litetui-0.22.0/tests/test_mcp_timeout_bounds.py +247 -0
  144. litetui-0.22.0/tests/test_message_queue.py +182 -0
  145. litetui-0.22.0/tests/test_modal_centering.py +125 -0
  146. litetui-0.22.0/tests/test_modals.py +148 -0
  147. litetui-0.22.0/tests/test_model_picked.py +97 -0
  148. litetui-0.22.0/tests/test_model_screen.py +164 -0
  149. litetui-0.22.0/tests/test_no_convo_pollution.py +90 -0
  150. litetui-0.22.0/tests/test_no_dead_controls.py +132 -0
  151. litetui-0.22.0/tests/test_no_fleet_registration.py +415 -0
  152. litetui-0.22.0/tests/test_no_implicit_model_load.py +166 -0
  153. litetui-0.22.0/tests/test_palette_groups.py +142 -0
  154. litetui-0.22.0/tests/test_paths.py +54 -0
  155. litetui-0.22.0/tests/test_persist_error_report.py +106 -0
  156. litetui-0.22.0/tests/test_picker_sidebar.py +182 -0
  157. litetui-0.22.0/tests/test_plugin_dogfood.py +76 -0
  158. litetui-0.22.0/tests/test_plugin_substrate.py +203 -0
  159. litetui-0.22.0/tests/test_plugins_command.py +75 -0
  160. litetui-0.22.0/tests/test_powershell_tool.py +133 -0
  161. litetui-0.22.0/tests/test_prompt_compiler.py +44 -0
  162. litetui-0.22.0/tests/test_prompt_compose.py +118 -0
  163. litetui-0.22.0/tests/test_queued_input_delivery.py +132 -0
  164. litetui-0.22.0/tests/test_request_overrides.py +79 -0
  165. litetui-0.22.0/tests/test_resume_identity.py +110 -0
  166. litetui-0.22.0/tests/test_router_coexistence.py +167 -0
  167. litetui-0.22.0/tests/test_router_record.py +160 -0
  168. litetui-0.22.0/tests/test_runtime_log.py +90 -0
  169. litetui-0.22.0/tests/test_runtime_log_producers.py +178 -0
  170. litetui-0.22.0/tests/test_sanitize.py +69 -0
  171. litetui-0.22.0/tests/test_schedule_builder.py +130 -0
  172. litetui-0.22.0/tests/test_scheduler.py +269 -0
  173. litetui-0.22.0/tests/test_seat_convo_identity.py +174 -0
  174. litetui-0.22.0/tests/test_seat_guard.py +278 -0
  175. litetui-0.22.0/tests/test_seat_guard_backends.py +88 -0
  176. litetui-0.22.0/tests/test_seat_identity.py +187 -0
  177. litetui-0.22.0/tests/test_seat_rebind.py +325 -0
  178. litetui-0.22.0/tests/test_self_store_writes.py +184 -0
  179. litetui-0.22.0/tests/test_settings.py +476 -0
  180. litetui-0.22.0/tests/test_settings_live.py +131 -0
  181. litetui-0.22.0/tests/test_sidebar_dialog.py +279 -0
  182. litetui-0.22.0/tests/test_sidebar_side.py +168 -0
  183. litetui-0.22.0/tests/test_skill_autocomplete.py +224 -0
  184. litetui-0.22.0/tests/test_skill_invocation.py +317 -0
  185. litetui-0.22.0/tests/test_skills_cache.py +193 -0
  186. litetui-0.22.0/tests/test_skills_library.py +144 -0
  187. litetui-0.22.0/tests/test_skills_picker.py +170 -0
  188. litetui-0.22.0/tests/test_skills_visible.py +219 -0
  189. litetui-0.22.0/tests/test_slash_autocomplete_commands.py +286 -0
  190. litetui-0.22.0/tests/test_store.py +113 -0
  191. litetui-0.22.0/tests/test_studio_tool.py +209 -0
  192. litetui-0.22.0/tests/test_swap_button_in_real_dialogs.py +194 -0
  193. litetui-0.22.0/tests/test_theme_extra_tokens.py +179 -0
  194. litetui-0.22.0/tests/test_themes.py +197 -0
  195. litetui-0.22.0/tests/test_thinking_autoscroll.py +116 -0
  196. litetui-0.22.0/tests/test_thinking_timer.py +167 -0
  197. litetui-0.22.0/tests/test_toggle_tools_persists.py +107 -0
  198. litetui-0.22.0/tests/test_token_counts.py +267 -0
  199. litetui-0.22.0/tests/test_tool_approval.py +310 -0
  200. litetui-0.22.0/tests/test_tool_approval_sidebar.py +269 -0
  201. litetui-0.22.0/tests/test_tool_cancel.py +428 -0
  202. litetui-0.22.0/tests/test_tool_context.py +168 -0
  203. litetui-0.22.0/tests/test_tool_context_wiring.py +155 -0
  204. litetui-0.22.0/tests/test_tool_denied.py +151 -0
  205. litetui-0.22.0/tests/test_tool_list_dialog.py +201 -0
  206. litetui-0.22.0/tests/test_tool_list_disable.py +211 -0
  207. litetui-0.22.0/tests/test_tool_policy.py +130 -0
  208. litetui-0.22.0/tests/test_tool_policy_wiring.py +170 -0
  209. litetui-0.22.0/tests/test_tool_profile_choices.py +137 -0
  210. litetui-0.22.0/tests/test_tool_schemas.py +143 -0
  211. litetui-0.22.0/tests/test_tool_standing_rules.py +110 -0
  212. litetui-0.22.0/tests/test_tools_disabled.py +144 -0
  213. litetui-0.22.0/tests/test_tools_prompt.py +103 -0
  214. litetui-0.22.0/tests/test_tools_registered.py +109 -0
  215. litetui-0.22.0/tests/test_ttyguard.py +161 -0
  216. litetui-0.22.0/tests/test_turn_engine.py +307 -0
  217. litetui-0.22.0/tests/test_turnstats_elapsed.py +152 -0
  218. litetui-0.22.0/tests/test_turnstats_eta.py +108 -0
  219. litetui-0.22.0/tests/test_unattended_profile.py +298 -0
  220. litetui-0.22.0/tests/test_version.py +84 -0
  221. litetui-0.22.0/tests/test_view_image.py +146 -0
  222. litetui-0.22.0/tests/test_wake_after_compact.py +290 -0
  223. litetui-0.22.0/tests/test_wake_guard_abandoned_turn.py +403 -0
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: litetui
3
+ Version: 0.22.0
4
+ Summary: TUI chat client for LM Studio with image support
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: textual>=0.89.0
7
+ Requires-Dist: openai>=1.0.0
8
+ Requires-Dist: Pillow>=10.0.0
9
+ Requires-Dist: lmstudio>=1.5.0
10
+ Requires-Dist: websockets>=15
@@ -0,0 +1,231 @@
1
+ # LiteTUI
2
+
3
+ A terminal chat client and agent harness for **local** LLMs. Textual TUI,
4
+ streaming, tool use, vision, per-conversation memory, and compaction.
5
+
6
+ **Two engines, one seam** (`src/litetui/llm_backend.py`): [LM Studio](https://lmstudio.ai)'s
7
+ desktop server, or LiteTUI's own `llama-server` from llama.cpp. Switch between
8
+ them mid-conversation with `/backend` — the history survives.
9
+
10
+ Built against `qwen3.8-27b` on an RTX 5090. Nothing here talks to a hosted API.
11
+
12
+ ```bash
13
+ uv sync
14
+ run.bat # or: uv run --locked litetui
15
+ ```
16
+
17
+ Either engine will do. When both are detected, a one-time picker runs at first
18
+ boot; `/backend` changes the answer later.
19
+
20
+ | engine | where | notes |
21
+ |---|---|---|
22
+ | `lmstudio` | `http://localhost:1234` | Needs LM Studio's server up. Control (load / **unload** / context length) goes through the official `lmstudio` SDK, not a `lms` shell-out. |
23
+ | `llamacpp` | `http://localhost:7470` | LiteTUI's own `llama-server` in **router mode**: one process, every chat GGUF on the box behind it. Attaches to LiteSuite's server on `:8088` when that is healthy, otherwise spawns its own — detached, console-safe, with a log file, and killed with the app. |
24
+
25
+ Neither engine loads anything at boot, and the router runs `--models-max 2` so a
26
+ model switch cannot quietly fill 32 GB of VRAM. The router's model list is
27
+ generated: LiteSuite's install dir, LM Studio's dirs, the HuggingFace cache and
28
+ any custom roots, deduplicated, with voice and embedding GGUFs filtered out by
29
+ the file's own `general.architecture` header rather than by guessing from names.
30
+
31
+ ## What it does
32
+
33
+ **Chat + agent loop.** Streams replies, renders the thinking trace in a
34
+ collapsible block, and runs a tool loop until the model gives a plain answer.
35
+
36
+ **Four tools**, pi-style, capped at 2000 lines / 50KB per result:
37
+ `bash` · `read` · `write` · `web_fetch`. Toggle with `Ctrl+T`.
38
+
39
+ **Vision.** Paste an image with `Ctrl+O`, or give it a path — quoted or bare,
40
+ with or without a question after it:
41
+
42
+ ```
43
+ C:\shots\screen.png what is the error in this dialog?
44
+ "C:\My Folder\screen.png"
45
+ ```
46
+
47
+ > A model can only see images if LM Studio reports it as `type: vlm`. A GGUF
48
+ > shipped without an `mmproj` projector loads as `type: llm` and returns
49
+ > HTTP 400 on image input — the fix is to place a matching `mmproj-*.gguf`
50
+ > beside the weights, not to change anything here.
51
+
52
+ ## Conversations
53
+
54
+ Every conversation gets a directory:
55
+
56
+ ```
57
+ .convos/<uuid>/
58
+ convo.jsonl append-only transcript
59
+ memory.md an INDEX the agent maintains
60
+ soul.md who it is here: preferences, standing corrections
61
+ handoff.md in flight / owed / absent-by-decision / caveats
62
+ memories/ the memories themselves, one file per idea
63
+ ```
64
+
65
+ The agent is told its own uuid and absolute path in the system prompt, and
66
+ `memory.md`, `soul.md` and `handoff.md` are injected **once**, into the system
67
+ message at the start of the conversation — a snapshot, not a live view. They
68
+ are not re-sent each turn: three files on every request is affordable at 1M
69
+ context and is not on a local 27B, where it crowds out the conversation. To
70
+ see current contents the agent reads them with the `read` tool, and the prompt
71
+ says so. Index lines are capped at ~50 tokens: pointers, never the memory itself.
72
+
73
+ ### The transcript is append-only, always
74
+
75
+ `convo.jsonl` is never rewritten and never backed up and replaced. A torn
76
+ append costs one line, which the reader tolerates; a failed rewrite costs the
77
+ conversation. Record types applied in file order:
78
+
79
+ | type | effect |
80
+ |---|---|
81
+ | `meta` | id / created / model |
82
+ | `msg` | one message, verbatim |
83
+ | `edit` | replace ONE message in place |
84
+ | `truncate` | drop a head range, splice new messages in front |
85
+ | `snapshot` | whole list (read for older files; no longer written) |
86
+
87
+ `/compact` records a `truncate` rather than a fresh copy of everything.
88
+ Measured on a 30-message conversation: **303 bytes instead of 5,445** — and the
89
+ raw history stays on disk behind the marker.
90
+
91
+ ## Commands
92
+
93
+ | | |
94
+ |---|---|
95
+ | `/new` `/clear` | start a new conversation (new folder on disk) |
96
+ | `/system <text>` | set the system prompt |
97
+ | `/model [n]` | show or switch model |
98
+ | `/backend` | switch engine — LM Studio or llama.cpp. The conversation survives |
99
+ | `/load` `/unload` | put a model into memory, or free it |
100
+ | `/modelcfg` | per-model Info / Load / Inference screen (see below) |
101
+ | `/think [level]` | `off · minimal · low · medium · high · xhigh · unset` |
102
+ | `/compact [hint]` | summarise older messages, keep the last 4 |
103
+ | `/convos` | list saved conversations with sizes |
104
+ | `/resume <n\|id>` | load one (id = uuid prefix) |
105
+ | `/reconnect` `/quit` | |
106
+
107
+ `Esc` stops the current turn (asks first; `Esc` again forces). `Ctrl+O` paste
108
+ image · `Ctrl+X` clear image · `Ctrl+L` new conversation · `Ctrl+T` tools.
109
+
110
+ ### `/modelcfg` — a control an engine cannot drive is greyed, never hidden
111
+
112
+ Three tabs per model: **Info**, **Load**, **Inference**. Load covers context
113
+ length, GPU offload, threads, batch sizes, parallel slots, flash attention, KV
114
+ cache quantization, mlock/mmap, RoPE, seed, a draft model for speculative
115
+ decoding, a vision `mmproj`, and the chat template. Inference layers per-model
116
+ sampling over your global `/settings`, plus structured output (a JSON schema)
117
+ and named presets.
118
+
119
+ The two engines do not expose the same knobs. **A control the active engine
120
+ cannot drive renders greyed with the reason attached** — it is never silently
121
+ absent and never a switch that does nothing. That distinction is the point: a
122
+ missing control is a question, and a fake one is a bug you find much later.
123
+
124
+ ### `/think` — unset is not off
125
+
126
+ `unset` sends no `reasoning_effort` at all, and **LM Studio's own default for an
127
+ absent value is `xhigh`**. So "unset" means maximum thinking, not none.
128
+ `/think off` is a different thing and sends `reasoning_effort: "none"`.
129
+
130
+ The six accepted values come from the server's own 400 body, not its docs,
131
+ which omit the parameter entirely:
132
+
133
+ ```
134
+ Invalid 'reasoning_effort' value: 'x'.
135
+ Supported values: none, minimal, low, medium, high, xhigh.
136
+ ```
137
+
138
+ ### `/compact` writes to memory before it summarises
139
+
140
+ Compaction is the moment context is about to be destroyed, so the compaction
141
+ prompt runs in two steps: **persist first** (durable lesson → a new file in
142
+ `memories/` plus one pointer line; a correction → `soul.md`; anything in flight
143
+ → `handoff.md`), **then** produce the summary. Tools are passed to that call so
144
+ the instruction can actually reach disk — without them it would be theatre.
145
+
146
+ It reports which of the three happened, including
147
+ `persisted: nothing — TOOLS ARE OFF, so it could not write`.
148
+
149
+ The summarisation call forces `reasoning_effort: "none"` regardless of
150
+ `/think`: at the server default a 12k budget was spent entirely on the thinking
151
+ trace and returned an **empty** answer, and a compaction that returns nothing is
152
+ worse than not compacting.
153
+
154
+ ## Skills
155
+
156
+ `skills/<name>/SKILL.md`, same shape Claude Code uses — YAML frontmatter with
157
+ `name` and `description`, then the body.
158
+
159
+ Only the **index** (name + one line each) goes into the system prompt. The body
160
+ loads on demand through the `skill` tool. Inlining every body would spend the
161
+ context window on instructions the model doesn't need this turn; an index with
162
+ no way to open it would be worse.
163
+
164
+ A directory with no `SKILL.md` is skipped. One that can't be read becomes a
165
+ skill whose description says so — a skill that vanishes on a decode error looks
166
+ exactly like one that was never written.
167
+
168
+ ## MCP
169
+
170
+ A standard `mcp.json` in the repo root:
171
+
172
+ ```json
173
+ { "mcpServers": {
174
+ "files": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "."] }
175
+ } }
176
+ ```
177
+
178
+ Each server's tools are registered as `mcp__<server>__<tool>` and dispatched
179
+ like any other tool, so the agent loop has no MCP-specific branch. `disabled:
180
+ true` skips one.
181
+
182
+ A server that fails to start is **recorded, not swallowed** — the others still
183
+ load. A tool that silently never appears looks like one the model chose not to
184
+ use.
185
+
186
+ > Server stderr goes to `mcp.log`, never to the terminal. An MCP server is a
187
+ > long-running child process, and a child that inherits this console paints
188
+ > straight over a TUI that owns every cell.
189
+
190
+ ## Harness seat
191
+
192
+ LiteTUI registers as a LiteHarness agent and monitors its own inbox, so other
193
+ agents can reach it and mail **wakes** it — the message is delivered as a turn.
194
+
195
+ It does **not** run `liteharness.hooks watch` or `check_inbox`. Both are
196
+ consumers that move files out of `inbox/new/` for whichever agent id they
197
+ resolve, and a second consumer on a shared mailbox is the defect the
198
+ `ls-liteharness` fix retracted — mail vanished for three hours. `watch_inbox`
199
+ also writes to stdout, which a Textual app cannot survive.
200
+
201
+ Instead it reads the maildir directly under one rule:
202
+
203
+ > **Only ever touch a file whose `to` is this agent.**
204
+
205
+ Anything addressed elsewhere is left in `new/` exactly as found — unread,
206
+ unmoved, unclaimed. Expired messages (past `ttl_minutes`) are cleared without
207
+ delivery; the agent's own echo is skipped.
208
+
209
+ ## Tests
210
+
211
+ ```bash
212
+ uv run --locked python tests/run_all.py # 138 files. THE gate — read its exit code
213
+ uv run --locked pytest -q # the 125 pytest-style files only
214
+ ```
215
+
216
+ **Run `run_all.py`, and believe its exit code rather than a pass count.**
217
+ `tests/` holds two mutually hostile styles: pytest-style files, and script-style
218
+ files whose module body ends in `sys.exit(...)`. A module-level exit fires during
219
+ pytest *collection*, so `pytest -q` cannot collect the script-style half at all —
220
+ it reports a confident green while measuring 125 of 138 files. `run_all.py` runs
221
+ each half with the runner it needs and is the only thing that sees all of them.
222
+
223
+ No network, no TUI, and neither engine required.
224
+
225
+ ## Bundled
226
+
227
+ `chrome-bridge/` — drive Chrome from Python (navigate, read page, click,
228
+ screenshot) via an MV3 extension. `pccontrol/` — Win32 mouse/keyboard/window
229
+ automation and OCR helpers. Both are separate tools that happen to live here;
230
+ `chrome-bridge` also has a canonical copy elsewhere, so treat this one as a
231
+ snapshot rather than the source of truth.
@@ -0,0 +1,91 @@
1
+ [build-system]
2
+ # Without this table there is no build backend, so `uv` cannot install the
3
+ # project and SKIPS the declared console entry point with a warning — which is
4
+ # why `uv run lmstudio-chat` could not import `app`. The entry point was never
5
+ # broken; nothing had ever built it.
6
+ requires = ["setuptools>=68"]
7
+ build-backend = "setuptools.build_meta"
8
+
9
+ [project]
10
+ name = "litetui"
11
+ # 🔴 DERIVED, NEVER TYPED. This literal used to read "0.1.0" while
12
+ # src/version.py read "0.21.0" and CHANGELOG.md read "0.21.0" — two of three
13
+ # agreed and the packaging metadata was the outlier, so anything reading the
14
+ # installed distribution got a version that was 20 minor releases stale.
15
+ # src/version.py is the single source; see [tool.setuptools.dynamic] below.
16
+ dynamic = ["version"]
17
+ description = "TUI chat client for LM Studio with image support"
18
+ requires-python = ">=3.11"
19
+ dependencies = [
20
+ "textual>=0.89.0",
21
+ "openai>=1.0.0",
22
+ "Pillow>=10.0.0",
23
+ # LM Studio control plane (load/unload/ttl) — chat stays on openai's
24
+ # OpenAI-compat client for BOTH backends; the SDK is control-only.
25
+ "lmstudio>=1.5.0",
26
+ # Chrome-bridge relay (tools/chrome-bridge/bridge.py): WS server on
27
+ # 127.0.0.1:7461, extension scripts attach via 7462; uses the websockets
28
+ # asyncio client+server API (floor >=15). Was hand-installed only - a fresh
29
+ # venv built from this file would lack it and the chrome tool dies with an
30
+ # unexplained ModuleNotFoundError.
31
+ "websockets>=15",
32
+ ]
33
+
34
+ [project.scripts]
35
+ # `cli:main` is a thin launcher (src/litetui/cli.py): it checks argv for
36
+ # --version/-V BEFORE importing app, so the probe never pays Textual's cold-start.
37
+ # The heavy path still lands on litetui.app.LiteTUI().run(), as before. It resolves
38
+ # once the package actually builds — which is what [build-system] above provides.
39
+ litetui = "litetui.cli:main"
40
+
41
+ [dependency-groups]
42
+ # The tooling the gates need, declared rather than assumed present. The project
43
+ # .venv could not run pytest at all because it was never listed here, so the
44
+ # only environment able to run the suite was whatever the developer's global
45
+ # interpreter happened to hold. Ruff and mypy were absent from BOTH.
46
+ dev = [
47
+ "pytest>=8.0",
48
+ # 🔴 LOAD-BEARING, AND IT WAS INVISIBLE. 126 `@pytest.mark.asyncio`
49
+ # decorations across 26 test files need this plugin, and it was declared
50
+ # NOWHERE — it simply happened to be installed in the developer's global
51
+ # interpreter (1.3.0). That is the whole of finding 3 in one package: the
52
+ # 976-passing baseline was a property of one machine's system Python, and
53
+ # the same suite in the locked environment failed 4 of the first 5 tests
54
+ # it reached. An undeclared test dependency does not fail loudly; it makes
55
+ # the gate's colour depend on which interpreter happens to run it.
56
+ "pytest-asyncio>=0.24",
57
+ "ruff>=0.6",
58
+ "mypy>=1.11",
59
+ ]
60
+
61
+ [tool.setuptools]
62
+ package-dir = { "" = "src" }
63
+
64
+ # 🔴 DISCOVERED, NOT LISTED. This was an explicit py-modules inventory of 26
65
+ # names and it went stale within hours: two modules arrived (tool_policy,
66
+ # tool_approval) and were silently ABSENT from a built wheel -- 26 .py shipped,
67
+ # 28 on disk. The editable dev install hides it completely, because it puts
68
+ # src/ on the path instead of copying declared modules, so the only way to see
69
+ # the gap was to build a wheel and look inside it.
70
+ # A hand-maintained list of files is a second copy of the filesystem, and it
71
+ # drifts the moment someone adds a file without reading this table.
72
+ [tool.setuptools.packages.find]
73
+ where = ["src"]
74
+
75
+ [tool.setuptools.dynamic]
76
+ version = { attr = "litetui.version.__version__" }
77
+
78
+ [tool.pytest.ini_options]
79
+ # Collection is SCOPED, not left to discovery defaults: without this, any
80
+ # stray *_test.py at the repo root (a scratch file, a tool's leftovers)
81
+ # aborts collection for the entire run before one real test executes.
82
+ #
83
+ # e2e/ is deliberately NOT here. That is the first of two independent gates on
84
+ # live-service tests: they are not collected by the default command at all.
85
+ # The second is the marker below plus the env gate in e2e/conftest.py, so
86
+ # neither a stray `pytest e2e/` nor a copied marker can start a model by
87
+ # accident.
88
+ testpaths = ["tests"]
89
+ markers = [
90
+ "live: requires a running model service (LM Studio / llama.cpp). Never collected by the default command; also needs LITETUI_E2E=1.",
91
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ """LiteTUI — a local-first TUI chat client.
2
+
3
+ The version lives in litetui.version and is the single source pyproject reads via
4
+ [tool.setuptools.dynamic]; it is deliberately NOT duplicated here.
5
+ """