limbo-code 0.1.1__tar.gz → 0.1.10__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 (191) hide show
  1. {limbo_code-0.1.1 → limbo_code-0.1.10}/.github/workflows/test.yml +13 -3
  2. {limbo_code-0.1.1 → limbo_code-0.1.10}/.gitignore +1 -0
  3. limbo_code-0.1.10/AGENTS.md +94 -0
  4. limbo_code-0.1.10/CONTEXT.md +43 -0
  5. limbo_code-0.1.10/PKG-INFO +268 -0
  6. limbo_code-0.1.10/README.md +246 -0
  7. limbo_code-0.1.10/docs/assets/walkthrough/limbo-dark/1_idle.svg +165 -0
  8. limbo_code-0.1.10/docs/assets/walkthrough/limbo-dark/2_thinking_tool_running.svg +166 -0
  9. limbo_code-0.1.10/docs/assets/walkthrough/limbo-dark/3_tool_success.svg +167 -0
  10. limbo_code-0.1.10/docs/assets/walkthrough/limbo-dark/4_tool_error.svg +168 -0
  11. limbo_code-0.1.10/docs/assets/walkthrough/limbo-dark/5_llm_error.svg +166 -0
  12. limbo_code-0.1.10/docs/assets/walkthrough/limbo-dark/6_edit_diff.svg +168 -0
  13. limbo_code-0.1.10/docs/assets/walkthrough/limbo-light/1_idle.svg +166 -0
  14. limbo_code-0.1.10/docs/assets/walkthrough/limbo-light/2_thinking_tool_running.svg +167 -0
  15. limbo_code-0.1.10/docs/assets/walkthrough/limbo-light/3_tool_success.svg +168 -0
  16. limbo_code-0.1.10/docs/assets/walkthrough/limbo-light/4_tool_error.svg +169 -0
  17. limbo_code-0.1.10/docs/assets/walkthrough/limbo-light/5_llm_error.svg +167 -0
  18. limbo_code-0.1.10/docs/assets/walkthrough/limbo-light/6_edit_diff.svg +169 -0
  19. {limbo_code-0.1.1 → limbo_code-0.1.10}/docs/skills.md +14 -2
  20. {limbo_code-0.1.1 → limbo_code-0.1.10}/pyproject.toml +15 -3
  21. limbo_code-0.1.10/scripts/check_contrast.py +28 -0
  22. limbo_code-0.1.10/scripts/gen_banner.py +103 -0
  23. limbo_code-0.1.10/scripts/ui_walkthrough.py +130 -0
  24. limbo_code-0.1.10/src/limbo/__init__.py +8 -0
  25. limbo_code-0.1.10/src/limbo/agent.py +982 -0
  26. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/app.py +12 -8
  27. limbo_code-0.1.10/src/limbo/attachments.py +65 -0
  28. limbo_code-0.1.10/src/limbo/compaction.py +173 -0
  29. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/config.py +92 -2
  30. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/history.py +22 -24
  31. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/llm/anthropic_client.py +109 -54
  32. limbo_code-0.1.10/src/limbo/llm/catalog.py +396 -0
  33. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/llm/client.py +6 -0
  34. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/llm/factory.py +3 -0
  35. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/llm/openai_client.py +169 -40
  36. limbo_code-0.1.10/src/limbo/llm/responses_client.py +409 -0
  37. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/llm/retry.py +8 -3
  38. limbo_code-0.1.10/src/limbo/llm/scaffold.py +92 -0
  39. limbo_code-0.1.10/src/limbo/llm/sse.py +49 -0
  40. limbo_code-0.1.10/src/limbo/llm/usage.py +160 -0
  41. limbo_code-0.1.10/src/limbo/model_switch.py +103 -0
  42. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/models.py +45 -3
  43. limbo_code-0.1.10/src/limbo/prompt.py +103 -0
  44. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/sessions.py +96 -6
  45. limbo_code-0.1.10/src/limbo/skills.py +190 -0
  46. limbo_code-0.1.10/src/limbo/steer.py +76 -0
  47. limbo_code-0.1.10/src/limbo/tools/base.py +314 -0
  48. limbo_code-0.1.10/src/limbo/tools/bash.py +393 -0
  49. limbo_code-0.1.10/src/limbo/tools/edit.py +253 -0
  50. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/tools/find.py +39 -5
  51. limbo_code-0.1.10/src/limbo/tools/grep.py +147 -0
  52. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/tools/ls.py +24 -3
  53. limbo_code-0.1.10/src/limbo/tools/mutation_queue.py +36 -0
  54. limbo_code-0.1.10/src/limbo/tools/read.py +205 -0
  55. limbo_code-0.1.10/src/limbo/tools/registry.py +112 -0
  56. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/tools/write.py +7 -5
  57. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/trace.py +10 -0
  58. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/ui/app.py +17 -6
  59. limbo_code-0.1.10/src/limbo/ui/app.tcss +316 -0
  60. limbo_code-0.1.10/src/limbo/ui/banner.py +117 -0
  61. limbo_code-0.1.10/src/limbo/ui/clipboard.py +314 -0
  62. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/ui/commands.py +4 -0
  63. limbo_code-0.1.10/src/limbo/ui/contrast.py +139 -0
  64. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/ui/screens/main.py +272 -18
  65. limbo_code-0.1.10/src/limbo/ui/screens/model_picker.py +122 -0
  66. limbo_code-0.1.10/src/limbo/ui/syntax.py +84 -0
  67. limbo_code-0.1.10/src/limbo/ui/theme.py +101 -0
  68. limbo_code-0.1.10/src/limbo/ui/widgets/chat.py +320 -0
  69. limbo_code-0.1.10/src/limbo/ui/widgets/input.py +409 -0
  70. limbo_code-0.1.10/src/limbo/ui/widgets/status_bar.py +116 -0
  71. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/ui/widgets/tool_card.py +18 -8
  72. limbo_code-0.1.10/src/limbo/user_paths.py +82 -0
  73. limbo_code-0.1.10/tests/conftest.py +21 -0
  74. limbo_code-0.1.10/tests/test_agent.py +2132 -0
  75. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/test_anthropic_client.py +315 -2
  76. limbo_code-0.1.10/tests/test_attachments.py +78 -0
  77. limbo_code-0.1.10/tests/test_catalog.py +293 -0
  78. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/test_cli.py +12 -0
  79. limbo_code-0.1.10/tests/test_clipboard.py +205 -0
  80. limbo_code-0.1.10/tests/test_compaction.py +163 -0
  81. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/test_config.py +74 -3
  82. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/test_history.py +27 -13
  83. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/test_llm_client.py +376 -4
  84. limbo_code-0.1.10/tests/test_llm_scaffold.py +89 -0
  85. limbo_code-0.1.10/tests/test_model_switch.py +140 -0
  86. limbo_code-0.1.10/tests/test_prompt.py +85 -0
  87. limbo_code-0.1.10/tests/test_responses_client.py +626 -0
  88. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/test_retry.py +4 -0
  89. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/test_sessions.py +181 -4
  90. limbo_code-0.1.10/tests/test_skills.py +187 -0
  91. limbo_code-0.1.10/tests/test_sse.py +92 -0
  92. limbo_code-0.1.10/tests/test_steer.py +66 -0
  93. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/test_trace.py +13 -0
  94. limbo_code-0.1.10/tests/test_usage.py +159 -0
  95. limbo_code-0.1.10/tests/test_user_paths.py +87 -0
  96. limbo_code-0.1.10/tests/tools/test_base.py +247 -0
  97. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/tools/test_bash.py +136 -8
  98. limbo_code-0.1.10/tests/tools/test_edit.py +289 -0
  99. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/tools/test_find.py +57 -0
  100. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/tools/test_grep.py +45 -37
  101. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/tools/test_ls.py +19 -1
  102. limbo_code-0.1.10/tests/tools/test_mutation_queue.py +47 -0
  103. limbo_code-0.1.10/tests/tools/test_read.py +309 -0
  104. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/tools/test_registry.py +32 -0
  105. limbo_code-0.1.10/tests/ui/__snapshots__/test_snapshots/test_snapshot_idle.raw +172 -0
  106. limbo_code-0.1.10/tests/ui/__snapshots__/test_snapshots/test_snapshot_session_picker.raw +168 -0
  107. limbo_code-0.1.10/tests/ui/__snapshots__/test_snapshots/test_snapshot_thinking_and_running_tool.raw +174 -0
  108. limbo_code-0.1.10/tests/ui/__snapshots__/test_snapshots/test_snapshot_tool_error_and_error_message.raw +175 -0
  109. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/ui/test_app_smoke.py +9 -4
  110. limbo_code-0.1.10/tests/ui/test_compact_ui.py +188 -0
  111. limbo_code-0.1.10/tests/ui/test_contrast.py +41 -0
  112. limbo_code-0.1.10/tests/ui/test_input_attachments.py +168 -0
  113. limbo_code-0.1.10/tests/ui/test_input_paste.py +314 -0
  114. limbo_code-0.1.10/tests/ui/test_main_screen.py +284 -0
  115. limbo_code-0.1.10/tests/ui/test_model_picker.py +393 -0
  116. limbo_code-0.1.10/tests/ui/test_scroll_follow.py +85 -0
  117. limbo_code-0.1.10/tests/ui/test_snapshots.py +136 -0
  118. limbo_code-0.1.10/tests/ui/test_startup_art.py +137 -0
  119. limbo_code-0.1.10/tests/ui/test_steer_ui.py +532 -0
  120. limbo_code-0.1.10/tests/ui/test_theme.py +104 -0
  121. limbo_code-0.1.10/uv.lock +936 -0
  122. limbo_code-0.1.1/AGENTS.md +0 -183
  123. limbo_code-0.1.1/PKG-INFO +0 -172
  124. limbo_code-0.1.1/README.md +0 -154
  125. limbo_code-0.1.1/src/limbo/__init__.py +0 -3
  126. limbo_code-0.1.1/src/limbo/agent.py +0 -456
  127. limbo_code-0.1.1/src/limbo/llm/catalog.py +0 -234
  128. limbo_code-0.1.1/src/limbo/skills.py +0 -89
  129. limbo_code-0.1.1/src/limbo/tools/base.py +0 -102
  130. limbo_code-0.1.1/src/limbo/tools/bash.py +0 -172
  131. limbo_code-0.1.1/src/limbo/tools/edit.py +0 -70
  132. limbo_code-0.1.1/src/limbo/tools/grep.py +0 -182
  133. limbo_code-0.1.1/src/limbo/tools/read.py +0 -105
  134. limbo_code-0.1.1/src/limbo/tools/registry.py +0 -66
  135. limbo_code-0.1.1/src/limbo/ui/app.tcss +0 -174
  136. limbo_code-0.1.1/src/limbo/ui/banner.py +0 -83
  137. limbo_code-0.1.1/src/limbo/ui/widgets/chat.py +0 -149
  138. limbo_code-0.1.1/src/limbo/ui/widgets/input.py +0 -199
  139. limbo_code-0.1.1/src/limbo/ui/widgets/status_bar.py +0 -32
  140. limbo_code-0.1.1/tests/test_agent.py +0 -729
  141. limbo_code-0.1.1/tests/test_catalog.py +0 -160
  142. limbo_code-0.1.1/tests/test_skills.py +0 -89
  143. limbo_code-0.1.1/tests/tools/test_base.py +0 -85
  144. limbo_code-0.1.1/tests/tools/test_edit.py +0 -130
  145. limbo_code-0.1.1/tests/tools/test_read.py +0 -156
  146. limbo_code-0.1.1/tests/ui/test_main_screen.py +0 -150
  147. limbo_code-0.1.1/tests/ui/test_startup_art.py +0 -94
  148. {limbo_code-0.1.1 → limbo_code-0.1.10}/.agents/skills/grill-with-docs/SKILL.md +0 -0
  149. {limbo_code-0.1.1 → limbo_code-0.1.10}/.agents/skills/grill-with-docs/agents/openai.yaml +0 -0
  150. {limbo_code-0.1.1 → limbo_code-0.1.10}/.agents/skills/improve-codebase-architecture/HTML-REPORT.md +0 -0
  151. {limbo_code-0.1.1 → limbo_code-0.1.10}/.agents/skills/improve-codebase-architecture/SKILL.md +0 -0
  152. {limbo_code-0.1.1 → limbo_code-0.1.10}/.agents/skills/improve-codebase-architecture/agents/openai.yaml +0 -0
  153. {limbo_code-0.1.1 → limbo_code-0.1.10}/.agents/skills/tdd/SKILL.md +0 -0
  154. {limbo_code-0.1.1 → limbo_code-0.1.10}/.agents/skills/tdd/agents/openai.yaml +0 -0
  155. {limbo_code-0.1.1 → limbo_code-0.1.10}/.agents/skills/tdd/mocking.md +0 -0
  156. {limbo_code-0.1.1 → limbo_code-0.1.10}/.agents/skills/tdd/tests.md +0 -0
  157. {limbo_code-0.1.1 → limbo_code-0.1.10}/.github/workflows/publish.yml +0 -0
  158. {limbo_code-0.1.1 → limbo_code-0.1.10}/design/confirm_view.html +0 -0
  159. {limbo_code-0.1.1 → limbo_code-0.1.10}/design/limbo-ui-minimal.md +0 -0
  160. {limbo_code-0.1.1 → limbo_code-0.1.10}/design/limbo-ui-redesign.md +0 -0
  161. {limbo_code-0.1.1 → limbo_code-0.1.10}/design/prototype-minimal-confirm.png +0 -0
  162. {limbo_code-0.1.1 → limbo_code-0.1.10}/design/prototype-minimal.html +0 -0
  163. {limbo_code-0.1.1 → limbo_code-0.1.10}/design/prototype-minimal.png +0 -0
  164. {limbo_code-0.1.1 → limbo_code-0.1.10}/design/prototype.html +0 -0
  165. {limbo_code-0.1.1 → limbo_code-0.1.10}/design/prototype.png +0 -0
  166. {limbo_code-0.1.1 → limbo_code-0.1.10}/docs/assets/limbo-current-ui.png +0 -0
  167. {limbo_code-0.1.1 → limbo_code-0.1.10}/docs/assets/limbo-new-ui.png +0 -0
  168. {limbo_code-0.1.1 → limbo_code-0.1.10}/docs/session-management.md +0 -0
  169. {limbo_code-0.1.1 → limbo_code-0.1.10}/docs/ui-redesign-proposal.md +0 -0
  170. {limbo_code-0.1.1 → limbo_code-0.1.10}/skills-lock.json +0 -0
  171. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/__main__.py +0 -0
  172. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/llm/__init__.py +0 -0
  173. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/tools/__init__.py +0 -0
  174. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/tools/ignore.py +0 -0
  175. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/ui/__init__.py +0 -0
  176. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/ui/screens/__init__.py +0 -0
  177. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/ui/screens/game2048.py +0 -0
  178. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/ui/screens/session_picker.py +0 -0
  179. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/ui/widgets/__init__.py +0 -0
  180. {limbo_code-0.1.1 → limbo_code-0.1.10}/src/limbo/ui/widgets/command_menu.py +0 -0
  181. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/test_integration.py +0 -0
  182. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/test_models.py +0 -0
  183. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/tools/test_ignore.py +0 -0
  184. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/tools/test_write.py +0 -0
  185. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/ui/test_command_menu.py +0 -0
  186. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/ui/test_commands.py +0 -0
  187. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/ui/test_game2048.py +0 -0
  188. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/ui/test_input_history.py +0 -0
  189. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/ui/test_sessions_ui.py +0 -0
  190. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/ui/test_skills_ui.py +0 -0
  191. {limbo_code-0.1.1 → limbo_code-0.1.10}/tests/ui/test_widgets.py +0 -0
