limbo-code 0.2.1__tar.gz → 0.3__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 (307) hide show
  1. {limbo_code-0.2.1 → limbo_code-0.3}/AGENTS.md +22 -10
  2. {limbo_code-0.2.1 → limbo_code-0.3}/CONTEXT.md +16 -1
  3. limbo_code-0.3/PKG-INFO +245 -0
  4. limbo_code-0.3/README.md +231 -0
  5. limbo_code-0.3/docs/assets/limbo-hero.png +0 -0
  6. limbo_code-0.3/docs/assets/limbo-hero.svg +173 -0
  7. limbo_code-0.3/docs/assets/limbo-models.png +0 -0
  8. limbo_code-0.3/docs/assets/limbo-models.svg +173 -0
  9. {limbo_code-0.2.1 → limbo_code-0.3}/docs/session-management.md +43 -7
  10. limbo_code-0.3/scripts/readme_hero.py +146 -0
  11. {limbo_code-0.2.1 → limbo_code-0.3}/scripts/scroll_bench.py +3 -1
  12. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/agent.py +633 -141
  13. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/app.py +41 -4
  14. limbo_code-0.3/src/limbo/auth.py +100 -0
  15. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/btw.py +10 -6
  16. limbo_code-0.3/src/limbo/cli_auth.py +187 -0
  17. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/config.py +47 -4
  18. limbo_code-0.3/src/limbo/conversation.py +282 -0
  19. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/headless.py +275 -155
  20. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/history.py +61 -12
  21. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/integrations/__init__.py +15 -1
  22. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/integrations/base.py +33 -8
  23. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/integrations/herdr.py +3 -0
  24. limbo_code-0.3/src/limbo/integrations/osc7501.py +111 -0
  25. limbo_code-0.3/src/limbo/journal.py +301 -0
  26. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/llm/catalog.py +64 -0
  27. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/llm/responses_client.py +6 -0
  28. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/llm/retry.py +51 -16
  29. limbo_code-0.3/src/limbo/llm/verify.py +58 -0
  30. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/prompt.py +3 -0
  31. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/pump.py +93 -19
  32. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/sessions.py +242 -29
  33. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/steer.py +32 -7
  34. limbo_code-0.3/src/limbo/toggles.py +18 -0
  35. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/tools/base.py +9 -1
  36. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/tools/find.py +1 -0
  37. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/tools/grep.py +1 -0
  38. limbo_code-0.3/src/limbo/tools/hooks.py +248 -0
  39. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/tools/ls.py +1 -0
  40. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/tools/read.py +1 -0
  41. limbo_code-0.3/src/limbo/tools/registry.py +233 -0
  42. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/tools/run_code.py +66 -1
  43. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/trace.py +9 -2
  44. limbo_code-0.3/src/limbo/tracing/__init__.py +1 -0
  45. limbo_code-0.3/src/limbo/tracing/command.py +100 -0
  46. limbo_code-0.3/src/limbo/tracing/index.py +154 -0
  47. limbo_code-0.3/src/limbo/tracing/model.py +369 -0
  48. limbo_code-0.3/src/limbo/tracing/server.py +301 -0
  49. limbo_code-0.3/src/limbo/tracing/static/index.html +560 -0
  50. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/app.py +47 -3
  51. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/app.tcss +181 -61
  52. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/banner.py +24 -0
  53. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/contrast.py +11 -3
  54. limbo_code-0.3/src/limbo/ui/gradient.py +88 -0
  55. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/key_fixes.py +6 -0
  56. limbo_code-0.3/src/limbo/ui/motion.py +224 -0
  57. limbo_code-0.3/src/limbo/ui/omarchy.py +227 -0
  58. limbo_code-0.3/src/limbo/ui/recent_models.py +31 -0
  59. limbo_code-0.3/src/limbo/ui/screens/auth_dialog.py +105 -0
  60. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/screens/btw.py +6 -0
  61. limbo_code-0.3/src/limbo/ui/screens/command_palette.py +206 -0
  62. limbo_code-0.3/src/limbo/ui/screens/dialog.py +75 -0
  63. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/screens/main.py +481 -131
  64. limbo_code-0.3/src/limbo/ui/screens/model_picker.py +195 -0
  65. limbo_code-0.3/src/limbo/ui/screens/session_picker.py +61 -0
  66. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/syntax.py +67 -0
  67. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/theme.py +81 -3
  68. limbo_code-0.3/src/limbo/ui/widgets/activity.py +107 -0
  69. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/widgets/chat.py +52 -8
  70. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/widgets/input.py +14 -2
  71. limbo_code-0.3/src/limbo/ui/widgets/key_hints.py +62 -0
  72. limbo_code-0.3/src/limbo/ui/widgets/landing.py +180 -0
  73. limbo_code-0.3/src/limbo/ui/widgets/pills.py +64 -0
  74. limbo_code-0.3/src/limbo/ui/widgets/sidebar.py +342 -0
  75. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/widgets/status_bar.py +80 -21
  76. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/widgets/tool_card.py +87 -12
  77. {limbo_code-0.2.1 → limbo_code-0.3}/tests/conftest.py +37 -0
  78. limbo_code-0.3/tests/fixtures/omarchy/README.md +4 -0
  79. limbo_code-0.3/tests/fixtures/omarchy/catppuccin-latte.toml +31 -0
  80. limbo_code-0.3/tests/fixtures/omarchy/gruvbox.toml +31 -0
  81. limbo_code-0.3/tests/fixtures/omarchy/hackerman.toml +33 -0
  82. limbo_code-0.3/tests/fixtures/omarchy/lumon.toml +34 -0
  83. limbo_code-0.3/tests/fixtures/omarchy/rose-pine.toml +31 -0
  84. limbo_code-0.3/tests/fixtures/omarchy/tokyo-night.toml +31 -0
  85. limbo_code-0.3/tests/test_agent.py +870 -0
  86. limbo_code-0.3/tests/test_anthropic_client.py +149 -0
  87. limbo_code-0.3/tests/test_auth.py +46 -0
  88. limbo_code-0.3/tests/test_btw.py +42 -0
  89. limbo_code-0.3/tests/test_catalog.py +31 -0
  90. limbo_code-0.3/tests/test_cli.py +65 -0
  91. {limbo_code-0.2.1 → limbo_code-0.3}/tests/test_code_mode_agent.py +13 -55
  92. limbo_code-0.3/tests/test_compaction.py +69 -0
  93. limbo_code-0.3/tests/test_config.py +44 -0
  94. limbo_code-0.3/tests/test_conversation.py +105 -0
  95. limbo_code-0.3/tests/test_goal.py +43 -0
  96. limbo_code-0.3/tests/test_headless.py +191 -0
  97. limbo_code-0.3/tests/test_herdr.py +52 -0
  98. limbo_code-0.3/tests/test_history.py +79 -0
  99. limbo_code-0.3/tests/test_interrupt.py +159 -0
  100. limbo_code-0.3/tests/test_llm_client.py +85 -0
  101. limbo_code-0.3/tests/test_model_switch.py +56 -0
  102. limbo_code-0.3/tests/test_osc7501.py +48 -0
  103. limbo_code-0.3/tests/test_plan_mode.py +91 -0
  104. limbo_code-0.3/tests/test_prompt.py +27 -0
  105. limbo_code-0.3/tests/test_pump.py +224 -0
  106. limbo_code-0.3/tests/test_responses_client.py +154 -0
  107. limbo_code-0.3/tests/test_retry.py +111 -0
  108. limbo_code-0.3/tests/test_sessions.py +143 -0
  109. limbo_code-0.3/tests/test_skills.py +33 -0
  110. limbo_code-0.3/tests/test_sse.py +33 -0
  111. limbo_code-0.3/tests/test_steer.py +24 -0
  112. limbo_code-0.3/tests/test_trace.py +19 -0
  113. limbo_code-0.3/tests/test_usage.py +57 -0
  114. limbo_code-0.3/tests/tools/test_base.py +52 -0
  115. limbo_code-0.3/tests/tools/test_bash.py +38 -0
  116. limbo_code-0.3/tests/tools/test_edit.py +40 -0
  117. limbo_code-0.3/tests/tools/test_grep.py +25 -0
  118. limbo_code-0.3/tests/tools/test_hooks.py +134 -0
  119. limbo_code-0.3/tests/tools/test_ignore.py +20 -0
  120. {limbo_code-0.2.1 → limbo_code-0.3}/tests/tools/test_mutation_queue.py +0 -19
  121. limbo_code-0.3/tests/tools/test_read.py +29 -0
  122. limbo_code-0.3/tests/tools/test_registry.py +23 -0
  123. limbo_code-0.3/tests/tools/test_run_code.py +67 -0
  124. limbo_code-0.3/tests/tools/test_write.py +21 -0
  125. limbo_code-0.3/tests/tracing/test_command.py +54 -0
  126. limbo_code-0.3/tests/tracing/test_index.py +36 -0
  127. limbo_code-0.3/tests/tracing/test_model.py +139 -0
  128. limbo_code-0.3/tests/tracing/test_server.py +101 -0
  129. limbo_code-0.3/tests/ui/__snapshots__/test_snapshots/test_snapshot_idle.raw +211 -0
  130. limbo_code-0.3/tests/ui/__snapshots__/test_snapshots/test_snapshot_wide_layout_with_sidebar.raw +237 -0
  131. limbo_code-0.3/tests/ui/test_app_smoke.py +12 -0
  132. limbo_code-0.3/tests/ui/test_bang_ui.py +146 -0
  133. limbo_code-0.3/tests/ui/test_btw_ui.py +82 -0
  134. limbo_code-0.3/tests/ui/test_command_menu.py +63 -0
  135. limbo_code-0.3/tests/ui/test_compact_ui.py +63 -0
  136. limbo_code-0.3/tests/ui/test_goal_ui.py +117 -0
  137. limbo_code-0.3/tests/ui/test_input_paste.py +63 -0
  138. limbo_code-0.3/tests/ui/test_interrupt_ui.py +90 -0
  139. limbo_code-0.3/tests/ui/test_main_screen.py +49 -0
  140. limbo_code-0.3/tests/ui/test_model_picker.py +171 -0
  141. limbo_code-0.3/tests/ui/test_omarchy.py +49 -0
  142. limbo_code-0.3/tests/ui/test_path_input.py +96 -0
  143. limbo_code-0.3/tests/ui/test_scroll_follow.py +44 -0
  144. limbo_code-0.3/tests/ui/test_sessions_ui.py +82 -0
  145. limbo_code-0.3/tests/ui/test_sidebar.py +76 -0
  146. limbo_code-0.3/tests/ui/test_skills_ui.py +81 -0
  147. {limbo_code-0.2.1 → limbo_code-0.3}/tests/ui/test_snapshots.py +29 -60
  148. limbo_code-0.3/tests/ui/test_steer_ui.py +146 -0
  149. limbo_code-0.3/tests/ui/test_tracing_viewer_ui.py +50 -0
  150. limbo_code-0.3/tests/ui/test_widgets.py +38 -0
  151. limbo_code-0.2.1/PKG-INFO +0 -311
  152. limbo_code-0.2.1/README.md +0 -297
  153. limbo_code-0.2.1/src/limbo/tools/registry.py +0 -101
  154. limbo_code-0.2.1/src/limbo/ui/screens/model_picker.py +0 -122
  155. limbo_code-0.2.1/src/limbo/ui/screens/session_picker.py +0 -61
  156. limbo_code-0.2.1/tests/test_agent.py +0 -2397
  157. limbo_code-0.2.1/tests/test_anthropic_client.py +0 -644
  158. limbo_code-0.2.1/tests/test_attachments.py +0 -78
  159. limbo_code-0.2.1/tests/test_btw.py +0 -90
  160. limbo_code-0.2.1/tests/test_catalog.py +0 -372
  161. limbo_code-0.2.1/tests/test_cli.py +0 -296
  162. limbo_code-0.2.1/tests/test_clipboard.py +0 -205
  163. limbo_code-0.2.1/tests/test_code_mode.py +0 -77
  164. limbo_code-0.2.1/tests/test_compaction.py +0 -163
  165. limbo_code-0.2.1/tests/test_config.py +0 -293
  166. limbo_code-0.2.1/tests/test_goal.py +0 -228
  167. limbo_code-0.2.1/tests/test_headless.py +0 -578
  168. limbo_code-0.2.1/tests/test_herdr.py +0 -164
  169. limbo_code-0.2.1/tests/test_history.py +0 -94
  170. limbo_code-0.2.1/tests/test_integrations.py +0 -145
  171. limbo_code-0.2.1/tests/test_interrupt.py +0 -570
  172. limbo_code-0.2.1/tests/test_llm_client.py +0 -755
  173. limbo_code-0.2.1/tests/test_llm_scaffold.py +0 -89
  174. limbo_code-0.2.1/tests/test_model_switch.py +0 -140
  175. limbo_code-0.2.1/tests/test_models.py +0 -23
  176. limbo_code-0.2.1/tests/test_prompt.py +0 -103
  177. limbo_code-0.2.1/tests/test_pump.py +0 -423
  178. limbo_code-0.2.1/tests/test_responses_client.py +0 -626
  179. limbo_code-0.2.1/tests/test_retry.py +0 -306
  180. limbo_code-0.2.1/tests/test_sessions.py +0 -468
  181. limbo_code-0.2.1/tests/test_skills.py +0 -187
  182. limbo_code-0.2.1/tests/test_sse.py +0 -92
  183. limbo_code-0.2.1/tests/test_steer.py +0 -66
  184. limbo_code-0.2.1/tests/test_trace.py +0 -132
  185. limbo_code-0.2.1/tests/test_usage.py +0 -159
  186. limbo_code-0.2.1/tests/tools/test_base.py +0 -184
  187. limbo_code-0.2.1/tests/tools/test_bash.py +0 -263
  188. limbo_code-0.2.1/tests/tools/test_edit.py +0 -292
  189. limbo_code-0.2.1/tests/tools/test_find.py +0 -100
  190. limbo_code-0.2.1/tests/tools/test_grep.py +0 -117
  191. limbo_code-0.2.1/tests/tools/test_ignore.py +0 -53
  192. limbo_code-0.2.1/tests/tools/test_ls.py +0 -73
  193. limbo_code-0.2.1/tests/tools/test_read.py +0 -240
  194. limbo_code-0.2.1/tests/tools/test_registry.py +0 -38
  195. limbo_code-0.2.1/tests/tools/test_run_code.py +0 -277
  196. limbo_code-0.2.1/tests/tools/test_write.py +0 -62
  197. limbo_code-0.2.1/tests/ui/__snapshots__/test_snapshots/test_snapshot_idle.raw +0 -172
  198. limbo_code-0.2.1/tests/ui/__snapshots__/test_snapshots/test_snapshot_session_picker.raw +0 -168
  199. limbo_code-0.2.1/tests/ui/__snapshots__/test_snapshots/test_snapshot_thinking_and_running_tool.raw +0 -174
  200. limbo_code-0.2.1/tests/ui/__snapshots__/test_snapshots/test_snapshot_tool_error_and_error_message.raw +0 -175
  201. limbo_code-0.2.1/tests/ui/test_app_smoke.py +0 -36
  202. limbo_code-0.2.1/tests/ui/test_bang_ui.py +0 -357
  203. limbo_code-0.2.1/tests/ui/test_btw_ui.py +0 -158
  204. limbo_code-0.2.1/tests/ui/test_code_mode_ui.py +0 -122
  205. limbo_code-0.2.1/tests/ui/test_command_menu.py +0 -224
  206. limbo_code-0.2.1/tests/ui/test_commands.py +0 -104
  207. limbo_code-0.2.1/tests/ui/test_compact_ui.py +0 -188
  208. limbo_code-0.2.1/tests/ui/test_contrast.py +0 -41
  209. limbo_code-0.2.1/tests/ui/test_copy.py +0 -233
  210. limbo_code-0.2.1/tests/ui/test_game2048.py +0 -277
  211. limbo_code-0.2.1/tests/ui/test_goal_ui.py +0 -278
  212. limbo_code-0.2.1/tests/ui/test_input_attachments.py +0 -168
  213. limbo_code-0.2.1/tests/ui/test_input_history.py +0 -222
  214. limbo_code-0.2.1/tests/ui/test_input_paste.py +0 -314
  215. limbo_code-0.2.1/tests/ui/test_interrupt_ui.py +0 -241
  216. limbo_code-0.2.1/tests/ui/test_key_fixes.py +0 -38
  217. limbo_code-0.2.1/tests/ui/test_main_screen.py +0 -216
  218. limbo_code-0.2.1/tests/ui/test_model_picker.py +0 -393
  219. limbo_code-0.2.1/tests/ui/test_paste_store.py +0 -172
  220. limbo_code-0.2.1/tests/ui/test_path_input.py +0 -275
  221. limbo_code-0.2.1/tests/ui/test_scroll_follow.py +0 -85
  222. limbo_code-0.2.1/tests/ui/test_sessions_ui.py +0 -294
  223. limbo_code-0.2.1/tests/ui/test_skills_ui.py +0 -174
  224. limbo_code-0.2.1/tests/ui/test_startup_art.py +0 -137
  225. limbo_code-0.2.1/tests/ui/test_steer_ui.py +0 -539
  226. limbo_code-0.2.1/tests/ui/test_theme.py +0 -104
  227. limbo_code-0.2.1/tests/ui/test_widgets.py +0 -620
  228. {limbo_code-0.2.1 → limbo_code-0.3}/.agents/skills/grill-with-docs/SKILL.md +0 -0
  229. {limbo_code-0.2.1 → limbo_code-0.3}/.agents/skills/grill-with-docs/agents/openai.yaml +0 -0
  230. {limbo_code-0.2.1 → limbo_code-0.3}/.agents/skills/improve-codebase-architecture/HTML-REPORT.md +0 -0
  231. {limbo_code-0.2.1 → limbo_code-0.3}/.agents/skills/improve-codebase-architecture/SKILL.md +0 -0
  232. {limbo_code-0.2.1 → limbo_code-0.3}/.agents/skills/improve-codebase-architecture/agents/openai.yaml +0 -0
  233. {limbo_code-0.2.1 → limbo_code-0.3}/.agents/skills/tdd/SKILL.md +0 -0
  234. {limbo_code-0.2.1 → limbo_code-0.3}/.agents/skills/tdd/agents/openai.yaml +0 -0
  235. {limbo_code-0.2.1 → limbo_code-0.3}/.agents/skills/tdd/mocking.md +0 -0
  236. {limbo_code-0.2.1 → limbo_code-0.3}/.agents/skills/tdd/tests.md +0 -0
  237. {limbo_code-0.2.1 → limbo_code-0.3}/.github/workflows/publish.yml +0 -0
  238. {limbo_code-0.2.1 → limbo_code-0.3}/.github/workflows/test.yml +0 -0
  239. {limbo_code-0.2.1 → limbo_code-0.3}/.gitignore +0 -0
  240. {limbo_code-0.2.1 → limbo_code-0.3}/Makefile +0 -0
  241. {limbo_code-0.2.1 → limbo_code-0.3}/design/bang-verification.md +0 -0
  242. {limbo_code-0.2.1 → limbo_code-0.3}/design/confirm_view.html +0 -0
  243. {limbo_code-0.2.1 → limbo_code-0.3}/design/limbo-ui-minimal.md +0 -0
  244. {limbo_code-0.2.1 → limbo_code-0.3}/design/limbo-ui-redesign.md +0 -0
  245. {limbo_code-0.2.1 → limbo_code-0.3}/design/prototype-minimal-confirm.png +0 -0
  246. {limbo_code-0.2.1 → limbo_code-0.3}/design/prototype-minimal.html +0 -0
  247. {limbo_code-0.2.1 → limbo_code-0.3}/design/prototype-minimal.png +0 -0
  248. {limbo_code-0.2.1 → limbo_code-0.3}/design/prototype.html +0 -0
  249. {limbo_code-0.2.1 → limbo_code-0.3}/design/prototype.png +0 -0
  250. {limbo_code-0.2.1 → limbo_code-0.3}/design/rfc-bang-command.md +0 -0
  251. {limbo_code-0.2.1 → limbo_code-0.3}/design/rfc-headless-cli.md +0 -0
  252. {limbo_code-0.2.1 → limbo_code-0.3}/docs/assets/limbo-current-ui.png +0 -0
  253. {limbo_code-0.2.1 → limbo_code-0.3}/docs/assets/limbo-new-ui.png +0 -0
  254. {limbo_code-0.2.1 → limbo_code-0.3}/docs/assets/walkthrough/limbo-dark/1_idle.svg +0 -0
  255. {limbo_code-0.2.1 → limbo_code-0.3}/docs/assets/walkthrough/limbo-dark/2_thinking_tool_running.svg +0 -0
  256. {limbo_code-0.2.1 → limbo_code-0.3}/docs/assets/walkthrough/limbo-dark/3_tool_success.svg +0 -0
  257. {limbo_code-0.2.1 → limbo_code-0.3}/docs/assets/walkthrough/limbo-dark/4_tool_error.svg +0 -0
  258. {limbo_code-0.2.1 → limbo_code-0.3}/docs/assets/walkthrough/limbo-dark/5_llm_error.svg +0 -0
  259. {limbo_code-0.2.1 → limbo_code-0.3}/docs/assets/walkthrough/limbo-dark/6_edit_diff.svg +0 -0
  260. {limbo_code-0.2.1 → limbo_code-0.3}/docs/assets/walkthrough/limbo-light/1_idle.svg +0 -0
  261. {limbo_code-0.2.1 → limbo_code-0.3}/docs/assets/walkthrough/limbo-light/2_thinking_tool_running.svg +0 -0
  262. {limbo_code-0.2.1 → limbo_code-0.3}/docs/assets/walkthrough/limbo-light/3_tool_success.svg +0 -0
  263. {limbo_code-0.2.1 → limbo_code-0.3}/docs/assets/walkthrough/limbo-light/4_tool_error.svg +0 -0
  264. {limbo_code-0.2.1 → limbo_code-0.3}/docs/assets/walkthrough/limbo-light/5_llm_error.svg +0 -0
  265. {limbo_code-0.2.1 → limbo_code-0.3}/docs/assets/walkthrough/limbo-light/6_edit_diff.svg +0 -0
  266. {limbo_code-0.2.1 → limbo_code-0.3}/docs/skills.md +0 -0
  267. {limbo_code-0.2.1 → limbo_code-0.3}/docs/ui-redesign-proposal.md +0 -0
  268. {limbo_code-0.2.1 → limbo_code-0.3}/pyproject.toml +0 -0
  269. {limbo_code-0.2.1 → limbo_code-0.3}/scripts/check_contrast.py +0 -0
  270. {limbo_code-0.2.1 → limbo_code-0.3}/scripts/gen_banner.py +0 -0
  271. {limbo_code-0.2.1 → limbo_code-0.3}/scripts/ui_walkthrough.py +0 -0
  272. {limbo_code-0.2.1 → limbo_code-0.3}/skills-lock.json +0 -0
  273. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/__init__.py +0 -0
  274. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/__main__.py +0 -0
  275. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/attachments.py +0 -0
  276. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/code_mode.py +0 -0
  277. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/compaction.py +0 -0
  278. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/goal.py +0 -0
  279. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/llm/__init__.py +0 -0
  280. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/llm/anthropic_client.py +0 -0
  281. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/llm/client.py +0 -0
  282. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/llm/factory.py +0 -0
  283. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/llm/openai_client.py +0 -0
  284. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/llm/scaffold.py +0 -0
  285. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/llm/sse.py +0 -0
  286. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/llm/usage.py +0 -0
  287. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/model_switch.py +0 -0
  288. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/models.py +0 -0
  289. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/skills.py +0 -0
  290. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/tools/__init__.py +0 -0
  291. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/tools/bash.py +0 -0
  292. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/tools/edit.py +0 -0
  293. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/tools/ignore.py +0 -0
  294. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/tools/mutation_queue.py +0 -0
  295. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/tools/write.py +0 -0
  296. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/__init__.py +0 -0
  297. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/clipboard.py +0 -0
  298. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/commands.py +0 -0
  299. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/paste_store.py +0 -0
  300. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/path_input.py +0 -0
  301. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/screens/__init__.py +0 -0
  302. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/screens/game2048.py +0 -0
  303. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/screens/verify_picker.py +0 -0
  304. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/widgets/__init__.py +0 -0
  305. {limbo_code-0.2.1 → limbo_code-0.3}/src/limbo/ui/widgets/command_menu.py +0 -0
  306. {limbo_code-0.2.1 → limbo_code-0.3}/tests/test_integration.py +0 -0
  307. {limbo_code-0.2.1 → limbo_code-0.3}/uv.lock +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
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. A `--headless` flag runs a plain-terminal REPL frontend instead of the TUI (Herdr/script control, RFC LIM-60).
4
4
 
