pal-code 0.1.0__tar.gz → 0.1.2__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 (296) hide show
  1. {pal_code-0.1.0 → pal_code-0.1.2}/LICENSE +1 -1
  2. {pal_code-0.1.0 → pal_code-0.1.2}/PKG-INFO +2 -2
  3. {pal_code-0.1.0 → pal_code-0.1.2}/pyproject.toml +2 -2
  4. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/__init__.py +1 -1
  5. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/cli.py +5 -2
  6. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/compression/components/__init__.py +7 -7
  7. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/compression/utils/__init__.py +5 -5
  8. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/context_manager/utils/relevance.py +1 -1
  9. pal_code-0.1.2/src/pal/context_window.py +42 -0
  10. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/local_llm/components/huggingface_local.py +1 -1
  11. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/storage_manager/components/vector_store_backend.py +1 -1
  12. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/studio/routes.py +2 -2
  13. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/studio/runs.py +21 -2
  14. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/bench.py +8 -1
  15. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/cli.py +111 -20
  16. pal_code-0.1.2/src/pal/terminal/logo.py +140 -0
  17. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/session.py +13 -0
  18. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/setup.py +104 -3
  19. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/ui.py +53 -29
  20. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/tool_manager/components/tool_registry.py +1 -1
  21. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/web/app.py +6 -0
  22. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/web/static/studio.css +2 -0
  23. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/web/templates/index.html +14 -6
  24. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/web/templates/studio.html +2 -2
  25. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal_code.egg-info/PKG-INFO +2 -2
  26. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal_code.egg-info/SOURCES.txt +1 -0
  27. pal_code-0.1.0/src/pal/terminal/logo.py +0 -117
  28. {pal_code-0.1.0 → pal_code-0.1.2}/README.md +0 -0
  29. {pal_code-0.1.0 → pal_code-0.1.2}/setup.cfg +0 -0
  30. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/__main__.py +0 -0
  31. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/access_manager/__init__.py +0 -0
  32. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/access_manager/components/__init__.py +0 -0
  33. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/access_manager/components/access_control.py +0 -0
  34. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/access_manager/components/base.py +0 -0
  35. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/access_manager/components/intervention.py +0 -0
  36. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/access_manager/components/privilege_registry.py +0 -0
  37. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/access_manager/pipeline.py +0 -0
  38. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/access_manager/utils/__init__.py +0 -0
  39. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/agent_constraints/__init__.py +0 -0
  40. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/agent_constraints/components/AGENT_OUTPUT.md +0 -0
  41. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/agent_constraints/components/__init__.py +0 -0
  42. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/agent_constraints/components/constraints.py +0 -0
  43. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/agent_constraints/pipeline.py +0 -0
  44. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/agent_constraints/utils/__init__.py +0 -0
  45. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/agents/__init__.py +0 -0
  46. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/agents/coding.py +0 -0
  47. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/agents/docs.py +0 -0
  48. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/chat/__init__.py +0 -0
  49. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/chat/demo.py +0 -0
  50. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/chat/providers.py +0 -0
  51. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/chat/service.py +0 -0
  52. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/chat/services/__init__.py +0 -0
  53. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/chat/services/base.py +0 -0
  54. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/chat/services/data.py +0 -0
  55. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/chat/services/media.py +0 -0
  56. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/chat/services/web.py +0 -0
  57. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/chat/sessions.py +0 -0
  58. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/chat/tools.py +0 -0
  59. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/chat/uploads.py +0 -0
  60. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/compression/__init__.py +0 -0
  61. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/compression/components/adverbs.py +0 -0
  62. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/compression/components/hedges.py +0 -0
  63. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/compression/components/interjections.py +0 -0
  64. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/compression/components/minifier.py +0 -0
  65. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/compression/components/output_compressor.py +0 -0
  66. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/compression/components/pleonasms.py +0 -0
  67. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/compression/components/punctuations.py +0 -0
  68. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/compression/components/stopwords.py +0 -0
  69. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/compression/history.py +0 -0
  70. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/compression/pipeline.py +0 -0
  71. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/compression/utils/negations.py +0 -0
  72. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/compression/utils/nltk_setup.py +0 -0
  73. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/compression/utils/pronouns.py +0 -0
  74. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/compression/utils/proselint_setup.py +0 -0
  75. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/compression/utils/spacy_setup.py +0 -0
  76. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/config/default.yaml +0 -0
  77. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/config.py +0 -0
  78. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/context_manager/__init__.py +0 -0
  79. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/context_manager/chat_context.py +0 -0
  80. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/context_manager/components/__init__.py +0 -0
  81. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/context_manager/components/assembler.py +0 -0
  82. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/context_manager/components/context_cache.py +0 -0
  83. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/context_manager/components/kv_bridge.py +0 -0
  84. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/context_manager/components/pager.py +0 -0
  85. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/context_manager/components/simple_context_manager.py +0 -0
  86. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/context_manager/components/turns.py +0 -0
  87. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/context_manager/pipeline.py +0 -0
  88. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/context_manager/utils/__init__.py +0 -0
  89. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/context_manager/utils/hashing.py +0 -0
  90. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/context_manager/utils/storage.py +0 -0
  91. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/documents/__init__.py +0 -0
  92. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/documents/components/__init__.py +0 -0
  93. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/documents/components/chunker.py +0 -0
  94. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/documents/components/index.py +0 -0
  95. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/documents/utils/__init__.py +0 -0
  96. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/errors.py +0 -0
  97. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/input_converters/__init__.py +0 -0
  98. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/input_converters/components/__init__.py +0 -0
  99. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/input_converters/components/base.py +0 -0
  100. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/input_converters/components/pandoc_converter.py +0 -0
  101. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/input_converters/components/pdf_converter.py +0 -0
  102. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/input_converters/components/pptx_converter.py +0 -0
  103. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/input_converters/components/xlsx_converter.py +0 -0
  104. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/input_converters/pipeline.py +0 -0
  105. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/input_converters/utils/__init__.py +0 -0
  106. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/input_converters/workspace.py +0 -0
  107. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/input_savers/INPUT_RULES.md +0 -0
  108. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/input_savers/__init__.py +0 -0
  109. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/input_savers/components/__init__.py +0 -0
  110. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/input_savers/components/base.py +0 -0
  111. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/input_savers/components/methods.py +0 -0
  112. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/input_savers/components/verifier.py +0 -0
  113. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/input_savers/pipeline.py +0 -0
  114. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/input_savers/utils/__init__.py +0 -0
  115. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/input_savers/utils/guards.py +0 -0
  116. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/knowledge_graph/__init__.py +0 -0
  117. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/knowledge_graph/components/__init__.py +0 -0
  118. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/knowledge_graph/components/base.py +0 -0
  119. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/knowledge_graph/components/builder.py +0 -0
  120. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/knowledge_graph/components/retriever.py +0 -0
  121. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/knowledge_graph/components/workspace.py +0 -0
  122. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/knowledge_graph/pipeline.py +0 -0
  123. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/knowledge_graph/utils/__init__.py +0 -0
  124. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/latency_tracker/__init__.py +0 -0
  125. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/latency_tracker/pipeline.py +0 -0
  126. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/llm_scheduler/__init__.py +0 -0
  127. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/llm_scheduler/api.py +0 -0
  128. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/llm_scheduler/code.py +0 -0
  129. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/llm_scheduler/core.py +0 -0
  130. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/llm_scheduler/local.py +0 -0
  131. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/llm_scheduler/pipeline.py +0 -0
  132. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/llm_scheduler/types.py +0 -0
  133. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/llm_wrapper/__init__.py +0 -0
  134. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/llm_wrapper/adapter.py +0 -0
  135. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/llm_wrapper/components/__init__.py +0 -0
  136. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/llm_wrapper/components/adapter_types.py +0 -0
  137. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/llm_wrapper/components/base.py +0 -0
  138. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/llm_wrapper/components/features.py +0 -0
  139. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/llm_wrapper/components/litellm_wrapper.py +0 -0
  140. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/llm_wrapper/components/openrouter_wrapper.py +0 -0
  141. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/llm_wrapper/modes.py +0 -0
  142. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/llm_wrapper/pipeline.py +0 -0
  143. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/llm_wrapper/utils/__init__.py +0 -0
  144. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/llm_wrapper/utils/registry.py +0 -0
  145. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/llm_wrapper/utils/token_counting.py +0 -0
  146. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/local_llm/__init__.py +0 -0
  147. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/local_llm/components/__init__.py +0 -0
  148. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/local_llm/components/local_llm.py +0 -0
  149. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/logging_setup.py +0 -0
  150. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/memory/__init__.py +0 -0
  151. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/memory/components/__init__.py +0 -0
  152. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/memory/components/lru_k.py +0 -0
  153. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/memory/components/manager.py +0 -0
  154. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/memory/components/note.py +0 -0
  155. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/memory/components/storage_manager.py +0 -0
  156. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/memory/pipeline.py +0 -0
  157. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/memory/utils/__init__.py +0 -0
  158. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/memory/utils/keyword_overlap.py +0 -0
  159. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/memory_tracker/__init__.py +0 -0
  160. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/memory_tracker/components/__init__.py +0 -0
  161. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/memory_tracker/components/process.py +0 -0
  162. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/memory_tracker/pipeline.py +0 -0
  163. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/metrics/__init__.py +0 -0
  164. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/metrics/components/__init__.py +0 -0
  165. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/metrics/components/collector.py +0 -0
  166. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/metrics/components/exporter.py +0 -0
  167. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/metrics/pipeline.py +0 -0
  168. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/metrics/utils/__init__.py +0 -0
  169. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/notes_manager/__init__.py +0 -0
  170. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/notes_manager/components/__init__.py +0 -0
  171. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/notes_manager/components/privacy.py +0 -0
  172. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/notes_manager/components/recall.py +0 -0
  173. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/notes_manager/components/tidy.py +0 -0
  174. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/notes_manager/pipeline.py +0 -0
  175. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/orchestrator/__init__.py +0 -0
  176. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/orchestrator/components/__init__.py +0 -0
  177. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/orchestrator/components/agent.py +0 -0
  178. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/orchestrator/components/cgroup_manager.py +0 -0
  179. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/orchestrator/components/context_interrupt.py +0 -0
  180. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/orchestrator/components/limits.py +0 -0
  181. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/orchestrator/components/scheduler.py +0 -0
  182. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/orchestrator/components/syscall_scheduler.py +0 -0
  183. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/orchestrator/pipeline.py +0 -0
  184. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/orchestrator/utils/__init__.py +0 -0
  185. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/orchestrator/utils/device.py +0 -0
  186. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/orchestrator/utils/protocol.py +0 -0
  187. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/os_kernel/__init__.py +0 -0
  188. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/os_kernel/filesystem.py +0 -0
  189. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/os_kernel/network.py +0 -0
  190. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/os_kernel/pipeline.py +0 -0
  191. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/os_kernel/sysinfo.py +0 -0
  192. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/paths.py +0 -0
  193. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/pipeline.py +0 -0
  194. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/py.typed +0 -0
  195. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/redact.py +0 -0
  196. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/sdk/__init__.py +0 -0
  197. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/sdk/adapters.py +0 -0
  198. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/sdk/agent.py +0 -0
  199. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/sdk/components/__init__.py +0 -0
  200. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/sdk/components/autogen_adapter.py +0 -0
  201. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/sdk/components/autogen_client.py +0 -0
  202. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/sdk/components/framework_adapter_base.py +0 -0
  203. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/sdk/components/metagpt_adapter.py +0 -0
  204. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/sdk/components/open_interpreter_adapter.py +0 -0
  205. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/sdk/components/tool_catalog.py +0 -0
  206. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/sdk/os_api.py +0 -0
  207. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/sdk/patterns.py +0 -0
  208. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/sdk/pipeline.py +0 -0
  209. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/sdk/queries.py +0 -0
  210. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/sdk/savers.py +0 -0
  211. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/sdk/utils/__init__.py +0 -0
  212. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/storage_manager/__init__.py +0 -0
  213. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/storage_manager/components/__init__.py +0 -0
  214. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/storage_manager/components/base.py +0 -0
  215. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/storage_manager/components/local_file_backend.py +0 -0
  216. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/storage_manager/components/storage_queue.py +0 -0
  217. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/storage_manager/pipeline.py +0 -0
  218. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/storage_manager/utils/__init__.py +0 -0
  219. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/storage_manager/utils/memory_adapter.py +0 -0
  220. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/storage_manager/utils/sdk.py +0 -0
  221. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/studio/__init__.py +0 -0
  222. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/studio/builder.py +0 -0
  223. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/studio/catalog.py +0 -0
  224. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/studio/checks.py +0 -0
  225. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/studio/clients.py +0 -0
  226. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/studio/editor.py +0 -0
  227. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/studio/files.py +0 -0
  228. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/studio/models.py +0 -0
  229. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/studio/plan.py +0 -0
  230. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/studio/planner.py +0 -0
  231. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/studio/policy.py +0 -0
  232. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/task_router/__init__.py +0 -0
  233. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/task_router/components/__init__.py +0 -0
  234. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/task_router/components/answerer.py +0 -0
  235. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/task_router/components/base.py +0 -0
  236. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/task_router/components/classifier.py +0 -0
  237. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/task_router/components/gates.py +0 -0
  238. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/task_router/pipeline.py +0 -0
  239. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/task_router/utils/__init__.py +0 -0
  240. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/__init__.py +0 -0
  241. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/backend.py +0 -0
  242. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/clients.py +0 -0
  243. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/commands.py +0 -0
  244. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/decide.py +0 -0
  245. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/files.py +0 -0
  246. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/flow_cli.py +0 -0
  247. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/hardware.py +0 -0
  248. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/history.py +0 -0
  249. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/keys.py +0 -0
  250. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/local_models.py +0 -0
  251. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/models.py +0 -0
  252. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/policy.py +0 -0
  253. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/project.py +0 -0
  254. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/review.py +0 -0
  255. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/security.py +0 -0
  256. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/syntax.py +0 -0
  257. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/theme.py +0 -0
  258. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/voice.py +0 -0
  259. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/terminal/workflow.py +0 -0
  260. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/token_tracker/__init__.py +0 -0
  261. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/token_tracker/pipeline.py +0 -0
  262. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/tokens.py +0 -0
  263. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/tool_catalog.py +0 -0
  264. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/tool_manager/__init__.py +0 -0
  265. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/tool_manager/builtin.py +0 -0
  266. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/tool_manager/components/__init__.py +0 -0
  267. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/tool_manager/components/base.py +0 -0
  268. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/tool_manager/components/conflict_resolver.py +0 -0
  269. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/tool_manager/components/param_validator.py +0 -0
  270. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/tool_manager/components/tool_call_queue.py +0 -0
  271. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/tool_manager/pipeline.py +0 -0
  272. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/tool_manager/utils/__init__.py +0 -0
  273. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/web/__init__.py +0 -0
  274. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/web/static/editor.js +0 -0
  275. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/web/static/fonts/LICENSE-Inter.txt +0 -0
  276. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/web/static/fonts/inter-400.woff2 +0 -0
  277. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/web/static/fonts/inter-500.woff2 +0 -0
  278. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/web/static/fonts/inter-600.woff2 +0 -0
  279. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/web/static/fonts/inter-700.woff2 +0 -0
  280. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/web/static/inter.css +0 -0
  281. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/web/static/pal-logo-256.png +0 -0
  282. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/web/static/pal-logo-32.png +0 -0
  283. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/web/static/pal-logo-64.png +0 -0
  284. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/web/static/studio.js +0 -0
  285. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal/web/static/voice.js +0 -0
  286. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal_code.egg-info/dependency_links.txt +0 -0
  287. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal_code.egg-info/entry_points.txt +0 -0
  288. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal_code.egg-info/requires.txt +0 -0
  289. {pal_code-0.1.0 → pal_code-0.1.2}/src/pal_code.egg-info/top_level.txt +0 -0
  290. {pal_code-0.1.0 → pal_code-0.1.2}/tests/test_cli.py +0 -0
  291. {pal_code-0.1.0 → pal_code-0.1.2}/tests/test_config.py +0 -0
  292. {pal_code-0.1.0 → pal_code-0.1.2}/tests/test_lockfile.py +0 -0
  293. {pal_code-0.1.0 → pal_code-0.1.2}/tests/test_paths.py +0 -0
  294. {pal_code-0.1.0 → pal_code-0.1.2}/tests/test_redact.py +0 -0
  295. {pal_code-0.1.0 → pal_code-0.1.2}/tests/test_run_script.py +0 -0
  296. {pal_code-0.1.0 → pal_code-0.1.2}/tests/test_tool_catalog.py +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Nandini Singh
