pal-code 0.1.2__tar.gz → 0.1.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 (296) hide show
  1. {pal_code-0.1.2 → pal_code-0.1.3}/PKG-INFO +5 -5
  2. {pal_code-0.1.2 → pal_code-0.1.3}/pyproject.toml +6 -3
  3. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/__init__.py +1 -1
  4. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/cli.py +17 -15
  5. pal_code-0.1.3/src/pal/llm_wrapper/__init__.py +34 -0
  6. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/cli.py +208 -67
  7. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/setup.py +26 -1
  8. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/ui.py +64 -34
  9. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/voice.py +3 -3
  10. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/workflow.py +3 -3
  11. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/web/static/voice.js +1 -1
  12. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/web/templates/index.html +4 -12
  13. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal_code.egg-info/PKG-INFO +5 -5
  14. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal_code.egg-info/requires.txt +4 -4
  15. pal_code-0.1.2/src/pal/llm_wrapper/__init__.py +0 -34
  16. {pal_code-0.1.2 → pal_code-0.1.3}/LICENSE +0 -0
  17. {pal_code-0.1.2 → pal_code-0.1.3}/README.md +0 -0
  18. {pal_code-0.1.2 → pal_code-0.1.3}/setup.cfg +0 -0
  19. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/__main__.py +0 -0
  20. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/access_manager/__init__.py +0 -0
  21. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/access_manager/components/__init__.py +0 -0
  22. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/access_manager/components/access_control.py +0 -0
  23. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/access_manager/components/base.py +0 -0
  24. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/access_manager/components/intervention.py +0 -0
  25. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/access_manager/components/privilege_registry.py +0 -0
  26. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/access_manager/pipeline.py +0 -0
  27. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/access_manager/utils/__init__.py +0 -0
  28. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/agent_constraints/__init__.py +0 -0
  29. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/agent_constraints/components/AGENT_OUTPUT.md +0 -0
  30. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/agent_constraints/components/__init__.py +0 -0
  31. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/agent_constraints/components/constraints.py +0 -0
  32. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/agent_constraints/pipeline.py +0 -0
  33. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/agent_constraints/utils/__init__.py +0 -0
  34. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/agents/__init__.py +0 -0
  35. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/agents/coding.py +0 -0
  36. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/agents/docs.py +0 -0
  37. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/chat/__init__.py +0 -0
  38. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/chat/demo.py +0 -0
  39. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/chat/providers.py +0 -0
  40. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/chat/service.py +0 -0
  41. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/chat/services/__init__.py +0 -0
  42. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/chat/services/base.py +0 -0
  43. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/chat/services/data.py +0 -0
  44. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/chat/services/media.py +0 -0
  45. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/chat/services/web.py +0 -0
  46. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/chat/sessions.py +0 -0
  47. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/chat/tools.py +0 -0
  48. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/chat/uploads.py +0 -0
  49. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/compression/__init__.py +0 -0
  50. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/compression/components/__init__.py +0 -0
  51. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/compression/components/adverbs.py +0 -0
  52. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/compression/components/hedges.py +0 -0
  53. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/compression/components/interjections.py +0 -0
  54. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/compression/components/minifier.py +0 -0
  55. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/compression/components/output_compressor.py +0 -0
  56. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/compression/components/pleonasms.py +0 -0
  57. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/compression/components/punctuations.py +0 -0
  58. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/compression/components/stopwords.py +0 -0
  59. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/compression/history.py +0 -0
  60. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/compression/pipeline.py +0 -0
  61. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/compression/utils/__init__.py +0 -0
  62. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/compression/utils/negations.py +0 -0
  63. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/compression/utils/nltk_setup.py +0 -0
  64. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/compression/utils/pronouns.py +0 -0
  65. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/compression/utils/proselint_setup.py +0 -0
  66. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/compression/utils/spacy_setup.py +0 -0
  67. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/config/default.yaml +0 -0
  68. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/config.py +0 -0
  69. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/context_manager/__init__.py +0 -0
  70. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/context_manager/chat_context.py +0 -0
  71. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/context_manager/components/__init__.py +0 -0
  72. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/context_manager/components/assembler.py +0 -0
  73. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/context_manager/components/context_cache.py +0 -0
  74. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/context_manager/components/kv_bridge.py +0 -0
  75. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/context_manager/components/pager.py +0 -0
  76. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/context_manager/components/simple_context_manager.py +0 -0
  77. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/context_manager/components/turns.py +0 -0
  78. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/context_manager/pipeline.py +0 -0
  79. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/context_manager/utils/__init__.py +0 -0
  80. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/context_manager/utils/hashing.py +0 -0
  81. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/context_manager/utils/relevance.py +0 -0
  82. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/context_manager/utils/storage.py +0 -0
  83. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/context_window.py +0 -0
  84. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/documents/__init__.py +0 -0
  85. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/documents/components/__init__.py +0 -0
  86. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/documents/components/chunker.py +0 -0
  87. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/documents/components/index.py +0 -0
  88. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/documents/utils/__init__.py +0 -0
  89. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/errors.py +0 -0
  90. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/input_converters/__init__.py +0 -0
  91. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/input_converters/components/__init__.py +0 -0
  92. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/input_converters/components/base.py +0 -0
  93. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/input_converters/components/pandoc_converter.py +0 -0
  94. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/input_converters/components/pdf_converter.py +0 -0
  95. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/input_converters/components/pptx_converter.py +0 -0
  96. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/input_converters/components/xlsx_converter.py +0 -0
  97. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/input_converters/pipeline.py +0 -0
  98. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/input_converters/utils/__init__.py +0 -0
  99. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/input_converters/workspace.py +0 -0
  100. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/input_savers/INPUT_RULES.md +0 -0
  101. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/input_savers/__init__.py +0 -0
  102. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/input_savers/components/__init__.py +0 -0
  103. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/input_savers/components/base.py +0 -0
  104. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/input_savers/components/methods.py +0 -0
  105. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/input_savers/components/verifier.py +0 -0
  106. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/input_savers/pipeline.py +0 -0
  107. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/input_savers/utils/__init__.py +0 -0
  108. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/input_savers/utils/guards.py +0 -0
  109. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/knowledge_graph/__init__.py +0 -0
  110. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/knowledge_graph/components/__init__.py +0 -0
  111. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/knowledge_graph/components/base.py +0 -0
  112. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/knowledge_graph/components/builder.py +0 -0
  113. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/knowledge_graph/components/retriever.py +0 -0
  114. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/knowledge_graph/components/workspace.py +0 -0
  115. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/knowledge_graph/pipeline.py +0 -0
  116. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/knowledge_graph/utils/__init__.py +0 -0
  117. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/latency_tracker/__init__.py +0 -0
  118. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/latency_tracker/pipeline.py +0 -0
  119. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/llm_scheduler/__init__.py +0 -0
  120. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/llm_scheduler/api.py +0 -0
  121. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/llm_scheduler/code.py +0 -0
  122. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/llm_scheduler/core.py +0 -0
  123. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/llm_scheduler/local.py +0 -0
  124. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/llm_scheduler/pipeline.py +0 -0
  125. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/llm_scheduler/types.py +0 -0
  126. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/llm_wrapper/adapter.py +0 -0
  127. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/llm_wrapper/components/__init__.py +0 -0
  128. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/llm_wrapper/components/adapter_types.py +0 -0
  129. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/llm_wrapper/components/base.py +0 -0
  130. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/llm_wrapper/components/features.py +0 -0
  131. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/llm_wrapper/components/litellm_wrapper.py +0 -0
  132. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/llm_wrapper/components/openrouter_wrapper.py +0 -0
  133. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/llm_wrapper/modes.py +0 -0
  134. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/llm_wrapper/pipeline.py +0 -0
  135. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/llm_wrapper/utils/__init__.py +0 -0
  136. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/llm_wrapper/utils/registry.py +0 -0
  137. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/llm_wrapper/utils/token_counting.py +0 -0
  138. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/local_llm/__init__.py +0 -0
  139. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/local_llm/components/__init__.py +0 -0
  140. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/local_llm/components/huggingface_local.py +0 -0
  141. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/local_llm/components/local_llm.py +0 -0
  142. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/logging_setup.py +0 -0
  143. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/memory/__init__.py +0 -0
  144. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/memory/components/__init__.py +0 -0
  145. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/memory/components/lru_k.py +0 -0
  146. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/memory/components/manager.py +0 -0
  147. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/memory/components/note.py +0 -0
  148. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/memory/components/storage_manager.py +0 -0
  149. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/memory/pipeline.py +0 -0
  150. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/memory/utils/__init__.py +0 -0
  151. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/memory/utils/keyword_overlap.py +0 -0
  152. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/memory_tracker/__init__.py +0 -0
  153. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/memory_tracker/components/__init__.py +0 -0
  154. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/memory_tracker/components/process.py +0 -0
  155. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/memory_tracker/pipeline.py +0 -0
  156. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/metrics/__init__.py +0 -0
  157. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/metrics/components/__init__.py +0 -0
  158. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/metrics/components/collector.py +0 -0
  159. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/metrics/components/exporter.py +0 -0
  160. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/metrics/pipeline.py +0 -0
  161. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/metrics/utils/__init__.py +0 -0
  162. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/notes_manager/__init__.py +0 -0
  163. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/notes_manager/components/__init__.py +0 -0
  164. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/notes_manager/components/privacy.py +0 -0
  165. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/notes_manager/components/recall.py +0 -0
  166. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/notes_manager/components/tidy.py +0 -0
  167. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/notes_manager/pipeline.py +0 -0
  168. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/orchestrator/__init__.py +0 -0
  169. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/orchestrator/components/__init__.py +0 -0
  170. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/orchestrator/components/agent.py +0 -0
  171. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/orchestrator/components/cgroup_manager.py +0 -0
  172. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/orchestrator/components/context_interrupt.py +0 -0
  173. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/orchestrator/components/limits.py +0 -0
  174. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/orchestrator/components/scheduler.py +0 -0
  175. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/orchestrator/components/syscall_scheduler.py +0 -0
  176. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/orchestrator/pipeline.py +0 -0
  177. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/orchestrator/utils/__init__.py +0 -0
  178. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/orchestrator/utils/device.py +0 -0
  179. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/orchestrator/utils/protocol.py +0 -0
  180. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/os_kernel/__init__.py +0 -0
  181. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/os_kernel/filesystem.py +0 -0
  182. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/os_kernel/network.py +0 -0
  183. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/os_kernel/pipeline.py +0 -0
  184. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/os_kernel/sysinfo.py +0 -0
  185. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/paths.py +0 -0
  186. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/pipeline.py +0 -0
  187. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/py.typed +0 -0
  188. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/redact.py +0 -0
  189. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/sdk/__init__.py +0 -0
  190. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/sdk/adapters.py +0 -0
  191. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/sdk/agent.py +0 -0
  192. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/sdk/components/__init__.py +0 -0
  193. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/sdk/components/autogen_adapter.py +0 -0
  194. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/sdk/components/autogen_client.py +0 -0
  195. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/sdk/components/framework_adapter_base.py +0 -0
  196. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/sdk/components/metagpt_adapter.py +0 -0
  197. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/sdk/components/open_interpreter_adapter.py +0 -0
  198. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/sdk/components/tool_catalog.py +0 -0
  199. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/sdk/os_api.py +0 -0
  200. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/sdk/patterns.py +0 -0
  201. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/sdk/pipeline.py +0 -0
  202. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/sdk/queries.py +0 -0
  203. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/sdk/savers.py +0 -0
  204. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/sdk/utils/__init__.py +0 -0
  205. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/storage_manager/__init__.py +0 -0
  206. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/storage_manager/components/__init__.py +0 -0
  207. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/storage_manager/components/base.py +0 -0
  208. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/storage_manager/components/local_file_backend.py +0 -0
  209. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/storage_manager/components/storage_queue.py +0 -0
  210. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/storage_manager/components/vector_store_backend.py +0 -0
  211. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/storage_manager/pipeline.py +0 -0
  212. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/storage_manager/utils/__init__.py +0 -0
  213. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/storage_manager/utils/memory_adapter.py +0 -0
  214. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/storage_manager/utils/sdk.py +0 -0
  215. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/studio/__init__.py +0 -0
  216. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/studio/builder.py +0 -0
  217. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/studio/catalog.py +0 -0
  218. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/studio/checks.py +0 -0
  219. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/studio/clients.py +0 -0
  220. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/studio/editor.py +0 -0
  221. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/studio/files.py +0 -0
  222. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/studio/models.py +0 -0
  223. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/studio/plan.py +0 -0
  224. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/studio/planner.py +0 -0
  225. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/studio/policy.py +0 -0
  226. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/studio/routes.py +0 -0
  227. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/studio/runs.py +0 -0
  228. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/task_router/__init__.py +0 -0
  229. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/task_router/components/__init__.py +0 -0
  230. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/task_router/components/answerer.py +0 -0
  231. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/task_router/components/base.py +0 -0
  232. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/task_router/components/classifier.py +0 -0
  233. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/task_router/components/gates.py +0 -0
  234. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/task_router/pipeline.py +0 -0
  235. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/task_router/utils/__init__.py +0 -0
  236. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/__init__.py +0 -0
  237. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/backend.py +0 -0
  238. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/bench.py +0 -0
  239. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/clients.py +0 -0
  240. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/commands.py +0 -0
  241. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/decide.py +0 -0
  242. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/files.py +0 -0
  243. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/flow_cli.py +0 -0
  244. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/hardware.py +0 -0
  245. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/history.py +0 -0
  246. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/keys.py +0 -0
  247. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/local_models.py +0 -0
  248. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/logo.py +0 -0
  249. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/models.py +0 -0
  250. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/policy.py +0 -0
  251. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/project.py +0 -0
  252. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/review.py +0 -0
  253. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/security.py +0 -0
  254. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/session.py +0 -0
  255. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/syntax.py +0 -0
  256. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/terminal/theme.py +0 -0
  257. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/token_tracker/__init__.py +0 -0
  258. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/token_tracker/pipeline.py +0 -0
  259. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/tokens.py +0 -0
  260. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/tool_catalog.py +0 -0
  261. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/tool_manager/__init__.py +0 -0
  262. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/tool_manager/builtin.py +0 -0
  263. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/tool_manager/components/__init__.py +0 -0
  264. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/tool_manager/components/base.py +0 -0
  265. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/tool_manager/components/conflict_resolver.py +0 -0
  266. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/tool_manager/components/param_validator.py +0 -0
  267. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/tool_manager/components/tool_call_queue.py +0 -0
  268. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/tool_manager/components/tool_registry.py +0 -0
  269. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/tool_manager/pipeline.py +0 -0
  270. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/tool_manager/utils/__init__.py +0 -0
  271. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/web/__init__.py +0 -0
  272. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/web/app.py +0 -0
  273. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/web/static/editor.js +0 -0
  274. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/web/static/fonts/LICENSE-Inter.txt +0 -0
  275. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/web/static/fonts/inter-400.woff2 +0 -0
  276. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/web/static/fonts/inter-500.woff2 +0 -0
  277. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/web/static/fonts/inter-600.woff2 +0 -0
  278. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/web/static/fonts/inter-700.woff2 +0 -0
  279. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/web/static/inter.css +0 -0
  280. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/web/static/pal-logo-256.png +0 -0
  281. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/web/static/pal-logo-32.png +0 -0
  282. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/web/static/pal-logo-64.png +0 -0
  283. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/web/static/studio.css +0 -0
  284. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/web/static/studio.js +0 -0
  285. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal/web/templates/studio.html +0 -0
  286. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal_code.egg-info/SOURCES.txt +0 -0
  287. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal_code.egg-info/dependency_links.txt +0 -0
  288. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal_code.egg-info/entry_points.txt +0 -0
  289. {pal_code-0.1.2 → pal_code-0.1.3}/src/pal_code.egg-info/top_level.txt +0 -0
  290. {pal_code-0.1.2 → pal_code-0.1.3}/tests/test_cli.py +0 -0
  291. {pal_code-0.1.2 → pal_code-0.1.3}/tests/test_config.py +0 -0
  292. {pal_code-0.1.2 → pal_code-0.1.3}/tests/test_lockfile.py +0 -0
  293. {pal_code-0.1.2 → pal_code-0.1.3}/tests/test_paths.py +0 -0
  294. {pal_code-0.1.2 → pal_code-0.1.3}/tests/test_redact.py +0 -0
  295. {pal_code-0.1.2 → pal_code-0.1.3}/tests/test_run_script.py +0 -0
  296. {pal_code-0.1.2 → pal_code-0.1.3}/tests/test_tool_catalog.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pal-code
