cachibot 0.2.5.dev1__tar.gz → 0.2.6.dev1__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 (185) hide show
  1. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/PKG-INFO +1 -1
  2. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/pyproject.toml +1 -1
  3. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/api/routes/auth.py +29 -3
  4. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/api/routes/bots.py +9 -9
  5. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/api/routes/chats.py +9 -9
  6. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/api/routes/connections.py +13 -9
  7. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/api/routes/contacts.py +5 -5
  8. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/api/routes/documents.py +5 -5
  9. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/api/routes/instructions.py +4 -4
  10. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/api/routes/models.py +14 -4
  11. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/api/routes/skills.py +7 -1
  12. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/api/routes/work.py +46 -46
  13. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/api/websocket.py +4 -2
  14. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/.dockerignore +0 -0
  15. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/.github/workflows/dev.yml +0 -0
  16. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/.github/workflows/publish.yml +0 -0
  17. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/.gitignore +0 -0
  18. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/CLAUDE.md +0 -0
  19. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/CONTRIBUTING.md +0 -0
  20. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/Dockerfile +0 -0
  21. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/LICENSE +0 -0
  22. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/README.md +0 -0
  23. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/cachibot.example.toml +0 -0
  24. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/docker-compose.yml +0 -0
  25. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/.dockerignore +0 -0
  26. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/.gitignore +0 -0
  27. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/Dockerfile +0 -0
  28. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/eslint.config.js +0 -0
  29. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/index.html +0 -0
  30. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/package-lock.json +0 -0
  31. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/package.json +0 -0
  32. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/postcss.config.js +0 -0
  33. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/public/favicon.svg +0 -0
  34. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/App.tsx +0 -0
  35. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/api/auth.ts +0 -0
  36. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/api/client.ts +0 -0
  37. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/api/connections.ts +0 -0
  38. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/api/contacts.ts +0 -0
  39. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/api/index.ts +0 -0
  40. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/api/knowledge.ts +0 -0
  41. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/api/models.ts +0 -0
  42. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/api/skills.ts +0 -0
  43. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/api/websocket.ts +0 -0
  44. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/auth/LoginPage.tsx +0 -0
  45. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/auth/ProtectedRoute.tsx +0 -0
  46. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/auth/SetupPage.tsx +0 -0
  47. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/chat/ChatPanel.tsx +0 -0
  48. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/chat/InputArea.tsx +0 -0
  49. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/chat/MessageList.tsx +0 -0
  50. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/chat/ThinkingIndicator.tsx +0 -0
  51. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/chat/ToolCallList.tsx +0 -0
  52. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/chat/UsageDisplay.tsx +0 -0
  53. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/common/BotIconRenderer.tsx +0 -0
  54. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/common/Button.tsx +0 -0
  55. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/common/Dialog/Dialog.tsx +0 -0
  56. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/common/Dialog/DialogContent.tsx +0 -0
  57. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/common/Dialog/DialogFooter.tsx +0 -0
  58. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/common/Dialog/DialogHeader.tsx +0 -0
  59. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/common/Dialog/DialogStepper.tsx +0 -0
  60. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/common/Dialog/index.ts +0 -0
  61. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/common/MarkdownRenderer.tsx +0 -0
  62. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/common/ModelSelect.tsx +0 -0
  63. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/common/Spinner.tsx +0 -0
  64. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/common/ToolIconRenderer.tsx +0 -0
  65. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/dialogs/ApprovalDialog.tsx +0 -0
  66. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/dialogs/CreateBotDialog.tsx +0 -0
  67. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/dialogs/CreateBotWizard/index.tsx +0 -0
  68. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/dialogs/CreateBotWizard/steps/AppearanceStep.tsx +0 -0
  69. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/dialogs/CreateBotWizard/steps/CapabilitiesStep.tsx +0 -0
  70. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/dialogs/CreateBotWizard/steps/ConfirmStep.tsx +0 -0
  71. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/dialogs/CreateBotWizard/steps/DetailsStep.tsx +0 -0
  72. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/dialogs/CreateBotWizard/steps/ImportStep.tsx +0 -0
  73. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/dialogs/CreateBotWizard/steps/MethodSelectStep.tsx +0 -0
  74. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/dialogs/CreateBotWizard/steps/NamePickerStep.tsx +0 -0
  75. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/dialogs/CreateBotWizard/steps/PersonalityStep.tsx +0 -0
  76. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/dialogs/CreateBotWizard/steps/PreviewStep.tsx +0 -0
  77. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/dialogs/CreateBotWizard/steps/PromptReviewStep.tsx +0 -0
  78. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/dialogs/CreateBotWizard/steps/PurposeStep.tsx +0 -0
  79. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/dialogs/CreateBotWizard/steps/TemplateSelectStep.tsx +0 -0
  80. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/dialogs/SettingsDialog.tsx +0 -0
  81. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/dialogs/ToolConfigDialog.tsx +0 -0
  82. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/knowledge/DocumentList.tsx +0 -0
  83. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/knowledge/DocumentUploader.tsx +0 -0
  84. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/knowledge/InstructionsEditor.tsx +0 -0
  85. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/knowledge/index.ts +0 -0
  86. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/layout/BotRail.tsx +0 -0
  87. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/layout/BotSidebar.tsx +0 -0
  88. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/layout/Header.tsx +0 -0
  89. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/layout/MainLayout.tsx +0 -0
  90. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/layout/Sidebar.tsx +0 -0
  91. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/marketplace/BotCard.tsx +0 -0
  92. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/marketplace/BotDetailDialog.tsx +0 -0
  93. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/marketplace/MarketplaceBrowser.tsx +0 -0
  94. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/marketplace/index.ts +0 -0
  95. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/settings/BotConnectionsPanel.tsx +0 -0
  96. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/settings/ContactsPanel.tsx +0 -0
  97. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/views/AppSettingsView.tsx +0 -0
  98. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/views/ChatView.tsx +0 -0
  99. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/views/ConnectionsView.tsx +0 -0
  100. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/views/DashboardView.tsx +0 -0
  101. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/views/JobsView.tsx +0 -0
  102. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/views/ModelsView.tsx +0 -0
  103. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/views/SchedulesView.tsx +0 -0
  104. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/views/SettingsView.tsx +0 -0
  105. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/views/TasksView.tsx +0 -0
  106. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/views/ToolsView.tsx +0 -0
  107. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/views/UsersView.tsx +0 -0
  108. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/views/WorkView.tsx +0 -0
  109. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/components/views/index.ts +0 -0
  110. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/hooks/useCommands.ts +0 -0
  111. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/hooks/useWebSocket.ts +0 -0
  112. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/index.css +0 -0
  113. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/lib/language-detector.ts +0 -0
  114. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/lib/prompt-generator.ts +0 -0
  115. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/lib/utils.ts +0 -0
  116. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/main.tsx +0 -0
  117. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/stores/auth.ts +0 -0
  118. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/stores/bots.ts +0 -0
  119. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/stores/config.ts +0 -0
  120. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/stores/connections.ts +0 -0
  121. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/stores/contacts.ts +0 -0
  122. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/stores/creation-flow.ts +0 -0
  123. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/stores/creation.ts +0 -0
  124. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/stores/index.ts +0 -0
  125. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/stores/knowledge.ts +0 -0
  126. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/stores/models.ts +0 -0
  127. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/stores/ui.ts +0 -0
  128. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/types/index.ts +0 -0
  129. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/src/vite-env.d.ts +0 -0
  130. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/tailwind.config.js +0 -0
  131. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/tsconfig.json +0 -0
  132. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/tsconfig.tsbuildinfo +0 -0
  133. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/frontend/vite.config.ts +0 -0
  134. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/__init__.py +0 -0
  135. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/agent.py +0 -0
  136. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/api/__init__.py +0 -0
  137. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/api/auth.py +0 -0
  138. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/api/routes/__init__.py +0 -0
  139. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/api/routes/chat.py +0 -0
  140. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/api/routes/config.py +0 -0
  141. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/api/routes/creation.py +0 -0
  142. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/api/routes/health.py +0 -0
  143. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/api/routes/marketplace.py +0 -0
  144. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/api/server.py +0 -0
  145. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/cli.py +0 -0
  146. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/config.py +0 -0
  147. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/data/marketplace_templates.py +0 -0
  148. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/models/__init__.py +0 -0
  149. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/models/auth.py +0 -0
  150. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/models/bot.py +0 -0
  151. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/models/capabilities.py +0 -0
  152. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/models/chat.py +0 -0
  153. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/models/chat_model.py +0 -0
  154. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/models/command.py +0 -0
  155. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/models/config.py +0 -0
  156. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/models/connection.py +0 -0
  157. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/models/job.py +0 -0
  158. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/models/knowledge.py +0 -0
  159. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/models/skill.py +0 -0
  160. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/models/websocket.py +0 -0
  161. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/models/work.py +0 -0
  162. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/services/__init__.py +0 -0
  163. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/services/adapters/__init__.py +0 -0
  164. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/services/adapters/base.py +0 -0
  165. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/services/adapters/discord.py +0 -0
  166. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/services/adapters/telegram.py +0 -0
  167. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/services/auth_service.py +0 -0
  168. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/services/bot_creation_service.py +0 -0
  169. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/services/command_processor.py +0 -0
  170. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/services/context_builder.py +0 -0
  171. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/services/document_processor.py +0 -0
  172. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/services/message_processor.py +0 -0
  173. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/services/name_generator.py +0 -0
  174. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/services/platform_manager.py +0 -0
  175. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/services/skills.py +0 -0
  176. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/services/vector_store.py +0 -0
  177. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/storage/__init__.py +0 -0
  178. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/storage/database.py +0 -0
  179. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/storage/repository.py +0 -0
  180. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/storage/user_repository.py +0 -0
  181. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/storage/work_repository.py +0 -0
  182. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/utils/__init__.py +0 -0
  183. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/src/cachibot/utils/markdown.py +0 -0
  184. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/tests/__init__.py +0 -0
  185. {cachibot-0.2.5.dev1 → cachibot-0.2.6.dev1}/tests/test_cachibot.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cachibot