@@ -17,14 +17,24 @@ jobs:
17
17
  steps:
18
18
  - uses: actions/checkout@v4
19
19
 
20
+ - name: Install uv
21
+ uses: astral-sh/setup-uv@v5
22
+
20
23
  - name: Set up Python
21
24
  uses: actions/setup-python@v5
22
25
  with:
23
26
  python-version: ${{ matrix.python-version }}
24
- cache: pip
25
27
 
28
+ # grep hard-depends on ripgrep (no Python fallback since LIM-24).
29
+ - name: Install ripgrep
30
+ run: sudo apt-get install -y ripgrep
31
+
32
+ # Install from uv.lock so CI renders snapshots with the exact same
33
+ # textual/rich/syrupy versions the baselines were generated with.
26
34
  - name: Install dependencies
27
- run: pip install -e ".[dev]"
35
+ run: uv sync --frozen --extra dev
36
+ env:
37
+ UV_PYTHON: ${{ matrix.python-version }}
28
38
 
29
39
  - name: Run tests
30
- run: python -m pytest -v
40
+ run: uv run python -m pytest -v
@@ -11,3 +11,4 @@ venv/
11
11
  dist/
12
12
  build/
13
13
  .pi-subagents/
14
+ snapshot_report.html
@@ -0,0 +1,94 @@
1
+ # Limbo — Agent Guide
2
+
3
+ **Limbo** is a minimal terminal AI coding agent (Python 3.11+, Textual). Users converse with an LLM in a TUI to explore, read, edit, and write code via 7 tools.
4
+
5
+ **Tools execute immediately — no confirmation flow.** Convenience guardrails (not a security boundary): a workdir scope on file tools (+ session-scoped grants from user-mentioned paths), a sensitive-file skip list shared by `read`/`grep`/`find`, and a heuristic dangerous-command filter on `bash` (best-effort, rejects matches outright; `bash` is otherwise not covered by any guardrail).
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ pip install -e .
11
+ limbo --workdir /path/to/project # config: ~/.limbo/config.toml
12
+ ```
13
+
14
+ ## Architecture
15
+
16
+ ```
17
+ src/limbo/
18
+ ├── app.py # CLI entry: args, config, launch
19
+ ├── config.py # Pydantic config: llm/ui/safety/tools/compaction/providers
20
+ ├── models.py # Message, Attachment, ToolResult, LLMEvent
21
+ ├── agent.py # Conversation loop (see below)
22
+ ├── attachments.py # Attachment policy: vision gate, inline vs path-reference degrade
23
+ ├── compaction.py # Context compaction decision/prompt logic (LIM-14)
24
+ ├── history.py # tool_call↔result pairing + resume repair
25
+ ├── model_switch.py # /model domain logic: validate, swap client, persist (UI is an adapter)
26
+ ├── prompt.py # System-prompt assembly (tools section derived from the registry)
27
+ ├── steer.py # Mid-turn steer queue (LIM-20): queueing semantics, cancel boundary
28
+ ├── sessions.py # Session JSONL save/load/list/export
29
+ ├── trace.py # Append-only JSONL run log (sessions/traces/)
30
+ ├── skills.py # SKILL.md discovery (user + project dirs)
31
+ ├── user_paths.py # Fence grants from paths in user messages
32
+ ├── llm/ # catalog.py (provider/model specs), factory.py (dialect→client),
33
+ │ # openai_client.py, anthropic_client.py, responses_client.py, retry.py, sse.py,
34
+ │ # usage.py (token accounting: usage normalization + prompt-size estimation),
35
+ │ # scaffold.py (plumbing shared by dialect clients: credentials, retry, images)
36
+ ├── tools/ # base.py (BaseTool + path guardrail + truncation), registry.py (dispatch, grants),
37
+ │ # mutation_queue.py (per-file locks), ignore.py (.gitignore),
38
+ │ # read/bash/edit/write/grep/find/ls.py
39
+ └── ui/ # app.py + app.tcss (ALL styles here; theme vars only, no bare hex),
40
+ # theme.py (limbo-dark/-light, RFC LIM-16), commands.py (slash registry),
41
+ # screens/ (main, session_picker, model_picker, game2048),
42
+ # widgets/ (chat, input, status_bar, tool_card, command_menu)
43
+ ```
44
+
45
+ ## Agent Loop
46
+
47
+ - `Agent.run()` yields `AgentEvent`: `TextDelta`, `ThinkingDelta`, `ToolCallRequest`, `ToolResultEvent`, `ErrorEvent`, `CompactionEvent`, `UsageUpdate`, `SteerEvent`
48
+ - Loop top: auto-compaction check → steer drain → LLM call. Turn ends on a tool-call-free response; `max_iterations` (default 50) cancels pending calls with placeholder results
49
+ - Tool calls in one turn run **concurrently** (`[tools] parallel`); results stream in completion order, recorded in source order; same-file mutations serialized by `mutation_queue`
50
+ - `finish_reason` `length`/`max_tokens` → whole batch failed without execution, model re-issues
51
+ - Mid-turn user input queues as *steer* (LIM-20): injected at loop top, or as turn-end follow-up
52
+ - Reasoning is stored on assistant messages and replayed per dialect (Anthropic thinking signatures, Kimi `reasoning_content`, Responses encrypted items)
53
+ - Usage counters normalize in `llm/usage.py` (`input_tokens`+`cache_read`, `prompt_tokens`, DeepSeek cache hits) and feed the compaction trigger via `PromptSizeEstimator`
54
+
55
+ ## LLM & Config
56
+
57
+ Providers/models live in `llm/catalog.py`; unknown models get generic OpenAI-compatible defaults. Resolution: `[providers.<id>]` overrides → explicit `[llm]` values → catalog defaults → env-var API keys.
58
+
59
+ ```toml
60
+ [llm] # model, api_key, base_url, temperature, max_iterations,
61
+ # max_tokens, thinking_effort, max_retries, timeout, ...
62
+ [tools] # bash_enabled = true, parallel = true
63
+ [compaction] # enabled, reserve_tokens = 16384, keep_recent_tokens = 20000
64
+ [safety] # dangerous_commands, sensitive_files, auto_grant_user_paths
65
+ [ui] # theme, show_banner
66
+ [providers.<id>] # base_url / api_key / api_key_env / headers
67
+ ```
68
+
69
+ `/model` rebuilds the client mid-session (refused while busy) and persists via tomlkit. Sessions resume via `--continue` / `--resume` / `/sessions`; resume repairs dangling tool calls and restores grants. Trace events: `session_start`, `user_message`, `llm_request` (full body), `llm_response`, `tool_call`, `tool_result`, `compaction*`, `error`, `turn_end`. `/export [path]` merges meta + trace + message snapshot (Markdown if `.md`).
70
+
71
+ Skills: `~/.limbo/skills/<name>/SKILL.md` (user) and `<workdir>/.agents/skills/<name>/SKILL.md` (project, wins collisions) — injected as a `<available_skills>` catalog in the system prompt and invocable via `/<name> [args]`.
72
+
73
+ ## UI
74
+
75
+ Single column: status bar (state/elapsed/tokens/model/workdir/queued) → scrolling chat flow (user `❯`, streaming Markdown, tool cards, errors) → input box (Enter submits, Shift+Enter newline, paste markers, `ctrl+v` image attach) → hint line. Slash commands: `/sessions /new /export /compact /model /help /2048` + skill commands. After palette changes run `python scripts/check_contrast.py` (≥ 4.5:1).
76
+
77
+ ## Key Decisions
78
+
79
+ - Async agent, sync tools via `asyncio.to_thread()`
80
+ - System prompt = hardcoded tool guidelines + `<workdir>/AGENTS.md` & `~/.limbo/AGENTS.md` (XML-wrapped) + skills catalog. **This file is injected into every request — keep it accurate and lean.**
81
+ - Session files fully rewritten per save (atomic, 0600); trace holds full-fidelity record
82
+
83
+ ## Testing
84
+
85
+ ```bash
86
+ pytest tests/ -v # pytest-asyncio, respx HTTP mocks, pytest-textual-snapshot
87
+ ruff check src tests && mypy src # lint + type check
88
+ pytest --snapshot-update # after intentional visual changes; review the SVG diff
89
+ ```
90
+
91
+ ## Adding Things
92
+
93
+ - **Tool**: subclass `BaseTool` in `tools/<name>.py` (`name`/`description`/`parameters` + `run()`; use `resolve_existing()`/`resolve_creatable()`, raise `ToolError`), register in `ToolRegistry.__init__()`, wrap mutations in `mutation_lock_for(path)`, add `tests/tools/test_<name>.py`
94
+ - **Provider/model**: add specs in `llm/catalog.py`; new wire format → implement `LLMClient` + `register_client()` in `llm/factory.py`. User-specific endpoint/key tweaks belong in `[providers.<id>]` config, not the catalog
@@ -0,0 +1,43 @@
1
+ # Context — Limbo domain glossary
2
+
3
+ Domain language for Limbo's concepts. Architecture reviews and refactor
4
+ proposals should name modules with these terms.
5
+
6
+ ## Token accounting
7
+
8
+ - **Usage** — the provider-reported token counters on one completion. Each
9
+ dialect reports them differently (OpenAI `prompt_tokens`, Anthropic
10
+ `input_tokens` + cache counters, DeepSeek `prompt_cache_hit_tokens`,
11
+ Responses `input_tokens_details`). Normalized by `llm/usage.py` into
12
+ `UsageTotals` (prompt / cached / total); the agent loop and compaction
13
+ never read provider field names.
14
+ - **Prompt-size estimation** — predicting the *next* request's prompt size:
15
+ the last real usage figure plus the chars/4 estimate of everything
16
+ appended since (the **watermark**). Owned by `PromptSizeEstimator` in
17
+ `llm/usage.py`; invalidated (reset) when compaction rewrites history.
18
+
19
+ ## Conversation
20
+
21
+ - **Turn** — one user input through the agent loop until a tool-call-free
22
+ response (or max_iterations). Turns are the save/trace unit.
23
+ - **Steer** — a user message queued mid-turn, injected at consistency
24
+ points (loop top, turn end, run head). The `SteerQueue` (`steer.py`) owns
25
+ queueing semantics — id generation, FIFO, and the cancel boundary (an
26
+ item can be cancelled by id only until drained); the Agent owns drain
27
+ timing.
28
+ - **Compaction** — summarizing the old region of history into a synthetic
29
+ summary message, keeping the recent tail. Triggered automatically
30
+ (loop-top, before context overflow) or manually (`/compact`).
31
+ - **Attachment policy** — how a submitted attachment reaches the model
32
+ (`attachments.py`): images become multimodal blocks for vision models
33
+ (encoded per dialect, `llm/scaffold.py`), everything else degrades to
34
+ text — small files inline (≤50KB), the rest as path references.
35
+ - **Model switch** — `/model` mid-session (`model_switch.py`): validate the
36
+ target (API key, thinking-effort compatibility), swap the client
37
+ (converging on the latest config model), persist to config.toml. The UI
38
+ screen is an adapter translating verdicts into chat messages.
39
+
40
+ ## Safety
41
+
42
+ - **Fence** — the workdir boundary on file tools, widened by session-scoped
43
+ **grants** (paths a real user mentions in a submitted message).
@@ -0,0 +1,268 @@
1
+ Metadata-Version: 2.4
2
+ Name: limbo-code
3
+ Version: 0.1.10
4
+ Summary: A minimal terminal AI coding agent
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: httpx>=0.27
7
+ Requires-Dist: openai>=1.30
8
+ Requires-Dist: pydantic>=2.0
9
+ Requires-Dist: pygments>=2.17
10
+ Requires-Dist: textual>=8.0
11
+ Requires-Dist: toml>=0.10
12
+ Requires-Dist: tomlkit>=0.13
13
+ Provides-Extra: dev
14
+ Requires-Dist: mypy>=1.10; extra == 'dev'
15
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
16
+ Requires-Dist: pytest-textual-snapshot>=1.0; extra == 'dev'
17
+ Requires-Dist: pytest>=8.0; extra == 'dev'
18
+ Requires-Dist: respx>=0.21; extra == 'dev'
19
+ Requires-Dist: ruff>=0.4; extra == 'dev'
20
+ Requires-Dist: types-pygments>=2.17; extra == 'dev'
21
+ Description-Content-Type: text/markdown
22
+
23
+ # Limbo
24
+
25
+ A minimal terminal AI coding agent.
26
+
27
+ ## Install
28
+
29
+ Requires Python 3.11 or later.
30
+
31
+ ```bash
32
+ pip install -e .
33
+ ```
34
+
35
+ ## Configure
36
+
37
+ Create `~/.limbo/config.toml`:
38
+
39
+ ```toml
40
+ [llm]
41
+ api_key = "your-api-key"
42
+ model = "deepseek-chat"
43
+ base_url = "https://api.deepseek.com/v1"
44
+ ```
45
+
46
+ Limbo speaks to LLMs through a **provider/model catalog**
47
+ (`src/limbo/llm/catalog.py`). Each provider declares its API dialect,
48
+ endpoint, and credential env var; each model carries its own context window,
49
+ max output tokens, and thinking/reasoning behavior. A client factory
50
+ (`src/limbo/llm/factory.py`) picks the client implementation from the
51
+ provider's API dialect, so non-OpenAI dialects can be added without touching
52
+ call sites. Models not in the catalog fall back to generic
53
+ OpenAI-compatible defaults driven by `base_url` + `model` + `api_key`.
54
+
55
+ Built-in providers:
56
+
57
+ | Provider | API dialect | Endpoint | Key env var |
58
+ |----------|-------------|----------|-------------|
59
+ | `deepseek` | openai-completions | `https://api.deepseek.com/v1` | `DEEPSEEK_API_KEY` |
60
+ | `moonshotai` (Kimi) | openai-completions | `https://api.moonshot.ai/v1` | `MOONSHOT_API_KEY` |
61
+ | `kimi-coding` (Kimi For Coding) | anthropic-messages | `https://api.kimi.com/coding` | `KIMI_API_KEY` |
62
+ | `glm` (GLM Coding Plan) | openai-completions | `https://open.bigmodel.cn/api/coding/paas/v4` | `ZHIPUAI_API_KEY` |
63
+ | `codex` (OpenAI Codex) | openai-responses | `https://api.openai.com/v1` (override with your relay) | `CODEX_API_KEY` |
64
+
65
+ Built-in Codex models include `gpt-5.5`, `gpt-5.6-sol`/`terra`/`luna`,
66
+ `gpt-5.4`, `gpt-5.4-mini`, and `gpt-5.3-codex-spark`. **`gpt-5.5` is
67
+ verified by live test against the target relay** (its 1M context is
68
+ relay-reported); the other entries mirror pi's built-in catalog and are
69
+ unverified — availability depends on the relay in use, and unknown or
70
+ unsupported models fall back to the generic OpenAI-compatible defaults.
71
+ Codex speaks the
72
+ OpenAI **Responses API** (`POST {base_url}/responses`), served by a
73
+ dedicated client (`src/limbo/llm/responses_client.py`, plain httpx SSE):
74
+ system messages become `instructions`, tool definitions are flattened, and
75
+ reasoning models stream `reasoning` summaries and replay encrypted
76
+ reasoning items across turns. Limbo targets **API-key relays**, not the
77
+ ChatGPT subscription backend (OAuth) — point the provider at your relay
78
+ and make sure its model IDs match the catalog:
79
+
80
+ ```toml
81
+ [llm]
82
+ model = "gpt-5.5"
83
+
84
+ [providers.codex]
85
+ base_url = "https://my-relay.example.com/v1"
86
+ api_key_env = "CODEX_API_KEY"
87
+ ```
88
+
89
+ Built-in GLM Coding Plan models: `glm-4.7`, `glm-5.1`, `glm-5.2` (1M
90
+ context), `glm-5-turbo`, `glm-5v-turbo` (vision), and `glm-4.5-air`. The
91
+ Coding Plan is a subscription with its own endpoint and keys — a coding-plan
92
+ key only works on the `/api/coding/paas/v4` endpoints (not the pay-per-token
93
+ `/api/paas/v4` ones) and vice versa. For the international endpoint set
94
+ `[providers.glm] base_url = "https://api.z.ai/api/coding/paas/v4"` (see
95
+ below).
96
+
97
+ Built-in Kimi models include `kimi-k3` (1M context, pay-per-token), and for
98
+ Kimi For Coding subscriptions: `k3` (1M context), `kimi-for-coding`, and
99
+ `kimi-for-coding-highspeed` (256K context). The two use different endpoints
100
+ and keys — a `sk-kimi-*` Kimi For Coding key only works with the
101
+ `kimi-coding` models (`k3`, ...) and vice versa.
102
+
103
+ Switching to a catalog model only requires changing `model` — the provider's
104
+ endpoint and key env var are picked up automatically:
105
+
106
+ ```toml
107
+ [llm]
108
+ model = "k3" # Kimi For Coding; base_url/api_key resolve from the catalog
109
+ ```
110
+
111
+ The `anthropic-messages` dialect is served by a dedicated client
112
+ (`src/limbo/llm/anthropic_client.py`, plain httpx SSE) selected by the
113
+ client factory. It converts OpenAI-style tool definitions to Anthropic's
114
+ shape, merges consecutive tool results into a single user turn, and replays
115
+ assistant thinking blocks with their signature.
116
+
117
+ For the mainland-China Moonshot endpoint, set `base_url =
118
+ "https://api.moonshot.cn/v1"` explicitly (a configured `base_url` always
119
+ wins over the catalog — unless a `[providers.<id>]` override says otherwise,
120
+ see below).
121
+
122
+ ### Per-provider overrides
123
+
124
+ An optional `[providers.<id>]` section overrides a single catalog provider
125
+ without touching the global `[llm]` settings — e.g. pointing a provider at a
126
+ relay, renaming its credential env var, or adding extra headers:
127
+
128
+ ```toml
129
+ [providers.glm]
130
+ base_url = "https://api.z.ai/api/coding/paas/v4" # international endpoint
131
+
132
+ [providers.codex]
133
+ base_url = "https://my-relay.example.com/v1"
134
+ api_key_env = "CODEX_API_KEY" # rename the env var read for the key
135
+ headers = { x-relay = "on" } # extra headers on every request
136
+ ```
137
+
138
+ All fields are optional; unset fields fall back to the catalog. Resolution
139
+ order for `base_url` (first hit wins):
140
+
141
+ 1. `[providers.<id>] base_url`
142
+ 2. `[llm] base_url` (when changed from the DeepSeek default)
143
+ 3. the catalog provider's built-in endpoint
144
+
145
+ and for the API key: `[providers.<id>] api_key` → `[llm] api_key` → the
146
+ environment variable (`[providers.<id>] api_key_env` rename, else the
147
+ catalog's default env var).
148
+
149
+ > **Note:** rule 1 is the single exception to the long-standing "an explicit
150
+ > `[llm] base_url` always wins" behavior — a per-provider override is more
151
+ > specific than the global setting. If you configure both, the
152
+ > `[providers.<id>]` value is used for that provider's models.
153
+
154
+ ### Optional LLM settings
155
+
156
+ ```toml
157
+ [llm]
158
+ temperature = 0.2 # 0.0 - 2.0, default 0.2
159
+ max_iterations = 50 # safety limit on tool-turn loops, default 50
160
+ max_tokens = 8192 # output cap; default = the model's catalog value
161
+ thinking_effort = "high" # reasoning control; default = provider behavior
162
+ ```
163
+
164
+ `thinking_effort` is interpreted per model dialect:
165
+
166
+ - `k3`, `kimi-for-coding*` (Anthropic adaptive thinking): `low` | `high` |
167
+ `max` → `thinking: {type: adaptive}` + `output_config.effort`. Thinking
168
+ cannot be disabled; temperature is omitted while thinking is enabled.
169
+ - `kimi-k3` (moonshotai, OpenAI-style): `low` | `high` | `max` → sent as
170
+ `reasoning_effort`. Thinking cannot be disabled on K3.
171
+ - `kimi-k2-thinking`, `kimi-k2.5`+ (DeepSeek-style): any non-`off` value →
172
+ `thinking: {type: enabled}`; `"off"` → `thinking: {type: disabled}`
173
+ (except `kimi-k2.7-code*`, where thinking is always on).
174
+ - `glm-*` (z.ai-style): like DeepSeek-style but with `clear_thinking: false`
175
+ so thinking is preserved across turns; `glm-5.2` additionally maps
176
+ `low`/`high`/`max` to `reasoning_effort` (`low` clamps to `high`).
177
+ - `gpt-5.*` codex models (Responses API): `low` | `medium` | `high` |
178
+ `xhigh` → `reasoning: {effort, summary: auto}`; 5.6-generation models
179
+ (`gpt-5.6-*`) also accept `max`. Temperature is omitted for reasoning
180
+ models.
181
+ - Non-reasoning models: ignored.
182
+
183
+ Reasoning output streams into the chat as muted thinking blocks and is
184
+ stored on the assistant message so it can be replayed to APIs that require
185
+ it (Kimi K3 rejects tool-call replays without `reasoning_content`).
186
+
187
+ Optional tool settings:
188
+
189
+ ```toml
190
+ [tools]
191
+ bash_enabled = true
192
+ ```
193
+
194
+ ### Session storage
195
+
196
+ Conversations are saved as JSONL files in `~/.limbo/sessions/` so you can
197
+ review or debug them later. Use the `--session-dir` argument to redirect them
198
+ to another location.
199
+
200
+ Old session files are not automatically cleaned up; remove them manually when
201
+ you no longer need them.
202
+
203
+ ## Run
204
+
205
+ ```bash
206
+ limbo --workdir /path/to/project
207
+ limbo --model glm-4.7 # override the configured model for this run
208
+ ```
209
+
210
+ ### Switching models at runtime
211
+
212
+ Use `/model` in the TUI: without an argument it opens a picker that lists
213
+ catalog models grouped by provider (context window, reasoning capability,
214
+ and the current model are annotated; providers without a resolvable API key
215
+ are dimmed with a hint). `/model <name>` switches directly — unknown names
216
+ fall back to generic OpenAI-compatible defaults. The picker re-reads
217
+ `config.toml` every time it opens, so edits (e.g. a newly added
218
+ `[providers.<id>]`) apply without a restart.
219
+
220
+ A switch takes effect immediately (no restart): the LLM client is rebuilt,
221
+ and the new model is written back to `config.toml` (comments preserved via
222
+ tomlkit) so the next launch keeps it. If the write fails, the switch still
223
+ applies for the current session. Switching is refused while a turn is in
224
+ flight.
225
+
226
+ ## Safety
227
+
228
+ Limbo executes every tool call immediately, without asking for confirmation.
229
+
230
+ File tools (`read`, `edit`, `write`, `grep`, `find`, `ls`) are scoped to the
231
+ current working directory plus any session grants (paths the user mentions in
232
+ a message), and `read`/`grep`/`find` skip a small blocklist of sensitive files
233
+ (`.env`, SSH keys) to avoid leaking secrets into the model context by accident.
234
+ **These are convenience guardrails against accidents, not a security boundary.**
235
+ The path check resolves the path before each operation, so a symlink swapped
236
+ between the check and the operation (a time-of-check-to-time-of-use race) could
237
+ escape it.
238
+
239
+ Bash is an exception: it is started in the working directory but is **not**
240
+ covered by any of these guardrails. Commands can `cd ..`, use absolute paths,
241
+ and read or write outside the workdir. Commands that match dangerous patterns
242
+ such as `rm` or `git reset --hard` are **rejected outright** by a best-effort
243
+ heuristic filter — ordinary shell constructs can bypass it, so it only
244
+ protects against accidents. The pattern list is configurable but cannot be
245
+ disabled from the UI. Only run Limbo with trusted commands and in
246
+ repositories you can afford to modify or lose.
247
+
248
+ If you need to work with untrusted projects, disable the bash tool entirely:
249
+
250
+ ```toml
251
+ [tools]
252
+ bash_enabled = false
253
+ ```
254
+
255
+ ## Development
256
+
257
+ Run tests:
258
+
259
+ ```bash
260
+ pytest tests/ -v
261
+ ```
262
+
263
+ Run linting and type checks:
264
+
265
+ ```bash
266
+ ruff check src tests
267
+ mypy src
268
+ ```