3
- Version: 0.1.2
3
+ Version: 0.1.3
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
5
  Author: Vrushal More
6
6
  License: MIT
@@ -26,6 +26,9 @@ Requires-Dist: graphifyy>=0.9
26
26
  Requires-Dist: pypdf>=4.0
27
27
  Requires-Dist: python-pptx>=0.6.23
28
28
  Requires-Dist: openpyxl>=3.1
29
+ Requires-Dist: sounddevice>=0.4
30
+ Requires-Dist: numpy>=1.24
31
+ Requires-Dist: faster-whisper>=1.0
29
32
  Provides-Extra: documents
30
33
  Requires-Dist: pymupdf4llm>=0.0.17; extra == "documents"
31
34
  Provides-Extra: graph
@@ -42,11 +45,8 @@ Provides-Extra: inprocess
42
45
  Requires-Dist: torch; extra == "inprocess"
43
46
  Requires-Dist: transformers; extra == "inprocess"
44
47
  Provides-Extra: voice
45
- Requires-Dist: sounddevice>=0.4; extra == "voice"
46
- Requires-Dist: numpy>=1.24; extra == "voice"
47
- Requires-Dist: faster-whisper>=1.0; extra == "voice"
48
48
  Provides-Extra: full
49
- Requires-Dist: pal-code[documents,voice]; extra == "full"
49
+ Requires-Dist: pal-code[documents]; extra == "full"
50
50
  Provides-Extra: dev
