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.
- {gemcode-0.4.18/src/gemcode.egg-info → gemcode-0.4.19}/PKG-INFO +14 -1
- {gemcode-0.4.18 → gemcode-0.4.19}/README.md +13 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/pyproject.toml +1 -1
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/agent.py +69 -7
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/callbacks.py +11 -2
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/cli.py +7 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/invoke.py +81 -4
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/org.py +11 -5
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/repl_commands.py +19 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/repl_slash.py +89 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/session_store.py +1 -2
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/skills.py +171 -1
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/__init__.py +52 -0
- gemcode-0.4.19/src/gemcode/web/chat_skills.py +143 -0
- gemcode-0.4.19/src/gemcode/web/customize_api.py +681 -0
- gemcode-0.4.19/src/gemcode/web/fleet_api.py +471 -0
- gemcode-0.4.19/src/gemcode/web/hitl_bridge.py +100 -0
- gemcode-0.4.19/src/gemcode/web/preview_api.py +82 -0
- gemcode-0.4.19/src/gemcode/web/project_root.py +21 -0
- gemcode-0.4.19/src/gemcode/web/runtime_api.py +412 -0
- gemcode-0.4.19/src/gemcode/web/serve_state.py +192 -0
- gemcode-0.4.19/src/gemcode/web/server.py +628 -0
- gemcode-0.4.19/src/gemcode/web/sessions_api.py +46 -0
- gemcode-0.4.19/src/gemcode/web/sse_adapter.py +1243 -0
- gemcode-0.4.19/src/gemcode/web/terminal_api.py +94 -0
- gemcode-0.4.19/src/gemcode/web/web_config_api.py +72 -0
- gemcode-0.4.19/src/gemcode/web/workspace_panel_api.py +407 -0
- {gemcode-0.4.18 → gemcode-0.4.19/src/gemcode.egg-info}/PKG-INFO +14 -1
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode.egg-info/SOURCES.txt +14 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_skills.py +50 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_tool_context_circulation.py +14 -11
- gemcode-0.4.19/tests/test_web_server.py +30 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_web_sse_adapter.py +1 -0
- gemcode-0.4.18/src/gemcode/web/sse_adapter.py +0 -247
- {gemcode-0.4.18 → gemcode-0.4.19}/LICENSE +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/MANIFEST.in +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/setup.cfg +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/__init__.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/__main__.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/a2a_bridge.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/agent_habits.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/agent_intelligence.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/agent_mesh.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/agent_triggers.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/audit.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/autocompact.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/automations.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/autotune.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/capability_routing.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/checkpoints.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/codebase_awareness.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/compaction.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/computer_use/__init__.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/computer_use/browser_computer.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/config.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/context_budget.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/context_warning.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/credentials.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/curated_memory.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/delegation_learning.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/dynamic_policy.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/evals/harness.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/event_bus.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/fleet_reports.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/hitl_session.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/hooks.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/ide_protocol.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/ide_stdio.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/intent_classifier.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/interactions.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/kaira_client.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/kaira_daemon.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/kaira_ipc.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/kaira_job_store.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/learning.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/limits.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/live_audio_engine.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/logging_config.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/mcp_loader.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/memory/__init__.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/memory/embedding_memory_service.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/memory/file_memory_service.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/modality_tools.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/model_errors.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/model_routing.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/multimodal_input.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/openapi_loader.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/output_styles.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/paths.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/permissions.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/plugins/__init__.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/policy_profile.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/pricing.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/prompt_suggestions.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/query/__init__.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/query/config.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/query/deps.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/query/engine.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/query/stop_hooks.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/query/token_budget.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/query/transitions.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/query_sanitizer.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/refine.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/review_agent.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/rules.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/self_healing.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/session_runtime.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/session_summariser.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/slash_commands.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/thinking.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tool_prompt_manifest.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tool_registry.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tool_result_store.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tool_synthesis.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/automations_tools.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/bash.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/browser.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/compress_memory.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/curated_memory.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/edit.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/filesystem.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/notebook.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/notes.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/org_tools.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/repo_map.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/search.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/shell.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/shell_gate.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/skills.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/subtask.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/tasks.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/think.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/todo.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/user_choice.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/veomem_tools.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/web.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools/web_search.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tools_inspector.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/trust.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tui/input_handler.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tui/scrollback.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tui/spinner.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tui/welcome_banner.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/tui/welcome_rich.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/veomem_bridge.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/version.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/vertex.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/wal.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/web/__init__.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/web/terminal_repl.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/web/web_sse_compat.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode/workspace_hints.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode.egg-info/dependency_links.txt +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode.egg-info/entry_points.txt +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode.egg-info/requires.txt +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/src/gemcode.egg-info/top_level.txt +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_add_dir.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_agent_habits.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_agent_instruction.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_agent_mesh.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_autocompact.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_automations.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_capability_routing.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_checkpoint_diff_command.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_cli_init.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_compress_memory_tool.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_computer_use_permissions.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_context_budget.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_context_warning.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_credentials.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_eval_harness_layout.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_event_bus.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_fleet_reports.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_ide_stdio_attachments.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_interactive_permission_ask.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_kaira_ipc_paths.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_kaira_scheduler.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_modality_tools.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_model_error_retry.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_model_errors.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_model_routing.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_multimodal_input.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_output_styles_and_rules.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_paths.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_permissions.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_prompt_suggestions.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_repl_commands.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_repl_slash.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_session_runtime_cache.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_slash_commands.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_slash_completion_registry.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_thinking_config.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_token_budget.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_tools.py +0 -0
- {gemcode-0.4.18 → gemcode-0.4.19}/tests/test_tools_inspector.py +0 -0
- {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.
|
|
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"
|
|
@@ -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 —
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
|
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
|
|
1275
|
-
#
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|