3
- Version: 0.2.5.dev1
3
+ Version: 0.2.6.dev1
4
4
  Summary: The Armored AI Agent. Cross-platform, secure, yours.
5
5
  Project-URL: Homepage, https://cachibot.dev
6
6
  Project-URL: Documentation, https://cachibot.dev/docs
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cachibot"
7
- version = "0.2.5.dev1"
7
+ version = "0.2.6.dev1"
8
8
  description = "The Armored AI Agent. Cross-platform, secure, yours."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,9 +1,11 @@
1
1
  """Authentication endpoints."""
2
2
 
3
+ import time
3
4
  import uuid
5
+ from collections import defaultdict
4
6
  from datetime import datetime
5
7
 
6
- from fastapi import APIRouter, Depends, HTTPException, status
8
+ from fastapi import APIRouter, Depends, HTTPException, Request, status
7
9
 
8
10
  from cachibot.api.auth import get_admin_user, get_current_user
9
11
  from cachibot.models.auth import (
@@ -26,6 +28,30 @@ from cachibot.storage.user_repository import UserRepository
26
28
 
27
29
  router = APIRouter(prefix="/auth")
28
30
 
31
+ # Simple in-memory rate limiter for auth endpoints
32
+ _rate_limit_store: dict[str, list[float]] = defaultdict(list)
33
+ RATE_LIMIT_WINDOW = 60 # seconds
34
+ RATE_LIMIT_MAX_REQUESTS = 5 # max attempts per window
35
+
36
+
37
+ async def rate_limit_auth(request: Request) -> None:
38
+ """Rate limit authentication endpoints by client IP."""
39
+ client_ip = request.client.host if request.client else "unknown"
40
+ now = time.monotonic()
41
+
42
+ # Prune old entries
43
+ _rate_limit_store[client_ip] = [
44
+ t for t in _rate_limit_store[client_ip] if now - t < RATE_LIMIT_WINDOW
45
+ ]
46
+
47
+ if len(_rate_limit_store[client_ip]) >= RATE_LIMIT_MAX_REQUESTS:
48
+ raise HTTPException(
49
+ status_code=status.HTTP_429_TOO_MANY_REQUESTS,
50
+ detail="Too many attempts. Please try again later.",
51
+ )
52
+
53
+ _rate_limit_store[client_ip].append(now)
54
+
29
55
 
30
56
  @router.get("/setup-required", response_model=SetupStatusResponse)
31
57
  async def check_setup_required() -> SetupStatusResponse:
@@ -35,7 +61,7 @@ async def check_setup_required() -> SetupStatusResponse:
35
61
  return SetupStatusResponse(setup_required=count == 0)
36
62
 
37
63
 
38
- @router.post("/setup", response_model=LoginResponse)
64
+ @router.post("/setup", response_model=LoginResponse, dependencies=[Depends(rate_limit_auth)])
39
65
  async def setup_initial_admin(request: SetupRequest) -> LoginResponse:
40
66
  """
41
67
  Create the initial admin user (first-time setup).
@@ -103,7 +129,7 @@ async def setup_initial_admin(request: SetupRequest) -> LoginResponse:
103
129
  )
104
130
 
105
131
 
106
- @router.post("/login", response_model=LoginResponse)
132
+ @router.post("/login", response_model=LoginResponse, dependencies=[Depends(rate_limit_auth)])
107
133
  async def login(request: LoginRequest) -> LoginResponse:
108
134
  """
109
135
  Login with email or username and password.
@@ -9,7 +9,7 @@ from datetime import datetime
9
9
  from fastapi import APIRouter, Depends, HTTPException
10
10
  from pydantic import BaseModel, Field
11
11
 
12
- from cachibot.api.auth import get_current_user
12
+ from cachibot.api.auth import get_current_user, require_bot_access
13
13
  from cachibot.models.auth import User
14
14
  from cachibot.models.bot import Bot, BotResponse
15
15
  from cachibot.models.skill import BotSkillRequest, SkillResponse
@@ -49,7 +49,7 @@ async def list_bots(
49
49
  @router.get("/{bot_id}")
50
50
  async def get_bot(
51
51
  bot_id: str,
52
- user: User = Depends(get_current_user),
52
+ user: User = Depends(require_bot_access),
53
53
  ) -> BotResponse:
54
54
  """Get a specific bot."""
55
55
  bot = await repo.get_bot(bot_id)
@@ -62,7 +62,7 @@ async def get_bot(
62
62
  async def sync_bot(
63
63
  bot_id: str,
64
64
  body: BotSyncRequest,
65
- user: User = Depends(get_current_user),
65
+ user: User = Depends(require_bot_access),
66
66
  ) -> BotResponse:
67
67
  """Sync a bot from frontend (create or update)."""
68
68
  if body.id != bot_id:
@@ -88,7 +88,7 @@ async def sync_bot(
88
88
  @router.delete("/{bot_id}", status_code=204)
89
89
  async def delete_bot(
90
90
  bot_id: str,
91
- user: User = Depends(get_current_user),
91
+ user: User = Depends(require_bot_access),
92
92
  ) -> None:
93
93
  """Delete a bot."""
94
94
  deleted = await repo.delete_bot(bot_id)
@@ -104,7 +104,7 @@ async def delete_bot(
104
104
  @router.get("/{bot_id}/skills")
105
105
  async def get_bot_skills(
106
106
  bot_id: str,
107
- user: User = Depends(get_current_user),
107
+ user: User = Depends(require_bot_access),
108
108
  ) -> list[SkillResponse]:
109
109
  """Get all activated skills for a bot."""
110
110
  skill_defs = await skills_repo.get_bot_skill_definitions(bot_id)
@@ -114,7 +114,7 @@ async def get_bot_skills(
114
114
  @router.get("/{bot_id}/skills/ids")
115
115
  async def get_bot_skill_ids(
116
116
  bot_id: str,
117
- user: User = Depends(get_current_user),
117
+ user: User = Depends(require_bot_access),
118
118
  ) -> list[str]:
119
119
  """Get just the IDs of activated skills for a bot."""
120
120
  return await skills_repo.get_bot_skills(bot_id)
@@ -124,7 +124,7 @@ async def get_bot_skill_ids(
124
124
  async def activate_bot_skill(
125
125
  bot_id: str,
126
126
  body: BotSkillRequest,
127
- user: User = Depends(get_current_user),
127
+ user: User = Depends(require_bot_access),
128
128
  ) -> dict:
129
129
  """Activate a skill for a bot."""
130
130
  # Verify skill exists
@@ -140,7 +140,7 @@ async def activate_bot_skill(
140
140
  async def deactivate_bot_skill(
141
141
  bot_id: str,
142
142
  skill_id: str,
143
- user: User = Depends(get_current_user),
143
+ user: User = Depends(require_bot_access),
144
144
  ) -> None:
145
145
  """Deactivate a skill for a bot."""
146
146
  deactivated = await skills_repo.deactivate_skill(bot_id, skill_id)
@@ -181,7 +181,7 @@ class BotImportResponse(BaseModel):
181
181
  @router.get("/{bot_id}/export")
182
182
  async def export_bot(
183
183
  bot_id: str,
184
- user: User = Depends(get_current_user),
184
+ user: User = Depends(require_bot_access),
185
185
  ) -> BotExportFormat:
186
186
  """
187
187
  Export a bot configuration as JSON.
@@ -7,7 +7,7 @@ Endpoints for managing bot chats, including platform conversations.
7
7
  from fastapi import APIRouter, Depends, HTTPException
8
8
  from pydantic import BaseModel
9
9
 
10
- from cachibot.api.auth import get_current_user
10
+ from cachibot.api.auth import get_current_user, require_bot_access
11
11
  from cachibot.models.auth import User
12
12
  from cachibot.models.chat_model import ChatResponse
13
13
  from cachibot.models.knowledge import BotMessage
@@ -46,7 +46,7 @@ class MessageResponse(BaseModel):
46
46
  async def list_chats(
47
47
  bot_id: str,
48
48
  include_archived: bool = False,
49
- user: User = Depends(get_current_user),
49
+ user: User = Depends(require_bot_access),
50
50
  ) -> list[ChatResponse]:
51
51
  """Get all chats for a bot (including platform chats). Excludes archived by default."""
52
52
  chats = await chat_repo.get_chats_by_bot(bot_id, include_archived=include_archived)
@@ -71,7 +71,7 @@ class ArchiveResponse(BaseModel):
71
71
  @router.post("/_clear", status_code=200)
72
72
  async def clear_all_chats(
73
73
  bot_id: str,
74
- user: User = Depends(get_current_user),
74
+ user: User = Depends(require_bot_access),
75
75
  ) -> ClearDataResponse:
76
76
  """Delete all chats and messages for a bot (platform data cleanup)."""
77
77
  # Delete all messages first (they reference chats)
@@ -90,7 +90,7 @@ async def clear_all_chats(
90
90
  async def get_chat(
91
91
  bot_id: str,
92
92
  chat_id: str,
93
- user: User = Depends(get_current_user),
93
+ user: User = Depends(require_bot_access),
94
94
  ) -> ChatResponse:
95
95
  """Get a specific chat."""
96
96
  chat = await chat_repo.get_chat(chat_id)
@@ -104,7 +104,7 @@ async def get_chat_messages(
104
104
  bot_id: str,
105
105
  chat_id: str,
106
106
  limit: int = 50,
107
- user: User = Depends(get_current_user),
107
+ user: User = Depends(require_bot_access),
108
108
  ) -> list[MessageResponse]:
109
109
  """Get messages for a chat."""
110
110
  chat = await chat_repo.get_chat(chat_id)
@@ -119,7 +119,7 @@ async def get_chat_messages(
119
119
  async def delete_chat(
120
120
  bot_id: str,
121
121
  chat_id: str,
122
- user: User = Depends(get_current_user),
122
+ user: User = Depends(require_bot_access),
123
123
  ) -> None:
124
124
  """Delete a chat permanently (including messages)."""
125
125
  chat = await chat_repo.get_chat(chat_id)
@@ -136,7 +136,7 @@ async def delete_chat(
136
136
  async def clear_chat_messages(
137
137
  bot_id: str,
138
138
  chat_id: str,
139
- user: User = Depends(get_current_user),
139
+ user: User = Depends(require_bot_access),
140
140
  ) -> ClearDataResponse:
141
141
  """Clear all messages for a chat but keep the chat itself."""
142
142
  chat = await chat_repo.get_chat(chat_id)
@@ -151,7 +151,7 @@ async def clear_chat_messages(
151
151
  async def archive_chat(
152
152
  bot_id: str,
153
153
  chat_id: str,
154
- user: User = Depends(get_current_user),
154
+ user: User = Depends(require_bot_access),
155
155
  ) -> ArchiveResponse:
156
156
  """Archive a chat. Archived chats are hidden and won't receive new messages."""
157
157
  chat = await chat_repo.get_chat(chat_id)
@@ -166,7 +166,7 @@ async def archive_chat(
166
166
  async def unarchive_chat(
167
167
  bot_id: str,
168
168
  chat_id: str,
169
- user: User = Depends(get_current_user),
169
+ user: User = Depends(require_bot_access),
170
170
  ) -> ArchiveResponse:
171
171
  """Unarchive a chat. It will appear in listings and receive messages again."""
172
172
  chat = await chat_repo.get_chat(chat_id)
@@ -4,18 +4,21 @@ Connections API Routes
4
4
  CRUD endpoints for managing bot platform connections.
5
5
  """
6
6
 
7
+ import logging
7
8
  import uuid
8
9
  from datetime import datetime
9
10
 
10
11
  from fastapi import APIRouter, Depends, HTTPException
11
12
  from pydantic import BaseModel
12
13
 
13
- from cachibot.api.auth import get_current_user
14
+ from cachibot.api.auth import get_current_user, require_bot_access
14
15
  from cachibot.models.auth import User
15
16
  from cachibot.models.connection import BotConnection, ConnectionPlatform, ConnectionStatus
16
17
  from cachibot.services.platform_manager import get_platform_manager
17
18
  from cachibot.storage.repository import ConnectionRepository
18
19
 
20
+ logger = logging.getLogger(__name__)
21
+
19
22
  router = APIRouter(prefix="/api/bots/{bot_id}/connections", tags=["connections"])
20
23
 
21
24
  # Repository instance
@@ -79,7 +82,7 @@ class ConnectionResponse(BaseModel):
79
82
  @router.get("")
80
83
  async def list_connections(
81
84
  bot_id: str,
82
- user: User = Depends(get_current_user),
85
+ user: User = Depends(require_bot_access),
83
86
  ) -> list[ConnectionResponse]:
84
87
  """Get all connections for a bot."""
85
88
  connections = await repo.get_connections_by_bot(bot_id)
@@ -90,7 +93,7 @@ async def list_connections(
90
93
  async def create_connection(
91
94
  bot_id: str,
92
95
  body: ConnectionCreate,
93
- user: User = Depends(get_current_user),
96
+ user: User = Depends(require_bot_access),
94
97
  ) -> ConnectionResponse:
95
98
  """Create a new connection for a bot."""
96
99
  if not body.name.strip():
@@ -126,7 +129,7 @@ async def create_connection(
126
129
  async def get_connection(
127
130
  bot_id: str,
128
131
  connection_id: str,
129
- user: User = Depends(get_current_user),
132
+ user: User = Depends(require_bot_access),
130
133
  ) -> ConnectionResponse:
131
134
  """Get a specific connection."""
132
135
  connection = await repo.get_connection(connection_id)
@@ -140,7 +143,7 @@ async def update_connection(
140
143
  bot_id: str,
141
144
  connection_id: str,
142
145
  body: ConnectionUpdate,
143
- user: User = Depends(get_current_user),
146
+ user: User = Depends(require_bot_access),
144
147
  ) -> ConnectionResponse:
145
148
  """Update an existing connection."""
146
149
  connection = await repo.get_connection(connection_id)
@@ -166,7 +169,7 @@ async def update_connection(
166
169
  async def delete_connection(
167
170
  bot_id: str,
168
171
  connection_id: str,
169
- user: User = Depends(get_current_user),
172
+ user: User = Depends(require_bot_access),
170
173
  ) -> None:
171
174
  """Delete a connection."""
172
175
  connection = await repo.get_connection(connection_id)
@@ -185,7 +188,7 @@ async def delete_connection(
185
188
  async def connect_platform(
186
189
  bot_id: str,
187
190
  connection_id: str,
188
- user: User = Depends(get_current_user),
191
+ user: User = Depends(require_bot_access),
189
192
  ) -> ConnectionResponse:
190
193
  """Start a platform connection."""
191
194
  connection = await repo.get_connection(connection_id)
@@ -199,14 +202,15 @@ async def connect_platform(
199
202
  connection = await repo.get_connection(connection_id)
200
203
  return ConnectionResponse.from_connection(connection)
201
204
  except Exception as e:
202
- raise HTTPException(status_code=500, detail=str(e))
205
+ logger.error(f"Failed to connect {connection_id}: {e}")
206
+ raise HTTPException(status_code=500, detail="Failed to start connection")
203
207
 
204
208
 
205
209
  @router.post("/{connection_id}/disconnect")
206
210
  async def disconnect_platform(
207
211
  bot_id: str,
208
212
  connection_id: str,
209
- user: User = Depends(get_current_user),
213
+ user: User = Depends(require_bot_access),
210
214
  ) -> ConnectionResponse:
211
215
  """Stop a platform connection."""
212
216
  connection = await repo.get_connection(connection_id)
@@ -61,7 +61,7 @@ class ContactResponse(BaseModel):
61
61
  @router.get("")
62
62
  async def list_contacts(
63
63
  bot_id: str,
64
- user: User = Depends(get_current_user),
64
+ user: User = Depends(require_bot_access),
65
65
  ) -> list[ContactResponse]:
66
66
  """Get all contacts for a bot."""
67
67
  contacts = await repo.get_contacts_by_bot(bot_id)
@@ -72,7 +72,7 @@ async def list_contacts(
72
72
  async def create_contact(
73
73
  bot_id: str,
74
74
  body: ContactCreate,
75
- user: User = Depends(get_current_user),
75
+ user: User = Depends(require_bot_access),
76
76
  ) -> ContactResponse:
77
77
  """Create a new contact for a bot."""
78
78
  if not body.name.strip():
@@ -95,7 +95,7 @@ async def create_contact(
95
95
  async def get_contact(
96
96
  bot_id: str,
97
97
  contact_id: str,
98
- user: User = Depends(get_current_user),
98
+ user: User = Depends(require_bot_access),
99
99
  ) -> ContactResponse:
100
100
  """Get a specific contact."""
101
101
  contact = await repo.get_contact(contact_id)
@@ -109,7 +109,7 @@ async def update_contact(
109
109
  bot_id: str,
110
110
  contact_id: str,
111
111
  body: ContactUpdate,
112
- user: User = Depends(get_current_user),
112
+ user: User = Depends(require_bot_access),
113
113
  ) -> ContactResponse:
114
114
  """Update an existing contact."""
115
115
  contact = await repo.get_contact(contact_id)
@@ -131,7 +131,7 @@ async def update_contact(
131
131
  async def delete_contact(
132
132
  bot_id: str,
133
133
  contact_id: str,
134
- user: User = Depends(get_current_user),
134
+ user: User = Depends(require_bot_access),
135
135
  ) -> None:
136
136
  """Delete a contact."""
137
137
  contact = await repo.get_contact(contact_id)
@@ -15,7 +15,7 @@ import aiofiles
15
15
  from fastapi import APIRouter, BackgroundTasks, Depends, File, HTTPException, UploadFile
16
16
  from pydantic import BaseModel
17
17
 
18
- from cachibot.api.auth import get_current_user
18
+ from cachibot.api.auth import get_current_user, require_bot_access
19
19
  from cachibot.models.auth import User
20
20
  from cachibot.models.knowledge import Document, DocumentStatus
21
21
  from cachibot.services.document_processor import get_document_processor
@@ -85,7 +85,7 @@ async def upload_document(
85
85
  bot_id: str,
86
86
  file: Annotated[UploadFile, File()],
87
87
  background_tasks: BackgroundTasks,
88
- user: User = Depends(get_current_user),
88
+ user: User = Depends(require_bot_access),
89
89
  ) -> UploadResponse:
90
90
  """
91
91
  Upload a document to the bot's knowledge base.
@@ -163,7 +163,7 @@ async def upload_document(
163
163
  @router.get("/", response_model=list[DocumentResponse])
164
164
  async def list_documents(
165
165
  bot_id: str,
166
- user: User = Depends(get_current_user),
166
+ user: User = Depends(require_bot_access),
167
167
  ) -> list[DocumentResponse]:
168
168
  """List all documents for a bot."""
169
169
  repo = KnowledgeRepository()
@@ -175,7 +175,7 @@ async def list_documents(
175
175
  async def get_document(
176
176
  bot_id: str,
177
177
  document_id: str,
178
- user: User = Depends(get_current_user),
178
+ user: User = Depends(require_bot_access),
179
179
  ) -> DocumentResponse:
180
180
  """Get a specific document."""
181
181
  repo = KnowledgeRepository()
@@ -191,7 +191,7 @@ async def get_document(
191
191
  async def delete_document(
192
192
  bot_id: str,
193
193
  document_id: str,
194
- user: User = Depends(get_current_user),
194
+ user: User = Depends(require_bot_access),
195
195
  ) -> dict:
196
196
  """Delete a document and its chunks."""
197
197
  repo = KnowledgeRepository()
@@ -7,7 +7,7 @@ Endpoints for managing bot custom instructions.
7
7
  from fastapi import APIRouter, Depends
8
8
  from pydantic import BaseModel, Field
9
9
 
10
- from cachibot.api.auth import get_current_user
10
+ from cachibot.api.auth import get_current_user, require_bot_access
11
11
  from cachibot.models.auth import User
12
12
  from cachibot.storage.repository import KnowledgeRepository
13
13
 
@@ -30,7 +30,7 @@ class InstructionsUpdate(BaseModel):
30
30
  @router.get("/", response_model=InstructionsResponse)
31
31
  async def get_instructions(
32
32
  bot_id: str,
33
- user: User = Depends(get_current_user),
33
+ user: User = Depends(require_bot_access),
34
34
  ) -> InstructionsResponse:
35
35
  """Get custom instructions for a bot."""
36
36
  repo = KnowledgeRepository()
@@ -49,7 +49,7 @@ async def get_instructions(
49
49
  async def update_instructions(
50
50
  bot_id: str,
51
51
  data: InstructionsUpdate,
52
- user: User = Depends(get_current_user),
52
+ user: User = Depends(require_bot_access),
53
53
  ) -> InstructionsResponse:
54
54
  """Update custom instructions for a bot."""
55
55
  repo = KnowledgeRepository()
@@ -64,7 +64,7 @@ async def update_instructions(
64
64
  @router.delete("/")
65
65
  async def delete_instructions(
66
66
  bot_id: str,
67
- user: User = Depends(get_current_user),
67
+ user: User = Depends(require_bot_access),
68
68
  ) -> dict:
69
69
  """Delete custom instructions for a bot."""
70
70
  repo = KnowledgeRepository()
@@ -7,9 +7,12 @@ import os
7
7
  import re
8
8
  from pathlib import Path
9
9
 
10
- from fastapi import APIRouter
10
+ from fastapi import APIRouter, Depends, HTTPException
11
11
  from pydantic import BaseModel, Field
12
12
 
13
+ from cachibot.api.auth import get_current_user
14
+ from cachibot.models.auth import User
15
+
13
16
  logger = logging.getLogger("cachibot.api.models")
14
17
  router = APIRouter()
15
18
 
@@ -52,7 +55,7 @@ class DefaultModelUpdate(BaseModel):
52
55
 
53
56
 
54
57
  @router.get("/models", response_model=ModelsResponse)
55
- async def get_models() -> ModelsResponse:
58
+ async def get_models(user: User = Depends(get_current_user)) -> ModelsResponse:
56
59
  """
57
60
  Get all available models from configured providers.
58
61
 
@@ -188,14 +191,17 @@ async def get_models() -> ModelsResponse:
188
191
 
189
192
 
190
193
  @router.get("/models/default", response_model=DefaultModelResponse)
191
- async def get_default_model() -> DefaultModelResponse:
194
+ async def get_default_model(user: User = Depends(get_current_user)) -> DefaultModelResponse:
192
195
  """Get the current default model."""
193
196
  model = os.getenv("CACHIBOT_DEFAULT_MODEL", DEFAULT_MODEL)
194
197
  return DefaultModelResponse(model=model)
195
198
 
196
199
 
197
200
  @router.put("/models/default")
198
- async def set_default_model(body: DefaultModelUpdate) -> dict:
201
+ async def set_default_model(
202
+ body: DefaultModelUpdate,
203
+ user: User = Depends(get_current_user),
204
+ ) -> dict:
199
205
  """
200
206
  Set the default model.
201
207
 
@@ -205,6 +211,10 @@ async def set_default_model(body: DefaultModelUpdate) -> dict:
205
211
  key = "CACHIBOT_DEFAULT_MODEL"
206
212
  value = body.model
207
213
 
214
+ # Reject values with newlines or control chars to prevent .env injection
215
+ if any(c in value for c in ("\n", "\r", "\0")):
216
+ raise HTTPException(status_code=400, detail="Invalid model ID")
217
+
208
218
  # Update .env file
209
219
  content = ""
210
220
  if ENV_PATH.exists():
@@ -84,9 +84,15 @@ async def create_skill(
84
84
  else:
85
85
  filename = "new-skill.md"
86
86
 
87
+ # Sanitize filename to prevent path traversal
88
+ base_name = filename.rsplit(".", 1)[0]
89
+ base_name = re.sub(r"[^a-z0-9_-]+", "-", base_name.lower()).strip("-")
90
+ if not base_name:
91
+ base_name = "new-skill"
92
+
87
93
  # Create in user's .claude/skills directory
88
94
  # Create skill directory with SKILL.md inside
89
- skill_dir = USER_SKILLS_DIR / filename.rsplit(".", 1)[0]
95
+ skill_dir = USER_SKILLS_DIR / base_name
90
96
  counter = 1
91
97
  while skill_dir.exists():
92
98
  base = filename.rsplit(".", 1)[0]