51
51
  Requires-Dist: pytest>=8; extra == "dev"
52
52
  Requires-Dist: ruff>=0.5; extra == "dev"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pal-code"
7
- version = "0.1.2"
7
+ version = "0.1.3"
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"
@@ -32,6 +32,9 @@ dependencies = [
32
32
  "pypdf>=4.0", # PDF -> markdown
33
33
  "python-pptx>=0.6.23", # PowerPoint -> markdown
34
34
  "openpyxl>=3.1", # Excel -> markdown
35
+ "sounddevice>=0.4", # the mic: recording (PortAudio comes with it on Windows and macOS)
36
+ "numpy>=1.24",
37
+ "faster-whisper>=1.0", # speech to text on this computer (Whisper); nothing leaves the computer
35
38
  ]
36
39
 
37
40
  [project.optional-dependencies]
@@ -42,8 +45,8 @@ litellm = [] # kept so older install lines still work: litel
42
45
  vector = ["chromadb"]
43
46
  embeddings = ["sentence-transformers"]
44
47
  inprocess = ["torch", "transformers"]
45
- voice = ["sounddevice>=0.4", "numpy>=1.24", "faster-whisper>=1.0"] # talk instead of typing (Ctrl+Space); speech-to-text on this computer
46
- full = ["pal-code[documents,voice]"]
48
+ voice = [] # kept so older install lines still work: the mic is installed by default now
49
+ full = ["pal-code[documents]"]
47
50
  dev = ["pytest>=8", "ruff>=0.5", "mypy>=1.10", "types-requests", "types-PyYAML"]
