snippbot 0.1.0__py3-none-any.whl

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 (740) hide show
  1. claude_gateway/__init__.py +16 -0
  2. claude_gateway/__main__.py +11 -0
  3. claude_gateway/claude/__init__.py +14 -0
  4. claude_gateway/claude/cli.py +174 -0
  5. claude_gateway/claude/parser.py +295 -0
  6. claude_gateway/config.py +428 -0
  7. claude_gateway/data/__init__.py +15 -0
  8. claude_gateway/data/stores.py +82 -0
  9. claude_gateway/handlers/__init__.py +5 -0
  10. claude_gateway/handlers/agents.py +1383 -0
  11. claude_gateway/handlers/anthropic.py +93 -0
  12. claude_gateway/handlers/artifacts.py +246 -0
  13. claude_gateway/handlers/awakening.py +357 -0
  14. claude_gateway/handlers/base.py +111 -0
  15. claude_gateway/handlers/embeddings.py +103 -0
  16. claude_gateway/handlers/health.py +104 -0
  17. claude_gateway/handlers/openai.py +393 -0
  18. claude_gateway/handlers/subagent.py +84 -0
  19. claude_gateway/logging.py +37 -0
  20. claude_gateway/models.py +143 -0
  21. claude_gateway/pipe_read.py +61 -0
  22. claude_gateway/security.py +79 -0
  23. claude_gateway/server.py +256 -0
  24. claude_gateway/ssh/__init__.py +21 -0
  25. claude_gateway/ssh/command.py +31 -0
  26. claude_gateway/ssh/file_access.py +488 -0
  27. claude_gateway/streaming/__init__.py +12 -0
  28. claude_gateway/streaming/sse.py +56 -0
  29. claude_gateway/streaming/state_machine.py +174 -0
  30. snippbot/__init__.py +18 -0
  31. snippbot/__main__.py +6 -0
  32. snippbot/api/__init__.py +16 -0
  33. snippbot/api/agents.py +603 -0
  34. snippbot/api/api_handler.py +2611 -0
  35. snippbot/api/approval.py +111 -0
  36. snippbot/api/assets.py +629 -0
  37. snippbot/api/audio.py +276 -0
  38. snippbot/api/auth_helpers.py +79 -0
  39. snippbot/api/browser.py +507 -0
  40. snippbot/api/browser_ws.py +214 -0
  41. snippbot/api/channels.py +1097 -0
  42. snippbot/api/chat.py +3815 -0
  43. snippbot/api/chat_files.py +366 -0
  44. snippbot/api/custom_providers.py +518 -0
  45. snippbot/api/device_auth.py +480 -0
  46. snippbot/api/device_ws.py +43 -0
  47. snippbot/api/devices.py +1537 -0
  48. snippbot/api/dispatcher.py +369 -0
  49. snippbot/api/email.py +114 -0
  50. snippbot/api/equipment.py +177 -0
  51. snippbot/api/events_ws.py +98 -0
  52. snippbot/api/execution.py +2761 -0
  53. snippbot/api/files.py +351 -0
  54. snippbot/api/game_engine.py +438 -0
  55. snippbot/api/game_saves.py +166 -0
  56. snippbot/api/health.py +117 -0
  57. snippbot/api/hooks.py +825 -0
  58. snippbot/api/image_gen.py +307 -0
  59. snippbot/api/insights.py +580 -0
  60. snippbot/api/internal_token.py +74 -0
  61. snippbot/api/issues.py +711 -0
  62. snippbot/api/license.py +96 -0
  63. snippbot/api/marketplace.py +3753 -0
  64. snippbot/api/marketplace_auth.py +171 -0
  65. snippbot/api/marketplace_oauth.py +315 -0
  66. snippbot/api/mcp_refresh.py +264 -0
  67. snippbot/api/memory_capture.py +402 -0
  68. snippbot/api/memory_maintenance.py +349 -0
  69. snippbot/api/monitor.py +241 -0
  70. snippbot/api/nodes.py +263 -0
  71. snippbot/api/package_builder.py +33 -0
  72. snippbot/api/package_credentials.py +254 -0
  73. snippbot/api/recall_feedback_task.py +99 -0
  74. snippbot/api/reflection_capture.py +819 -0
  75. snippbot/api/remote_sessions.py +796 -0
  76. snippbot/api/router_settings.py +80 -0
  77. snippbot/api/routes.py +13161 -0
  78. snippbot/api/sandbox.py +706 -0
  79. snippbot/api/scenarios.py +129 -0
  80. snippbot/api/scheduler.py +981 -0
  81. snippbot/api/security.py +318 -0
  82. snippbot/api/setup.py +1396 -0
  83. snippbot/api/skill_builder.py +1751 -0
  84. snippbot/api/sub_agents.py +793 -0
  85. snippbot/api/sync.py +108 -0
  86. snippbot/api/thinking.py +730 -0
  87. snippbot/api/tool_metadata_cache.py +145 -0
  88. snippbot/api/wallet.py +846 -0
  89. snippbot/api/workflows.py +843 -0
  90. snippbot/api/workspaces.py +435 -0
  91. snippbot/api/world.py +334 -0
  92. snippbot/api/world_map.py +211 -0
  93. snippbot/channel_adapter/__init__.py +5 -0
  94. snippbot/channel_adapter/__main__.py +92 -0
  95. snippbot/channel_adapter/adapters/__init__.py +47 -0
  96. snippbot/channel_adapter/adapters/discord.py +376 -0
  97. snippbot/channel_adapter/adapters/email.py +503 -0
  98. snippbot/channel_adapter/adapters/google_chat.py +504 -0
  99. snippbot/channel_adapter/adapters/slack.py +311 -0
  100. snippbot/channel_adapter/adapters/teams.py +312 -0
  101. snippbot/channel_adapter/adapters/telegram.py +377 -0
  102. snippbot/channel_adapter/adapters/whatsapp.py +286 -0
  103. snippbot/channel_adapter/app.py +500 -0
  104. snippbot/channel_adapter/attachments.py +92 -0
  105. snippbot/channel_adapter/base.py +146 -0
  106. snippbot/channel_adapter/conversations.py +256 -0
  107. snippbot/channel_adapter/dispatch.py +332 -0
  108. snippbot/channel_adapter/loader.py +190 -0
  109. snippbot/channel_adapter/manager.py +120 -0
  110. snippbot/channel_adapter/models.py +85 -0
  111. snippbot/channel_adapter/rate_limiter.py +101 -0
  112. snippbot/channel_adapter/router.py +161 -0
  113. snippbot/channel_adapter/space_history.py +182 -0
  114. snippbot/channel_adapter/tunnel.py +134 -0
  115. snippbot/cli.py +11 -0
  116. snippbot/daemon.py +435 -0
  117. snippbot/discovery.py +117 -0
  118. snippbot/server.py +2636 -0
  119. snippbot/ui/_x9b404ae321ef/snake.html +283 -0
  120. snippbot/ui/_x9b404ae321ef/space-platformer.html +1132 -0
  121. snippbot/ui/_x9b404ae321ef/space_jumper.html +904 -0
  122. snippbot/ui/_x9b404ae321ef/tower-defense.html +928 -0
  123. snippbot/ui/assets/BrowserPage-B134jSc9.js +14 -0
  124. snippbot/ui/assets/BrowserPage-B134jSc9.js.map +1 -0
  125. snippbot/ui/assets/DeviceDetailPage-B1CdI77u.js +2 -0
  126. snippbot/ui/assets/DeviceDetailPage-B1CdI77u.js.map +1 -0
  127. snippbot/ui/assets/DevicesPage-CfhY-0tL.js +6 -0
  128. snippbot/ui/assets/DevicesPage-CfhY-0tL.js.map +1 -0
  129. snippbot/ui/assets/SandboxPage-B9h2NGJP.js +27 -0
  130. snippbot/ui/assets/SandboxPage-B9h2NGJP.js.map +1 -0
  131. snippbot/ui/assets/SecurityDashboardPage-DOjO-MVc.js +2 -0
  132. snippbot/ui/assets/SecurityDashboardPage-DOjO-MVc.js.map +1 -0
  133. snippbot/ui/assets/StepOutputPreview-B5gWdezJ.js +13 -0
  134. snippbot/ui/assets/StepOutputPreview-B5gWdezJ.js.map +1 -0
  135. snippbot/ui/assets/StepOutputPreview-BZV40eAE.css +1 -0
  136. snippbot/ui/assets/WorkflowBuilderPage-Da_pwRhO.js +367 -0
  137. snippbot/ui/assets/WorkflowBuilderPage-Da_pwRhO.js.map +1 -0
  138. snippbot/ui/assets/WorkflowRunPage-CgcCiDo_.js +2 -0
  139. snippbot/ui/assets/WorkflowRunPage-CgcCiDo_.js.map +1 -0
  140. snippbot/ui/assets/WorkflowsPage-Q0cIvHFm.js +2 -0
  141. snippbot/ui/assets/WorkflowsPage-Q0cIvHFm.js.map +1 -0
  142. snippbot/ui/assets/index-C54biiMe.js +2 -0
  143. snippbot/ui/assets/index-C54biiMe.js.map +1 -0
  144. snippbot/ui/assets/index-D7ZcuCt6.css +1 -0
  145. snippbot/ui/assets/index-FRfh1_if.js +1234 -0
  146. snippbot/ui/assets/index-FRfh1_if.js.map +1 -0
  147. snippbot/ui/assets/logo-alt-D_oF903h.png +0 -0
  148. snippbot/ui/assets/logo-alt-icon-Dt-XLW7h.png +0 -0
  149. snippbot/ui/assets/logo-d_fNF9Hk.png +0 -0
  150. snippbot/ui/assets/logo-icon-C0v6NQ9j.png +0 -0
  151. snippbot/ui/assets/state-BOtVR23t.js +26 -0
  152. snippbot/ui/assets/state-BOtVR23t.js.map +1 -0
  153. snippbot/ui/assets/stepIcons-BL48dla-.js +72 -0
  154. snippbot/ui/assets/stepIcons-BL48dla-.js.map +1 -0
  155. snippbot/ui/assets/ui-BDp1HqTB.js +10 -0
  156. snippbot/ui/assets/ui-BDp1HqTB.js.map +1 -0
  157. snippbot/ui/assets/vendor-C1G_MATc.js +60 -0
  158. snippbot/ui/assets/vendor-C1G_MATc.js.map +1 -0
  159. snippbot/ui/audio/ambient-setup.mp3 +0 -0
  160. snippbot/ui/favicon.png +0 -0
  161. snippbot/ui/frames/common.png +0 -0
  162. snippbot/ui/frames/epic.png +0 -0
  163. snippbot/ui/frames/legendary.png +0 -0
  164. snippbot/ui/frames/rare.png +0 -0
  165. snippbot/ui/frames/uncommon.png +0 -0
  166. snippbot/ui/game/title-bg.png +0 -0
  167. snippbot/ui/index.html +28 -0
  168. snippbot/ui/manifest.json +48 -0
  169. snippbot/ui/old_frames/common.png +0 -0
  170. snippbot/ui/old_frames/epic.png +0 -0
  171. snippbot/ui/old_frames/legendary.png +0 -0
  172. snippbot/ui/old_frames/rare.png +0 -0
  173. snippbot/ui/old_frames/uncommon.png +0 -0
  174. snippbot/ui/sw.js +137 -0
  175. snippbot/wallet/__init__.py +34 -0
  176. snippbot/wallet/manager.py +656 -0
  177. snippbot/websocket/__init__.py +1 -0
  178. snippbot/websocket/websocket_handler.py +640 -0
  179. snippbot-0.1.0.dist-info/METADATA +191 -0
  180. snippbot-0.1.0.dist-info/RECORD +740 -0
  181. snippbot-0.1.0.dist-info/WHEEL +4 -0
  182. snippbot-0.1.0.dist-info/entry_points.txt +2 -0
  183. snippbot_cli/__init__.py +7 -0
  184. snippbot_cli/checks.py +696 -0
  185. snippbot_cli/commands/__init__.py +1 -0
  186. snippbot_cli/commands/agents.py +200 -0
  187. snippbot_cli/commands/auth.py +361 -0
  188. snippbot_cli/commands/channel.py +147 -0
  189. snippbot_cli/commands/completions.py +102 -0
  190. snippbot_cli/commands/config.py +168 -0
  191. snippbot_cli/commands/device.py +241 -0
  192. snippbot_cli/commands/dispatcher.py +547 -0
  193. snippbot_cli/commands/doctor.py +148 -0
  194. snippbot_cli/commands/export.py +133 -0
  195. snippbot_cli/commands/marketplace.py +2686 -0
  196. snippbot_cli/commands/project.py +195 -0
  197. snippbot_cli/commands/reset.py +660 -0
  198. snippbot_cli/commands/secrets.py +263 -0
  199. snippbot_cli/commands/security.py +432 -0
  200. snippbot_cli/commands/service.py +312 -0
  201. snippbot_cli/commands/setup.py +366 -0
  202. snippbot_cli/commands/start.py +138 -0
  203. snippbot_cli/commands/status.py +92 -0
  204. snippbot_cli/commands/stop.py +167 -0
  205. snippbot_cli/commands/uninstall.py +196 -0
  206. snippbot_cli/daemon_client.py +101 -0
  207. snippbot_cli/main.py +111 -0
  208. snippbot_cli/user_config.py +437 -0
  209. snippbot_core/__init__.py +22 -0
  210. snippbot_core/agent_settings.py +188 -0
  211. snippbot_core/approvals.py +513 -0
  212. snippbot_core/assets/__init__.py +10 -0
  213. snippbot_core/assets/folder_store.py +403 -0
  214. snippbot_core/assets/store.py +383 -0
  215. snippbot_core/audio/__init__.py +15 -0
  216. snippbot_core/audio/models.py +98 -0
  217. snippbot_core/audio/providers/__init__.py +4 -0
  218. snippbot_core/audio/providers/base.py +55 -0
  219. snippbot_core/audio/providers/elevenlabs_stt.py +88 -0
  220. snippbot_core/audio/providers/elevenlabs_tts.py +153 -0
  221. snippbot_core/audio/providers/hume_tts.py +172 -0
  222. snippbot_core/audio/providers/local_stt.py +106 -0
  223. snippbot_core/audio/providers/local_tts.py +164 -0
  224. snippbot_core/audio/providers/openai_stt.py +76 -0
  225. snippbot_core/audio/providers/openai_tts.py +110 -0
  226. snippbot_core/audio/service.py +191 -0
  227. snippbot_core/audio/settings.py +96 -0
  228. snippbot_core/audit_service.py +367 -0
  229. snippbot_core/autonomy/__init__.py +22 -0
  230. snippbot_core/autonomy/bridge.py +140 -0
  231. snippbot_core/browser_settings.py +306 -0
  232. snippbot_core/capability_inference.py +131 -0
  233. snippbot_core/channel_store.py +1054 -0
  234. snippbot_core/chat/__init__.py +78 -0
  235. snippbot_core/chat/agent_store.py +588 -0
  236. snippbot_core/chat/agent_sync.py +244 -0
  237. snippbot_core/chat/chat_store.py +1438 -0
  238. snippbot_core/chat/command_processor.py +684 -0
  239. snippbot_core/chat/complexity_classifier.py +126 -0
  240. snippbot_core/chat/context_builder.py +721 -0
  241. snippbot_core/chat/context_refs.py +626 -0
  242. snippbot_core/chat/context_window.py +1500 -0
  243. snippbot_core/chat/gateway_client.py +626 -0
  244. snippbot_core/chat/model_router.py +224 -0
  245. snippbot_core/chat/models.py +190 -0
  246. snippbot_core/chat/prepare.py +1124 -0
  247. snippbot_core/chat/providers/__init__.py +33 -0
  248. snippbot_core/chat/providers/anthropic.py +213 -0
  249. snippbot_core/chat/providers/anthropic_api.py +846 -0
  250. snippbot_core/chat/providers/auth_checks.py +57 -0
  251. snippbot_core/chat/providers/base.py +120 -0
  252. snippbot_core/chat/providers/claude_native.py +2073 -0
  253. snippbot_core/chat/providers/custom.py +391 -0
  254. snippbot_core/chat/providers/deepseek.py +64 -0
  255. snippbot_core/chat/providers/gemini.py +577 -0
  256. snippbot_core/chat/providers/grok.py +25 -0
  257. snippbot_core/chat/providers/groq.py +23 -0
  258. snippbot_core/chat/providers/mistral.py +19 -0
  259. snippbot_core/chat/providers/openai.py +19 -0
  260. snippbot_core/chat/providers/openai_compat.py +522 -0
  261. snippbot_core/chat/providers/openrouter.py +36 -0
  262. snippbot_core/chat/providers/process_manager.py +538 -0
  263. snippbot_core/chat/providers/registry.py +473 -0
  264. snippbot_core/chat/roster.py +503 -0
  265. snippbot_core/chat/router_settings.py +277 -0
  266. snippbot_core/chat/routing.py +226 -0
  267. snippbot_core/chat/runtime_context.py +230 -0
  268. snippbot_core/chat/session.py +1001 -0
  269. snippbot_core/chat/session_context.py +90 -0
  270. snippbot_core/chat/sse.py +74 -0
  271. snippbot_core/chat/token_counter.py +426 -0
  272. snippbot_core/chat/tool_dispatch.py +364 -0
  273. snippbot_core/chat/tool_profile_merge.py +42 -0
  274. snippbot_core/chat/tool_result_aging.py +334 -0
  275. snippbot_core/chat/turn.py +1004 -0
  276. snippbot_core/chat/turn_orchestrator.py +113 -0
  277. snippbot_core/claude_cli.py +140 -0
  278. snippbot_core/compat.py +26 -0
  279. snippbot_core/config.py +772 -0
  280. snippbot_core/dag_builder.py +796 -0
  281. snippbot_core/db/__init__.py +17 -0
  282. snippbot_core/db/migrations.py +239 -0
  283. snippbot_core/device/__init__.py +25 -0
  284. snippbot_core/device/auth.py +1114 -0
  285. snippbot_core/device/capabilities.py +320 -0
  286. snippbot_core/device/execution.py +274 -0
  287. snippbot_core/device/file_transfer.py +507 -0
  288. snippbot_core/device/groups.py +251 -0
  289. snippbot_core/device/health.py +230 -0
  290. snippbot_core/device/manager.py +232 -0
  291. snippbot_core/device/pairing.py +380 -0
  292. snippbot_core/device/protocol.py +1499 -0
  293. snippbot_core/device/queue.py +193 -0
  294. snippbot_core/device/registry.py +1633 -0
  295. snippbot_core/device/router.py +278 -0
  296. snippbot_core/digest/__init__.py +18 -0
  297. snippbot_core/digest/composer.py +158 -0
  298. snippbot_core/digest/scheduler.py +187 -0
  299. snippbot_core/digest/store.py +189 -0
  300. snippbot_core/dispatch/__init__.py +131 -0
  301. snippbot_core/dispatch/bridge.py +347 -0
  302. snippbot_core/dispatch/decision_log.py +230 -0
  303. snippbot_core/dispatch/dispatcher.py +376 -0
  304. snippbot_core/dispatch/executors.py +447 -0
  305. snippbot_core/dispatch/idempotency.py +95 -0
  306. snippbot_core/dispatch/inject.py +244 -0
  307. snippbot_core/dispatch/policy_gate.py +82 -0
  308. snippbot_core/dispatch/proactive.py +379 -0
  309. snippbot_core/dispatch/proactivity_gate.py +92 -0
  310. snippbot_core/dispatch/settings.py +459 -0
  311. snippbot_core/dispatch/shadow.py +246 -0
  312. snippbot_core/dispatch/tier1_deterministic.py +100 -0
  313. snippbot_core/dispatch/tier2_policy.py +65 -0
  314. snippbot_core/dispatch/tier3_classifier.py +359 -0
  315. snippbot_core/dispatch/tier3_provider.py +386 -0
  316. snippbot_core/dispatch/tier4_fallback.py +67 -0
  317. snippbot_core/dispatch/types.py +218 -0
  318. snippbot_core/email/__init__.py +32 -0
  319. snippbot_core/email/config.py +29 -0
  320. snippbot_core/email/imap_client.py +317 -0
  321. snippbot_core/email/insight_email_scheduler.py +169 -0
  322. snippbot_core/email/service.py +156 -0
  323. snippbot_core/email/store.py +187 -0
  324. snippbot_core/email/subscriber.py +147 -0
  325. snippbot_core/email/templates.py +195 -0
  326. snippbot_core/event_bus.py +342 -0
  327. snippbot_core/events.py +1271 -0
  328. snippbot_core/execution_orchestrator.py +1401 -0
  329. snippbot_core/execution_store.py +1225 -0
  330. snippbot_core/executor.py +682 -0
  331. snippbot_core/files/__init__.py +18 -0
  332. snippbot_core/files/models.py +81 -0
  333. snippbot_core/files/pdf_extractor.py +77 -0
  334. snippbot_core/files/storage.py +201 -0
  335. snippbot_core/files/store.py +373 -0
  336. snippbot_core/game/__init__.py +10 -0
  337. snippbot_core/game/combat.py +624 -0
  338. snippbot_core/game/engine.py +358 -0
  339. snippbot_core/game/game_save_store.py +258 -0
  340. snippbot_core/game/gm_prompts.py +365 -0
  341. snippbot_core/game/world_store.py +1033 -0
  342. snippbot_core/history.py +797 -0
  343. snippbot_core/hooks/__init__.py +46 -0
  344. snippbot_core/hooks/analytics.py +346 -0
  345. snippbot_core/hooks/bridge.py +105 -0
  346. snippbot_core/hooks/bundled/__init__.py +122 -0
  347. snippbot_core/hooks/bundled/audit_logger.py +111 -0
  348. snippbot_core/hooks/bundled/boot_md.py +149 -0
  349. snippbot_core/hooks/bundled/context_files.py +144 -0
  350. snippbot_core/hooks/bundled/dispatcher_audit.py +93 -0
  351. snippbot_core/hooks/bundled/session_memory.py +110 -0
  352. snippbot_core/hooks/chains.py +208 -0
  353. snippbot_core/hooks/dispatcher.py +314 -0
  354. snippbot_core/hooks/engine.py +531 -0
  355. snippbot_core/hooks/executor.py +530 -0
  356. snippbot_core/hooks/filters.py +314 -0
  357. snippbot_core/hooks/models.py +451 -0
  358. snippbot_core/hooks/sandbox.py +222 -0
  359. snippbot_core/hooks/sdk.py +280 -0
  360. snippbot_core/hooks/store.py +1528 -0
  361. snippbot_core/hooks/webhook_auth.py +237 -0
  362. snippbot_core/idle_messages.py +59 -0
  363. snippbot_core/image/__init__.py +0 -0
  364. snippbot_core/image/generate.py +149 -0
  365. snippbot_core/image/ocr.py +113 -0
  366. snippbot_core/image/scene_prompts.py +129 -0
  367. snippbot_core/image/vision.py +324 -0
  368. snippbot_core/insight_generator.py +2176 -0
  369. snippbot_core/insight_queue.py +1278 -0
  370. snippbot_core/interface_settings.py +263 -0
  371. snippbot_core/issues/__init__.py +50 -0
  372. snippbot_core/issues/investigator.py +471 -0
  373. snippbot_core/issues/magic_link.py +86 -0
  374. snippbot_core/issues/models.py +314 -0
  375. snippbot_core/issues/notifier.py +175 -0
  376. snippbot_core/issues/security_audit.py +295 -0
  377. snippbot_core/issues/snapshot.py +67 -0
  378. snippbot_core/issues/store.py +1056 -0
  379. snippbot_core/legacy_data_migration.py +290 -0
  380. snippbot_core/license/__init__.py +14 -0
  381. snippbot_core/license/models.py +103 -0
  382. snippbot_core/license/store.py +136 -0
  383. snippbot_core/license/validator.py +155 -0
  384. snippbot_core/marketplace/__init__.py +64 -0
  385. snippbot_core/marketplace/agent_config_support.py +428 -0
  386. snippbot_core/marketplace/auto_updater.py +636 -0
  387. snippbot_core/marketplace/bundled.py +236 -0
  388. snippbot_core/marketplace/channel_support.py +201 -0
  389. snippbot_core/marketplace/essentials.py +45 -0
  390. snippbot_core/marketplace/essentials.toml +29 -0
  391. snippbot_core/marketplace/hook_support.py +302 -0
  392. snippbot_core/marketplace/ignore.py +203 -0
  393. snippbot_core/marketplace/installer.py +2015 -0
  394. snippbot_core/marketplace/job_support.py +251 -0
  395. snippbot_core/marketplace/manifest.py +953 -0
  396. snippbot_core/marketplace/manifest_facts.py +333 -0
  397. snippbot_core/marketplace/mcp_support.py +195 -0
  398. snippbot_core/marketplace/oauth_manager.py +534 -0
  399. snippbot_core/marketplace/oauth_providers.py +83 -0
  400. snippbot_core/marketplace/packaging.py +522 -0
  401. snippbot_core/marketplace/permission_grants.py +377 -0
  402. snippbot_core/marketplace/registry_store.py +428 -0
  403. snippbot_core/marketplace/sandbox_support.py +359 -0
  404. snippbot_core/marketplace/tool_support.py +997 -0
  405. snippbot_core/marketplace/workflow_support.py +828 -0
  406. snippbot_core/mcp/__init__.py +27 -0
  407. snippbot_core/mcp/bridge_registry.py +140 -0
  408. snippbot_core/mcp/catalog.py +202 -0
  409. snippbot_core/mcp/catalog_data.json +492 -0
  410. snippbot_core/mcp/client.py +651 -0
  411. snippbot_core/mcp/manager.py +368 -0
  412. snippbot_core/mcp/oauth.py +195 -0
  413. snippbot_core/mcp/sandbox.py +408 -0
  414. snippbot_core/mcp/tool_bridge_server.py +786 -0
  415. snippbot_core/memory/__init__.py +97 -0
  416. snippbot_core/memory/audit_log.py +338 -0
  417. snippbot_core/memory/clustering.py +349 -0
  418. snippbot_core/memory/consolidation.py +822 -0
  419. snippbot_core/memory/episodic.py +1100 -0
  420. snippbot_core/memory/forgetting.py +644 -0
  421. snippbot_core/memory/hybrid_search.py +697 -0
  422. snippbot_core/memory/keyword_search.py +622 -0
  423. snippbot_core/memory/knowledge_graph.py +1550 -0
  424. snippbot_core/memory/llm_extraction.py +718 -0
  425. snippbot_core/memory/recall_feedback.py +203 -0
  426. snippbot_core/memory/sensory_buffer.py +214 -0
  427. snippbot_core/memory/session.py +143 -0
  428. snippbot_core/memory/vector_index.py +743 -0
  429. snippbot_core/memory/write_pipeline.py +628 -0
  430. snippbot_core/memory_search.py +341 -0
  431. snippbot_core/memory_settings.py +482 -0
  432. snippbot_core/models_dev_catalog.py +387 -0
  433. snippbot_core/multimodal/__init__.py +44 -0
  434. snippbot_core/multimodal/image_blocks.py +230 -0
  435. snippbot_core/node_registry.py +399 -0
  436. snippbot_core/package_builder/__init__.py +70 -0
  437. snippbot_core/package_builder/engine.py +2116 -0
  438. snippbot_core/package_builder/fix_verifier.py +510 -0
  439. snippbot_core/package_builder/packager.py +100 -0
  440. snippbot_core/package_builder/plan.py +680 -0
  441. snippbot_core/package_builder/plugins/__init__.py +190 -0
  442. snippbot_core/package_builder/plugins/hook_plugin.py +389 -0
  443. snippbot_core/package_builder/plugins/mcp_server_plugin.py +497 -0
  444. snippbot_core/package_builder/plugins/tool_plugin.py +535 -0
  445. snippbot_core/package_builder/plugins/workflow_plugin.py +732 -0
  446. snippbot_core/package_builder/quality_audit.py +547 -0
  447. snippbot_core/package_builder/rate_limiter.py +242 -0
  448. snippbot_core/package_builder/sandbox.py +399 -0
  449. snippbot_core/package_builder/session.py +424 -0
  450. snippbot_core/package_builder/spec.py +246 -0
  451. snippbot_core/package_builder/store.py +582 -0
  452. snippbot_core/package_builder/system_prompt.py +561 -0
  453. snippbot_core/package_builder/telemetry.py +234 -0
  454. snippbot_core/payments/__init__.py +26 -0
  455. snippbot_core/payments/spend_authorization.py +231 -0
  456. snippbot_core/permissions.py +1099 -0
  457. snippbot_core/proactivity.py +975 -0
  458. snippbot_core/proactivity_settings.py +142 -0
  459. snippbot_core/profile_settings.py +299 -0
  460. snippbot_core/project_store.py +1349 -0
  461. snippbot_core/projects/__init__.py +7 -0
  462. snippbot_core/projects/plan_buffer.py +177 -0
  463. snippbot_core/projects/refine_agent.py +597 -0
  464. snippbot_core/projects/refine_lock.py +82 -0
  465. snippbot_core/projects/refine_session_registry.py +99 -0
  466. snippbot_core/projects/refine_tools.py +177 -0
  467. snippbot_core/provider_settings.py +555 -0
  468. snippbot_core/provider_sync.py +1082 -0
  469. snippbot_core/provider_sync_task.py +150 -0
  470. snippbot_core/providers.py +141 -0
  471. snippbot_core/push/__init__.py +30 -0
  472. snippbot_core/push/models.py +147 -0
  473. snippbot_core/push/service.py +393 -0
  474. snippbot_core/push/store.py +451 -0
  475. snippbot_core/py.typed +0 -0
  476. snippbot_core/remote_session/__init__.py +111 -0
  477. snippbot_core/remote_session/fan_out.py +287 -0
  478. snippbot_core/remote_session/manager.py +1010 -0
  479. snippbot_core/remote_session/models.py +361 -0
  480. snippbot_core/remote_session/security_gate.py +753 -0
  481. snippbot_core/remote_session/security_store.py +1393 -0
  482. snippbot_core/remote_session/settings.py +306 -0
  483. snippbot_core/remote_session/store.py +1041 -0
  484. snippbot_core/risk_scorer.py +512 -0
  485. snippbot_core/sandbox/__init__.py +66 -0
  486. snippbot_core/sandbox/audit.py +630 -0
  487. snippbot_core/sandbox/config.py +400 -0
  488. snippbot_core/sandbox/dockerfiles/Dockerfile.base +7 -0
  489. snippbot_core/sandbox/dockerfiles/Dockerfile.datascience +11 -0
  490. snippbot_core/sandbox/dockerfiles/Dockerfile.node +9 -0
  491. snippbot_core/sandbox/dockerfiles/Dockerfile.python +9 -0
  492. snippbot_core/sandbox/dockerfiles/Dockerfile.rust +9 -0
  493. snippbot_core/sandbox/gpu.py +311 -0
  494. snippbot_core/sandbox/manager.py +1317 -0
  495. snippbot_core/sandbox/network.py +863 -0
  496. snippbot_core/sandbox/pool.py +242 -0
  497. snippbot_core/sandbox/runtime/__init__.py +20 -0
  498. snippbot_core/sandbox/runtime/base.py +390 -0
  499. snippbot_core/sandbox/runtime/docker.py +1137 -0
  500. snippbot_core/sandbox/runtime/podman.py +1207 -0
  501. snippbot_core/sandbox/runtime/process.py +985 -0
  502. snippbot_core/sandbox/smart.py +205 -0
  503. snippbot_core/sandbox/snapshot.py +520 -0
  504. snippbot_core/sandbox/templates.py +894 -0
  505. snippbot_core/scheduler/__init__.py +31 -0
  506. snippbot_core/scheduler/chains.py +372 -0
  507. snippbot_core/scheduler/chat_commands.py +247 -0
  508. snippbot_core/scheduler/conditions.py +521 -0
  509. snippbot_core/scheduler/delivery.py +1058 -0
  510. snippbot_core/scheduler/detector_settings.py +158 -0
  511. snippbot_core/scheduler/engine.py +652 -0
  512. snippbot_core/scheduler/executor.py +953 -0
  513. snippbot_core/scheduler/models.py +493 -0
  514. snippbot_core/scheduler/nl_parser.py +716 -0
  515. snippbot_core/scheduler/nl_patterns.py +220 -0
  516. snippbot_core/scheduler/retry.py +102 -0
  517. snippbot_core/scheduler/schedule_types.py +431 -0
  518. snippbot_core/scheduler/sessions.py +174 -0
  519. snippbot_core/scheduler/skills_loader.py +366 -0
  520. snippbot_core/scheduler/smart.py +203 -0
  521. snippbot_core/scheduler/store.py +1382 -0
  522. snippbot_core/security/__init__.py +219 -0
  523. snippbot_core/security/csrf.py +323 -0
  524. snippbot_core/security/db_encryption.py +420 -0
  525. snippbot_core/security/dep_scanner.py +233 -0
  526. snippbot_core/security/dlp.py +374 -0
  527. snippbot_core/security/egress.py +312 -0
  528. snippbot_core/security/env_filter.py +157 -0
  529. snippbot_core/security/error_sanitizer.py +57 -0
  530. snippbot_core/security/file_permissions.py +267 -0
  531. snippbot_core/security/input_validator.py +155 -0
  532. snippbot_core/security/master_key.py +303 -0
  533. snippbot_core/security/mcp_validation.py +178 -0
  534. snippbot_core/security/package_audit.py +1176 -0
  535. snippbot_core/security/package_signing.py +631 -0
  536. snippbot_core/security/prompt_injection.py +527 -0
  537. snippbot_core/security/rate_limiter.py +348 -0
  538. snippbot_core/security/registry_security.py +281 -0
  539. snippbot_core/security/sandbox_profile.py +314 -0
  540. snippbot_core/security/scanner.py +1027 -0
  541. snippbot_core/security/secret_store.py +578 -0
  542. snippbot_core/security/skill_vetting.py +551 -0
  543. snippbot_core/security/tls.py +192 -0
  544. snippbot_core/security/url_validation.py +259 -0
  545. snippbot_core/security/workflow_audit.py +406 -0
  546. snippbot_core/security_settings.py +339 -0
  547. snippbot_core/settings_manager.py +279 -0
  548. snippbot_core/settings_store.py +79 -0
  549. snippbot_core/skill_builder/__init__.py +53 -0
  550. snippbot_core/skill_builder/engine.py +13 -0
  551. snippbot_core/skill_builder/packager.py +7 -0
  552. snippbot_core/skill_builder/session.py +7 -0
  553. snippbot_core/skill_builder/spec.py +7 -0
  554. snippbot_core/skill_builder/system_prompt.py +7 -0
  555. snippbot_core/skills_store.py +950 -0
  556. snippbot_core/snipp/__init__.py +110 -0
  557. snippbot_core/snipp/balance.py +403 -0
  558. snippbot_core/snipp/encrypted_storage.py +482 -0
  559. snippbot_core/snipp/recovery_phrase.py +459 -0
  560. snippbot_core/snipp/signing.py +478 -0
  561. snippbot_core/snipp/wallet.py +340 -0
  562. snippbot_core/strategy.py +231 -0
  563. snippbot_core/sub_agent/__init__.py +57 -0
  564. snippbot_core/sub_agent/aggregator.py +376 -0
  565. snippbot_core/sub_agent/concurrency.py +200 -0
  566. snippbot_core/sub_agent/event_digest.py +176 -0
  567. snippbot_core/sub_agent/lifecycle.py +534 -0
  568. snippbot_core/sub_agent/messaging.py +336 -0
  569. snippbot_core/sub_agent/models.py +545 -0
  570. snippbot_core/sub_agent/orchestrator.py +639 -0
  571. snippbot_core/sub_agent/resource_manager.py +227 -0
  572. snippbot_core/sub_agent/role_prompts.py +242 -0
  573. snippbot_core/sub_agent/session.py +1340 -0
  574. snippbot_core/sub_agent/shared_context.py +238 -0
  575. snippbot_core/sub_agent/spawner.py +264 -0
  576. snippbot_core/sub_agent/store.py +987 -0
  577. snippbot_core/sub_agent/team_models.py +123 -0
  578. snippbot_core/sub_agent/team_prompts.py +188 -0
  579. snippbot_core/subprocess_utils.py +80 -0
  580. snippbot_core/sync.py +222 -0
  581. snippbot_core/task_executor.py +1703 -0
  582. snippbot_core/thinking/__init__.py +25 -0
  583. snippbot_core/thinking/daemon.py +955 -0
  584. snippbot_core/thinking/engagement.py +261 -0
  585. snippbot_core/thinking/engine.py +632 -0
  586. snippbot_core/thinking/models.py +251 -0
  587. snippbot_core/thinking/store.py +626 -0
  588. snippbot_core/thinking/triggers.py +193 -0
  589. snippbot_core/tier_inference.py +187 -0
  590. snippbot_core/tools/__init__.py +22 -0
  591. snippbot_core/tools/_python_dispatcher.py +226 -0
  592. snippbot_core/tools/browser/__init__.py +14 -0
  593. snippbot_core/tools/browser/actions.py +438 -0
  594. snippbot_core/tools/browser/auth_manager.py +439 -0
  595. snippbot_core/tools/browser/device_emulation.py +378 -0
  596. snippbot_core/tools/browser/dom_snapshot.py +722 -0
  597. snippbot_core/tools/browser/exceptions.py +126 -0
  598. snippbot_core/tools/browser/file_handler.py +261 -0
  599. snippbot_core/tools/browser/live_stream.py +293 -0
  600. snippbot_core/tools/browser/manager.py +884 -0
  601. snippbot_core/tools/browser/network_manager.py +511 -0
  602. snippbot_core/tools/browser/profiles.py +438 -0
  603. snippbot_core/tools/browser/recorder.py +514 -0
  604. snippbot_core/tools/browser/screen_recorder.py +995 -0
  605. snippbot_core/tools/browser/ssrf_guard.py +331 -0
  606. snippbot_core/tools/browser/stealth.py +141 -0
  607. snippbot_core/tools/browser/tab_manager.py +277 -0
  608. snippbot_core/tools/browser_manager.py +9 -0
  609. snippbot_core/tools/channel_sender.py +406 -0
  610. snippbot_core/tools/definitions.py +1681 -0
  611. snippbot_core/tools/execution_mode.py +104 -0
  612. snippbot_core/tools/executor.py +4917 -0
  613. snippbot_core/tools/invocation_log.py +182 -0
  614. snippbot_core/tools/marketplace_types.py +39 -0
  615. snippbot_core/tools/registry.py +584 -0
  616. snippbot_core/tools/search_providers.py +349 -0
  617. snippbot_core/tools/summarization.py +255 -0
  618. snippbot_core/tools/workflow_dispatch.py +296 -0
  619. snippbot_core/user_model.py +1709 -0
  620. snippbot_core/worker_pool.py +118 -0
  621. snippbot_core/workflows/__init__.py +51 -0
  622. snippbot_core/workflows/dry_run.py +211 -0
  623. snippbot_core/workflows/dsl.py +365 -0
  624. snippbot_core/workflows/executor.py +825 -0
  625. snippbot_core/workflows/models.py +675 -0
  626. snippbot_core/workflows/parser.py +144 -0
  627. snippbot_core/workflows/run_store.py +556 -0
  628. snippbot_core/workflows/scheduler.py +455 -0
  629. snippbot_core/workflows/schema.py +616 -0
  630. snippbot_core/workflows/state_machine.py +277 -0
  631. snippbot_core/workflows/step_executors/__init__.py +502 -0
  632. snippbot_core/workflows/step_executors/approval_gate.py +197 -0
  633. snippbot_core/workflows/step_executors/conditional.py +103 -0
  634. snippbot_core/workflows/step_executors/llm_step.py +405 -0
  635. snippbot_core/workflows/step_executors/loop_step.py +157 -0
  636. snippbot_core/workflows/step_executors/parallel_step.py +197 -0
  637. snippbot_core/workflows/step_executors/subworkflow.py +116 -0
  638. snippbot_core/workflows/step_executors/tool_step.py +138 -0
  639. snippbot_core/workflows/store.py +568 -0
  640. snippbot_core/workflows/template_store.py +590 -0
  641. snippbot_core/workflows/version_control.py +149 -0
  642. snippbot_core/workspace/__init__.py +67 -0
  643. snippbot_core/workspace/evolution.py +278 -0
  644. snippbot_core/workspace/identity_proposals.py +362 -0
  645. snippbot_core/workspace/pending_writes.py +256 -0
  646. snippbot_core/workspace/tools.py +877 -0
  647. snippbot_core/workspace_manager.py +493 -0
  648. vps/__init__.py +7 -0
  649. vps/config/strategy-presets.json +34 -0
  650. vps/data/.gitkeep +0 -0
  651. vps/data/memory_schema.sql +80 -0
  652. vps/data/schema.sql +268 -0
  653. vps/data/user_model_schema.sql +125 -0
  654. vps/lib/__init__.py +101 -0
  655. vps/lib/adaptive_throttle.py +978 -0
  656. vps/lib/agent_workspace.py +731 -0
  657. vps/lib/anticipation.py +1266 -0
  658. vps/lib/api_handler.py +2586 -0
  659. vps/lib/approvals.py +507 -0
  660. vps/lib/auth.py +968 -0
  661. vps/lib/awakening_api.py +639 -0
  662. vps/lib/benchmark.py +351 -0
  663. vps/lib/chaos.py +959 -0
  664. vps/lib/checkpoint.py +497 -0
  665. vps/lib/config.py +227 -0
  666. vps/lib/connection_pool.py +658 -0
  667. vps/lib/credentials.py +841 -0
  668. vps/lib/daemon.py +437 -0
  669. vps/lib/dag_builder.py +871 -0
  670. vps/lib/delight_tracker.py +707 -0
  671. vps/lib/error_handler.py +880 -0
  672. vps/lib/event_bus.py +343 -0
  673. vps/lib/events.py +354 -0
  674. vps/lib/executor.py +681 -0
  675. vps/lib/expertise_detector.py +716 -0
  676. vps/lib/failure_handler.py +946 -0
  677. vps/lib/failure_learning.py +1196 -0
  678. vps/lib/feedback_learner.py +1035 -0
  679. vps/lib/focus_mode.py +1049 -0
  680. vps/lib/frustration_detector.py +713 -0
  681. vps/lib/history.py +837 -0
  682. vps/lib/idle_daemon.py +640 -0
  683. vps/lib/insight_delivery.py +863 -0
  684. vps/lib/insight_queue.py +1072 -0
  685. vps/lib/interest_detector.py +750 -0
  686. vps/lib/memory/__init__.py +49 -0
  687. vps/lib/memory/consolidation.py +643 -0
  688. vps/lib/memory/episodic.py +956 -0
  689. vps/lib/memory/forgetting.py +644 -0
  690. vps/lib/memory/hybrid_search.py +697 -0
  691. vps/lib/memory/keyword_search.py +565 -0
  692. vps/lib/memory/knowledge_graph.py +1244 -0
  693. vps/lib/memory/vector_index.py +736 -0
  694. vps/lib/memory/write_pipeline.py +622 -0
  695. vps/lib/memory_search.py +328 -0
  696. vps/lib/metrics.py +515 -0
  697. vps/lib/parallel_executor.py +871 -0
  698. vps/lib/permissions.py +1046 -0
  699. vps/lib/preference_learner.py +714 -0
  700. vps/lib/proactivity.py +780 -0
  701. vps/lib/profiling.py +659 -0
  702. vps/lib/project_store.py +928 -0
  703. vps/lib/prometheus.py +596 -0
  704. vps/lib/query_optimizer.py +700 -0
  705. vps/lib/rate_limiter.py +416 -0
  706. vps/lib/recovery.py +813 -0
  707. vps/lib/replay.py +935 -0
  708. vps/lib/request_validator.py +665 -0
  709. vps/lib/resource_limits.py +458 -0
  710. vps/lib/risk_scorer.py +512 -0
  711. vps/lib/room_reader.py +690 -0
  712. vps/lib/sandbox.py +1161 -0
  713. vps/lib/snipp/__init__.py +497 -0
  714. vps/lib/snipp/aggregation.py +449 -0
  715. vps/lib/snipp/api_interceptor.py +494 -0
  716. vps/lib/snipp/audit.py +459 -0
  717. vps/lib/snipp/balance.py +379 -0
  718. vps/lib/snipp/encrypted_storage.py +481 -0
  719. vps/lib/snipp/escrow.py +932 -0
  720. vps/lib/snipp/rating.py +855 -0
  721. vps/lib/snipp/receipt.py +400 -0
  722. vps/lib/snipp/receipt_store.py +556 -0
  723. vps/lib/snipp/recovery_phrase.py +459 -0
  724. vps/lib/snipp/reputation_sync.py +544 -0
  725. vps/lib/snipp/request_router.py +961 -0
  726. vps/lib/snipp/revision_handler.py +550 -0
  727. vps/lib/snipp/reward_callback.py +549 -0
  728. vps/lib/snipp/service_registry.py +812 -0
  729. vps/lib/snipp/signing.py +368 -0
  730. vps/lib/snipp/snipp_client.py +567 -0
  731. vps/lib/snipp/utils.py +152 -0
  732. vps/lib/snipp/wallet.py +243 -0
  733. vps/lib/snipp/work_claim.py +550 -0
  734. vps/lib/speculative.py +804 -0
  735. vps/lib/strategy.py +231 -0
  736. vps/lib/style_adapter.py +661 -0
  737. vps/lib/tool_selector.py +1123 -0
  738. vps/lib/user_goals_api.py +824 -0
  739. vps/lib/user_model.py +1565 -0
  740. vps/lib/websocket_handler.py +545 -0
@@ -0,0 +1,16 @@
1
+ """
2
+ Claude Gateway - HTTP Gateway for Claude CLI with Multi-Agent Support
3
+
4
+ A unified gateway server that provides:
5
+ - OpenAI Chat Completions API compatibility (/v1/chat/completions)
6
+ - Anthropic Messages API compatibility (/v1/messages)
7
+ - Multi-agent support with SSH-based file access
8
+ - Real-time streaming via Server-Sent Events (SSE)
9
+ - Tool/function call parsing
10
+ - Embeddings API (with optional sentence-transformers)
11
+ - Artifacts management
12
+ - Awakening experience for onboarding
13
+ """
14
+
15
+ __version__ = "5.1.0"
16
+ __all__ = ["__version__"]
@@ -0,0 +1,11 @@
1
+ """
2
+ Entry point for running the gateway as a module.
3
+
4
+ Usage:
5
+ python -m claude_gateway [port] [config_path]
6
+ """
7
+
8
+ from claude_gateway.server import main
9
+
10
+ if __name__ == "__main__":
11
+ main()
@@ -0,0 +1,14 @@
1
+ """Claude CLI integration - subprocess handling and message parsing."""
2
+
3
+ from claude_gateway.claude.cli import call_claude, call_claude_streaming, find_claude_path, get_claude_path
4
+ from claude_gateway.claude.parser import parse_function_calls, messages_to_prompt, tools_to_prompt
5
+
6
+ __all__ = [
7
+ "call_claude",
8
+ "call_claude_streaming",
9
+ "find_claude_path",
10
+ "get_claude_path",
11
+ "parse_function_calls",
12
+ "messages_to_prompt",
13
+ "tools_to_prompt",
14
+ ]
@@ -0,0 +1,174 @@
1
+ """
2
+ Claude CLI subprocess integration.
3
+
4
+ Handles:
5
+ - Finding the Claude CLI executable
6
+ - Executing non-streaming requests
7
+ - Executing streaming requests with subprocess.Popen
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+ import shutil
14
+ import subprocess
15
+ import sys
16
+ import time
17
+
18
+ from claude_gateway.logging import log
19
+ from claude_gateway.models import map_model_name, DEFAULT_SYSTEM_PROMPT
20
+
21
+
22
+ def find_claude_path() -> str | None:
23
+ """Find claude CLI, checking env var first, then common locations.
24
+
25
+ Returns the path to the Claude CLI binary, or None if not found.
26
+ """
27
+ env_path = os.environ.get("CLAUDE_PATH")
28
+ if env_path and os.path.exists(env_path):
29
+ return env_path
30
+
31
+ # Common locations to check
32
+ candidates = [
33
+ os.path.expanduser("~/.local/bin/claude"),
34
+ os.path.expanduser("~/.npm-global/bin/claude"),
35
+ os.path.expanduser("~/node_modules/.bin/claude"),
36
+ os.path.expanduser("~/.npm/bin/claude"),
37
+ "/usr/local/bin/claude",
38
+ os.path.expanduser("~/bin/claude"),
39
+ ]
40
+
41
+ # On Windows, also check AppData
42
+ if sys.platform == "win32":
43
+ candidates.extend([
44
+ os.path.expanduser("~/AppData/Local/Programs/claude/claude.exe"),
45
+ os.path.expanduser("~/.claude/claude.exe"),
46
+ ])
47
+
48
+ for path in candidates:
49
+ if os.path.exists(path) and os.access(path, os.X_OK):
50
+ return path
51
+
52
+ # Try system PATH as last resort
53
+ return shutil.which("claude")
54
+
55
+
56
+ def get_claude_path() -> str:
57
+ """Get the Claude CLI path, raising FileNotFoundError if not found."""
58
+ path = find_claude_path()
59
+ if path is None:
60
+ raise FileNotFoundError(
61
+ "Claude CLI not found. Install with: npm install -g @anthropic-ai/claude-code"
62
+ )
63
+ return path
64
+
65
+
66
+ def call_claude(
67
+ prompt: str,
68
+ model: str,
69
+ system_prompt: str | None = None,
70
+ disable_tools: bool = True,
71
+ image_temp_dir: str | None = None,
72
+ ) -> tuple[subprocess.CompletedProcess, float, str]:
73
+ """Call Claude CLI and return result (non-streaming).
74
+
75
+ Args:
76
+ prompt: The prompt to send
77
+ model: Model name
78
+ system_prompt: Optional system prompt
79
+ disable_tools: If True, disables internal tool execution so we get raw tool calls
80
+ image_temp_dir: If set, enables Read tool and grants access to this directory
81
+ so Claude can read saved image files.
82
+
83
+ Returns:
84
+ Tuple of (CompletedProcess, elapsed_seconds, cli_model_name)
85
+
86
+ Raises:
87
+ FileNotFoundError: If Claude CLI is not installed
88
+ """
89
+ claude_path = get_claude_path()
90
+ cli_model = map_model_name(model)
91
+ cmd = [claude_path, '-p', prompt, '--model', cli_model, '--output-format', 'json']
92
+
93
+ if image_temp_dir:
94
+ # Enable only the Read tool so Claude can view saved image files
95
+ cmd.extend(['--tools', 'Read'])
96
+ cmd.extend(['--add-dir', image_temp_dir])
97
+ elif disable_tools:
98
+ # Prevent CLI from executing tools so we get raw tool_calls in output.
99
+ # NOTE: --tools '' breaks when MCP tools are configured (disables entire
100
+ # tool_use mechanism, causing hangs). Use --max-turns 1 instead — it lets
101
+ # the model produce tool calls normally but stops the CLI from executing them.
102
+ cmd.extend(['--max-turns', '1'])
103
+
104
+ # Use --system-prompt to override Claude Code's default identity
105
+ effective_system = system_prompt if system_prompt else DEFAULT_SYSTEM_PROMPT
106
+ cmd.extend(['--system-prompt', effective_system])
107
+
108
+ start_time = time.time()
109
+ result = subprocess.run(
110
+ cmd,
111
+ capture_output=True,
112
+ text=True,
113
+ encoding="utf-8",
114
+ timeout=300 # 5 minute timeout
115
+ )
116
+ elapsed = time.time() - start_time
117
+
118
+ return result, elapsed, cli_model
119
+
120
+
121
+ def call_claude_streaming(
122
+ prompt: str,
123
+ model: str,
124
+ system_prompt: str | None = None,
125
+ disable_tools: bool = True,
126
+ image_temp_dir: str | None = None,
127
+ ) -> tuple[subprocess.Popen, str]:
128
+ """Call Claude CLI and return a subprocess with streaming stdout.
129
+
130
+ Returns (process, cli_model). Read process.stdout line by line for
131
+ streaming JSON events. Caller must handle process cleanup.
132
+
133
+ Args:
134
+ prompt: The prompt to send
135
+ model: Model name
136
+ system_prompt: Optional system prompt
137
+ disable_tools: If True, disables internal tool execution so we get raw tool calls
138
+ image_temp_dir: If set, enables Read tool and grants access to this directory
139
+ so Claude can read saved image files.
140
+
141
+ Raises:
142
+ FileNotFoundError: If Claude CLI is not installed
143
+ """
144
+ claude_path = get_claude_path()
145
+ cli_model = map_model_name(model)
146
+ log(f"[CLI] Model mapping: '{model}' -> '{cli_model}'")
147
+ cmd = [
148
+ claude_path, '-p', prompt, '--model', cli_model,
149
+ '--output-format', 'stream-json', '--verbose',
150
+ '--include-partial-messages'
151
+ ]
152
+
153
+ if image_temp_dir:
154
+ # Enable only the Read tool so Claude can view saved image files
155
+ cmd.extend(['--tools', 'Read'])
156
+ cmd.extend(['--add-dir', image_temp_dir])
157
+ log(f"[CLI] Image temp dir: {image_temp_dir} — Read tool enabled")
158
+ elif disable_tools:
159
+ # Prevent CLI from executing tools — see call_claude() for rationale
160
+ cmd.extend(['--max-turns', '1'])
161
+
162
+ effective_system = system_prompt if system_prompt else DEFAULT_SYSTEM_PROMPT
163
+ cmd.extend(['--system-prompt', effective_system])
164
+
165
+ process = subprocess.Popen(
166
+ cmd,
167
+ stdout=subprocess.PIPE,
168
+ stderr=subprocess.PIPE,
169
+ text=True,
170
+ encoding="utf-8",
171
+ bufsize=1 # line-buffered
172
+ )
173
+
174
+ return process, cli_model
@@ -0,0 +1,295 @@
1
+ """
2
+ Message parsing and tool/function call conversion.
3
+
4
+ Handles:
5
+ - Parsing XML function_calls from Claude's response text
6
+ - Converting OpenAI tools array to text description
7
+ - Converting messages array to prompt string
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import base64
13
+ import json
14
+ import os
15
+ import re
16
+ import tempfile
17
+ import uuid
18
+
19
+
20
+ def parse_function_calls(text: str) -> tuple[str, list[dict]]:
21
+ """Parse XML function_calls from Claude's response text.
22
+
23
+ Returns tuple: (clean_text, tool_calls_list)
24
+ - clean_text: response with function_calls XML removed
25
+ - tool_calls_list: list of OpenAI-format tool_calls
26
+ """
27
+ tool_calls = []
28
+
29
+ # Find all <invoke> blocks (can be inside <function_calls> or standalone)
30
+ invoke_pattern = r'<invoke\s+name="([^"]+)">\s*(.*?)\s*</invoke>'
31
+ matches = re.findall(invoke_pattern, text, re.DOTALL)
32
+
33
+ print(f"[PARSER] Found {len(matches)} invoke blocks in text ({len(text)} chars)", flush=True)
34
+
35
+ for i, (func_name, params_block) in enumerate(matches):
36
+ print(f"[PARSER] Invoke {i}: func_name='{func_name}', params_block_len={len(params_block)}", flush=True)
37
+ # Parse parameters - use non-greedy match to handle content with < characters
38
+ # Old pattern [^<]* would truncate at first < in content (breaks heredocs, etc.)
39
+ param_pattern = r'<parameter\s+name="([^"]+)">(.*?)</parameter>'
40
+ params = dict(re.findall(param_pattern, params_block, re.DOTALL))
41
+ print(f"[PARSER] Invoke {i}: parsed {len(params)} params: {list(params.keys())}", flush=True)
42
+ for pname, pval in params.items():
43
+ print(f"[PARSER] param '{pname}': {len(pval)} chars, preview='{pval[:100]}...'", flush=True)
44
+
45
+ tool_call = {
46
+ "id": f"call_{uuid.uuid4().hex[:24]}",
47
+ "type": "function",
48
+ "function": {
49
+ "name": func_name,
50
+ "arguments": json.dumps(params)
51
+ }
52
+ }
53
+ tool_calls.append(tool_call)
54
+
55
+ # Remove all function_calls blocks and invoke blocks from text
56
+ clean_text = text
57
+ # Remove complete function_calls blocks
58
+ clean_text = re.sub(r'<function_calls>.*?</function_calls>', '', clean_text, flags=re.DOTALL)
59
+ # Remove standalone invoke blocks
60
+ clean_text = re.sub(r'<invoke\s+name="[^"]+">.*?</invoke>', '', clean_text, flags=re.DOTALL)
61
+ # Remove orphaned function_calls tags
62
+ clean_text = re.sub(r'</?function_calls>', '', clean_text)
63
+ # Clean up extra whitespace
64
+ clean_text = re.sub(r'\n\s*\n\s*\n', '\n\n', clean_text).strip()
65
+
66
+ return clean_text, tool_calls
67
+
68
+
69
+ def tools_to_prompt(tools: list | None) -> str:
70
+ """Convert OpenAI tools array to a text description for the prompt.
71
+
72
+ Uses mandatory language to ensure Claude uses tools when appropriate,
73
+ especially for interactive tools like ask_questions.
74
+ """
75
+ if not tools:
76
+ return ""
77
+
78
+ # Check if ask_questions tool is present - use extra strong language
79
+ has_ask_questions = any(
80
+ t.get("function", {}).get("name") == "ask_questions" or t.get("name") == "ask_questions"
81
+ for t in tools if t.get("type") == "function" or "name" in t
82
+ )
83
+
84
+ tool_desc = "\n\n"
85
+ tool_desc += "=" * 60 + "\n"
86
+ tool_desc += "MANDATORY TOOL USAGE INSTRUCTIONS\n"
87
+ tool_desc += "=" * 60 + "\n\n"
88
+
89
+ tool_desc += "You MUST use tools when they are relevant to the conversation.\n"
90
+ tool_desc += "DO NOT write plain text responses when a tool should be used instead.\n\n"
91
+
92
+ if has_ask_questions:
93
+ tool_desc += "CRITICAL: When you need to ask the user questions, you MUST use the `ask_questions` tool.\n"
94
+ tool_desc += "DO NOT write questions as plain markdown text. ALWAYS use the ask_questions tool.\n"
95
+ tool_desc += "This enables interactive UI elements (checkboxes, radio buttons, text inputs).\n\n"
96
+
97
+ tool_desc += "To invoke a tool, output XML in EXACTLY this format:\n"
98
+ tool_desc += '<function_calls>\n<invoke name="TOOL_NAME">\n<parameter name="PARAM">value</parameter>\n</invoke>\n</function_calls>\n\n'
99
+
100
+ tool_desc += "IMPORTANT:\n"
101
+ tool_desc += "- You MUST output the <function_calls> XML block when using tools\n"
102
+ tool_desc += "- The XML must be well-formed with proper opening and closing tags\n"
103
+ tool_desc += "- Use the exact parameter names shown below\n\n"
104
+
105
+ tool_desc += "Available tools:\n"
106
+
107
+ for tool in tools:
108
+ if tool.get("type") == "function":
109
+ func = tool.get("function", {})
110
+ name = func.get("name", "unknown")
111
+ desc = func.get("description", "No description")
112
+ params = func.get("parameters", {})
113
+
114
+ tool_desc += f"\n- {name}: {desc}\n"
115
+ if params.get("properties"):
116
+ tool_desc += " Parameters:\n"
117
+ for pname, pinfo in params["properties"].items():
118
+ ptype = pinfo.get("type", "string")
119
+ pdesc = pinfo.get("description", "")
120
+ required = pname in params.get("required", [])
121
+ req_str = " (required)" if required else ""
122
+ tool_desc += f" - {pname} ({ptype}){req_str}: {pdesc}\n"
123
+ elif "name" in tool:
124
+ # Handle simplified tool format without "function" wrapper
125
+ name = tool.get("name", "unknown")
126
+ desc = tool.get("description", "No description")
127
+ params = tool.get("input_schema", tool.get("parameters", {}))
128
+
129
+ tool_desc += f"\n- {name}: {desc}\n"
130
+ if params.get("properties"):
131
+ tool_desc += " Parameters:\n"
132
+ for pname, pinfo in params["properties"].items():
133
+ ptype = pinfo.get("type", "string")
134
+ pdesc = pinfo.get("description", "")
135
+ required = pname in params.get("required", [])
136
+ req_str = " (required)" if required else ""
137
+ tool_desc += f" - {pname} ({ptype}){req_str}: {pdesc}\n"
138
+
139
+ tool_desc += "\n" + "=" * 60 + "\n"
140
+ tool_desc += "Remember: USE THE TOOLS. Do not write plain text alternatives.\n"
141
+ tool_desc += "=" * 60 + "\n"
142
+
143
+ return tool_desc
144
+
145
+
146
+ def save_message_images(messages: list) -> tuple[list, str | None]:
147
+ """Extract base64 images from messages, save to temp files, replace with file path refs.
148
+
149
+ Scans all messages for image content blocks (OpenAI ``image_url`` and
150
+ Anthropic ``image`` formats). Base64 image data is decoded and written to
151
+ a temporary directory. The original image blocks are replaced with plain
152
+ text blocks containing the temp file path so that Claude CLI can read them
153
+ via the Read tool.
154
+
155
+ Returns:
156
+ (modified_messages, temp_dir) — *temp_dir* is ``None`` when no images
157
+ were found. The caller is responsible for cleaning up *temp_dir* after
158
+ the CLI call completes.
159
+ """
160
+ temp_dir: str | None = None
161
+ modified: list[dict] = []
162
+
163
+ for msg in messages:
164
+ content = msg.get("content", "")
165
+ if not isinstance(content, list):
166
+ modified.append(msg)
167
+ continue
168
+
169
+ new_blocks: list = []
170
+ for block in content:
171
+ if not isinstance(block, dict):
172
+ new_blocks.append(block)
173
+ continue
174
+
175
+ btype = block.get("type")
176
+
177
+ # Anthropic format: {"type": "image", "source": {"type": "base64", ...}}
178
+ if btype == "image" and block.get("source", {}).get("type") == "base64":
179
+ if temp_dir is None:
180
+ temp_dir = tempfile.mkdtemp(prefix="snippbot-images-")
181
+ source = block["source"]
182
+ ext = source.get("media_type", "image/png").split("/")[-1]
183
+ fname = f"{uuid.uuid4().hex[:12]}.{ext}"
184
+ fpath = os.path.join(temp_dir, fname)
185
+ with open(fpath, "wb") as f:
186
+ f.write(base64.b64decode(source["data"]))
187
+ new_blocks.append({"type": "text", "text": f"[Attached image: {fpath}]"})
188
+
189
+ # OpenAI format: {"type": "image_url", "image_url": {"url": "data:..."}}
190
+ elif btype == "image_url":
191
+ url = block.get("image_url", {}).get("url", "")
192
+ if url.startswith("data:"):
193
+ if temp_dir is None:
194
+ temp_dir = tempfile.mkdtemp(prefix="snippbot-images-")
195
+ # data:image/png;base64,AAAA...
196
+ header, data = url.split(",", 1)
197
+ media = header.split(":")[1].split(";")[0]
198
+ ext = media.split("/")[-1]
199
+ fname = f"{uuid.uuid4().hex[:12]}.{ext}"
200
+ fpath = os.path.join(temp_dir, fname)
201
+ with open(fpath, "wb") as f:
202
+ f.write(base64.b64decode(data))
203
+ new_blocks.append({"type": "text", "text": f"[Attached image: {fpath}]"})
204
+ else:
205
+ # Keep HTTP URL references as-is
206
+ new_blocks.append(block)
207
+
208
+ else:
209
+ new_blocks.append(block)
210
+
211
+ modified.append({**msg, "content": new_blocks})
212
+
213
+ return modified, temp_dir
214
+
215
+
216
+ def messages_to_prompt(
217
+ messages: list,
218
+ system_prompt: str | None = None,
219
+ tools: list | None = None
220
+ ) -> str:
221
+ """Convert messages array to a single prompt string."""
222
+ parts = []
223
+
224
+ # Add system prompt first if provided
225
+ if system_prompt:
226
+ parts.append(f"System: {system_prompt}")
227
+
228
+ # Add tools description if provided
229
+ if tools:
230
+ tool_desc = tools_to_prompt(tools)
231
+ if tool_desc:
232
+ parts.append(tool_desc)
233
+
234
+ for msg in messages:
235
+ role = msg.get("role", "user")
236
+ content = msg.get("content", "")
237
+
238
+ # Handle content that's a list of content blocks
239
+ if isinstance(content, list):
240
+ text_parts = []
241
+ for block in content:
242
+ if isinstance(block, dict):
243
+ block_type = block.get("type")
244
+
245
+ if block_type == "text":
246
+ text_parts.append(block.get("text", ""))
247
+
248
+ elif block_type == "image_url":
249
+ # OpenAI format: {"type": "image_url", "image_url": {"url": "..."}}
250
+ image_url = block.get("image_url", {})
251
+ url = image_url.get("url", "")
252
+ if url.startswith("data:"):
253
+ text_parts.append("[Image: base64-encoded image attached]")
254
+ else:
255
+ text_parts.append(f"[Image URL: {url}]")
256
+
257
+ elif block_type == "image":
258
+ # Anthropic format: {"type": "image", "source": {"type": "base64", ...}}
259
+ source = block.get("source", {})
260
+ source_type = source.get("type", "")
261
+ if source_type == "base64":
262
+ media_type = source.get("media_type", "image/png")
263
+ text_parts.append(f"[Image: {media_type} attached]")
264
+ elif source_type == "url":
265
+ url = source.get("url", "")
266
+ text_parts.append(f"[Image URL: {url}]")
267
+
268
+ elif block_type == "file":
269
+ # File attachment
270
+ file_name = block.get("file_name", block.get("name", "file"))
271
+ file_content = block.get("content", block.get("text", ""))
272
+ text_parts.append(f"\n--- Attached File: {file_name} ---\n{file_content}\n--- End File ---\n")
273
+
274
+ elif block_type == "document":
275
+ # Document attachment
276
+ doc_name = block.get("name", block.get("file_name", "document"))
277
+ doc_text = block.get("text", block.get("content", ""))
278
+ text_parts.append(f"\n--- Attached Document: {doc_name} ---\n{doc_text}\n--- End Document ---\n")
279
+
280
+ elif isinstance(block, str):
281
+ text_parts.append(block)
282
+ content = "\n".join(text_parts)
283
+
284
+ if role == "user":
285
+ parts.append(f"Human: {content}")
286
+ elif role == "assistant":
287
+ parts.append(f"Assistant: {content}")
288
+ elif role == "system":
289
+ parts.insert(0, f"System: {content}")
290
+ elif role == "tool":
291
+ # Tool result message
292
+ tool_call_id = msg.get("tool_call_id", "")
293
+ parts.append(f"Tool Result ({tool_call_id}): {content}")
294
+
295
+ return "\n\n".join(parts)