gemcode 0.4.18__tar.gz → 0.4.19__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 (198) hide show
  1. {gemcode-0.4.18/src/gemcode.egg-info → gemcode-0.4.19}/PKG-INFO +14 -1
  2. {gemcode-0.4.18 → gemcode-0.4.19}/README.md +13 -0
  3. {gemcode-0.4.18 → gemcode-0.4.19}/pyproject.toml +1 -1
  4. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/agent.py +69 -7
  5. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/callbacks.py +11 -2
  6. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/cli.py +7 -0
  7. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/invoke.py +81 -4
  8. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/org.py +11 -5
  9. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/repl_commands.py +19 -0
  10. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/repl_slash.py +89 -0
  11. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/session_store.py +1 -2
  12. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/skills.py +171 -1
  13. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/__init__.py +52 -0
  14. gemcode-0.4.19/src/gemcode/web/chat_skills.py +143 -0
  15. gemcode-0.4.19/src/gemcode/web/customize_api.py +681 -0
  16. gemcode-0.4.19/src/gemcode/web/fleet_api.py +471 -0
  17. gemcode-0.4.19/src/gemcode/web/hitl_bridge.py +100 -0
  18. gemcode-0.4.19/src/gemcode/web/preview_api.py +82 -0
  19. gemcode-0.4.19/src/gemcode/web/project_root.py +21 -0
  20. gemcode-0.4.19/src/gemcode/web/runtime_api.py +412 -0
  21. gemcode-0.4.19/src/gemcode/web/serve_state.py +192 -0
  22. gemcode-0.4.19/src/gemcode/web/server.py +628 -0
  23. gemcode-0.4.19/src/gemcode/web/sessions_api.py +46 -0
  24. gemcode-0.4.19/src/gemcode/web/sse_adapter.py +1243 -0
  25. gemcode-0.4.19/src/gemcode/web/terminal_api.py +94 -0
  26. gemcode-0.4.19/src/gemcode/web/web_config_api.py +72 -0
  27. gemcode-0.4.19/src/gemcode/web/workspace_panel_api.py +407 -0
  28. {gemcode-0.4.18 → gemcode-0.4.19/src/gemcode.egg-info}/PKG-INFO +14 -1
  29. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode.egg-info/SOURCES.txt +14 -0
  30. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_skills.py +50 -0
  31. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_tool_context_circulation.py +14 -11
  32. gemcode-0.4.19/tests/test_web_server.py +30 -0
  33. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_web_sse_adapter.py +1 -0
  34. gemcode-0.4.18/src/gemcode/web/sse_adapter.py +0 -247
  35. {gemcode-0.4.18 → gemcode-0.4.19}/LICENSE +0 -0
  36. {gemcode-0.4.18 → gemcode-0.4.19}/MANIFEST.in +0 -0
  37. {gemcode-0.4.18 → gemcode-0.4.19}/setup.cfg +0 -0
  38. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/__init__.py +0 -0
  39. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/__main__.py +0 -0
  40. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/a2a_bridge.py +0 -0
  41. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/agent_habits.py +0 -0
  42. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/agent_intelligence.py +0 -0
  43. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/agent_mesh.py +0 -0
  44. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/agent_triggers.py +0 -0
  45. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/audit.py +0 -0
  46. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/autocompact.py +0 -0
  47. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/automations.py +0 -0
  48. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/autotune.py +0 -0
  49. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/capability_routing.py +0 -0
  50. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/checkpoints.py +0 -0
  51. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/codebase_awareness.py +0 -0
  52. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/compaction.py +0 -0
  53. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/computer_use/__init__.py +0 -0
  54. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/computer_use/browser_computer.py +0 -0
  55. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/config.py +0 -0
  56. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/context_budget.py +0 -0
  57. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/context_warning.py +0 -0
  58. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/credentials.py +0 -0
  59. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/curated_memory.py +0 -0
  60. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/delegation_learning.py +0 -0
  61. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/dynamic_policy.py +0 -0
  62. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/evals/harness.py +0 -0
  63. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/event_bus.py +0 -0
  64. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/fleet_reports.py +0 -0
  65. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/hitl_session.py +0 -0
  66. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/hooks.py +0 -0
  67. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/ide_protocol.py +0 -0
  68. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/ide_stdio.py +0 -0
  69. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/intent_classifier.py +0 -0
  70. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/interactions.py +0 -0
  71. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/kaira_client.py +0 -0
  72. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/kaira_daemon.py +0 -0
  73. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/kaira_ipc.py +0 -0
  74. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/kaira_job_store.py +0 -0
  75. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/learning.py +0 -0
  76. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/limits.py +0 -0
  77. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/live_audio_engine.py +0 -0
  78. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/logging_config.py +0 -0
  79. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/mcp_loader.py +0 -0
  80. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/memory/__init__.py +0 -0
  81. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/memory/embedding_memory_service.py +0 -0
  82. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/memory/file_memory_service.py +0 -0
  83. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/modality_tools.py +0 -0
  84. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/model_errors.py +0 -0
  85. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/model_routing.py +0 -0
  86. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/multimodal_input.py +0 -0
  87. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/openapi_loader.py +0 -0
  88. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/output_styles.py +0 -0
  89. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/paths.py +0 -0
  90. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/permissions.py +0 -0
  91. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/plugins/__init__.py +0 -0
  92. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
  93. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  94. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/policy_profile.py +0 -0
  95. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/pricing.py +0 -0
  96. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/prompt_suggestions.py +0 -0
  97. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/query/__init__.py +0 -0
  98. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/query/config.py +0 -0
  99. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/query/deps.py +0 -0
  100. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/query/engine.py +0 -0
  101. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/query/stop_hooks.py +0 -0
  102. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/query/token_budget.py +0 -0
  103. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/query/transitions.py +0 -0
  104. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/query_sanitizer.py +0 -0
  105. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/refine.py +0 -0
  106. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/review_agent.py +0 -0
  107. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/rules.py +0 -0
  108. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/self_healing.py +0 -0
  109. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/session_runtime.py +0 -0
  110. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/session_summariser.py +0 -0
  111. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/slash_commands.py +0 -0
  112. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/thinking.py +0 -0
  113. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tool_prompt_manifest.py +0 -0
  114. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tool_registry.py +0 -0
  115. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tool_result_store.py +0 -0
  116. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tool_synthesis.py +0 -0
  117. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/automations_tools.py +0 -0
  118. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/bash.py +0 -0
  119. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/browser.py +0 -0
  120. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/compress_memory.py +0 -0
  121. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/curated_memory.py +0 -0
  122. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/edit.py +0 -0
  123. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/filesystem.py +0 -0
  124. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/notebook.py +0 -0
  125. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/notes.py +0 -0
  126. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/org_tools.py +0 -0
  127. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/repo_map.py +0 -0
  128. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/search.py +0 -0
  129. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/shell.py +0 -0
  130. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/shell_gate.py +0 -0
  131. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/skills.py +0 -0
  132. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/subtask.py +0 -0
  133. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/tasks.py +0 -0
  134. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/think.py +0 -0
  135. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/todo.py +0 -0
  136. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/user_choice.py +0 -0
  137. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/veomem_tools.py +0 -0
  138. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/web.py +0 -0
  139. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/web_search.py +0 -0
  140. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools_inspector.py +0 -0
  141. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/trust.py +0 -0
  142. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tui/input_handler.py +0 -0
  143. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tui/scrollback.py +0 -0
  144. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tui/spinner.py +0 -0
  145. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tui/welcome_banner.py +0 -0
  146. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tui/welcome_rich.py +0 -0
  147. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/veomem_bridge.py +0 -0
  148. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/version.py +0 -0
  149. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/vertex.py +0 -0
  150. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/wal.py +0 -0
  151. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/web/__init__.py +0 -0
  152. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/web/terminal_repl.py +0 -0
  153. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/web/web_sse_compat.py +0 -0
  154. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/workspace_hints.py +0 -0
  155. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode.egg-info/dependency_links.txt +0 -0
  156. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode.egg-info/entry_points.txt +0 -0
  157. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode.egg-info/requires.txt +0 -0
  158. {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode.egg-info/top_level.txt +0 -0
  159. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_add_dir.py +0 -0
  160. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_agent_habits.py +0 -0
  161. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_agent_instruction.py +0 -0
  162. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_agent_mesh.py +0 -0
  163. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_autocompact.py +0 -0
  164. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_automations.py +0 -0
  165. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_capability_routing.py +0 -0
  166. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_checkpoint_diff_command.py +0 -0
  167. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_cli_init.py +0 -0
  168. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_compress_memory_tool.py +0 -0
  169. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_computer_use_permissions.py +0 -0
  170. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_context_budget.py +0 -0
  171. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_context_warning.py +0 -0
  172. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_credentials.py +0 -0
  173. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_eval_harness_layout.py +0 -0
  174. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_event_bus.py +0 -0
  175. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_fleet_reports.py +0 -0
  176. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_ide_stdio_attachments.py +0 -0
  177. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_interactive_permission_ask.py +0 -0
  178. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_kaira_ipc_paths.py +0 -0
  179. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_kaira_scheduler.py +0 -0
  180. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_modality_tools.py +0 -0
  181. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_model_error_retry.py +0 -0
  182. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_model_errors.py +0 -0
  183. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_model_routing.py +0 -0
  184. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_multimodal_input.py +0 -0
  185. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_output_styles_and_rules.py +0 -0
  186. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_paths.py +0 -0
  187. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_permissions.py +0 -0
  188. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_prompt_suggestions.py +0 -0
  189. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_repl_commands.py +0 -0
  190. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_repl_slash.py +0 -0
  191. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_session_runtime_cache.py +0 -0
  192. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_slash_commands.py +0 -0
  193. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_slash_completion_registry.py +0 -0
  194. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_thinking_config.py +0 -0
  195. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_token_budget.py +0 -0
  196. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_tools.py +0 -0
  197. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_tools_inspector.py +0 -0
  198. {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_workspace_hints.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.4.18
3
+ Version: 0.4.19
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -220,6 +220,7 @@ All state lives under `.gemcode/` in the project root. No external services requ
220
220
  | IDE stdio | Editor integration over JSONL stdin/stdout |
221
221
  | Agent Mesh | In-process multi-agent orchestration (automatic) |
222
222
  | GemCode Runtime | Optional always-on background scheduler (`gemcode runtime`; alias `gemcode kaira`) |
223
+ | HTTP API (`gemcode serve`) | Built-in web/custom UI backend on `http://127.0.0.1:3001` — chat SSE, sessions, panel, HITL |
223
224
  | A2A server | Cross-machine agent communication via Google A2A protocol |
224
225
  | Live audio (experimental) | Microphone-driven Gemini Live sessions |
225
226
 
@@ -273,6 +274,18 @@ export GOOGLE_API_KEY="your-key"
273
274
  gemcode -C /path/to/project
274
275
  ```
275
276
 
277
+ ### Connect a web or custom UI (optional)
278
+ GemCode ships the **HTTP API** in the PyPI package; frontends are separate clients.
279
+
280
+ ```bash
281
+ gemcode serve -C /path/to/project
282
+ # default: http://127.0.0.1:3001 — point any UI at this URL (Settings → API URL)
283
+ ```
284
+
285
+ From the REPL/TUI: `/serve` (background), `/serve status`, `/serve stop`, `/serve url`.
286
+
287
+ Contract and routes: [`../docs/web-ui-contract.md`](../docs/web-ui-contract.md).
288
+
276
289
  ### One-shot run
277
290
  ```bash
278
291
  gemcode -C /path/to/project "Explain this repository"
@@ -27,6 +27,7 @@ All state lives under `.gemcode/` in the project root. No external services requ
27
27
  | IDE stdio | Editor integration over JSONL stdin/stdout |
28
28
  | Agent Mesh | In-process multi-agent orchestration (automatic) |
29
29
  | GemCode Runtime | Optional always-on background scheduler (`gemcode runtime`; alias `gemcode kaira`) |
30
+ | HTTP API (`gemcode serve`) | Built-in web/custom UI backend on `http://127.0.0.1:3001` — chat SSE, sessions, panel, HITL |
30
31
  | A2A server | Cross-machine agent communication via Google A2A protocol |
31
32
  | Live audio (experimental) | Microphone-driven Gemini Live sessions |
32
33
 
@@ -80,6 +81,18 @@ export GOOGLE_API_KEY="your-key"
80
81
  gemcode -C /path/to/project
81
82
  ```
82
83
 
84
+ ### Connect a web or custom UI (optional)
85
+ GemCode ships the **HTTP API** in the PyPI package; frontends are separate clients.
86
+
87
+ ```bash
88
+ gemcode serve -C /path/to/project
89
+ # default: http://127.0.0.1:3001 — point any UI at this URL (Settings → API URL)
90
+ ```
91
+
92
+ From the REPL/TUI: `/serve` (background), `/serve status`, `/serve stop`, `/serve url`.
93
+
94
+ Contract and routes: [`../docs/web-ui-contract.md`](../docs/web-ui-contract.md).
95
+
83
96
  ### One-shot run
84
97
  ```bash
85
98
  gemcode -C /path/to/project "Explain this repository"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gemcode"
7
- version = "0.4.18"
7
+ version = "0.4.19"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -9,6 +9,7 @@ from __future__ import annotations
9
9
 
10
10
  import inspect
11
11
  from pathlib import Path
12
+ from typing import Any
12
13
 
13
14
  from google.adk.agents.llm_agent import LlmAgent
14
15
 
@@ -25,7 +26,7 @@ from gemcode.config import GemCodeConfig
25
26
  from gemcode.context_budget import make_before_model_context_shrink_callback
26
27
  from gemcode.limits import make_before_model_limits_callback, make_before_model_token_budget_callback
27
28
  from gemcode.thinking import build_thinking_config
28
- from gemcode.tools import build_function_tools
29
+ from gemcode.tools import build_function_tools, filter_tools_for_web_workspace_mode
29
30
  from gemcode.tool_prompt_manifest import build_tool_manifest
30
31
  from gemcode.skills import (
31
32
  build_skill_manifest_text,
@@ -380,6 +381,14 @@ def _build_runtime_facts(cfg: GemCodeConfig) -> str:
380
381
  "(cap via `GEMCODE_FLEET_REPORTS_AUTO_CONTINUE_MAX`, debounce `GEMCODE_FLEET_REPORTS_ENQUEUE_DEBOUNCE_S`)."
381
382
  )
382
383
 
384
+ web_serve_section = (
385
+ "- **Web / custom UI** — `gemcode serve -C <project>` (or `/serve` in the REPL) starts the built-in "
386
+ "HTTP API on `http://127.0.0.1:3001` by default. Any frontend (official web app, your own dashboard, "
387
+ "editor plugin) connects to `/api/*` — chat SSE, sessions, panel, preview ports, HITL approve, org/mesh, "
388
+ "runtime, terminal. The UI is optional and lives outside the PyPI package; GemCode is the server. "
389
+ "Contract: `docs/web-ui-contract.md`."
390
+ )
391
+
383
392
  # ── Git context ───────────────────────────────────────────────────────────
384
393
  git_ctx = _get_git_context(root)
385
394
  git_section = f"\n\n## Git context (snapshot at session start)\n{git_ctx}" if git_ctx else ""
@@ -427,6 +436,7 @@ def _build_runtime_facts(cfg: GemCodeConfig) -> str:
427
436
  - **Your tool palette can grow mid-session:** if the user enables a capability via a slash command, the runner rebuilds and you get new tools on the next turn.
428
437
  - **Memory system:** when `memory ON`, ADK automatically searches `.gemcode/memories.jsonl` and injects relevant past context before each turn. Facts the user tells you in one session can appear in future sessions. You do not need to manage memory explicitly — it is loaded automatically.
429
438
  {kaira_section}
439
+ {web_serve_section}
430
440
  - **UI banner** phrases like "GemCode Pro" are terminal marketing, not a separate API tier.
431
441
  - **Env toggles** (`GEMCODE_ENABLE_COMPUTER_USE`, `GEMCODE_MODEL`, etc.) affect only the OS process that launched gemcode. Pasting `VAR=1` in chat does NOT reconfigure a running session—tell the user to export in their shell, use project `.env`, or restart the CLI.
432
442
  - **Working in subfolders** — call `list_directory(\"Desktop\")`, `glob_files(\"**/query.ts\")`, `read_file(\"testing/ai-edtech-app/src/app/page.tsx\")` directly. Never claim access is blocked unless a tool returned an explicit error.{git_section}{curated_section}{veomem_section}"""
@@ -717,7 +727,7 @@ def build_instruction(cfg: GemCodeConfig) -> str:
717
727
  else ""
718
728
  )
719
729
 
720
- base = f"""You are GemCode — an alternative to Claude Code built on Google ADK and Gemini.
730
+ base = f"""You are GemCode — a local coding agent built on Google ADK and Gemini.
721
731
  You run locally via the GemCode CLI. You are the same agent the user launched — not a hosted portal.
722
732
 
723
733
  {_build_runtime_facts(cfg)}
@@ -1171,7 +1181,26 @@ You have two tools to persist project insights across sessions (auto-memory styl
1171
1181
  workspace_md = _load_agent_workspace_md(cfg.project_root)
1172
1182
  if workspace_md:
1173
1183
  base = f"{base}\n\n{workspace_md}"
1184
+ web_mode = getattr(cfg, "_web_workspace_mode", None)
1185
+ if web_mode in ("code", "agents"):
1186
+ mode_note = (
1187
+ "Agents mode emphasizes org_delegate, org_spawn, run_subtask, and mesh tools for multi-agent work. "
1188
+ if web_mode == "agents"
1189
+ else ""
1190
+ )
1191
+ base = (
1192
+ f"{base}\n\n## Web IDE ({web_mode} mode)\n"
1193
+ f"{mode_note}"
1194
+ "You are running in the GemCode **web UI** with a live connection to the user's local workspace. "
1195
+ "You **have** filesystem tools — use `read_file`, `list_directory`, `repo_map`, etc. "
1196
+ "Never claim you cannot browse or read local files. "
1197
+ "Each user message may include a **Web IDE context** block with the active editor file and workspace root — "
1198
+ 'honor it for phrases like "this file", "the current one", or whole-project analysis requests.'
1199
+ )
1174
1200
  extra = _load_gemini_md(cfg.project_root)
1201
+ web_prompt = getattr(cfg, "_web_system_prompt", None)
1202
+ if isinstance(web_prompt, str) and web_prompt.strip():
1203
+ base = f"{base}\n\n## User preferences (web UI)\n{web_prompt.strip()}"
1175
1204
  if extra.strip():
1176
1205
  return f"{base}\n\n## Project instructions (gemcode.md)\n{extra}"
1177
1206
  return base
@@ -1207,6 +1236,10 @@ def build_root_agent(
1207
1236
  tools = list(_tools)
1208
1237
  else:
1209
1238
  tools = build_function_tools(cfg)
1239
+ tools = filter_tools_for_web_workspace_mode(
1240
+ tools,
1241
+ getattr(cfg, "_web_workspace_mode", None),
1242
+ )
1210
1243
  if getattr(cfg, "enable_memory", False):
1211
1244
  # ADK preload_memory injects retrieved memories into the next llm_request.
1212
1245
  from google.adk.tools import preload_memory
@@ -1214,18 +1247,23 @@ def build_root_agent(
1214
1247
 
1215
1248
  # ADK built-in interactive + artifact tools — always available when ADK supports them.
1216
1249
  # In super mode, ``get_user_choice`` auto-picks the first option (no UI).
1250
+ function_tool_count = len(tools)
1217
1251
  try:
1218
1252
  from gemcode.tools.user_choice import append_user_choice_load_artifacts_exit_loop
1219
1253
 
1220
1254
  append_user_choice_load_artifacts_exit_loop(cfg, tools)
1221
1255
  except Exception:
1222
1256
  pass
1257
+ mixes_adk_builtin_with_functions = (
1258
+ len(tools) > function_tool_count and function_tool_count > 0
1259
+ )
1223
1260
 
1224
- # Agent auto-notes: write project insights to .gemcode/notes.md (project notes file)
1261
+ # Notes tools write to the repo chat mode stays conversational.
1225
1262
  try:
1226
1263
  from gemcode.tools.notes import build_notes_tools
1227
1264
  notes_tools = build_notes_tools(cfg.project_root)
1228
- tools = [*tools, *notes_tools]
1265
+ if getattr(cfg, "_web_workspace_mode", None) != "chat":
1266
+ tools = [*tools, *notes_tools]
1229
1267
  except Exception:
1230
1268
  pass
1231
1269
 
@@ -1268,11 +1306,13 @@ def build_root_agent(
1268
1306
  # Unknown values: stay conservative.
1269
1307
  enable_for_run = bool(getattr(cfg, "enable_deep_research", False))
1270
1308
 
1271
- if enable_for_run and is_gemini_3:
1309
+ if mixes_adk_builtin_with_functions or (
1310
+ enable_for_run and is_gemini_3 and comb_mode != "never"
1311
+ ):
1272
1312
  from google.genai import types
1273
1313
 
1274
- # Gemini "tool context circulation" enables built-in tools results to
1275
- # be combined with your client-side function tools in the same workflow.
1314
+ # Gemini requires this when ADK built-in tools (load_artifacts, get_user_choice, …)
1315
+ # are combined with GemCode function tools in the same turn.
1276
1316
  tool_cfg = types.ToolConfig(include_server_side_tool_invocations=True)
1277
1317
 
1278
1318
  if thinking_cfg is not None or tool_cfg is not None:
@@ -1283,6 +1323,28 @@ def build_root_agent(
1283
1323
  tool_config=tool_cfg,
1284
1324
  )
1285
1325
 
1326
+ web_temp = getattr(cfg, "_web_temperature", None)
1327
+ web_max = getattr(cfg, "_web_max_output_tokens", None)
1328
+ if web_temp is not None or web_max is not None:
1329
+ from google.genai import types
1330
+
1331
+ gen_kwargs: dict[str, Any] = {}
1332
+ if gen_cfg is not None:
1333
+ if getattr(gen_cfg, "thinking_config", None) is not None:
1334
+ gen_kwargs["thinking_config"] = gen_cfg.thinking_config
1335
+ if getattr(gen_cfg, "tool_config", None) is not None:
1336
+ gen_kwargs["tool_config"] = gen_cfg.tool_config
1337
+ elif thinking_cfg is not None:
1338
+ gen_kwargs["thinking_config"] = thinking_cfg
1339
+ if tool_cfg is not None:
1340
+ gen_kwargs["tool_config"] = tool_cfg
1341
+ if web_temp is not None:
1342
+ gen_kwargs["temperature"] = float(web_temp)
1343
+ if web_max is not None:
1344
+ gen_kwargs["max_output_tokens"] = int(web_max)
1345
+ if gen_kwargs:
1346
+ gen_cfg = types.GenerateContentConfig(**gen_kwargs)
1347
+
1286
1348
  # ── ADK multi-agent tree (LLM-controlled transfer) ───────────────────────
1287
1349
  sub_agents = []
1288
1350
  if getattr(cfg, "enable_adk_agent_transfer", True) and _tools is None:
@@ -35,6 +35,15 @@ _ERROR_KIND_PERMISSION_DENIED = "permission_denied"
35
35
  _ERROR_KIND_CIRCUIT_BREAKER = "circuit_breaker"
36
36
  _ERROR_KIND_TOOL_EXCEPTION = "tool_exception"
37
37
  _ERROR_KIND_PERMISSION_BLOCK = "permission_block"
38
+
39
+
40
+ def _interactive_hitl_enabled(cfg: GemCodeConfig) -> bool:
41
+ """True when in-run y/n (or web Approve/Deny) should gate mutating/shell tools."""
42
+ if getattr(cfg, "interactive_permission_ask", False):
43
+ return True
44
+ if getattr(cfg, "_gemcode_web_sse", False) or getattr(cfg, "_web_interactive_hitl", False):
45
+ return True
46
+ return False
38
47
  _BT_CC = "gemcode:bt_cc"
39
48
  _BT_LD = "gemcode:bt_ld"
40
49
  _BT_LG = "gemcode:bt_lg"
@@ -259,7 +268,7 @@ def make_before_tool_callback(cfg: GemCodeConfig):
259
268
  if not cfg.yes_to_all:
260
269
  # In-run HITL: request ADK tool confirmation and pause execution until
261
270
  # the user approves in the current terminal session.
262
- if getattr(cfg, "interactive_permission_ask", False):
271
+ if _interactive_hitl_enabled(cfg):
263
272
  # After one approval this ADK session, optional skip (see GEMCODE_HITL_STICKY_SESSION).
264
273
  if _hitl_sticky_enabled(tool_context):
265
274
  return None
@@ -310,7 +319,7 @@ def make_before_tool_callback(cfg: GemCodeConfig):
310
319
  "error_kind": _ERROR_KIND_PERMISSION_DENIED,
311
320
  }
312
321
  if not cfg.yes_to_all:
313
- if getattr(cfg, "interactive_permission_ask", False):
322
+ if _interactive_hitl_enabled(cfg):
314
323
  if _hitl_sticky_enabled(tool_context):
315
324
  return None
316
325
  tc_state = _tool_confirmation_state(tool_context)
@@ -598,6 +598,13 @@ def main() -> None:
598
598
  print(f"Saved to {credentials_path()}", file=sys.stderr)
599
599
  return
600
600
 
601
+ # HTTP API for web UIs and other clients: `gemcode serve`
602
+ if len(sys.argv) > 1 and sys.argv[1] == "serve":
603
+ from gemcode.web.server import main as serve_main
604
+
605
+ serve_main(sys.argv[2:])
606
+ return
607
+
601
608
  # Quick command bypass (no prompt parsing): list available Gemini models.
602
609
  if (
603
610
  len(sys.argv) > 1
@@ -90,6 +90,80 @@ def _is_simple_prompt(prompt: str) -> bool:
90
90
  return False
91
91
 
92
92
 
93
+ def _is_simple_prompt_for_cfg(cfg: GemCodeConfig, prompt: str, attachment_paths) -> bool:
94
+ """Code/Agents mode in the web UI always gets full workspace enrichment."""
95
+ if getattr(cfg, "_web_workspace_mode", None) in ("code", "agents"):
96
+ return False
97
+ if attachment_paths:
98
+ return False
99
+ return _is_simple_prompt(prompt)
100
+
101
+
102
+ def prepare_turn_prompt(
103
+ cfg: "GemCodeConfig",
104
+ prompt: str,
105
+ *,
106
+ session_id: str,
107
+ attachment_paths: Sequence[Path | str] | None = None,
108
+ consume_fleet_reports: bool = True,
109
+ ) -> str:
110
+ """Apply the same prompt enrichment as ``run_turn`` before invoking the runner."""
111
+ is_simple = _is_simple_prompt_for_cfg(cfg, prompt, attachment_paths)
112
+
113
+ if not is_simple:
114
+ try:
115
+ from gemcode.agent_mesh import ensure_mesh
116
+ mesh = ensure_mesh(cfg)
117
+ mesh.start()
118
+ except Exception:
119
+ pass
120
+
121
+ try:
122
+ if not getattr(cfg, "_intelligence_bootstrapped", False):
123
+ object.__setattr__(cfg, "_intelligence_bootstrapped", True)
124
+ from gemcode.agent_intelligence import first_session_bootstrap
125
+ first_session_bootstrap(cfg)
126
+ except Exception:
127
+ pass
128
+
129
+ try:
130
+ object.__setattr__(cfg, "_active_session_id", session_id)
131
+ except Exception:
132
+ pass
133
+
134
+ if consume_fleet_reports and not is_simple:
135
+ try:
136
+ from gemcode.fleet_reports import drain_for_prompt
137
+ preamble = drain_for_prompt(cfg.project_root)
138
+ if preamble:
139
+ prompt = preamble + "\n\n---\n\n" + (prompt or "")
140
+ except Exception:
141
+ pass
142
+
143
+ if not is_simple:
144
+ try:
145
+ from gemcode.agent_intelligence import enhance_turn
146
+ prompt = enhance_turn(cfg, prompt)
147
+ except Exception:
148
+ pass
149
+
150
+ try:
151
+ from gemcode.codebase_awareness import build_awareness_context
152
+ if getattr(cfg, "_web_workspace_mode", None) != "chat":
153
+ awareness = build_awareness_context(cfg.project_root)
154
+ if awareness:
155
+ prompt = awareness + "\n\n" + prompt
156
+ except Exception:
157
+ pass
158
+
159
+ try:
160
+ object.__setattr__(cfg, "_risk_score", _compute_risk_score(prompt, attachment_paths))
161
+ except Exception:
162
+ pass
163
+
164
+ return prompt
165
+
166
+
93
167
  async def run_turn(
94
168
  runner: Runner,
95
169
  *,
@@ -104,7 +178,9 @@ async def run_turn(
104
178
  """Execute one user message; collect all Events (caller aggregates text)."""
105
179
 
106
180
  # ── Fast path: skip heavy machinery for simple prompts ──────────────────
107
- is_simple = _is_simple_prompt(prompt) and not attachment_paths
181
+ is_simple = _is_simple_prompt_for_cfg(cfg, prompt, attachment_paths) if cfg is not None else (
182
+ _is_simple_prompt(prompt) and not attachment_paths
183
+ )
108
184
 
109
185
  # ── Mesh + bootstrap (only on first call or complex prompts) ────────────
110
186
  if cfg is not None and not is_simple:
@@ -151,9 +227,10 @@ async def run_turn(
151
227
  # Codebase awareness: inject persistent project understanding
152
228
  try:
153
229
  from gemcode.codebase_awareness import build_awareness_context
154
- awareness = build_awareness_context(cfg.project_root)
155
- if awareness:
156
- prompt = awareness + "\n\n" + prompt
230
+ if getattr(cfg, "_web_workspace_mode", None) != "chat":
231
+ awareness = build_awareness_context(cfg.project_root)
232
+ if awareness:
233
+ prompt = awareness + "\n\n" + prompt
157
234
  except Exception:
158
235
  pass
159
236
 
@@ -26,8 +26,10 @@ def resolve_fleet_root(start_root: Path) -> Path:
26
26
  we still want org tools to operate on the shared `.gemcode/org.json` at the
27
27
  parent project root.
28
28
 
29
- Strategy: walk up ancestors until we find `.gemcode/org.json`. If none is found,
30
- fall back to the provided start_root.
29
+ Strategy: walk up from ``start_root`` and return the **nearest** ancestor whose
30
+ ``.gemcode/`` directory contains ``org.json``, ``habits.json``, or
31
+ ``fleet_reports.jsonl``. This keeps workspace-local habits/reports from being
32
+ shadowed by a distant ``org.json`` (e.g. in the user home directory).
31
33
  """
32
34
  try:
33
35
  cur = start_root.resolve()
@@ -35,12 +37,16 @@ def resolve_fleet_root(start_root: Path) -> Path:
35
37
  cur = start_root
36
38
  try:
37
39
  while True:
38
- if (cur / ".gemcode" / "org.json").is_file():
40
+ gem = cur / ".gemcode"
41
+ if (gem / "org.json").is_file():
42
+ return cur
43
+ if (gem / "habits.json").is_file():
44
+ return cur
45
+ if (gem / "fleet_reports.jsonl").is_file():
39
46
  return cur
40
47
  if cur == cur.parent:
41
48
  break
42
- nxt = cur.parent
43
- cur = nxt
49
+ cur = cur.parent
44
50
  except Exception:
45
51
  return start_root
46
52
  return start_root
@@ -56,6 +56,22 @@ def format_doctor_lines(cfg: GemCodeConfig) -> list[str]:
56
56
  lines.append(
57
57
  f" gemcode_version: {os.environ.get('GEMCODE_VERSION', get_version())}"
58
58
  )
59
+ try:
60
+ from gemcode.web.serve_state import DEFAULT_SERVE_PORT, is_serve_running, serve_base_url
61
+
62
+ running, info = is_serve_running(cfg.project_root, port=DEFAULT_SERVE_PORT)
63
+ url = (info or {}).get("url") or serve_base_url("127.0.0.1", DEFAULT_SERVE_PORT)
64
+ if running:
65
+ lines.append(f" web_api: running at {url}")
66
+ health = (info or {}).get("health") if isinstance(info, dict) else None
67
+ if isinstance(health, dict) and health.get("has_api_key") is False:
68
+ lines.append(" web_api_key: MISSING (set GOOGLE_API_KEY or gemcode login)")
69
+ else:
70
+ lines.append(
71
+ f" web_api: not running (try `/serve` or `gemcode serve -C \"{cfg.project_root}\"`)"
72
+ )
73
+ except Exception:
74
+ pass
59
75
  return lines
60
76
 
61
77
 
@@ -234,6 +250,7 @@ SLASH_COMMANDS: list[tuple[str, str]] = [
234
250
  # NOTE: /file /image /img are aliases of /attach; keep alias working but do not list them here.
235
251
  ("kaira", "Background jobs — gemcode runtime (alias: gemcode kaira)"),
236
252
  ("runtime", "Fleet socket status · gemcode runtime · attach/connect"),
253
+ ("serve", "HTTP API for web/custom UIs — gemcode serve on :3001"),
237
254
  ("bus", "Runtime bus — send/publish lightweight messages over IPC"),
238
255
  ("inbox", "Bus inbox filters for this UI (to/topics)"),
239
256
  ("fleet", "Fleet inbox — /fleet show | digest (habits / mesh reports)"),
@@ -331,6 +348,7 @@ def slash_help_lines() -> list[str]:
331
348
  "Slash commands:",
332
349
  " (CLI) gemcode -C DIR Use a project folder as root (recommended vs. ~ )",
333
350
  " (CLI) gemcode login Save or change API key (~/.gemcode/credentials.json)",
351
+ " (CLI) gemcode serve HTTP API for web/custom UIs (default http://127.0.0.1:3001)",
334
352
  "",
335
353
  " Project setup:",
336
354
  " /attach <path> Queue file(s) for the **next** message (PDF, images, …); /attach list|clear",
@@ -394,6 +412,7 @@ def slash_help_lines() -> list[str]:
394
412
  " /login How to run gemcode login (API key outside REPL)",
395
413
  " /live-audio How to run gemcode live-audio (mic → Gemini Live)",
396
414
  " /doctor Environment sanity check",
415
+ " /serve [start|status|stop|url] Start HTTP API for web UI (gemcode serve on :3001)",
397
416
  " /version Print GemCode version hint",
398
417
  " /exit Exit the REPL",
399
418
  "",
@@ -2544,6 +2544,95 @@ async def process_repl_slash(
2544
2544
  out()
2545
2545
  return ReplSlashResult(skip_model_turn=True)
2546
2546
 
2547
+ # ── /serve (built-in HTTP API for web UI) ───────────────────────────────
2548
+ if name == "serve":
2549
+ from gemcode.web.serve_state import (
2550
+ DEFAULT_SERVE_PORT,
2551
+ is_serve_running,
2552
+ serve_base_url,
2553
+ start_background_serve,
2554
+ stop_background_serve,
2555
+ )
2556
+
2557
+ args_s = (sc.args or "").strip().lower()
2558
+ first = args_s.split()[0] if args_s else "start"
2559
+ host = "127.0.0.1"
2560
+ port = DEFAULT_SERVE_PORT
2561
+
2562
+ if first in ("help", "?"):
2563
+ out("Web API (any UI can connect):")
2564
+ out(" /serve Start background server on http://127.0.0.1:3001")
2565
+ out(" /serve status Show whether the API is running")
2566
+ out(" /serve stop Stop the background server for this project")
2567
+ out(" /serve url Print API URL for the web UI")
2568
+ out("")
2569
+ out("Foreground (blocks terminal):")
2570
+ out(f" gemcode serve -C \"{cfg.project_root}\"")
2571
+ out("")
2572
+ out("Web UI: set API URL to http://127.0.0.1:3001 (default) or npm run dev proxies to it.")
2573
+ out()
2574
+ return ReplSlashResult(skip_model_turn=True)
2575
+
2576
+ if first in ("stop", "kill"):
2577
+ result = stop_background_serve(cfg.project_root)
2578
+ if result.get("stopped"):
2579
+ out(f"[serve] stopped pid {result.get('pid') or '(unknown)'}")
2580
+ else:
2581
+ out(f"[serve] {result.get('message') or result.get('error') or 'not running'}")
2582
+ out()
2583
+ return ReplSlashResult(skip_model_turn=True)
2584
+
2585
+ if first in ("status", "ps"):
2586
+ running, info = is_serve_running(cfg.project_root, host=host, port=port)
2587
+ if running and info:
2588
+ out("[serve] running")
2589
+ out(f" url: {info.get('url') or serve_base_url(host, port)}")
2590
+ out(f" project: {info.get('project_root') or cfg.project_root}")
2591
+ if info.get("session_id"):
2592
+ out(f" session_id: {info.get('session_id')}")
2593
+ if info.get("pid"):
2594
+ out(f" pid: {info.get('pid')}")
2595
+ health = info.get("health") if isinstance(info.get("health"), dict) else None
2596
+ if health:
2597
+ out(f" has_api_key: {health.get('has_api_key')}")
2598
+ else:
2599
+ out("[serve] not running — try `/serve` or `gemcode serve`")
2600
+ out()
2601
+ return ReplSlashResult(skip_model_turn=True)
2602
+
2603
+ if first in ("url", "link"):
2604
+ running, info = is_serve_running(cfg.project_root, host=host, port=port)
2605
+ url = (info or {}).get("url") or serve_base_url(host, port)
2606
+ out(f"[serve] API URL: {url}")
2607
+ out(f"[serve] CLI session_id: {session_id}")
2608
+ out("Paste the URL into GemCode web UI → Settings → API URL if not using default.")
2609
+ out()
2610
+ return ReplSlashResult(skip_model_turn=True)
2611
+
2612
+ # start (default)
2613
+ result = start_background_serve(
2614
+ cfg.project_root,
2615
+ host=host,
2616
+ port=port,
2617
+ session_id=session_id,
2618
+ )
2619
+ if not result.get("ok"):
2620
+ out(f"[serve] failed: {result.get('error') or 'unknown error'}")
2621
+ if result.get("log"):
2622
+ out(f" log: {result.get('log')}")
2623
+ elif result.get("already_running"):
2624
+ out(f"[serve] already running at {result.get('url')}")
2625
+ else:
2626
+ out(f"[serve] started at {result.get('url')}")
2627
+ if result.get("warming"):
2628
+ out("[serve] warming up — retry /serve status in a moment")
2629
+ if result.get("log"):
2630
+ out(f" log: {result.get('log')}")
2631
+ out(f"[serve] session_id: {session_id}")
2632
+ out("Open the web UI (npm run dev) — it proxies to port 3001 by default.")
2633
+ out()
2634
+ return ReplSlashResult(skip_model_turn=True)
2635
+
2547
2636
  # ── /runtime ────────────────────────────────────────────────────────────
2548
2637
  if name == "runtime":
2549
2638
  from gemcode.kaira_ipc import fleet_manager_ipc_path_for_workspace
@@ -94,8 +94,7 @@ def list_sessions(project_root: Path, *, max_items: int = 20) -> list[dict[str,
94
94
  events : int — approximate turn count
95
95
  short_id : str — first 8 chars of id
96
96
  """
97
- from gemcode.session_runtime import session_db_path
98
- db = session_db_path(project_root)
97
+ db = project_root / ".gemcode" / "sessions.sqlite"
99
98
  rows = _read_sqlite_sessions(db)
100
99
  meta = _load_meta(project_root)
101
100