5
- **Tools execute immediately — no confirmation flow, no safety fences.** File tools are not scoped to the workdir (absolute paths and symlinks are honored as-is), there is no sensitive-file skip list, and `bash` runs every command as given (no dangerous-command filter).
5
+ **Tools execute immediately — no confirmation flow, no safety fences.** File tools are not scoped to the workdir (absolute paths and symlinks are honored as-is), there is no sensitive-file skip list, and `bash` runs every command as given (no dangerous-command filter). `ToolRegistry` runs a `before_tool`/`after_tool` hook chain (`admit()` then `run()`; the agent journals a call's intent only after `admit()` lets it through) (`tools/hooks.py`: `Block` / `Rewrite` / replace result; a raising hook blocks) that is empty by default; the only built-in hook is `/plan` (read-only plan mode: replay-safe tools and read-only bash only, also enforced on Code Mode sub-calls, persisted as `SessionMeta.plan_mode`).
6
6
 
7
7
  ## Quick Start
8
8
 
@@ -37,10 +37,19 @@ src/limbo/
37
37
  ├── headless.py # --headless frontend (RFC LIM-60): line-oriented REPL on the
38
38
  │ # primary screen for Herdr (agent prompt/send-keys/read/wait);
39
39
  │ # same TurnPump seam as the TUI, no textual import
40
- ├── sessions.py # Session JSONL save/load/list/export
40
+ ├── conversation.py # Conversation: the live agent+pump pair both frontends drive —
41
+ │ # new/switch/fork/handoff (load before close), lifecycle reporting
42
+ │ # (drive), /goal verify-proposal flow
43
+ ├── sessions.py # Session JSONL file format: records, save/load/list/export
44
+ ├── journal.py # SessionJournal: when/in what order records reach the file
45
+ │ # (watermark, rewrite fallback, queue re-journal, meta patches)
41
46
  ├── trace.py # Append-only JSONL run log (sessions/traces/)
47
+ ├── tracing/ # /tracing-viewer: loopback web viewer over trace files (stdlib
48
+ │ # http.server; index.py offsets, model.py tree, static/index.html)
42
49
  ├── integrations/ # External mux/orchestrator lifecycle reporters: base.py (protocol
43
- │ # + CompositeReporter + AgentState), herdr.py; create_reporters() fans out
50
+ │ # + CompositeReporter + AgentState + TurnOutcome), herdr.py,
51
+ │ # osc7501.py (terminal program status; TTY only, LIMBO_PROGRAM_STATUS=0
52
+ │ # opts out, tmux passthrough); create_reporters() fans out
44
53
  ├── skills.py # SKILL.md discovery (user + project dirs)
45
54
  ├── llm/ # catalog.py (provider/model specs), factory.py (dialect→client),
46
55
  │ # openai_client.py, anthropic_client.py, responses_client.py, retry.py, sse.py,
@@ -48,7 +57,7 @@ src/limbo/
48
57
  │ # scaffold.py (plumbing shared by dialect clients: credentials, retry, images)
49
58
  ├── tools/ # base.py (BaseTool + path resolution + truncation), registry.py (dispatch;
50
59
  │ # code_mode flag filters definitions() to [run_code], all_definitions() feeds
51
- │ # the SDK), mutation_queue.py (per-file locks), ignore.py (.gitignore),
60
+ │ # the SDK), hooks.py (before/after tool hook chain + plan mode), mutation_queue.py (per-file locks), ignore.py (.gitignore),
52
61
  │ # read/bash/edit/write/grep/find/ls/run_code.py (the Code Mode transport)
53
62
  └── ui/ # app.py + app.tcss (ALL styles here; theme vars only, no bare hex),
54
63
  # theme.py (limbo-dark/-light, RFC LIM-16), commands.py (slash registry
@@ -62,12 +71,13 @@ src/limbo/
62
71
  ## Agent Loop
63
72
 
64
73
  - `Agent.run()` yields `AgentEvent`: `TextDelta`, `ThinkingDelta`, `ToolCallRequest`, `ToolResultEvent`, `ErrorEvent`, `CompactionEvent`, `UsageUpdate`, `SteerEvent`
65
- - 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
74
+ - Loop top: auto-compaction check → steer drain → LLM call. A provider context-overflow error (`llm.retry.is_context_overflow`: 413 or known overflow texts, never rate limits) triggers `compact(trigger="overflow")` and one resend per turn; a second overflow or a failed compaction surfaces as `ErrorEvent`. Turn ends on a tool-call-free response; `max_iterations` (default 50) cancels pending calls with placeholder results
66
75
  - 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`
67
76
  - `finish_reason` `length`/`max_tokens` → whole batch failed without execution; the error result names the effective `max_tokens` and steers the model to write large scripts via `write` instead of re-issuing the same call. 3 consecutive length stops abort the turn with actionable guidance (trace `kind=length_stop_loop`); a text-only truncated response emits a "may be incomplete" warning
68
- - Mid-turn user input queues as *steer* (LIM-20): injected at loop top, or as turn-end follow-up
77
+ - Mid-turn user input queues as *steer* (LIM-20): injected at loop top, or as turn-end follow-up. Alt+Enter (headless: `/queue <text>`) queues a `follow_up` instead: only `TurnPump._supervise` drains it, after the whole task (goal loop included). Every queue change is journaled in the session file (`queue` records: `queued` immediately, `dequeued` after the message carrying it is saved), so queued messages survive a crash and are restored (and re-rendered) on resume
69
78
  - Reasoning is stored on assistant messages and replayed per dialect (Anthropic thinking signatures, Kimi `reasoning_content`, Responses encrypted items)
70
- - Usage counters normalize in `llm/usage.py` (`input_tokens`+`cache_read`, `prompt_tokens`, DeepSeek cache hits) and feed the compaction trigger via `PromptSizeEstimator`
79
+ - A retryable failure after the stream started (dropped connection, 5xx, overload) resends the same request up to `max_retries` times (`RetryEvent`; the partial response is discarded and the TUI strikes it through); failures before the first event are retried inside the client (`llm/retry.py`)
80
+ - Usage counters normalize in `llm/usage.py` (`input_tokens`+`cache_read`, `prompt_tokens`, DeepSeek cache hits) and feed the compaction trigger via `PromptSizeEstimator`; every billed call (turns, compaction summaries, `/btw`) is added to the per-`provider/model` ledger `SessionMeta.usage`, so the token counter survives resume. The Responses client sends `prompt_cache_key = session id`
71
81
 
72
82
  ## LLM & Config
73
83
 
@@ -79,17 +89,19 @@ Providers/models live in `llm/catalog.py`; unknown models get generic OpenAI-com
79
89
  [tools] # bash_enabled = true, parallel = true
80
90
  [compaction] # enabled, reserve_tokens = 16384, keep_recent_tokens = 20000
81
91
  [goal] # max_rounds = 10, verify_timeout_ms = 600000
92
+ [queue] # after_failed_run = "continue" | "keep" | "withdraw" (queued
93
+ # messages after an LLM-error turn; keep/withdraw end the task)
82
94
  [ui] # theme, show_banner
83
95
  [providers.<id>] # base_url / api_key / api_key_env / headers
84
96
  ```
85
97
 
86
- `/model` rebuilds the client mid-session (refused while busy) and persists via tomlkit. Sessions resume via `--continue` / `--resume` / `/sessions`; resume repairs dangling tool calls. 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`).
98
+ `/model` rebuilds the client mid-session (refused while busy) and persists via tomlkit. Sessions resume via `--continue` / `--resume` / `/sessions`; resume repairs dangling tool calls: each call commits a `tool_intent` record before it runs, so a crashed call is re-run on the next turn if its tool is `replay = "safe"` (read/grep/find/ls), otherwise marked "may have partially run" (intent committed) or "did not run" (no intent); legacy sessions without `meta.tool_intents` get the generic placeholder. Trace events: `session_start`, `user_message`, `llm_request` (full body), `llm_response`, `tool_call`, `tool_result`, `tool_recovery`, `compaction*`, `error`, `turn_end`. `/export [path]` merges meta + trace + message snapshot (Markdown if `.md`). `/fork [n]` copies the active history (first n user turns) into a new self-contained session, `/handoff <note>` starts a clean linked session from a note; both record `parent_session`/`fork_at` and leave the parent untouched. Code Mode sub-calls are traced as `tool_call`/`tool_result` with `parent_call_id`. `limbo --json` (implies `--headless`) prints every event as one JSON object per line.
87
99
 
88
100
  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]`.
89
101
 
90
102
  ## UI
91
103
 
92
- Single column: status bar (state/elapsed/tokens/model/workdir/queued + rainbow 🎯GOAL badge while a goal loop runs + ⌨code badge while Code Mode is on) → 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 /code-mode /help /2048 /goal` + skill commands. '/'-leading input that matches no command but looks like a path is sent as a normal message with existing files auto-attached (`ui/path_input.py`); a leading space forces plain text. After palette changes run `python scripts/check_contrast.py` (≥ 4.5:1).
104
+ Single column: status bar (state/elapsed/tokens/model/workdir/queued + rainbow 🎯GOAL badge while a goal loop runs + ⌨code badge while Code Mode is on) → scrolling chat flow (user `❯`, streaming Markdown, tool cards, errors) → input box (Enter submits, Shift+Enter newline, Alt+Enter queues a follow-up while busy (`ui/key_fixes.py` maps its modifyOtherKeys/kitty/ESC forms), paste markers, `ctrl+v` image attach) → hint line. Slash commands: `/sessions /new /fork /handoff /export /tracing-viewer /compact /model /code-mode /plan /help /2048 /goal` + skill commands. '/'-leading input that matches no command but looks like a path is sent as a normal message with existing files auto-attached (`ui/path_input.py`); a leading space forces plain text. After palette changes run `python scripts/check_contrast.py` (≥ 4.5:1).
93
105
 
94
106
  `/goal <text>` starts closed-loop mode (LIM-40): the first round is a proposal round — the model explores the repo and proposes acceptance command(s) in a `<verify_proposal>` block; the user confirms in a picker (accept / edit / skip), then the goal branch of the `TurnPump` (non-UI, shared by any frontend) runs each round as a complete `Agent.run()` turn and executes the verify command; exit 0 ends the loop, otherwise the failure output is fed back verbatim into the next round until `max_rounds`, when a no-tool wrap-up turn summarizes and any user message resumes with a fresh budget. Goal state persists on `SessionMeta.goal`. Esc during verify cancels the subprocess.
95
107
 
@@ -103,7 +115,7 @@ Single column: status bar (state/elapsed/tokens/model/workdir/queued + rainbow
103
115
  history — sub-calls never do. `SessionMeta.code_mode` persists the mode
104
116
  across resume
105
117
  - 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.**
106
- - Session files fully rewritten per save (atomic, 0600); trace holds full-fidelity record
118
+ - Session JSONL is checkpointed mid-turn (before/after each LLM request, after each tool batch): new messages are appended; full atomic rewrite (0600) only on first save, after a failed append, or when resume repaired dangling calls. Meta changes append `meta_patch` (a title change rewrites just the meta line via `rewrite_meta_line`, which keeps every other record). Compaction is non-destructive: a `compaction` record is appended, summarized messages stay in the file, and loading selects the active range from the latest record's `first_kept_entry_id`. Typed records `meta_patch` (merged over the meta line on load; goal state via `Agent.persist_meta()`) and `tool_intent` sit between message lines; unknown types are skipped. Trace holds the full-fidelity record
107
119
 
108
120
  ## Testing
109
121
 
@@ -19,7 +19,8 @@ proposals should name modules with these terms.
19
19
  ## Conversation
20
20
 
21
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.
22
+ response (or max_iterations). Turns are the trace unit; the session
23
+ file is checkpointed at every consistency point inside a turn.
23
24
  - **Turn pump** — the non-UI module that runs turns back-to-back
24
25
  (`pump.py`, `TurnPump`): pulls events out of the lazy `Agent.run()`
25
26
  stream, owns the single-flight busy flag (turns and `/compact`
@@ -28,6 +29,20 @@ proposals should name modules with these terms.
28
29
  verify/wrap-up window, teardown race — become one more turn); frontends
29
30
  never drain. Frontends are adapters translating its events into UI
30
31
  updates.
32
+ - **Conversation** — the live session a frontend drives
33
+ (`conversation.py`): the current Agent + Turn pump pair, session
34
+ switching (`/new`, `/sessions`, `/fork`, `/handoff`; the target loads
35
+ before the current session closes), lifecycle reporting around each
36
+ run, and the `/goal` verify-proposal flow. The TUI and headless
37
+ frontends are its adapters; busy is the Turn pump's flag.
38
+ - **Session journal** — the write side of the session file
39
+ (`journal.py`, `SessionJournal`): which in-memory messages are already
40
+ on disk, when a checkpoint appends vs rewrites, and the record order
41
+ that keeps a crash recoverable (summarized messages before their
42
+ compaction record, `dequeued` after the message carrying the item). The
43
+ Agent calls its verbs (`checkpoint`, `queued`/`dequeued`,
44
+ `record_intents`, `compacted`, `patch_meta`); `sessions.py` owns the
45
+ file format and loading.
31
46
  - **Steer** — a user message queued mid-turn, injected at consistency
32
47
  points (loop top, turn end, run head). The `SteerQueue` (`steer.py`) owns
33
48
  queueing semantics — id generation, FIFO, and the cancel boundary (an
@@ -0,0 +1,245 @@
1
+ Metadata-Version: 2.5
2
+ Name: limbo-code
3
+ Version: 0.3
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
+ Description-Content-Type: text/markdown
14
+
15
+ # Limbo
16
+
17
+ **A terminal AI coding agent that is small enough to understand and sturdy
18
+ enough to trust with long work.**
19
+
20
+ Tell Limbo what you want in plain language. It explores your repository, reads
21
+ and edits files, runs commands, and streams its reasoning and every tool call
22
+ as it goes. When the work runs long, Limbo keeps it safe: every turn is
23
+ journaled to disk, a crash or a closed terminal picks up where it stopped, and
24
+ you can branch, hand off, or replay any session.
25
+
26
+ ![Limbo](docs/assets/limbo-hero.png)
27
+
28
+ ## Who it's for
29
+
30
+ - **Developers who live in the terminal** and want an agent that fits next to
31
+ their editor, multiplexer, and dotfiles instead of a separate app.
32
+ - **People who use more than one model.** DeepSeek, Kimi, GLM, OpenAI Codex, or
33
+ any OpenAI-compatible endpoint, switchable mid-conversation.
34
+ - **Anyone who wants to see what the agent did.** Every request, tool call, and
35
+ token is recorded and browsable; nothing happens off-screen.
36
+ - **Hackers who want to read the code.** Seven tools, one agent loop, plain
37
+ JSONL on disk. `AGENTS.md` explains every design decision.
38
+
39
+ ## What you get
40
+
41
+ ### Work that survives
42
+
43
+ - **Durable sessions.** Messages, tool-call intents, queued follow-ups, and
44
+ compactions are appended to a JSONL journal as they happen. Kill the process
45
+ mid-turn and resume: read-only calls that were interrupted are re-run,
46
+ calls that may have had side effects are flagged so the model checks before
47
+ retrying, and nothing you queued is lost.
48
+ - **Resume, branch, hand off.** `limbo --continue` or `/sessions` to pick up,
49
+ `/fork` to try another direction without touching the original, `/handoff`
50
+ to start a clean context with a written brief that links back.
51
+ - **Long runs stay alive.** Automatic compaction summarizes old turns when the
52
+ context fills up, while the full record stays in the journal.
53
+
54
+ ### You stay in control
55
+
56
+ - **`/plan`** puts the agent in read-only mode: it explores and proposes a
57
+ step-by-step plan, while writes, edits, and mutating shell commands are
58
+ blocked until you say `/plan off`.
59
+ - **`/goal <task>`** closes the loop: the model proposes a verify command, you
60
+ confirm it, and Limbo iterates until it passes.
61
+ - **Steer while it works.** Type while a turn is running to redirect it;
62
+ `Alt+Enter` queues a follow-up for after the task; `Esc` interrupts.
63
+ - **Side channels.** `/btw <question>` asks something quick without polluting
64
+ the history; `!<command>` runs a shell command yourself, never sent to the
65
+ model.
66
+
67
+ ### Any model, set up in seconds
68
+
69
+ - **Guided setup.** On first run with no key, Limbo asks for one and saves it
70
+ to `~/.limbo/auth.json` (readable only by you). `limbo auth login` verifies a
71
+ key against the provider before saving; `limbo models` lists the catalog and
72
+ which models are ready to use.
73
+ - **Switch without restarting.** `Ctrl+L` (or `/model`) opens a searchable
74
+ picker with recent models first. Pick a model whose provider has no key yet
75
+ and it asks for one inline. `Shift+Tab` steps through thinking levels.
76
+ - **Three wire formats** (OpenAI completions, Anthropic messages, OpenAI
77
+ responses) behind one catalog, with per-provider overrides for relays,
78
+ renamed key variables, and extra headers.
79
+
80
+ ![The Ctrl+L model picker](docs/assets/limbo-models.png)
81
+
82
+ ### Fast turns
83
+
84
+ - Tool calls issued together run concurrently; edits to the same file are
85
+ serialized.
86
+ - **Code Mode** (`/code-mode`) collapses the tool catalog into a single
87
+ `run_code` tool: the model writes one Python program against a generated SDK,
88
+ turning many round trips into one.
89
+
90
+ ### Nothing hidden
91
+
92
+ - **`/tracing-viewer`** opens a local web page with a live view of every LLM
93
+ request, tool call, and token count in the session.
94
+ - **`/export`** dumps the session as a full JSONL log (request bodies, usage,
95
+ tool results, errors) or as readable Markdown.
96
+
97
+ ### A TUI worth living in
98
+
99
+ - **Limbo Dusk**, the default theme, with `limbo-dark` and `limbo-light`
100
+ alternatives, all checked for contrast.
101
+ - **Follows Omarchy.** On an Omarchy desktop Limbo picks up the system theme
102
+ and switches live when you change it.
103
+ - **Limbo Motion.** Subtle animation that tells the story from unsettled to
104
+ settled. Set `[ui] motion = "reduced"` or `"off"` if you prefer stillness
105
+ (SSH sessions default to reduced).
106
+ - `Ctrl+P` command palette, image and file paste with `Ctrl+V`, collapsible
107
+ tool output with `Ctrl+O`.
108
+
109
+ ### Scriptable
110
+
111
+ `limbo --headless` gives the same agent as a line-oriented REPL, and
112
+ `limbo --json` prints one JSON event per line so scripts can drive it without
113
+ parsing the screen. Inside the [Herdr](https://herdr.dev) multiplexer, Limbo
114
+ reports its working, idle, and blocked state to the pane.
115
+
116
+ ## Quick start
117
+
118
+ Requires Python 3.11 or later.
119
+
120
+ ```bash
121
+ pip install limbo-code
122
+ cd ~/code/my-project
123
+ limbo # first run asks for an API key
124
+ ```
125
+
126
+ Prefer to set up keys first:
127
+
128
+ ```bash
129
+ limbo auth login # choose a provider, paste a key, verified and saved
130
+ limbo auth list # where each provider's key comes from
131
+ limbo models # what you can run right now
132
+ limbo -m glm-4.7 # one run on a different model
133
+ limbo -m kimi-k3:high # ...with a thinking level
134
+ ```
135
+
136
+ Then just describe the task: *"find why the login test is flaky and fix it"*.
137
+
138
+ ## Everyday use
139
+
140
+ | Key | Action |
141
+ |-----|--------|
142
+ | `Enter` / `Shift+Enter` | send / new line |
143
+ | `Alt+Enter` | queue a follow-up for after the current task |
144
+ | `Esc` | interrupt |
145
+ | `Ctrl+L` | model picker |
146
+ | `Shift+Tab` | cycle thinking level |
147
+ | `Ctrl+P` | command palette |
148
+ | `Ctrl+S` | sessions |
149
+ | `Ctrl+O` | expand or collapse tool output |
150
+
151
+ | Command | What it does |
152
+ |---------|--------------|
153
+ | `/model [name]` | switch model (picker when bare); persists to config |
154
+ | `/plan [on\|off]` | read-only mode: explore and propose, mutations blocked |
155
+ | `/goal <task>` | iterate until a confirmed verify command passes |
156
+ | `/code-mode [on\|off]` | one `run_code` tool: a Python program, many tool calls |
157
+ | `/btw <question>` | side question that stays out of history |
158
+ | `/compact` | compact the conversation now |
159
+ | `/sessions` `/new` | switch to a past session, or start fresh |
160
+ | `/fork [n]` | branch into a new session (optionally keep only the first n turns) |
161
+ | `/handoff <brief>` | clean context seeded with a brief, linked to this session |
162
+ | `/export [path]` | full JSONL log, or Markdown when the path ends in `.md` |
163
+ | `/tracing-viewer [id\|off]` | live trace of this session in your browser |
164
+ | `!<command>` | run a shell command yourself, outside the conversation |
165
+
166
+ From the shell: `limbo --continue` resumes the latest session for the current
167
+ directory, `limbo --resume <id-prefix>` resumes any session.
168
+
169
+ ## Models and providers
170
+
171
+ | Provider | Wire format | Endpoint |
172
+ |----------|-------------|----------|
173
+ | `deepseek` | openai-completions | `https://api.deepseek.com/v1` |
174
+ | `moonshotai` (Kimi) | openai-completions | `https://api.moonshot.ai/v1` |
175
+ | `kimi-coding` | anthropic-messages | `https://api.kimi.com/coding` |
176
+ | `glm` (GLM Coding Plan) | openai-completions | `https://open.bigmodel.cn/api/coding/paas/v4` |
177
+ | `codex` (OpenAI Codex) | openai-responses | `https://api.openai.com/v1` (relay expected) |
178
+
179
+ Unknown model ids fall back to generic OpenAI-compatible defaults, and the
180
+ model picker can connect any OpenAI-compatible endpoint. Everything can also
181
+ live in `~/.limbo/config.toml`:
182
+
183
+ ```toml
184
+ [llm]
185
+ model = "deepseek-v4-pro" # endpoint and key variable come from the catalog
186
+
187
+ [providers.codex] # override one provider only
188
+ base_url = "https://my-relay.example.com/v1"
189
+ api_key_env = "CODEX_API_KEY"
190
+ headers = { x-relay = "on" }
191
+
192
+ [ui]
193
+ theme = "auto" # Omarchy if installed, else limbo-dusk
194
+ motion = "full" # full | reduced | off
195
+ ```
196
+
197
+ ## Make it yours
198
+
199
+ - **Project instructions.** Limbo reads `AGENTS.md` from the working directory
200
+ and `~/.limbo/AGENTS.md` into its system prompt.
201
+ - **Skills.** Drop a `SKILL.md` into `<project>/.agents/skills/<name>/` or
202
+ `~/.limbo/skills/<name>/`. The model loads it when the task matches, or you
203
+ call it as `/<name>`. See [docs/skills.md](docs/skills.md).
204
+
205
+ ## Safety
206
+
207
+ **There are no fences.** Tools run immediately and unconfined: file tools
208
+ honor absolute paths and symlinks anywhere, there is no sensitive-file
209
+ blocklist, and `bash` runs every command as written, `rm -rf` included. Use
210
+ `/plan` when you only want the agent to look, run Limbo with trusted models,
211
+ and keep it in repositories you can afford to modify or lose.
212
+
213
+ ```toml
214
+ [tools]
215
+ bash_enabled = false # if you need a hard stop
216
+ ```
217
+
218
+ ## Development
219
+
220
+ ```bash
221
+ uv sync # dev tools are a PEP 735 group, installed by default
222
+ make check # sync + tests + lint + typecheck
223
+ ```
224
+
225
+ Individually, always `python -m pytest`, never `uv run pytest` (which can fall
226
+ through to a global pytest on `PATH`):
227
+
228
+ ```bash
229
+ uv run python -m pytest tests/ -v
230
+ uv run ruff check src tests
231
+ uv run mypy src
232
+ ```
233
+
234
+ The screenshot above is rendered from the real UI by
235
+ `scripts/readme_hero.py` (`uv run python scripts/readme_hero.py`), so it can't
236
+ drift from what ships.
237
+
238
+ ## Learn more
239
+
240
+ - [`AGENTS.md`](AGENTS.md): architecture and design decisions
241
+ - [`CONTEXT.md`](CONTEXT.md): domain glossary
242
+ - [`docs/session-management.md`](docs/session-management.md): sessions,
243
+ recovery, fork and handoff, tracing viewer
244
+ - [`docs/skills.md`](docs/skills.md): writing skills
245
+ - [`design/`](design/): RFCs for headless mode, bang commands, and the UI
@@ -0,0 +1,231 @@
1
+ # Limbo
2
+
3
+ **A terminal AI coding agent that is small enough to understand and sturdy
4
+ enough to trust with long work.**
5
+
6
+ Tell Limbo what you want in plain language. It explores your repository, reads
7
+ and edits files, runs commands, and streams its reasoning and every tool call
8
+ as it goes. When the work runs long, Limbo keeps it safe: every turn is
9
+ journaled to disk, a crash or a closed terminal picks up where it stopped, and
10
+ you can branch, hand off, or replay any session.
11
+
12
+ ![Limbo](docs/assets/limbo-hero.png)
13
+
14
+ ## Who it's for
15
+
16
+ - **Developers who live in the terminal** and want an agent that fits next to
17
+ their editor, multiplexer, and dotfiles instead of a separate app.
18
+ - **People who use more than one model.** DeepSeek, Kimi, GLM, OpenAI Codex, or
19
+ any OpenAI-compatible endpoint, switchable mid-conversation.
20
+ - **Anyone who wants to see what the agent did.** Every request, tool call, and
21
+ token is recorded and browsable; nothing happens off-screen.
22
+ - **Hackers who want to read the code.** Seven tools, one agent loop, plain
23
+ JSONL on disk. `AGENTS.md` explains every design decision.
24
+
25
+ ## What you get
26
+
27
+ ### Work that survives
28
+
29
+ - **Durable sessions.** Messages, tool-call intents, queued follow-ups, and
30
+ compactions are appended to a JSONL journal as they happen. Kill the process
31
+ mid-turn and resume: read-only calls that were interrupted are re-run,
32
+ calls that may have had side effects are flagged so the model checks before
33
+ retrying, and nothing you queued is lost.
34
+ - **Resume, branch, hand off.** `limbo --continue` or `/sessions` to pick up,
35
+ `/fork` to try another direction without touching the original, `/handoff`
36
+ to start a clean context with a written brief that links back.
37
+ - **Long runs stay alive.** Automatic compaction summarizes old turns when the
38
+ context fills up, while the full record stays in the journal.
39
+
40
+ ### You stay in control
41
+
42
+ - **`/plan`** puts the agent in read-only mode: it explores and proposes a
43
+ step-by-step plan, while writes, edits, and mutating shell commands are
44
+ blocked until you say `/plan off`.
45
+ - **`/goal <task>`** closes the loop: the model proposes a verify command, you
46
+ confirm it, and Limbo iterates until it passes.
47
+ - **Steer while it works.** Type while a turn is running to redirect it;
48
+ `Alt+Enter` queues a follow-up for after the task; `Esc` interrupts.
49
+ - **Side channels.** `/btw <question>` asks something quick without polluting
50
+ the history; `!<command>` runs a shell command yourself, never sent to the
51
+ model.
52
+
53
+ ### Any model, set up in seconds
54
+
55
+ - **Guided setup.** On first run with no key, Limbo asks for one and saves it
56
+ to `~/.limbo/auth.json` (readable only by you). `limbo auth login` verifies a
57
+ key against the provider before saving; `limbo models` lists the catalog and
58
+ which models are ready to use.
59
+ - **Switch without restarting.** `Ctrl+L` (or `/model`) opens a searchable
60
+ picker with recent models first. Pick a model whose provider has no key yet
61
+ and it asks for one inline. `Shift+Tab` steps through thinking levels.
62
+ - **Three wire formats** (OpenAI completions, Anthropic messages, OpenAI
63
+ responses) behind one catalog, with per-provider overrides for relays,
64
+ renamed key variables, and extra headers.
65
+
66
+ ![The Ctrl+L model picker](docs/assets/limbo-models.png)
67
+
68
+ ### Fast turns
69
+
70
+ - Tool calls issued together run concurrently; edits to the same file are
71
+ serialized.
72
+ - **Code Mode** (`/code-mode`) collapses the tool catalog into a single
73
+ `run_code` tool: the model writes one Python program against a generated SDK,
74
+ turning many round trips into one.
75
+
76
+ ### Nothing hidden
77
+
78
+ - **`/tracing-viewer`** opens a local web page with a live view of every LLM
79
+ request, tool call, and token count in the session.
80
+ - **`/export`** dumps the session as a full JSONL log (request bodies, usage,
81
+ tool results, errors) or as readable Markdown.
82
+
83
+ ### A TUI worth living in
84
+
85
+ - **Limbo Dusk**, the default theme, with `limbo-dark` and `limbo-light`
86
+ alternatives, all checked for contrast.
87
+ - **Follows Omarchy.** On an Omarchy desktop Limbo picks up the system theme
88
+ and switches live when you change it.
89
+ - **Limbo Motion.** Subtle animation that tells the story from unsettled to
90
+ settled. Set `[ui] motion = "reduced"` or `"off"` if you prefer stillness
91
+ (SSH sessions default to reduced).
92
+ - `Ctrl+P` command palette, image and file paste with `Ctrl+V`, collapsible
93
+ tool output with `Ctrl+O`.
94
+
95
+ ### Scriptable
96
+
97
+ `limbo --headless` gives the same agent as a line-oriented REPL, and
98
+ `limbo --json` prints one JSON event per line so scripts can drive it without
99
+ parsing the screen. Inside the [Herdr](https://herdr.dev) multiplexer, Limbo
100
+ reports its working, idle, and blocked state to the pane.
101
+
102
+ ## Quick start
103
+
104
+ Requires Python 3.11 or later.
105
+
106
+ ```bash
107
+ pip install limbo-code
108
+ cd ~/code/my-project
109
+ limbo # first run asks for an API key
110
+ ```
111
+
112
+ Prefer to set up keys first:
113
+
114
+ ```bash
115
+ limbo auth login # choose a provider, paste a key, verified and saved
116
+ limbo auth list # where each provider's key comes from
117
+ limbo models # what you can run right now
118
+ limbo -m glm-4.7 # one run on a different model
119
+ limbo -m kimi-k3:high # ...with a thinking level
120
+ ```
121
+
122
+ Then just describe the task: *"find why the login test is flaky and fix it"*.
123
+
124
+ ## Everyday use
125
+
126
+ | Key | Action |
127
+ |-----|--------|
128
+ | `Enter` / `Shift+Enter` | send / new line |
129
+ | `Alt+Enter` | queue a follow-up for after the current task |
130
+ | `Esc` | interrupt |
131
+ | `Ctrl+L` | model picker |
132
+ | `Shift+Tab` | cycle thinking level |
133
+ | `Ctrl+P` | command palette |
134
+ | `Ctrl+S` | sessions |
135
+ | `Ctrl+O` | expand or collapse tool output |
136
+
137
+ | Command | What it does |
138
+ |---------|--------------|
139
+ | `/model [name]` | switch model (picker when bare); persists to config |
140
+ | `/plan [on\|off]` | read-only mode: explore and propose, mutations blocked |
141
+ | `/goal <task>` | iterate until a confirmed verify command passes |
142
+ | `/code-mode [on\|off]` | one `run_code` tool: a Python program, many tool calls |
143
+ | `/btw <question>` | side question that stays out of history |
144
+ | `/compact` | compact the conversation now |
145
+ | `/sessions` `/new` | switch to a past session, or start fresh |
146
+ | `/fork [n]` | branch into a new session (optionally keep only the first n turns) |
147
+ | `/handoff <brief>` | clean context seeded with a brief, linked to this session |
148
+ | `/export [path]` | full JSONL log, or Markdown when the path ends in `.md` |
149
+ | `/tracing-viewer [id\|off]` | live trace of this session in your browser |
150
+ | `!<command>` | run a shell command yourself, outside the conversation |
151
+
152
+ From the shell: `limbo --continue` resumes the latest session for the current
153
+ directory, `limbo --resume <id-prefix>` resumes any session.
154
+
155
+ ## Models and providers
156
+
157
+ | Provider | Wire format | Endpoint |
158
+ |----------|-------------|----------|
159
+ | `deepseek` | openai-completions | `https://api.deepseek.com/v1` |
160
+ | `moonshotai` (Kimi) | openai-completions | `https://api.moonshot.ai/v1` |
161
+ | `kimi-coding` | anthropic-messages | `https://api.kimi.com/coding` |
162
+ | `glm` (GLM Coding Plan) | openai-completions | `https://open.bigmodel.cn/api/coding/paas/v4` |
163
+ | `codex` (OpenAI Codex) | openai-responses | `https://api.openai.com/v1` (relay expected) |
164
+
165
+ Unknown model ids fall back to generic OpenAI-compatible defaults, and the
166
+ model picker can connect any OpenAI-compatible endpoint. Everything can also
167
+ live in `~/.limbo/config.toml`:
168
+
169
+ ```toml
170
+ [llm]
171
+ model = "deepseek-v4-pro" # endpoint and key variable come from the catalog
172
+
173
+ [providers.codex] # override one provider only
174
+ base_url = "https://my-relay.example.com/v1"
175
+ api_key_env = "CODEX_API_KEY"
176
+ headers = { x-relay = "on" }
177
+
178
+ [ui]
179
+ theme = "auto" # Omarchy if installed, else limbo-dusk
180
+ motion = "full" # full | reduced | off
181
+ ```
182
+
183
+ ## Make it yours
184
+
185
+ - **Project instructions.** Limbo reads `AGENTS.md` from the working directory
186
+ and `~/.limbo/AGENTS.md` into its system prompt.
187
+ - **Skills.** Drop a `SKILL.md` into `<project>/.agents/skills/<name>/` or
188
+ `~/.limbo/skills/<name>/`. The model loads it when the task matches, or you
189
+ call it as `/<name>`. See [docs/skills.md](docs/skills.md).
190
+
191
+ ## Safety
192
+
193
+ **There are no fences.** Tools run immediately and unconfined: file tools
194
+ honor absolute paths and symlinks anywhere, there is no sensitive-file
195
+ blocklist, and `bash` runs every command as written, `rm -rf` included. Use
196
+ `/plan` when you only want the agent to look, run Limbo with trusted models,
197
+ and keep it in repositories you can afford to modify or lose.
198
+
199
+ ```toml
200
+ [tools]
201
+ bash_enabled = false # if you need a hard stop
202
+ ```
203
+
204
+ ## Development
205
+
206
+ ```bash
207
+ uv sync # dev tools are a PEP 735 group, installed by default
208
+ make check # sync + tests + lint + typecheck
209
+ ```
210
+
211
+ Individually, always `python -m pytest`, never `uv run pytest` (which can fall
212
+ through to a global pytest on `PATH`):
213
+
214
+ ```bash
215
+ uv run python -m pytest tests/ -v
216
+ uv run ruff check src tests
217
+ uv run mypy src
218
+ ```
219
+
220
+ The screenshot above is rendered from the real UI by
221
+ `scripts/readme_hero.py` (`uv run python scripts/readme_hero.py`), so it can't
222
+ drift from what ships.
223
+
224
+ ## Learn more
225
+
226
+ - [`AGENTS.md`](AGENTS.md): architecture and design decisions
227
+ - [`CONTEXT.md`](CONTEXT.md): domain glossary
228
+ - [`docs/session-management.md`](docs/session-management.md): sessions,
229
+ recovery, fork and handoff, tracing viewer
230
+ - [`docs/skills.md`](docs/skills.md): writing skills
231
+ - [`design/`](design/): RFCs for headless mode, bang commands, and the UI