48
51
 
49
52
  [project.scripts]
@@ -13,7 +13,7 @@ def _version() -> str:
13
13
  return version(name)
14
14
  except PackageNotFoundError:
15
15
  continue
16
- return "0.1.2" # a source checkout that is not installed
16
+ return "0.1.3" # a source checkout that is not installed
17
17
 
18
18
 
19
19
  __version__ = _version()
@@ -212,7 +212,8 @@ 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, project_dir=None) -> int:
215
+ def _serve(settings: Settings, open_browser: bool = False, page: str = "", graph=None, documents: list | None = None, project_dir=None,
216
+ background=None) -> int:
216
217
  from pal.chat import ChatService
217
218
  from pal.web import create_app
218
219
 
@@ -222,6 +223,9 @@ def _serve(settings: Settings, open_browser: bool = False, page: str = "", graph
222
223
  if documents:
223
224
  service.preload_docs = list(documents)
224
225
  app = create_app(service, studio={"project_dir": project_dir}) if project_dir else create_app(service)
226
+ if background is not None: # pal web: the folder's setup runs while the page is already usable
227
+ import threading as _bg
228
+ _bg.Thread(target=background, args=(service,), daemon=True, name="pal-web-setup").start()
225
229
  if s.host not in _LOOPBACK:
226
230
  print(f"WARNING: listening on {s.host}. Pal has no login: anyone who can reach this port can use "
227
231
  "your API keys' quota and read stored memory. Put it behind an authenticating reverse proxy.",
@@ -229,23 +233,21 @@ def _serve(settings: Settings, open_browser: bool = False, page: str = "", graph
229
233
  shown = "localhost" if s.host in ("0.0.0.0", "::") else s.host # noqa: S104
230
234
  url = f"http://{shown}:{s.port}" + ("/" + page.strip("/") if page else "")
231
235
  print(f"Pal {__version__} on {url} (Ctrl+C to stop)")
232
- from pal.chat.providers import API_PROVIDERS
233
- keys = [p for p, var in API_PROVIDERS.items() if os.environ.get(var)]
234
- print("API key found for: " + ", ".join(keys) if keys else
235
- "No API key in the environment: paste one in the page (API key menu), or set e.g. GEMINI_API_KEY.")
236
- status = service.local_status()
237
- if status["ok"] is False:
238
- print(f"WARNING: local model: {status['detail']}. Input reduction and local answers are off until it runs; "
239
- "the app still works and sends everything to the API model.", file=sys.stderr)
240
- else:
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
236
+ if background is None: # pal serve: say what is set up; pal web says nothing more and starts at once
237
+ from pal.chat.providers import API_PROVIDERS
238
+ keys = [p for p, var in API_PROVIDERS.items() if os.environ.get(var)]
239
+ print("API key found for: " + ", ".join(keys) if keys else
240
+ "No API key in the environment: paste one in the page (API key menu), or set e.g. GEMINI_API_KEY.")
241
+ status = service.local_status()
242
+ if status["ok"] is False:
243
+ print(f"WARNING: local model: {status['detail']}. Input reduction and local answers are off until it runs; "
244
+ "the app still works and sends everything to the API model.", file=sys.stderr)
245
+ else:
246
+ print(f"Local model: {status['detail']}")
245
247
  if open_browser:
246
248
  import threading
247
249
  import webbrowser
248
- threading.Timer(1.2, lambda: webbrowser.open(url)).start()
250
+ threading.Timer(0.3, lambda: webbrowser.open(url)).start()
249
251
  try:
250
252
  try:
251
253
  from waitress import serve
@@ -0,0 +1,34 @@
1
+ """The model layer. Its names are loaded on first use (PEP 562), so importing a light submodule such as pal.llm_wrapper.modes (the provider
2
+ names and key variables) does not load the whole model stack: pal cli draws its first screen before any of it is needed."""
3
+ from importlib import import_module
4
+
5
+ _WHERE = {
6
+ "LLMAdapter": "pal.llm_wrapper.adapter",
7
+ "LLMAdapterResponse": "pal.llm_wrapper.components.adapter_types",
8
+ "LLMAdapterSyscall": "pal.llm_wrapper.components.adapter_types",
9
+ "LLMConfig": "pal.llm_wrapper.components.adapter_types",
10
+ "RouterStrategy": "pal.llm_wrapper.components.adapter_types",
11
+ "DeploymentType": "pal.llm_wrapper.components.base",
12
+ "LLMResponse": "pal.llm_wrapper.components.base",
13
+ "LLMSyscall": "pal.llm_wrapper.components.base",
14
+ "LLMWrapper": "pal.llm_wrapper.components.base",
15
+ "WrapperCapabilities": "pal.llm_wrapper.components.base",
16
+ "WrapperConfig": "pal.llm_wrapper.components.base",
17
+ "LLMWrapperPipeline": "pal.llm_wrapper.pipeline",
18
+ "available_backends": "pal.llm_wrapper.utils.registry",
19
+ "build_wrapper": "pal.llm_wrapper.utils.registry",
20
+ }
21
+
22
+ __all__ = list(_WHERE)
23
+
24
+
25
+ def __getattr__(name: str):
26
+ if name in _WHERE:
27
+ value = getattr(import_module(_WHERE[name]), name)
28
+ globals()[name] = value
29
+ return value
30
+ raise AttributeError(f"module 'pal.llm_wrapper' has no attribute {name!r}")
31
+
32
+
33
+ def __dir__():
34
+ return sorted(set(globals()) | set(_WHERE))
@@ -239,36 +239,43 @@ def prepare_folder(args, console, core_factory: Callable | None = None, choose:
239
239
 
240
240
 
241
241
  def web(args) -> int:
242
- """Start the web interface on this computer and open it in the default browser. It listens on 127.0.0.1 only, unless the settings say otherwise.
243
- The helper model chosen in setup does the web interface's small jobs too."""
242
+ """Start the web interface on this computer and open it in the default browser, at once. It listens on 127.0.0.1 only, unless the settings
243
+ say otherwise. The setup (Ollama, the helper, this folder's documents, map and graph) runs in the background; the chat gets the folder's
244
+ documents as soon as they are ready."""
244
245
  from pal.cli import _serve, load_dotenv
245
246
  from pal.config import load_settings
246
247
  from pal.errors import ConfigError
248
+ from pal.terminal.project import Project, ProjectGraph
247
249
  load_dotenv()
248
- console = ui.make_console()
249
- local, project = prepare_folder(args, console)
250
- if local:
251
- ui.say(console, Text(f"Local LLM: {local}", style=theme.MUTED))
252
- if project is not None:
253
- r = project.report
254
- ui.say(console, Text(f"This folder: {r.code_files} files \u00B7 {len(r.converted)} documents as markdown \u00B7 code graph {r.graph}", style=theme.MUTED))
255
250
  overrides: dict = {"server": {"port": args.port}} if args.port else {}
256
251
  helper = backend.load_prefs().get("helper_model")
257
- if api_only(backend.hardware()):
258
- overrides["local_llm"] = {"enabled": False} # no GPU: API models only, no local helper jobs
259
- elif helper:
260
- overrides["local_llm"] = {"backend": "ollama", "model": helper}
252
+ overrides["local_llm"] = {"backend": "ollama", "model": helper} if helper else {"enabled": False} # no helper yet: API models only
261
253
  try:
262
254
  settings = load_settings(overrides=overrides or None)
263
255
  except ConfigError as e:
264
256
  print(f"config error: {e}", file=sys.stderr)
265
257
  return 2
266
- graph = None
267
- if project is not None: # the web chat gets the folder too: its graph, documents and map
268
- from pal.terminal.project import ProjectGraph
269
- graph = ProjectGraph(project.root / "graph", settings.knowledge_graph)
258
+ folder = Path(getattr(args, "dir", ".") or ".").resolve()
259
+ project = None if os.environ.get("PAL_SKIP_PROJECT") else Project(folder)
260
+ graph = ProjectGraph(project.root / "graph", settings.knowledge_graph) if project is not None else None
261
+
262
+ def in_background(service) -> None:
263
+ import io
264
+
265
+ from rich.console import Console
266
+ from pal.terminal.setup import ensure_local
267
+ try:
268
+ host = args.host or backend.DEFAULT_HOST
269
+ hw = backend.hardware()
270
+ ensure_local(Console(file=io.StringIO()), hw, host, interactive=False, choose=False)
271
+ background_helper(hw, backend.local_info(host))
272
+ if project is not None:
273
+ project.prepare()
274
+ service.preload_docs = list(project.documents()) # new chats get the folder's documents and map
275
+ except Exception: # noqa: BLE001 (the web interface works without the folder's setup)
276
+ pass
270
277
  return _serve(settings, open_browser=not args.no_open, page="studio" if args.page == "studio" else "", graph=graph,
271
- documents=project.documents() if project is not None else None, project_dir=Path(getattr(args, "dir", ".") or ".").resolve())
278
+ project_dir=folder, background=in_background)
272
279
 
273
280
 
274
281
  def _utf8() -> None:
@@ -284,7 +291,7 @@ NO_GPU = ("No GPU on this computer, so Pal uses an API model. Choose one with:\n
284
291
  " browse models: pal models --free")
285
292
 
286
293
 
287
- def start(args, core_factory: Callable | None = None, project=None) -> PalSession | None:
294
+ def start(args, core_factory: Callable | None = None, project=None, prompt: bool = True) -> PalSession | None:
288
295
  """The session for these arguments, or None (after saying why) when there is no model to talk to.
289
296
  On a computer without a GPU there is no local model: the session uses an API model and Pal's local helper jobs are off."""
290
297
  console = ui.make_console()
@@ -292,9 +299,11 @@ def start(args, core_factory: Callable | None = None, project=None) -> PalSessio
292
299
  prefs = backend.load_prefs()
293
300
  no_gpu = core_factory is None and api_only(backend.hardware())
294
301
  if no_gpu:
295
- return _start_api_only(args, prefs, console, project)
302
+ return _start_api_only(args, prefs, console, project, prompt)
296
303
  want = args.provider or prefs.get("provider", "")
297
304
  if want in backend.PROVIDER_KEYS and not os.environ.get(backend.PROVIDER_KEYS[want]) and core_factory is None and sys.stdin.isatty():
305
+ if not prompt:
306
+ return None # the key is asked for in the foreground
298
307
  ask_key(console, want) # the main model is an API model: its key (never saved) for this session
299
308
  info = backend.local_info(host)
300
309
  if args.provider and args.provider != backend.LOCAL:
@@ -338,7 +347,7 @@ def ask_key(console, provider: str) -> bool:
338
347
  return bool(key)
339
348
 
340
349
 
341
- def _start_api_only(args, prefs: dict, console, project=None) -> PalSession | None:
350
+ def _start_api_only(args, prefs: dict, console, project=None, prompt: bool = True) -> PalSession | None:
342
351
  from pal.terminal.setup import ask_api
343
352
  keyed = lambda p: p in backend.OPEN_PROVIDERS or (p in backend.PROVIDER_KEYS and bool(os.environ.get(backend.PROVIDER_KEYS[p]))) # noqa: E731
344
353
  if args.provider == backend.LOCAL:
@@ -347,6 +356,8 @@ def _start_api_only(args, prefs: dict, console, project=None) -> PalSession | No
347
356
  if provider == backend.LOCAL:
348
357
  provider, model = "", ""
349
358
  if not (provider and model and keyed(provider)):
359
+ if not prompt:
360
+ return None
350
361
  if sys.stdin.isatty() and sys.stdout.isatty():
351
362
  console.print(Text("\n No GPU on this computer, so Pal uses an API model.\n", style=f"bold {theme.RED_HI}"))
352
363
  chosen = ask_api(console, backend.hardware().system)
@@ -726,12 +737,12 @@ def voice_flow(console) -> str:
726
737
 
727
738
  from pal.terminal import voice
728
739
  if not voice.can_record():
729
- ui.say(console, Text('Talking needs a microphone library: pip install "pal-code[voice]" (Linux also: sudo apt install libportaudio2). '
730
- "Dictation apps such as Wispr Flow work without it: they type into the box.", style=theme.MUTED))
740
+ ui.say(console, Text("The mic is not available here: no microphone was found, or (on Linux) PortAudio is missing: sudo apt install libportaudio2. "
741
+ "Windows voice typing (Win+H) and apps such as Wispr Flow type into the box too.", style=theme.MUTED))
731
742
  console.print()
732
743
  return ""
733
744
  if not voice.engines():
734
- ui.say(console, Text('No speech-to-text yet: pip install "pal-code[voice]" (on this computer), or set OPENAI_API_KEY or GROQ_API_KEY.', style=theme.MUTED))
745
+ ui.say(console, Text("Speech to text is not available: reinstall Pal (pip install -U pal-code), or set OPENAI_API_KEY or GROQ_API_KEY.", style=theme.MUTED))
735
746
  console.print()
736
747
  return ""
737
748
  stop, out, err = threading.Event(), {}, {}
@@ -757,7 +768,9 @@ def voice_flow(console) -> str:
757
768
  ui.say(console, Text(err.get("e", "Nothing was recorded."), style=theme.RED_HI))
758
769
  return ""
759
770
  try:
760
- with console.status(Text("Turning speech into text…", style=theme.MUTED), spinner="dots", spinner_style=theme.RED):
771
+ first_time = "local" in voice.engines() and not voice._LOCAL
772
+ label = "Turning speech into text (the first time, the speech model is downloaded once, about 150 MB)…" if first_time else "Turning speech into text…"
773
+ with console.status(Text(label, style=theme.MUTED), spinner="dots", spinner_style=theme.RED):
761
774
  heard = voice.transcribe(out["wav"])
762
775
  except voice.VoiceUnavailable as e:
763
776
  ui.say(console, Text(str(e), style=theme.RED_HI))
@@ -766,60 +779,186 @@ def voice_flow(console) -> str:
766
779
  return heard.text
767
780
 
768
781
 
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()))
782
+ def automatic(args, core_factory: Callable | None = None) -> bool:
783
+ """A real terminal session on a computer with a GPU: Pal gets its helper ready by itself and asks nothing at start."""
784
+ return (core_factory is None and sys.stdin.isatty() and sys.stdout.isatty() and not os.environ.get("PAL_SKIP_SETUP")
785
+ and not api_only(backend.hardware()))
773
786
 
774
787
 
775
788
  def ensure_ready_line(args) -> str:
776
- """The local-model line again, after the models were chosen (and maybe downloaded)."""
789
+ """The local-model line again, after a model was downloaded."""
777
790
  from pal.terminal.setup import ensure_local
778
791
  return ensure_local(ui.make_console(), backend.hardware(), args.host or backend.DEFAULT_HOST, interactive=False, choose=False).detail
779
792
 
780
793
 
781
- def interactive(args, core_factory: Callable | None = None) -> int:
782
- console = ui.make_console()
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)
795
- session = start(args, core_factory, project)
794
+ def has_main_model(args, host: str) -> bool:
795
+ """Is there a model to start with: one on the command line, or the last one used (local and installed, or an API model with its key)?"""
796
+ if args.provider or args.model:
797
+ return True
798
+ _, model = backend.pick_default(backend.local_info(host), backend.load_prefs())
799
+ return bool(model)
800
+
801
+
802
+ class Boot:
803
+ """What the first screen needs before the real session exists: the agent and the model from the saved settings, nothing probed.
804
+ It lets pal cli draw its screen at once while the setup runs in the background."""
805
+
806
+ def __init__(self, args, prefs: dict):
807
+ self.mode = args.mode or prefs.get("mode", "build")
808
+ if self.mode not in MODES:
809
+ self.mode = "build"
810
+ self.provider = args.provider or prefs.get("provider", "") or backend.LOCAL
811
+ self.model = args.model or prefs.get("model", "") or "choose a model"
812
+ self.cwd = Path(getattr(args, "dir", ".") or ".").resolve()
813
+ self.brand = "bench" if getattr(args, "bench_screen", False) else "cli"
814
+ self.pending: list = []
815
+ self.role_models: dict = {}
816
+ self.turns, self.pal_tokens, self.saved, self.budget = 0, 0, 0, 0
817
+ self.notice, self.helper_model, self.local, self.project, self.local_status = "", "", True, None, ""
818
+
819
+ def model_for(self, mode: str | None = None) -> tuple[str, str]:
820
+ return self.provider, self.model
821
+
822
+ def toggle_mode(self) -> str:
823
+ self.mode = MODES[(MODES.index(self.mode) + 1) % len(MODES)]
824
+ return self.mode
825
+
826
+
827
+ def quiet_pull(tag: str) -> bool:
828
+ """`ollama pull` with no output: for the helper, downloaded in the background."""
829
+ import shutil
830
+ import subprocess
831
+ exe = shutil.which("ollama")
832
+ if not exe:
833
+ return False
834
+ try:
835
+ return subprocess.call([exe, "pull", tag], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0
836
+ except OSError:
837
+ return False
838
+
839
+
840
+ def background_helper(hw, info: dict) -> None:
841
+ """Pal's helper: the best small model for this computer. Installed: used. Missing: downloaded in a background thread (used from the next
842
+ start), so nothing waits for it."""
843
+ import threading
844
+
845
+ from pal.terminal.local_models import pick_helper
846
+ if api_only(hw) or not info.get("ok"):
847
+ return
848
+ best = pick_helper(hw).tag
849
+ if best in backend.chat_models(info):
850
+ backend.save_prefs(helper_model=best)
851
+ return
852
+
853
+ def get() -> None:
854
+ if quiet_pull(best):
855
+ backend.save_prefs(helper_model=best)
856
+ threading.Thread(target=get, daemon=True, name="pal-helper-download").start()
857
+
858
+
859
+ def prepare_quietly(args, core_factory: Callable | None, out: dict) -> None:
860
+ """Everything a session needs, without a word on the screen: the hardware, Ollama (started if needed), the folder (documents to markdown,
861
+ the map, the graph), the helper, and the session itself. What needs the person (a model to choose, a key) is left to the foreground."""
862
+ import io
863
+
864
+ from rich.console import Console
865
+ from pal.terminal.project import Project
866
+ from pal.terminal.setup import ensure_local
867
+ quiet = Console(file=io.StringIO())
868
+ try:
869
+ host = args.host or backend.DEFAULT_HOST
870
+ if core_factory is None:
871
+ hw = backend.hardware()
872
+ out["local"] = ensure_local(quiet, hw, host, interactive=False, choose=False).detail
873
+ info = backend.local_info(host)
874
+ if automatic(args, core_factory):
875
+ background_helper(hw, info)
876
+ project = None
877
+ if not os.environ.get("PAL_SKIP_PROJECT"):
878
+ try:
879
+ project = Project(Path(getattr(args, "dir", ".") or "."))
880
+ project.prepare()
881
+ except Exception: # noqa: BLE001 (the session still starts without the folder's map, graph and documents)
882
+ project = None
883
+ out["project"] = project
884
+ out["need_model"] = automatic(args, core_factory) and not has_main_model(args, host)
885
+ if not out["need_model"]:
886
+ out["session"] = start(args, core_factory, project, prompt=False)
887
+ except Exception as e: # noqa: BLE001 (the foreground tries again and shows what went wrong)
888
+ out["error"] = e
889
+
890
+
891
+ def finish_session(args, core_factory: Callable | None, ready: dict, console, boot: Boot) -> PalSession | None:
892
+ """The real session, once the background setup is done. Asks the person only what it must (a first model, an API key)."""
893
+ session = ready.get("session")
796
894
  if session is None:
797
- return 1
798
- session.local_status = local
799
- session.brand = "bench" if getattr(args, "bench_screen", False) else "cli"
800
- if project is not None:
801
- ui.show_project(console, session)
802
- console.print()
803
- box = {"wf": Workflow(session)}
804
- _bind_status(box["wf"])
805
- if box["wf"].notice:
806
- ui.say(console, Text(box["wf"].notice, style=theme.RED_HI))
807
- console.print()
808
- session.notice = box["wf"].notice.split(",")[0] + ": started a new plan"
809
- first, draft = True, ""
895
+ host = args.host or backend.DEFAULT_HOST
896
+ if ready.get("need_model"):
897
+ from pal.terminal.setup import choose_models
898
+ if not choose_models(console, backend.hardware(), host, helper=False):
899
+ console.print(Text("\nNo model chosen. Run pal cli again to choose one.\n", style=theme.MUTED))
900
+ return None
901
+ session = start(args, core_factory, ready.get("project"))
902
+ if session is None:
903
+ return None
904
+ session.mode = boot.mode
905
+ session.local_status = ready.get("local", "")
906
+ session.brand = boot.brand
907
+ return session
908
+
909
+
910
+ def interactive(args, core_factory: Callable | None = None) -> int:
911
+ """pal cli: the screen is drawn at once; the setup (Ollama, the folder, the helper, the model) runs in the background while you type.
912
+ The model is changed by clicking it (or /model). Only when there is no model at all yet is the person asked for one."""
913
+ import threading
914
+
810
915
  from pal.terminal.history import History
811
- history = History(session.cwd)
916
+ console = ui.make_console()
917
+ boot = Boot(args, backend.load_prefs())
918
+ ready: dict = {}
919
+ prep = threading.Thread(target=prepare_quietly, args=(args, core_factory, ready), daemon=True, name="pal-setup")
920
+ prep.start()
921
+ history = History(boot.cwd)
922
+ state: dict = {"session": None, "box": None}
923
+
924
+ def session_now() -> PalSession | None:
925
+ """The real session: waits for the background setup the first time (only if it has not finished yet)."""
926
+ if state["session"] is None:
927
+ if prep.is_alive():
928
+ with console.status(Text("Getting ready\u2026", style=theme.MUTED), spinner="dots", spinner_style=theme.RED):
929
+ prep.join()
930
+ s = finish_session(args, core_factory, ready, console, boot)
931
+ if s is None:
932
+ return None
933
+ state["session"], state["box"] = s, {"wf": Workflow(s)}
934
+ _bind_status(state["box"]["wf"])
935
+ return state["session"]
936
+
937
+ first, draft, code = True, "", 0
812
938
  try:
813
939
  while True:
814
- text = ui.read_input(session, console, backend.save_prefs, landing=first, version=__version__, history=history, initial=draft,
815
- on_shift_tab=lambda: cycle_autonomy(box["wf"]))
816
- first, session.notice, draft = False, "", ""
940
+ shown = state["session"] or boot
941
+ box = state["box"]
942
+ text = ui.read_input(shown, console, backend.save_prefs, landing=first, version=__version__, history=history, initial=draft,
943
+ on_shift_tab=(lambda: cycle_autonomy(state["box"]["wf"])) if box else None)
944
+ landing, first, shown.notice, draft = first, False, "", ""
817
945
  if text is None:
818
946
  break
819
- if text.startswith("\x00voice\x00"): # Ctrl+Space: record, then back to the box with the words added
947
+ if text.startswith("\x00voice\x00"): # the mic (click or Ctrl+Space): record, then back to the box with the words added
820
948
  before = text[len("\x00voice\x00"):]
821
949
  heard = voice_flow(console)
822
950
  draft = (before + (" " if before and heard else "") + heard) if heard else before
951
+ first = landing
952
+ continue
953
+ session = session_now()
954
+ if session is None:
955
+ code = 1
956
+ break
957
+ box = state["box"]
958
+ if text.startswith("\x00model\x00"): # the model was clicked on the first screen: choose one, then back to it
959
+ draft = text[len("\x00model\x00"):]
960
+ state["session"], _ = handle("/model", args, session, console, core_factory, box)
961
+ first = landing
823
962
  continue
824
963
  history.add(text)
825
964
  text = text.strip()
@@ -833,7 +972,7 @@ def interactive(args, core_factory: Callable | None = None) -> int:
833
972
  continue
834
973
  try:
835
974
  if text.startswith("/"):
836
- session, go = handle(text, args, session, console, core_factory, box)
975
+ state["session"], go = handle(text, args, session, console, core_factory, box)
837
976
  if not go:
838
977
  break
839
978
  elif text and getattr(args, "bench_screen", False): # pal bench: the task both ways, then the comparison
@@ -850,10 +989,12 @@ def interactive(args, core_factory: Callable | None = None) -> int:
850
989
  ui.say(console, Text("Stopped.", style=theme.MUTED))
851
990
  console.print()
852
991
  finally:
853
- box["wf"].close()
854
- session.close()
992
+ if state["box"] is not None:
993
+ state["box"]["wf"].close()
994
+ if state["session"] is not None:
995
+ state["session"].close()
855
996
  console.print(Text("Bye.", style=theme.MUTED))
856
- return 0
997
+ return code
857
998
 
858
999
 
859
1000
  def ask_once(args, core_factory: Callable | None = None) -> int:
@@ -398,7 +398,7 @@ def _get(console: Console, tag: str, installed: list[str], confirm: Callable[[st
398
398
 
399
399
  def choose_models(console: Console, hw: Hardware, host: str = backend.DEFAULT_HOST, pick: Callable[..., str | None] | None = None,
400
400
  puller: Callable[[str], bool] = pull, api_chooser: Callable | None = None, confirm: Callable[[str], bool] | None = None,
401
- info: dict | None = None) -> bool:
401
+ info: dict | None = None, helper: bool = True) -> bool:
402
402
  """Ask which models to use for this session; nothing is chosen in advance. The main model is an external API model (any provider, the key
403
403
  is never saved) or a local model that suits this GPU (installed ones are marked; others are downloaded after asking). Then the helper: a small
404
404
  local model for Pal's own jobs, or none. The choice is saved. False when the person chose nothing."""
@@ -428,6 +428,9 @@ def choose_models(console: Console, hw: Hardware, host: str = backend.DEFAULT_HO
428
428
  if not ready:
429
429
  backend.save_prefs(helper_model="", setup_done="1")
430
430
  return True
431
+ if not helper: # Pal chooses the helper itself (auto_helper)
432
+ backend.save_prefs(setup_done="1")
433
+ return True
431
434
  main = "" if answer == API_OPTION else answer
432
435
  helpers = helper_choices(hw, installed, exclude=main) + [(NO_HELPER, "the main model also does Pal's small jobs (uses more of its tokens)")]
433
436
  helper = pick("Which small local helper should do Pal's own jobs? (routing easy questions, trimming what you type)", helpers, None, True, "Helper")
@@ -453,3 +456,25 @@ def choose_agent(console: Console, pick: Callable[..., str | None] | None = None
453
456
  if mode:
454
457
  backend.save_prefs(mode=mode)
455
458
  return mode
459
+
460
+
461
+ def auto_helper(console: Console, hw: Hardware, host: str = backend.DEFAULT_HOST, puller: Callable[[str], bool] = pull,
462
+ info: dict | None = None) -> str:
463
+ """Pal chooses its own helper: the best small model for this computer. Already installed: used as it is. Not installed: downloaded now
464
+ (Ollama shows its progress). Nothing is asked. Returns the helper's name, or '' when there is none (no GPU, Ollama not running, failed)."""
465
+ from pal.terminal.local_models import api_only, pick_helper
466
+ if api_only(hw):
467
+ return ""
468
+ info = info if info is not None else backend.local_info(host)
469
+ if not info.get("ok"):
470
+ return ""
471
+ best = pick_helper(hw)
472
+ if best.tag not in backend.chat_models(info):
473
+ _say(console, Text(f"Getting Pal's helper model ready: {best.tag} (about {best.size_gb:g} GB, once). It does Pal's small jobs, like trimming what you type.",
474
+ style=theme.MUTED))
475
+ if not puller(best.tag):
476
+ _say(console, Text(f"The helper {best.tag} could not be downloaded: Pal works without it for now and tries again next time.", style=theme.MUTED))
477
+ backend.save_prefs(helper_model="")
478
+ return ""
479
+ backend.save_prefs(helper_model=best.tag)
480
+ return best.tag