3
+ Copyright (c) 2026 Vrushal More
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pal-code
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Pal: build and plan with AI in your terminal (pal cli) or browser (pal web), on a layer that cuts tokens and latency.
5
- Author: Nandini Singh
5
+ Author: Vrushal More
6
6
  License: MIT
7
7
  Keywords: ai,coding-assistant,cli,llm,ollama,tokens,code-graph
8
8
  Classifier: Development Status :: 4 - Beta
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pal-code"
7
- version = "0.1.0"
7
+ version = "0.1.2"
8
8
  description = "Pal: build and plan with AI in your terminal (pal cli) or browser (pal web), on a layer that cuts tokens and latency."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
11
11
  license = { text = "MIT" }
12
- authors = [{ name = "Nandini Singh" }]
12
+ authors = [{ name = "Vrushal More" }]
13
13
  keywords = ["ai", "coding-assistant", "cli", "llm", "ollama", "tokens", "code-graph"]
14
14
  classifiers = [
15
15
  "Development Status :: 4 - Beta",
@@ -13,7 +13,7 @@ def _version() -> str:
13
13
  return version(name)
14
14
  except PackageNotFoundError:
15
15
  continue
16
- return "0.1.0" # a source checkout that is not installed
16
+ return "0.1.2" # a source checkout that is not installed
17
17
 
18
18
 
19
19
  __version__ = _version()
@@ -212,7 +212,7 @@ def load_dotenv(path: str | os.PathLike | None = None, env=None) -> list[str]:
212
212
  return loaded
213
213
 
214
214
 
215
- def _serve(settings: Settings, open_browser: bool = False, page: str = "", graph=None, documents: list | None = None) -> int:
215
+ def _serve(settings: Settings, open_browser: bool = False, page: str = "", graph=None, documents: list | None = None, project_dir=None) -> int:
216
216
  from pal.chat import ChatService
217
217
  from pal.web import create_app
218
218
 
@@ -221,7 +221,7 @@ def _serve(settings: Settings, open_browser: bool = False, page: str = "", graph
221
221
  service = ChatService(settings, graph=graph) if graph is not None else ChatService(settings)
222
222
  if documents:
223
223
  service.preload_docs = list(documents)
224
- app = create_app(service)
224
+ app = create_app(service, studio={"project_dir": project_dir}) if project_dir else create_app(service)
225
225
  if s.host not in _LOOPBACK:
226
226
  print(f"WARNING: listening on {s.host}. Pal has no login: anyone who can reach this port can use "
227
227
  "your API keys' quota and read stored memory. Put it behind an authenticating reverse proxy.",
@@ -239,6 +239,9 @@ def _serve(settings: Settings, open_browser: bool = False, page: str = "", graph
239
239
  "the app still works and sends everything to the API model.", file=sys.stderr)
240
240
  else:
241
241
  print(f"Local model: {status['detail']}")
242
+ import threading as _t
243
+ from pal.context_window import context_window
244
+ _t.Thread(target=lambda: context_window("openai", "gpt-4o"), daemon=True).start() # loads LiteLLM's model list now, not on the first page load
242
245
  if open_browser:
243
246
  import threading
244
247
  import webbrowser
@@ -16,37 +16,37 @@ TextMinifier itself can't be built for the same reason.
16
16
 
17
17
  try:
18
18
  from pal.compression.components.adverbs import Adverbs
19
- except ImportError:
19
+ except Exception: # missing OR broken (e.g. a torch DLL error)
20
20
  Adverbs = None
21
21
 
22
22
  try:
23
23
  from pal.compression.components.hedges import Hedges
24
- except ImportError:
24
+ except Exception: # missing OR broken (e.g. a torch DLL error)
25
25
  Hedges = None
26
26
 
27
27
  try:
28
28
  from pal.compression.components.interjections import Interjections
29
- except ImportError:
29
+ except Exception: # missing OR broken (e.g. a torch DLL error)
30
30
  Interjections = None
31
31
 
32
32
  try:
33
33
  from pal.compression.components.pleonasms import Pleonasms
34
- except ImportError:
34
+ except Exception: # missing OR broken (e.g. a torch DLL error)
35
35
  Pleonasms = None
36
36
 
37
37
  try:
38
38
  from pal.compression.components.punctuations import Punctuations
39
- except ImportError:
39
+ except Exception: # missing OR broken (e.g. a torch DLL error)
40
40
  Punctuations = None
41
41
 
42
42
  try:
43
43
  from pal.compression.components.stopwords import Stopwords
44
- except ImportError:
44
+ except Exception: # missing OR broken (e.g. a torch DLL error)
45
45
  Stopwords = None
46
46
 
47
47
  try:
48
48
  from pal.compression.components.minifier import TextMinifier
49
- except ImportError:
49
+ except Exception: # missing OR broken (e.g. a torch DLL error)
50
50
  TextMinifier = None
51
51
 
52
52
  # No optional deps -- always available.
@@ -4,27 +4,27 @@ raise straight out of this package's __init__."""
4
4
 
5
5
  try:
6
6
  from pal.compression.utils.negations import Negations
7
- except ImportError:
7
+ except Exception: # missing OR broken (e.g. a torch DLL error)
8
8
  Negations = None
9
9
 
10
10
  try:
11
11
  from pal.compression.utils.nltk_setup import NltkSetup
12
- except ImportError:
12
+ except Exception: # missing OR broken (e.g. a torch DLL error)
13
13
  NltkSetup = None
14
14
 
15
15
  try:
16
16
  from pal.compression.utils.proselint_setup import ProselintSetup
17
- except ImportError:
17
+ except Exception: # missing OR broken (e.g. a torch DLL error)
18
18
  ProselintSetup = None
19
19
 
20
20
  try:
21
21
  from pal.compression.utils.pronouns import Pronouns
22
- except ImportError:
22
+ except Exception: # missing OR broken (e.g. a torch DLL error)
23
23
  Pronouns = None
24
24
 
25
25
  try:
26
26
  from pal.compression.utils.spacy_setup import SpacySetup
27
- except ImportError:
27
+ except Exception: # missing OR broken (e.g. a torch DLL error)
28
28
  SpacySetup = None
29
29
 
30
30
  __all__ = ["Negations", "NltkSetup", "ProselintSetup", "Pronouns", "SpacySetup"]
@@ -6,7 +6,7 @@ try:
6
6
  from sentence_transformers import SentenceTransformer
7
7
  _EMBEDDER = None
8
8
  _EMBED_AVAILABLE = True
9
- except ImportError:
9
+ except Exception: # missing OR broken (e.g. a torch DLL error)
10
10
  _EMBED_AVAILABLE = False
11
11
 
12
12
 
@@ -0,0 +1,42 @@
1
+ """How many tokens a model can take in one request (its context window), for the "tokens left" counters in the terminal and the web page.
2
+
3
+ API models: the size LiteLLM knows for the model, else a safe default. Local models: the context Pal gives Ollama (num_ctx), which is what
4
+ really limits them here, whatever the model could do on paper."""
5
+ from __future__ import annotations
6
+
7
+ from functools import lru_cache
8
+
9
+ LOCAL_CONTEXT = 8192 # Pal runs local models with num_ctx 8192 (pal.terminal.backend.make_core)
10
+ API_DEFAULT = 128_000 # a model LiteLLM does not know: most current API models take at least this
11
+ LOCAL_PROVIDERS = ("local", "ollama", "lmstudio", "vllm", "llamacpp", "llama.cpp", "huggingface")
12
+
13
+
14
+ @lru_cache(maxsize=256)
15
+ def context_window(provider: str, model: str) -> int:
16
+ provider = (provider or "").lower()
17
+ if provider in LOCAL_PROVIDERS:
18
+ return LOCAL_CONTEXT
19
+ if not model:
20
+ return API_DEFAULT
21
+ try:
22
+ import litellm
23
+ for name in (f"{provider}/{model}" if provider else model, model, model.split("/")[-1]):
24
+ try:
25
+ info = litellm.get_model_info(name)
26
+ except Exception: # noqa: BLE001 (not in LiteLLM's table under this name)
27
+ continue
28
+ size = info.get("max_input_tokens") or info.get("max_tokens")
29
+ if size:
30
+ return int(size)
31
+ except Exception: # noqa: BLE001 (LiteLLM missing or broken: the default)
32
+ pass
33
+ return API_DEFAULT
34
+
35
+
36
+ def tokens_left(window: int, last_prompt: int) -> int:
37
+ """What is left of the window after the last request's input (the next one is usually at least as big)."""
38
+ return max(0, window - max(0, last_prompt))
39
+
40
+
41
+ def short(n: int) -> str:
42
+ return f"{n / 1000:.1f}k".replace(".0k", "k") if n >= 10_000 else f"{n:,}"
@@ -68,7 +68,7 @@ class HuggingFaceLocalLLM(LLMWrapper):
68
68
  try:
69
69
  import torch
70
70
  from transformers import AutoModelForCausalLM, AutoTokenizer
71
- except ImportError as e:
71
+ except (ImportError, OSError) as e:
72
72
  raise ImportError(
73
73
  "transformers and torch are required for the in-process local "
74
74
  "core. Run: pip install torch transformers"
@@ -19,7 +19,7 @@ log = logging.getLogger(__name__)
19
19
  try:
20
20
  import chromadb
21
21
  _CHROMADB_AVAILABLE = True
22
- except ImportError:
22
+ except Exception: # missing OR broken (e.g. a torch DLL error)
23
23
  _CHROMADB_AVAILABLE = False
24
24
 
25
25
 
@@ -49,7 +49,7 @@ def clean_references(raw) -> list[dict]:
49
49
 
50
50
 
51
51
  def register_studio(app, service, *, planner_core: Callable | None = None, core_factory: Callable | None = None, discover_fn: Callable | None = None,
52
- hot_turns: int | None = None) -> RunStore:
52
+ hot_turns: int | None = None, project_dir: Path | None = None) -> RunStore:
53
53
  """Add the Studio page and endpoints to the app. The three callables are for tests: the planner's model, the builder's model, local-model discovery."""
54
54
  cfg = service.settings
55
55
  home = Path(cfg.storage_dir).expanduser() if cfg.storage_dir else data_dir()
@@ -71,7 +71,7 @@ def register_studio(app, service, *, planner_core: Callable | None = None, core_
71
71
  raise UserInputError("no local model is chosen: start Ollama and pull a model, or switch to an API model")
72
72
  return local_core(m["model"], num_ctx())
73
73
 
74
- runs = RunStore(home / "studio", core_factory or default_core_factory, max_runs=cfg.studio.max_runs, hot_turns=hot_turns)
74
+ runs = RunStore(home / "studio", core_factory or default_core_factory, max_runs=cfg.studio.max_runs, hot_turns=hot_turns, project_dir=project_dir)
75
75
  app.extensions["studio_runs"] = runs
76
76
 
77
77
  def local() -> dict:
@@ -95,9 +95,22 @@ class Run:
95
95
  return self.arms[name]
96
96
 
97
97
 
98
+ def build_target(project: Path, folder: str) -> Path:
99
+ """Where a build is written in the folder Pal was started in: the folder itself when it is empty, else a new subfolder named after the
100
+ product (with -2, -3 ... when that name is taken), so nothing that is already there is overwritten."""
101
+ project = Path(project)
102
+ if not any(p for p in project.iterdir() if not p.name.startswith(".")):
103
+ return project
104
+ target, n = project / folder, 2
105
+ while target.exists() and (not target.is_dir() or any(target.iterdir())):
106
+ target, n = project / f"{folder}-{n}", n + 1
107
+ return target
108
+
109
+
98
110
  class RunStore:
99
- def __init__(self, root: Path, core_factory: Callable, max_runs: int = 6, hot_turns: int | None = None):
111
+ def __init__(self, root: Path, core_factory: Callable, max_runs: int = 6, hot_turns: int | None = None, project_dir: Path | None = None):
100
112
  self.root, self.core_factory, self.max_runs, self.hot_turns = Path(root), core_factory, max_runs, hot_turns
113
+ self.project_dir = Path(project_dir).resolve() if project_dir else None # pal web's folder: the build is written there
101
114
  self._runs: dict[str, Run] = {}
102
115
  self._editors: dict[tuple[str, str], EditorSession] = {}
103
116
  self._lock = threading.Lock()
@@ -140,11 +153,17 @@ class RunStore:
140
153
  break
141
154
  policy = Policy(dict(plan["permissions"]))
142
155
  save = plan["permissions"].get("write_project")
143
- state = ArmState(arm, label, (self.root / run.id / arm / plan["product"]["folder"]) if save else None)
156
+ where = None
157
+ if save:
158
+ where = (build_target(self.project_dir, plan["product"]["folder"]) if self.project_dir and arm != "baseline"
159
+ else self.root / run.id / arm / plan["product"]["folder"]) # the plain client's comparison copy stays in Pal's folder
160
+ state = ArmState(arm, label, where)
144
161
  run.arms[arm] = state
145
162
  if state.workspace:
146
163
  state.workspace.mkdir(parents=True, exist_ok=True)
147
164
  run.emit("arm_start", f"{label}: starting" if twice else "Starting", arm=arm)
165
+ if state.workspace and self.project_dir and arm != "baseline":
166
+ run.emit("saved_to", f"Files are written to {state.workspace}", arm=arm, path=str(state.workspace))
148
167
  client: PlainClient | PalClient
149
168
  if arm == "baseline":
150
169
  client = PlainClient(core, SYSTEM)
@@ -67,6 +67,7 @@ class BenchResult:
67
67
  plain: Arm
68
68
  pal: Arm
69
69
  when: float = field(default_factory=time.time)
70
+ pal_files: dict = field(default_factory=dict, repr=False) # what Pal's way wrote: {path: bytes}, offered to keep in your folder
70
71
 
71
72
  @property
72
73
  def saved(self) -> int:
@@ -78,6 +79,7 @@ class BenchResult:
78
79
 
79
80
  def to_dict(self) -> dict:
80
81
  d = asdict(self)
82
+ d.pop("pal_files", None)
81
83
  d.update(saved=self.saved, saved_pct=self.saved_pct, plain_total=self.plain.total, pal_total=self.pal.total)
82
84
  return d
83
85
 
@@ -161,7 +163,12 @@ def run_bench(folder: Path, task: str, core_factory: Callable[[], Any], model: s
161
163
  pal_arm = _run_arm("pal", pal, steps, progress)
162
164
  finally:
163
165
  client.close()
164
- result = BenchResult(task, model, str(folder), plain_arm, pal_arm)
166
+ kept = {}
167
+ for rel in sorted({f for st in pal_arm.steps for f in st.files}):
168
+ f = pal_dir / rel
169
+ if f.is_file():
170
+ kept[rel] = f.read_bytes()
171
+ result = BenchResult(task, model, str(folder), plain_arm, pal_arm, pal_files=kept)
165
172
  save(result, home)
166
173
  return result
167
174
 
@@ -70,7 +70,7 @@ def build_parser() -> argparse.ArgumentParser:
70
70
  a.add_argument("--build", action="store_true", help="use the Build agent: it may propose file changes (asks before writing)")
71
71
  b = sub.add_parser("bench", help="run one task as a plain chat and through Pal, on the same model, and compare the real tokens")
72
72
  _common(b, suppress=True)
73
- b.add_argument("task", nargs="+", help="what to build, as you would type it")
73
+ b.add_argument("task", nargs="*", help="what to build, as you would type it (leave it out to open the bench screen and type tasks there)")
74
74
  b.add_argument("--then", action="append", default=None, metavar="MESSAGE", help="a follow-up message (repeat for more; default: two standard ones)")
75
75
  b.add_argument("--no-follow-ups", action="store_true", help="only the task itself")
76
76
  b.add_argument("--json", action="store_true", help="print the result as JSON")
@@ -79,13 +79,17 @@ def build_parser() -> argparse.ArgumentParser:
79
79
  return p
80
80
 
81
81
 
82
- def bench(args) -> int:
83
- """pal bench: the same task, plain chat versus Pal, on the same model."""
82
+ def bench(args, core_factory: Callable | None = None) -> int:
83
+ """pal bench: the same task, plain chat versus Pal, on the same model. Without a task it opens the usual Pal screen (\"pal bench\"), and every
84
+ task typed there is run both ways, with the results as a table and bar charts."""
84
85
  import json as _json
85
86
 
86
- from rich.table import Table
87
-
88
- from pal.terminal.bench import run_bench
87
+ if not args.task:
88
+ if not (sys.stdin.isatty() and sys.stdout.isatty()):
89
+ print("pal bench: give a task, e.g. pal bench \"make a todo app\"", file=sys.stderr)
90
+ return 2
91
+ args.bench_screen = True
92
+ return interactive(args, core_factory)
89
93
  console = ui.make_console()
90
94
  host = args.host or backend.DEFAULT_HOST
91
95
  prefs = backend.load_prefs()
@@ -103,17 +107,36 @@ def bench(args) -> int:
103
107
  except PalError as e:
104
108
  print(f"pal bench: {e}", file=sys.stderr)
105
109
  return 2
106
- follow = [] if args.no_follow_ups else args.then
107
- task = " ".join(args.task)
108
- status = console.status(Text("Starting\u2026", style=theme.MUTED), spinner="dots", spinner_style=theme.RED)
109
- with status:
110
- result = run_bench(Path(args.dir), task, lambda: backend.make_core(provider, model, host), model, follow,
111
- progress=lambda m: status.update(Text(m, style=theme.MUTED)))
110
+ result = bench_run(console, Path(args.dir), " ".join(args.task), provider, model, host, [] if args.no_follow_ups else args.then)
112
111
  if args.json:
113
112
  print(_json.dumps(result.to_dict(), indent=1))
114
113
  return 0
115
- ui.say(console, Text.assemble(("pal bench ", f"bold {theme.WHITE}"), (f"{model} ({provider}) \u00B7 {ui.short_path(Path(args.dir).resolve())}", theme.MUTED)))
116
- ui.say(console, Text(f"Task: {task}", style=theme.INK))
114
+ show_bench(console, result, provider, model, Path(args.dir))
115
+ return 0
116
+
117
+
118
+ def bench_run(console, folder: Path, task: str, provider: str, model: str, host: str, follow=None, core_factory: Callable | None = None):
119
+ from pal.terminal.bench import run_bench
120
+ status = console.status(Text("Starting\u2026", style=theme.MUTED), spinner="dots", spinner_style=theme.RED)
121
+ with status:
122
+ return run_bench(folder, task, lambda: (core_factory or backend.make_core)(provider, model, host), model, follow,
123
+ progress=lambda m: status.update(Text(m, style=theme.MUTED)))
124
+
125
+
126
+ def bars(console, title: str, rows: list[tuple[str, float, str]], width: int = 46) -> None:
127
+ """A horizontal bar chart in the terminal: (label, value, shown value). Plain chat in grey, Pal in red."""
128
+ top = max((v for _, v, _ in rows), default=0) or 1
129
+ ui.say(console, Text(title, style=f"bold {theme.WHITE}"))
130
+ for label, value, shown in rows:
131
+ n = max(1, round(width * value / top)) if value > 0 else 0
132
+ colour = theme.RED if label.lower().startswith("pal") else theme.MUTED
133
+ ui.say(console, Text.assemble((" " + f"{label:<11}", theme.INK), ("\u2588" * n, colour), (" " * (width - n + 2), ""), (shown, f"bold {theme.WHITE}")))
134
+
135
+
136
+ def show_bench(console, result, provider: str, model: str, folder: Path) -> None:
137
+ from rich.table import Table
138
+ ui.say(console, Text.assemble(("pal bench ", f"bold {theme.WHITE}"), (f"{model} ({provider}) \u00B7 {ui.short_path(Path(folder).resolve())}", theme.MUTED)))
139
+ ui.say(console, Text(f"Task: {result.task}", style=theme.INK))
117
140
  console.print()
118
141
  grid = Table(box=None, padding=(0, 2), header_style=f"bold {theme.WHITE}")
119
142
  for col, just in (("step", "left"), ("agent", "left"), ("plain chat", "right"), ("Pal", "right"), ("saved", "right")):
@@ -129,6 +152,13 @@ def bench(args) -> int:
129
152
  Text(f"{result.saved:,} ({result.saved_pct:g}%)", style=f"bold {theme.RED_HI}"))
130
153
  ui.say(console, grid)
131
154
  console.print()
155
+ bars(console, "Tokens", [("plain chat", result.plain.total, f"{result.plain.total:,}"), ("Pal", result.pal.total, f"{result.pal.total:,}")])
156
+ console.print()
157
+ bars(console, "Time", [("plain chat", result.plain.seconds, f"{result.plain.seconds:g}s"), ("Pal", result.pal.seconds, f"{result.pal.seconds:g}s")])
158
+ console.print()
159
+ verdict = (f"Pal used {result.saved:,} fewer tokens ({result.saved_pct:g}% less)." if result.saved > 0 else
160
+ f"Pal used {-result.saved:,} more tokens on this task (it pays off with bigger projects, documents and longer conversations).")
161
+ ui.say(console, Text(verdict, style=f"bold {theme.RED_HI}"))
132
162
  for arm in (result.plain, result.pal):
133
163
  files = sorted({f for s in arm.steps for f in s.files})
134
164
  ui.say(console, Text(f"{arm.name:<6} {arm.seconds:g}s \u00B7 {arm.calls} model call{'s' if arm.calls != 1 else ''} \u00B7 wrote: {', '.join(files) or 'no files'}"
@@ -137,7 +167,28 @@ def bench(args) -> int:
137
167
  ui.say(console, Text("Token counts are the model's own." if not est else f"{est} call(s) had no token count from the model, so those are estimates (characters / 4).", style=theme.MUTED))
138
168
  ui.say(console, Text("Pal's local helper jobs are off in the bench, so its saving here comes from the project map, graph, documents and history alone.", style=theme.MUTED))
139
169
  console.print()
140
- return 0
170
+
171
+
172
+ def keep_bench_files(console, result, folder: Path) -> list[str]:
173
+ """Both ways build in temporary copies; offer to write Pal's version into the folder (asking first, never overwriting without saying so)."""
174
+ if not result.pal_files:
175
+ return []
176
+ folder = Path(folder).resolve()
177
+ clash = [p for p in result.pal_files if (folder / p).exists()]
178
+ question = f"Save Pal's version ({len(result.pal_files)} file{'s' if len(result.pal_files) != 1 else ''}) in {ui.short_path(folder)}?" + (
179
+ f" It replaces {len(clash)} existing file{'s' if len(clash) != 1 else ''}." if clash else "")
180
+ if not ui.confirm(console, question):
181
+ return []
182
+ written = []
183
+ for rel, data in result.pal_files.items():
184
+ target = (folder / rel).resolve()
185
+ if folder not in target.parents:
186
+ continue # never outside the folder
187
+ target.parent.mkdir(parents=True, exist_ok=True)
188
+ target.write_bytes(data)
189
+ written.append(rel)
190
+ ui.say(console, Text(f"Saved: {', '.join(written)}", style=theme.INK))
191
+ return written
141
192
 
142
193
 
143
194
  def setup(args) -> int:
@@ -165,7 +216,7 @@ def first_run() -> None:
165
216
  console.print()
166
217
 
167
218
 
168
- def prepare_folder(args, console, core_factory: Callable | None = None) -> tuple[str, Project | None]:
219
+ def prepare_folder(args, console, core_factory: Callable | None = None, choose: bool = True) -> tuple[str, Project | None]:
169
220
  """Before a session: get local models ready (install and start Ollama, download the models for this GPU, asking first), then set up the
170
221
  folder: documents to markdown, the project map, the code graph (in the background). Returns (a line about the local model, the Project)."""
171
222
  from pal.terminal.project import Project
@@ -173,7 +224,7 @@ def prepare_folder(args, console, core_factory: Callable | None = None) -> tuple
173
224
  local = ""
174
225
  if core_factory is None:
175
226
  tty = sys.stdin.isatty() and sys.stdout.isatty()
176
- local = ensure_local(console, backend.hardware(), args.host or backend.DEFAULT_HOST, interactive=tty).detail
227
+ local = ensure_local(console, backend.hardware(), args.host or backend.DEFAULT_HOST, interactive=tty, choose=choose).detail
177
228
  folder = Path(getattr(args, "dir", ".") or ".")
178
229
  if os.environ.get("PAL_SKIP_PROJECT"):
179
230
  return local, None
@@ -217,7 +268,7 @@ def web(args) -> int:
217
268
  from pal.terminal.project import ProjectGraph
218
269
  graph = ProjectGraph(project.root / "graph", settings.knowledge_graph)
219
270
  return _serve(settings, open_browser=not args.no_open, page="studio" if args.page == "studio" else "", graph=graph,
220
- documents=project.documents() if project is not None else None)
271
+ documents=project.documents() if project is not None else None, project_dir=Path(getattr(args, "dir", ".") or ".").resolve())
221
272
 
222
273
 
223
274
  def _utf8() -> None:
@@ -488,6 +539,14 @@ def handle(line: str, args, session: PalSession, console, core_factory, box: dic
488
539
  return session, True
489
540
  if cmd == "/help":
490
541
  ui.show_help(console)
542
+ elif cmd == "/agent":
543
+ from pal.terminal.setup import choose_agent
544
+ mode = rest if rest in MODES else choose_agent(console, current=session.mode)
545
+ if mode:
546
+ session.mode = mode
547
+ backend.save_prefs(mode=mode)
548
+ ui.say(console, Text(f"Agent: {mode.capitalize()} ({AGENT_ABOUT[mode]})", style=theme.MUTED))
549
+ console.print()
491
550
  elif cmd == "/mode":
492
551
  if rest in MODES:
493
552
  session.mode = rest
@@ -707,13 +766,37 @@ def voice_flow(console) -> str:
707
766
  return heard.text
708
767
 
709
768
 
769
+ def choosing(args, core_factory: Callable | None = None) -> bool:
770
+ """pal cli asks for its models at every start (nothing preselected), unless they were given on the command line or this is not a terminal."""
771
+ return (core_factory is None and not args.provider and not args.model and sys.stdin.isatty() and sys.stdout.isatty()
772
+ and not os.environ.get("PAL_SKIP_SETUP") and not api_only(backend.hardware()))
773
+
774
+
775
+ def ensure_ready_line(args) -> str:
776
+ """The local-model line again, after the models were chosen (and maybe downloaded)."""
777
+ from pal.terminal.setup import ensure_local
778
+ return ensure_local(ui.make_console(), backend.hardware(), args.host or backend.DEFAULT_HOST, interactive=False, choose=False).detail
779
+
780
+
710
781
  def interactive(args, core_factory: Callable | None = None) -> int:
711
782
  console = ui.make_console()
712
- local, project = prepare_folder(args, console, core_factory)
783
+ ask = choosing(args, core_factory)
784
+ local, project = prepare_folder(args, console, core_factory, choose=not ask)
785
+ if ask:
786
+ from pal.terminal.setup import choose_models
787
+ console.print()
788
+ if not choose_models(console, backend.hardware(), args.host or backend.DEFAULT_HOST):
789
+ console.print(Text("\nNo model chosen. Run pal cli again to choose one.\n", style=theme.MUTED))
790
+ return 1
791
+ if not args.mode:
792
+ from pal.terminal.setup import choose_agent
793
+ args.mode = choose_agent(console) or None
794
+ local = ensure_ready_line(args)
713
795
  session = start(args, core_factory, project)
714
796
  if session is None:
715
797
  return 1
716
798
  session.local_status = local
799
+ session.brand = "bench" if getattr(args, "bench_screen", False) else "cli"
717
800
  if project is not None:
718
801
  ui.show_project(console, session)
719
802
  console.print()
@@ -753,6 +836,14 @@ def interactive(args, core_factory: Callable | None = None) -> int:
753
836
  session, go = handle(text, args, session, console, core_factory, box)
754
837
  if not go:
755
838
  break
839
+ elif text and getattr(args, "bench_screen", False): # pal bench: the task both ways, then the comparison
840
+ host = args.host or backend.DEFAULT_HOST
841
+ try:
842
+ result = bench_run(console, session.cwd, text, session.provider, session.model, host, core_factory=core_factory)
843
+ show_bench(console, result, session.provider, session.model, session.cwd)
844
+ keep_bench_files(console, result, session.cwd)
845
+ except PalError as e:
846
+ ui.say(console, Text(f"The bench stopped: {e}", style=theme.RED_HI))
756
847
  elif text:
757
848
  flow_cli.run_turn(box["wf"], text, console)
758
849
  except KeyboardInterrupt: # Esc or Ctrl+C at an awkward moment: back to the box, never out of Pal
@@ -813,7 +904,7 @@ def main(argv: Sequence[str] | None = None, core_factory: Callable | None = None
813
904
  if args.cmd == "setup":
814
905
  return setup(args)
815
906
  if args.cmd == "bench":
816
- return bench(args)
907
+ return bench(args, core_factory)
817
908
  if args.cmd == "ask":
818
909
  return ask_once(args, core_factory)
819
910
  if not (sys.stdin.isatty() and sys.stdout.isatty()) and core_factory is None: