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.
- litetui-0.22.0/PKG-INFO +10 -0
- litetui-0.22.0/README.md +231 -0
- litetui-0.22.0/pyproject.toml +91 -0
- litetui-0.22.0/setup.cfg +4 -0
- litetui-0.22.0/src/litetui/__init__.py +5 -0
- litetui-0.22.0/src/litetui/app.py +4629 -0
- litetui-0.22.0/src/litetui/appsvc.py +157 -0
- litetui-0.22.0/src/litetui/ask_user_question.py +677 -0
- litetui-0.22.0/src/litetui/calendar_view.py +185 -0
- litetui-0.22.0/src/litetui/chrome_tool.py +237 -0
- litetui-0.22.0/src/litetui/cli.py +35 -0
- litetui-0.22.0/src/litetui/colorpicker.py +232 -0
- litetui-0.22.0/src/litetui/conversation.py +332 -0
- litetui-0.22.0/src/litetui/cron.py +223 -0
- litetui-0.22.0/src/litetui/dialog_demo.py +129 -0
- litetui-0.22.0/src/litetui/file_state.py +52 -0
- litetui-0.22.0/src/litetui/file_tools.py +181 -0
- litetui-0.22.0/src/litetui/fmt.py +16 -0
- litetui-0.22.0/src/litetui/goal_loop.py +464 -0
- litetui-0.22.0/src/litetui/harness.py +608 -0
- litetui-0.22.0/src/litetui/jobkill.py +182 -0
- litetui-0.22.0/src/litetui/listen_tool.py +416 -0
- litetui-0.22.0/src/litetui/llm_backend.py +1458 -0
- litetui-0.22.0/src/litetui/loop_list.py +171 -0
- litetui-0.22.0/src/litetui/mcp_client.py +411 -0
- litetui-0.22.0/src/litetui/paths.py +48 -0
- litetui-0.22.0/src/litetui/pccontrol_tool.py +176 -0
- litetui-0.22.0/src/litetui/picker.py +176 -0
- litetui-0.22.0/src/litetui/plugins/__init__.py +563 -0
- litetui-0.22.0/src/litetui/plugins/ask_user_question.py +25 -0
- litetui-0.22.0/src/litetui/plugins/chrome.py +19 -0
- litetui-0.22.0/src/litetui/plugins/convo.py +215 -0
- litetui-0.22.0/src/litetui/plugins/core_tools.py +376 -0
- litetui-0.22.0/src/litetui/plugins/file_tools.py +18 -0
- litetui-0.22.0/src/litetui/plugins/glassbox_plugin.py +267 -0
- litetui-0.22.0/src/litetui/plugins/goal_loop_plugin.py +36 -0
- litetui-0.22.0/src/litetui/plugins/harness_plugin.py +29 -0
- litetui-0.22.0/src/litetui/plugins/help_plugin.py +93 -0
- litetui-0.22.0/src/litetui/plugins/listen.py +31 -0
- litetui-0.22.0/src/litetui/plugins/mark_plugin.py +72 -0
- litetui-0.22.0/src/litetui/plugins/mcp_plugin.py +26 -0
- litetui-0.22.0/src/litetui/plugins/misc.py +202 -0
- litetui-0.22.0/src/litetui/plugins/model_switch.py +634 -0
- litetui-0.22.0/src/litetui/plugins/pccontrol.py +23 -0
- litetui-0.22.0/src/litetui/plugins/runtime_log_plugin.py +15 -0
- litetui-0.22.0/src/litetui/plugins/scheduler_plugin.py +63 -0
- litetui-0.22.0/src/litetui/plugins/scheduler_ui.py +791 -0
- litetui-0.22.0/src/litetui/plugins/settings_ui.py +67 -0
- litetui-0.22.0/src/litetui/plugins/skills_plugin.py +271 -0
- litetui-0.22.0/src/litetui/plugins/studio.py +31 -0
- litetui-0.22.0/src/litetui/plugins/themes_plugin.py +30 -0
- litetui-0.22.0/src/litetui/plugins/view_image.py +32 -0
- litetui-0.22.0/src/litetui/prompt_compiler.py +60 -0
- litetui-0.22.0/src/litetui/router_record.py +224 -0
- litetui-0.22.0/src/litetui/runtime_log.py +194 -0
- litetui-0.22.0/src/litetui/sanitize.py +93 -0
- litetui-0.22.0/src/litetui/schedule_builder.py +147 -0
- litetui-0.22.0/src/litetui/scheduler.py +422 -0
- litetui-0.22.0/src/litetui/seat_guard.py +210 -0
- litetui-0.22.0/src/litetui/settings.py +482 -0
- litetui-0.22.0/src/litetui/settings_screen.py +797 -0
- litetui-0.22.0/src/litetui/side_panel.py +711 -0
- litetui-0.22.0/src/litetui/skills.py +365 -0
- litetui-0.22.0/src/litetui/studio_tool.py +310 -0
- litetui-0.22.0/src/litetui/textfmt.py +365 -0
- litetui-0.22.0/src/litetui/themes.py +291 -0
- litetui-0.22.0/src/litetui/ticker.py +135 -0
- litetui-0.22.0/src/litetui/tool_approval.py +253 -0
- litetui-0.22.0/src/litetui/tool_context.py +161 -0
- litetui-0.22.0/src/litetui/tool_list.py +205 -0
- litetui-0.22.0/src/litetui/tool_policy.py +605 -0
- litetui-0.22.0/src/litetui/tool_schemas.py +91 -0
- litetui-0.22.0/src/litetui/ttyguard.py +238 -0
- litetui-0.22.0/src/litetui/turn_engine.py +231 -0
- litetui-0.22.0/src/litetui/turnstats.py +240 -0
- litetui-0.22.0/src/litetui/version.py +20 -0
- litetui-0.22.0/src/litetui/widgets.py +767 -0
- litetui-0.22.0/src/litetui.egg-info/PKG-INFO +10 -0
- litetui-0.22.0/src/litetui.egg-info/SOURCES.txt +221 -0
- litetui-0.22.0/src/litetui.egg-info/dependency_links.txt +1 -0
- litetui-0.22.0/src/litetui.egg-info/entry_points.txt +2 -0
- litetui-0.22.0/src/litetui.egg-info/requires.txt +5 -0
- litetui-0.22.0/src/litetui.egg-info/top_level.txt +2 -0
- litetui-0.22.0/tests/test_ask_user_question.py +255 -0
- litetui-0.22.0/tests/test_ask_user_question_sidebar.py +243 -0
- litetui-0.22.0/tests/test_authority_footer_and_key.py +451 -0
- litetui-0.22.0/tests/test_autocompact.py +131 -0
- litetui-0.22.0/tests/test_autonomous_profile.py +149 -0
- litetui-0.22.0/tests/test_autoscroll.py +316 -0
- litetui-0.22.0/tests/test_backend_switch.py +92 -0
- litetui-0.22.0/tests/test_calendar.py +362 -0
- litetui-0.22.0/tests/test_calendar_ui.py +506 -0
- litetui-0.22.0/tests/test_cancel_tool_placement.py +182 -0
- litetui-0.22.0/tests/test_chat_ready_call_sites.py +551 -0
- litetui-0.22.0/tests/test_chrome_tool.py +101 -0
- litetui-0.22.0/tests/test_cli.py +42 -0
- litetui-0.22.0/tests/test_collector_integrity.py +111 -0
- litetui-0.22.0/tests/test_colorpicker.py +100 -0
- litetui-0.22.0/tests/test_command_palette.py +315 -0
- litetui-0.22.0/tests/test_compaction_card_renders.py +135 -0
- litetui-0.22.0/tests/test_compaction_ui.py +346 -0
- litetui-0.22.0/tests/test_confirm_stop_sidebar.py +213 -0
- litetui-0.22.0/tests/test_connect_banner.py +193 -0
- litetui-0.22.0/tests/test_context_length.py +238 -0
- litetui-0.22.0/tests/test_convo_picked.py +68 -0
- litetui-0.22.0/tests/test_convo_rename.py +182 -0
- litetui-0.22.0/tests/test_convos.py +151 -0
- litetui-0.22.0/tests/test_convos_picker.py +117 -0
- litetui-0.22.0/tests/test_cron_wiring.py +294 -0
- litetui-0.22.0/tests/test_ctx_resync.py +132 -0
- litetui-0.22.0/tests/test_deny_stops_the_turn.py +263 -0
- litetui-0.22.0/tests/test_desktop_tools.py +110 -0
- litetui-0.22.0/tests/test_dialog_geometry.py +321 -0
- litetui-0.22.0/tests/test_elapsed.py +124 -0
- litetui-0.22.0/tests/test_eta.py +160 -0
- litetui-0.22.0/tests/test_file_tools.py +299 -0
- litetui-0.22.0/tests/test_first_boot.py +114 -0
- litetui-0.22.0/tests/test_fleet_identity.py +144 -0
- litetui-0.22.0/tests/test_footer.py +180 -0
- litetui-0.22.0/tests/test_footer_fields.py +127 -0
- litetui-0.22.0/tests/test_glassbox_emission.py +321 -0
- litetui-0.22.0/tests/test_glassbox_events.py +149 -0
- litetui-0.22.0/tests/test_glassbox_plugin.py +214 -0
- litetui-0.22.0/tests/test_goal_loop.py +348 -0
- litetui-0.22.0/tests/test_harness_tool.py +122 -0
- litetui-0.22.0/tests/test_input.py +138 -0
- litetui-0.22.0/tests/test_integrations.py +224 -0
- litetui-0.22.0/tests/test_job_builder_ui.py +259 -0
- litetui-0.22.0/tests/test_keys_toggle.py +72 -0
- litetui-0.22.0/tests/test_kill_tree_honesty.py +343 -0
- litetui-0.22.0/tests/test_lazy_convo.py +150 -0
- litetui-0.22.0/tests/test_live_state_isolation.py +99 -0
- litetui-0.22.0/tests/test_llama_chat_ready.py +345 -0
- litetui-0.22.0/tests/test_llama_discovery.py +130 -0
- litetui-0.22.0/tests/test_llama_flags.py +73 -0
- litetui-0.22.0/tests/test_llama_ini.py +101 -0
- litetui-0.22.0/tests/test_llama_router.py +294 -0
- litetui-0.22.0/tests/test_llama_single_model.py +242 -0
- litetui-0.22.0/tests/test_lms_backend.py +119 -0
- litetui-0.22.0/tests/test_loop_list.py +212 -0
- litetui-0.22.0/tests/test_mark.py +79 -0
- litetui-0.22.0/tests/test_markup_safety.py +94 -0
- litetui-0.22.0/tests/test_mcp_timeout_bounds.py +247 -0
- litetui-0.22.0/tests/test_message_queue.py +182 -0
- litetui-0.22.0/tests/test_modal_centering.py +125 -0
- litetui-0.22.0/tests/test_modals.py +148 -0
- litetui-0.22.0/tests/test_model_picked.py +97 -0
- litetui-0.22.0/tests/test_model_screen.py +164 -0
- litetui-0.22.0/tests/test_no_convo_pollution.py +90 -0
- litetui-0.22.0/tests/test_no_dead_controls.py +132 -0
- litetui-0.22.0/tests/test_no_fleet_registration.py +415 -0
- litetui-0.22.0/tests/test_no_implicit_model_load.py +166 -0
- litetui-0.22.0/tests/test_palette_groups.py +142 -0
- litetui-0.22.0/tests/test_paths.py +54 -0
- litetui-0.22.0/tests/test_persist_error_report.py +106 -0
- litetui-0.22.0/tests/test_picker_sidebar.py +182 -0
- litetui-0.22.0/tests/test_plugin_dogfood.py +76 -0
- litetui-0.22.0/tests/test_plugin_substrate.py +203 -0
- litetui-0.22.0/tests/test_plugins_command.py +75 -0
- litetui-0.22.0/tests/test_powershell_tool.py +133 -0
- litetui-0.22.0/tests/test_prompt_compiler.py +44 -0
- litetui-0.22.0/tests/test_prompt_compose.py +118 -0
- litetui-0.22.0/tests/test_queued_input_delivery.py +132 -0
- litetui-0.22.0/tests/test_request_overrides.py +79 -0
- litetui-0.22.0/tests/test_resume_identity.py +110 -0
- litetui-0.22.0/tests/test_router_coexistence.py +167 -0
- litetui-0.22.0/tests/test_router_record.py +160 -0
- litetui-0.22.0/tests/test_runtime_log.py +90 -0
- litetui-0.22.0/tests/test_runtime_log_producers.py +178 -0
- litetui-0.22.0/tests/test_sanitize.py +69 -0
- litetui-0.22.0/tests/test_schedule_builder.py +130 -0
- litetui-0.22.0/tests/test_scheduler.py +269 -0
- litetui-0.22.0/tests/test_seat_convo_identity.py +174 -0
- litetui-0.22.0/tests/test_seat_guard.py +278 -0
- litetui-0.22.0/tests/test_seat_guard_backends.py +88 -0
- litetui-0.22.0/tests/test_seat_identity.py +187 -0
- litetui-0.22.0/tests/test_seat_rebind.py +325 -0
- litetui-0.22.0/tests/test_self_store_writes.py +184 -0
- litetui-0.22.0/tests/test_settings.py +476 -0
- litetui-0.22.0/tests/test_settings_live.py +131 -0
- litetui-0.22.0/tests/test_sidebar_dialog.py +279 -0
- litetui-0.22.0/tests/test_sidebar_side.py +168 -0
- litetui-0.22.0/tests/test_skill_autocomplete.py +224 -0
- litetui-0.22.0/tests/test_skill_invocation.py +317 -0
- litetui-0.22.0/tests/test_skills_cache.py +193 -0
- litetui-0.22.0/tests/test_skills_library.py +144 -0
- litetui-0.22.0/tests/test_skills_picker.py +170 -0
- litetui-0.22.0/tests/test_skills_visible.py +219 -0
- litetui-0.22.0/tests/test_slash_autocomplete_commands.py +286 -0
- litetui-0.22.0/tests/test_store.py +113 -0
- litetui-0.22.0/tests/test_studio_tool.py +209 -0
- litetui-0.22.0/tests/test_swap_button_in_real_dialogs.py +194 -0
- litetui-0.22.0/tests/test_theme_extra_tokens.py +179 -0
- litetui-0.22.0/tests/test_themes.py +197 -0
- litetui-0.22.0/tests/test_thinking_autoscroll.py +116 -0
- litetui-0.22.0/tests/test_thinking_timer.py +167 -0
- litetui-0.22.0/tests/test_toggle_tools_persists.py +107 -0
- litetui-0.22.0/tests/test_token_counts.py +267 -0
- litetui-0.22.0/tests/test_tool_approval.py +310 -0
- litetui-0.22.0/tests/test_tool_approval_sidebar.py +269 -0
- litetui-0.22.0/tests/test_tool_cancel.py +428 -0
- litetui-0.22.0/tests/test_tool_context.py +168 -0
- litetui-0.22.0/tests/test_tool_context_wiring.py +155 -0
- litetui-0.22.0/tests/test_tool_denied.py +151 -0
- litetui-0.22.0/tests/test_tool_list_dialog.py +201 -0
- litetui-0.22.0/tests/test_tool_list_disable.py +211 -0
- litetui-0.22.0/tests/test_tool_policy.py +130 -0
- litetui-0.22.0/tests/test_tool_policy_wiring.py +170 -0
- litetui-0.22.0/tests/test_tool_profile_choices.py +137 -0
- litetui-0.22.0/tests/test_tool_schemas.py +143 -0
- litetui-0.22.0/tests/test_tool_standing_rules.py +110 -0
- litetui-0.22.0/tests/test_tools_disabled.py +144 -0
- litetui-0.22.0/tests/test_tools_prompt.py +103 -0
- litetui-0.22.0/tests/test_tools_registered.py +109 -0
- litetui-0.22.0/tests/test_ttyguard.py +161 -0
- litetui-0.22.0/tests/test_turn_engine.py +307 -0
- litetui-0.22.0/tests/test_turnstats_elapsed.py +152 -0
- litetui-0.22.0/tests/test_turnstats_eta.py +108 -0
- litetui-0.22.0/tests/test_unattended_profile.py +298 -0
- litetui-0.22.0/tests/test_version.py +84 -0
- litetui-0.22.0/tests/test_view_image.py +146 -0
- litetui-0.22.0/tests/test_wake_after_compact.py +290 -0
- litetui-0.22.0/tests/test_wake_guard_abandoned_turn.py +403 -0
litetui-0.22.0/PKG-INFO
ADDED
|
@@ -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
|
litetui-0.22.0/README.md
ADDED
|
@@ -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
|
+
]
|
litetui-0.22.0/setup.cfg
ADDED