gemcode 0.3.53__tar.gz → 0.3.55__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 (122) hide show
  1. {gemcode-0.3.53/src/gemcode.egg-info → gemcode-0.3.55}/PKG-INFO +1 -1
  2. {gemcode-0.3.53 → gemcode-0.3.55}/pyproject.toml +1 -1
  3. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/model_routing.py +4 -1
  4. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/session_runtime.py +68 -24
  5. gemcode-0.3.55/src/gemcode/tui/welcome_banner.py +127 -0
  6. {gemcode-0.3.53 → gemcode-0.3.55/src/gemcode.egg-info}/PKG-INFO +1 -1
  7. gemcode-0.3.53/src/gemcode/tui/welcome_banner.py +0 -137
  8. {gemcode-0.3.53 → gemcode-0.3.55}/LICENSE +0 -0
  9. {gemcode-0.3.53 → gemcode-0.3.55}/MANIFEST.in +0 -0
  10. {gemcode-0.3.53 → gemcode-0.3.55}/README.md +0 -0
  11. {gemcode-0.3.53 → gemcode-0.3.55}/setup.cfg +0 -0
  12. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/__init__.py +0 -0
  13. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/__main__.py +0 -0
  14. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/agent.py +0 -0
  15. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/audit.py +0 -0
  16. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/autocompact.py +0 -0
  17. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/callbacks.py +0 -0
  18. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/capability_routing.py +0 -0
  19. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/cli.py +0 -0
  20. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/compaction.py +0 -0
  21. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/computer_use/__init__.py +0 -0
  22. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/computer_use/browser_computer.py +0 -0
  23. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/config.py +0 -0
  24. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/context_budget.py +0 -0
  25. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/context_warning.py +0 -0
  26. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/credentials.py +0 -0
  27. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/hitl_session.py +0 -0
  28. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/hooks.py +0 -0
  29. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/intent_classifier.py +0 -0
  30. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/interactions.py +0 -0
  31. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/invoke.py +0 -0
  32. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/kairos_daemon.py +0 -0
  33. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/limits.py +0 -0
  34. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/live_audio_engine.py +0 -0
  35. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/logging_config.py +0 -0
  36. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/mcp_loader.py +0 -0
  37. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/memory/__init__.py +0 -0
  38. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/memory/embedding_memory_service.py +0 -0
  39. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/memory/file_memory_service.py +0 -0
  40. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/modality_tools.py +0 -0
  41. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/model_errors.py +0 -0
  42. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/openapi_loader.py +0 -0
  43. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/paths.py +0 -0
  44. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/permissions.py +0 -0
  45. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/plugins/__init__.py +0 -0
  46. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
  47. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  48. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/pricing.py +0 -0
  49. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/prompt_suggestions.py +0 -0
  50. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/query/__init__.py +0 -0
  51. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/query/config.py +0 -0
  52. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/query/deps.py +0 -0
  53. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/query/engine.py +0 -0
  54. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/query/stop_hooks.py +0 -0
  55. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/query/token_budget.py +0 -0
  56. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/query/transitions.py +0 -0
  57. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/refine.py +0 -0
  58. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/repl_commands.py +0 -0
  59. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/repl_slash.py +0 -0
  60. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/review_agent.py +0 -0
  61. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/session_store.py +0 -0
  62. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/slash_commands.py +0 -0
  63. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/thinking.py +0 -0
  64. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/tool_prompt_manifest.py +0 -0
  65. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/tool_registry.py +0 -0
  66. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/tools/__init__.py +0 -0
  67. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/tools/bash.py +0 -0
  68. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/tools/browser.py +0 -0
  69. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/tools/edit.py +0 -0
  70. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/tools/filesystem.py +0 -0
  71. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/tools/notes.py +0 -0
  72. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/tools/search.py +0 -0
  73. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/tools/shell.py +0 -0
  74. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/tools/shell_gate.py +0 -0
  75. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/tools/subtask.py +0 -0
  76. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/tools/think.py +0 -0
  77. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/tools/todo.py +0 -0
  78. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/tools/web.py +0 -0
  79. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/tools_inspector.py +0 -0
  80. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/trust.py +0 -0
  81. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/tui/input_handler.py +0 -0
  82. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/tui/scrollback.py +0 -0
  83. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/tui/spinner.py +0 -0
  84. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/tui/welcome_rich.py +0 -0
  85. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/version.py +0 -0
  86. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/vertex.py +0 -0
  87. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/web/__init__.py +0 -0
  88. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/web/claude_sse_adapter.py +0 -0
  89. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/web/terminal_repl.py +0 -0
  90. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode/workspace_hints.py +0 -0
  91. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode.egg-info/SOURCES.txt +0 -0
  92. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode.egg-info/dependency_links.txt +0 -0
  93. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode.egg-info/entry_points.txt +0 -0
  94. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode.egg-info/requires.txt +0 -0
  95. {gemcode-0.3.53 → gemcode-0.3.55}/src/gemcode.egg-info/top_level.txt +0 -0
  96. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_agent_instruction.py +0 -0
  97. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_autocompact.py +0 -0
  98. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_capability_routing.py +0 -0
  99. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_claude_web_adapter_sse.py +0 -0
  100. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_cli_init.py +0 -0
  101. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_computer_use_permissions.py +0 -0
  102. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_context_budget.py +0 -0
  103. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_context_warning.py +0 -0
  104. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_credentials.py +0 -0
  105. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_interactive_permission_ask.py +0 -0
  106. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_kairos_scheduler.py +0 -0
  107. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_modality_tools.py +0 -0
  108. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_model_error_retry.py +0 -0
  109. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_model_errors.py +0 -0
  110. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_model_routing.py +0 -0
  111. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_paths.py +0 -0
  112. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_permissions.py +0 -0
  113. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_prompt_suggestions.py +0 -0
  114. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_repl_commands.py +0 -0
  115. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_repl_slash.py +0 -0
  116. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_slash_commands.py +0 -0
  117. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_thinking_config.py +0 -0
  118. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_token_budget.py +0 -0
  119. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_tool_context_circulation.py +0 -0
  120. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_tools.py +0 -0
  121. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_tools_inspector.py +0 -0
  122. {gemcode-0.3.53 → gemcode-0.3.55}/tests/test_workspace_hints.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.53
3
+ Version: 0.3.55
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gemcode"
7
- version = "0.3.53"
7
+ version = "0.3.55"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -60,7 +60,10 @@ def pick_effective_model(cfg: GemCodeConfig, prompt: str) -> str:
60
60
  # (Deep research already handled above.)
61
61
  if getattr(cfg, "enable_audio", False):
62
62
  return getattr(cfg, "model_audio_live", None) or cfg.model
63
- if getattr(cfg, "enable_computer_use", False):
63
+ # Only switch to the computer-use model when Playwright is actually available.
64
+ # `_computer_use_available` is set False by session_runtime when the probe fails,
65
+ # preventing an HTTP 400 ("model requires Computer Use tool") from the API.
66
+ if getattr(cfg, "enable_computer_use", False) and getattr(cfg, "_computer_use_available", True):
64
67
  return getattr(cfg, "model_computer_use", None) or cfg.model
65
68
 
66
69
  primary_fast = cfg.model
@@ -12,8 +12,13 @@ token budget helpers, and `GemCodeQueryEngine`.
12
12
  from __future__ import annotations
13
13
 
14
14
  import os
15
+ import warnings
15
16
  from pathlib import Path
16
17
 
18
+ # Suppress ADK's noisy "EXPERIMENTAL feature" UserWarning globally.
19
+ # Users get enough context from gemcode's own messages; the warning is redundant.
20
+ warnings.filterwarnings("ignore", category=UserWarning, message=".*EXPERIMENTAL.*")
21
+
17
22
  from google.adk.runners import Runner
18
23
  from google.adk.sessions.sqlite_session_service import SqliteSessionService
19
24
 
@@ -30,6 +35,29 @@ def session_db_path(cfg: GemCodeConfig) -> Path:
30
35
  return cfg.project_root / ".gemcode" / "sessions.sqlite"
31
36
 
32
37
 
38
+ def _playwright_available() -> bool:
39
+ """
40
+ Quick synchronous check: does a usable Playwright browser executable exist?
41
+
42
+ Runs playwright.sync_api.sync_playwright() briefly to resolve the browser
43
+ path, without actually launching a browser. Falls back to a path probe if
44
+ playwright is not installed.
45
+
46
+ Returns True only when Playwright AND at least one browser binary are present.
47
+ This is called before building the runner so we can disable computer-use
48
+ early and prevent model routing from switching to gemini-2.5-computer-use-*.
49
+ """
50
+ try:
51
+ from playwright.sync_api import sync_playwright
52
+ with sync_playwright() as p:
53
+ exe = p.chromium.executable_path
54
+ return bool(exe) and Path(exe).exists()
55
+ except Exception:
56
+ pass
57
+ # playwright not installed at all
58
+ return False
59
+
60
+
33
61
  def _make_safe_computer_toolset(computer):
34
62
  """
35
63
  Build a BaseToolset-compatible wrapper around ComputerUseToolset that catches
@@ -183,31 +211,47 @@ def create_runner(cfg: GemCodeConfig, extra_tools: list | None = None) -> Runner
183
211
  pass # OpenAPIToolset not in this ADK version — continue without
184
212
 
185
213
  # Computer-use: ADK ComputerUseToolset backed by our Playwright BrowserComputer.
186
- # Also inject standalone browser inspection tools (screenshot, get_text, etc.)
187
- # so the agent can read page state without performing side-effecting actions.
214
+ # Probe Playwright BEFORE building the agent so model routing (which runs
215
+ # inside build_root_agent pick_effective_model) never switches to
216
+ # gemini-2.5-computer-use-* when the browser binary is missing.
188
217
  if getattr(cfg, "enable_computer_use", False):
189
- headless_env = os.environ.get("GEMCODE_COMPUTER_HEADLESS", "1").lower()
190
- headless = headless_env in ("1", "true", "yes", "on")
191
- viewport_w = int(os.environ.get("GEMCODE_BROWSER_WIDTH", "1280"))
192
- viewport_h = int(os.environ.get("GEMCODE_BROWSER_HEIGHT", "720"))
193
- from gemcode.computer_use.browser_computer import BrowserComputer
194
-
195
- computer = BrowserComputer(
196
- headless=headless,
197
- viewport_size=(viewport_w, viewport_h),
198
- )
199
- computer_toolset = _make_safe_computer_toolset(computer)
200
- merged_extra_tools = list(merged_extra_tools or [])
201
- if computer_toolset is not None:
202
- merged_extra_tools.append(computer_toolset)
203
-
204
- # Standalone read-only browser tools (browser_screenshot, browser_get_text, etc.)
205
- from gemcode.tools.browser import build_browser_inspection_tools
206
- browser_tools = build_browser_inspection_tools(cfg, computer)
207
- merged_extra_tools.extend(browser_tools)
208
-
209
- # Store reference on cfg so slash commands / TUI can check browser state.
210
- cfg._browser_computer = computer # type: ignore[attr-defined]
218
+ if not _playwright_available():
219
+ import sys
220
+ print(
221
+ "\n[gemcode] Browser (computer-use) is unavailable — Playwright browsers "
222
+ "are not installed.\n"
223
+ " Run: playwright install chromium\n"
224
+ " Then restart GemCode with /computer on (or --computer flag).\n"
225
+ " Disabling computer-use for this session.\n",
226
+ file=sys.stderr,
227
+ )
228
+ # Disable so model_routing stays on the normal model (not computer-use preview).
229
+ cfg.enable_computer_use = False
230
+ cfg._computer_use_available = False # type: ignore[attr-defined]
231
+ else:
232
+ cfg._computer_use_available = True # type: ignore[attr-defined]
233
+ headless_env = os.environ.get("GEMCODE_COMPUTER_HEADLESS", "1").lower()
234
+ headless = headless_env in ("1", "true", "yes", "on")
235
+ viewport_w = int(os.environ.get("GEMCODE_BROWSER_WIDTH", "1280"))
236
+ viewport_h = int(os.environ.get("GEMCODE_BROWSER_HEIGHT", "720"))
237
+ from gemcode.computer_use.browser_computer import BrowserComputer
238
+
239
+ computer = BrowserComputer(
240
+ headless=headless,
241
+ viewport_size=(viewport_w, viewport_h),
242
+ )
243
+ computer_toolset = _make_safe_computer_toolset(computer)
244
+ merged_extra_tools = list(merged_extra_tools or [])
245
+ if computer_toolset is not None:
246
+ merged_extra_tools.append(computer_toolset)
247
+
248
+ # Standalone read-only browser tools (browser_screenshot, browser_get_text, etc.)
249
+ from gemcode.tools.browser import build_browser_inspection_tools
250
+ browser_tools = build_browser_inspection_tools(cfg, computer)
251
+ merged_extra_tools.extend(browser_tools)
252
+
253
+ # Store reference on cfg so slash commands / TUI can check browser state.
254
+ cfg._browser_computer = computer # type: ignore[attr-defined]
211
255
 
212
256
  agent = build_root_agent(cfg, extra_tools=merged_extra_tools)
213
257
  db = session_db_path(cfg)
@@ -0,0 +1,127 @@
1
+ """ASCII welcome banner + provider box (scrollback + full-screen TUI)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from pathlib import Path
7
+
8
+ from gemcode.version import get_version
9
+ from gemcode.vertex import vertex_env_active
10
+ from gemcode.workspace_hints import narrow_workspace_tip
11
+
12
+ _GEM_LINES = (
13
+ r" ██████╗ ███████╗███╗ ███╗",
14
+ r" ██╔════╝ ██╔════╝████╗ ████║",
15
+ r" ██║ ███╗█████╗ ██╔████╔██║",
16
+ r" ██║ ██║██╔══╝ ██║╚██╔╝██║",
17
+ r" ╚██████╔╝███████╗██║ ╚═╝ ██║",
18
+ r" ╚═════╝ ╚══════╝╚═╝ ╚═╝",
19
+ )
20
+
21
+ _CODE_LINES = (
22
+ r" ██████╗ ██████╗ ██████╗ ███████╗",
23
+ r" ██╔════╝██╔═══██╗██╔══██╗██╔════╝",
24
+ r" ██║ ██║ ██║██║ ██║█████╗ ",
25
+ r" ██║ ██║ ██║██║ ██║██╔══╝ ",
26
+ r" ╚██████╗╚██████╔╝██████╔╝███████╗",
27
+ r" ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝",
28
+ )
29
+
30
+ _TAGLINE = "✦ Gemini-powered coding agent. Fast. Capable. Local. ✦"
31
+
32
+
33
+ def _banner_width() -> int:
34
+ return max(len(x) for x in _GEM_LINES + _CODE_LINES)
35
+
36
+
37
+ def _center(line: str, width: int) -> str:
38
+ if len(line) >= width:
39
+ return line
40
+ pad = width - len(line)
41
+ left = pad // 2
42
+ return " " * left + line + " " * (pad - left)
43
+
44
+
45
+ def _provider_model_endpoint(cfg) -> tuple[str, str, str]:
46
+ model = (getattr(cfg, "model", None) or "").strip() or "gemini-2.5-flash"
47
+ if vertex_env_active():
48
+ provider = "Vertex AI"
49
+ loc = (os.environ.get("GOOGLE_CLOUD_LOCATION") or "us-central1").strip()
50
+ proj = (os.environ.get("GOOGLE_CLOUD_PROJECT") or "").strip()
51
+ end = f"vertex://{loc}"
52
+ if proj:
53
+ end = f"{end} · {proj}"
54
+ else:
55
+ provider = "Google Gemini"
56
+ end = "https://generativelanguage.googleapis.com/"
57
+ return provider, model, end
58
+
59
+
60
+ def _kv_line(inner: int, label: str, value: str) -> str:
61
+ """One row inside the box: leading space + label column + value (total width ``inner``)."""
62
+ lead = " "
63
+ prefix = f"{label:<10}"
64
+ room = inner - len(lead) - len(prefix)
65
+ if room < 4:
66
+ raw = (lead + prefix + value)[:inner]
67
+ return raw.ljust(inner)
68
+ v = value
69
+ if len(v) > room:
70
+ v = v[: max(0, room - 3)] + "..."
71
+ return (lead + prefix + v).ljust(inner)[:inner]
72
+
73
+
74
+ def format_welcome_banner(cfg, *, term_width: int = 100) -> str:
75
+ inner = min(60, max(48, term_width - 4))
76
+ top = "╔" + ("═" * inner) + "╗"
77
+ mid = "╠" + ("═" * inner) + "╣"
78
+ bot = "╚" + ("═" * inner) + "╝"
79
+
80
+ provider, model, endpoint = _provider_model_endpoint(cfg)
81
+ row_p = _kv_line(inner, "Provider", provider)
82
+ row_m = _kv_line(inner, "Model", model)
83
+ row_e = _kv_line(inner, "Endpoint", endpoint)
84
+
85
+ mode = "vertex" if vertex_env_active() else "cloud"
86
+ status_raw = f" ● {mode} Ready — type /help to begin"
87
+ if len(status_raw) > inner:
88
+ status_raw = status_raw[: max(0, inner - 3)] + "..."
89
+ row_s = status_raw.ljust(inner)[:inner]
90
+
91
+ bw = max(_banner_width(), inner + 2)
92
+ lines: list[str] = []
93
+ for row in _GEM_LINES:
94
+ lines.append(_center(row, bw))
95
+ lines.append("")
96
+ for row in _CODE_LINES:
97
+ lines.append(_center(row, bw))
98
+ lines.append("")
99
+ lines.append(_center(f" {_TAGLINE} ", bw))
100
+ lines.append("")
101
+
102
+ box_w = inner + 2
103
+ pad_b = max(0, (bw - box_w) // 2)
104
+ bp = " " * pad_b
105
+
106
+ def box_row(body: str) -> str:
107
+ b = body[:inner].ljust(inner)
108
+ return bp + "│" + b + "│"
109
+
110
+ lines.append(bp + top)
111
+ lines.append(box_row(row_p))
112
+ lines.append(box_row(row_m))
113
+ lines.append(box_row(row_e))
114
+ lines.append(bp + mid)
115
+ lines.append(box_row(row_s))
116
+ lines.append(bp + bot)
117
+
118
+ ver = os.environ.get("GEMCODE_VERSION", get_version())
119
+ lines.append(_center(f" gemcode v{ver} ", bw))
120
+
121
+ root = getattr(cfg, "project_root", None)
122
+ if isinstance(root, Path):
123
+ nt = narrow_workspace_tip(root)
124
+ if nt:
125
+ lines.append(_center(nt, bw))
126
+ lines.append("")
127
+ return "\n".join(lines)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.53
3
+ Version: 0.3.55
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -1,137 +0,0 @@
1
- """ASCII welcome banner + provider box (scrollback + full-screen TUI)."""
2
-
3
- from __future__ import annotations
4
-
5
- import os
6
- from pathlib import Path
7
-
8
- from gemcode.version import get_version
9
- from gemcode.vertex import vertex_env_active
10
- from gemcode.workspace_hints import narrow_workspace_tip
11
-
12
- _GEM_LINES = (
13
- r"██████╗ ███████╗███╗ ███╗",
14
- r"██╔════╝ ██╔════╝████╗ ████║",
15
- r"██║ ███╗█████╗ ██╔████╔██║",
16
- r"██║ ██║██╔══╝ ██║╚██╔╝██║",
17
- r"╚██████╔╝███████╗██║ ╚═╝ ██║",
18
- r" ╚═════╝ ╚══════╝╚═╝ ╚═╝",
19
- )
20
-
21
- _CODE_LINES = (
22
- r"██████╗ ██████╗ ██████╗ ███████╗",
23
- r"██╔════╝██╔═══██╗██╔══██╗██╔════╝",
24
- r"██║ ██║ ██║██║ ██║█████╗ ",
25
- r"██║ ██║ ██║██║ ██║██╔══╝ ",
26
- r"╚██████╗╚██████╔╝██████╔╝███████╗",
27
- r" ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝",
28
- )
29
-
30
- _TAGLINE = "✦ Gemini-powered coding agent. Fast. Capable. Local. ✦"
31
-
32
-
33
- def _banner_width() -> int:
34
- return max(len(x) for x in _GEM_LINES + _CODE_LINES)
35
-
36
-
37
- def _center(line: str, width: int) -> str:
38
- if len(line) >= width:
39
- return line
40
- pad = width - len(line)
41
- left = pad // 2
42
- return " " * left + line + " " * (pad - left)
43
-
44
-
45
- def _provider_model_endpoint(cfg) -> tuple[str, str, str]:
46
- model = (getattr(cfg, "model", None) or "").strip() or "gemini-2.5-flash"
47
- if vertex_env_active():
48
- provider = "Vertex AI"
49
- loc = (os.environ.get("GOOGLE_CLOUD_LOCATION") or "us-central1").strip()
50
- proj = (os.environ.get("GOOGLE_CLOUD_PROJECT") or "").strip()
51
- end = f"vertex://{loc}"
52
- if proj:
53
- end = f"{end} · {proj}"
54
- else:
55
- provider = "Google Gemini"
56
- end = "https://generativelanguage.googleapis.com/"
57
- return provider, model, end
58
-
59
-
60
- def _kv_line(inner: int, label: str, value: str) -> str:
61
- """One row inside the box: leading space + label column + value (total width ``inner``)."""
62
- lead = " "
63
- prefix = f"{label:<10}"
64
- room = inner - len(lead) - len(prefix)
65
- if room < 4:
66
- raw = (lead + prefix + value)[:inner]
67
- return raw.ljust(inner)
68
- v = value
69
- if len(v) > room:
70
- v = v[: max(0, room - 3)] + "..."
71
- return (lead + prefix + v).ljust(inner)[:inner]
72
-
73
-
74
- def format_welcome_banner(cfg, *, term_width: int = 100) -> str:
75
- provider, model, endpoint = _provider_model_endpoint(cfg)
76
- # Banner style: "solid" removes the boxed provider block and blank spacer
77
- # lines for a single rigid header (OpenClaude-like).
78
- style = (os.environ.get("GEMCODE_TUI_BANNER_STYLE", "solid") or "solid").strip().lower()
79
-
80
- bw = max(_banner_width(), min(80, max(48, term_width)))
81
- lines: list[str] = []
82
- for row in _GEM_LINES:
83
- lines.append(_center(row, bw))
84
- for row in _CODE_LINES:
85
- lines.append(_center(row, bw))
86
- lines.append(_center(_TAGLINE, bw))
87
-
88
- if style not in ("solid", "rigid", "compact"):
89
- # Backward-compat: unknown values fall back to the old boxed layout.
90
- style = "boxed"
91
-
92
- if style == "boxed":
93
- inner = min(60, max(48, term_width - 4))
94
- top = "╔" + ("═" * inner) + "╗"
95
- mid = "╠" + ("═" * inner) + "╣"
96
- bot = "╚" + ("═" * inner) + "╝"
97
-
98
- row_p = _kv_line(inner, "Provider", provider)
99
- row_m = _kv_line(inner, "Model", model)
100
- row_e = _kv_line(inner, "Endpoint", endpoint)
101
-
102
- mode = "vertex" if vertex_env_active() else "cloud"
103
- status_raw = f" ● {mode} Ready — type /help to begin"
104
- if len(status_raw) > inner:
105
- status_raw = status_raw[: max(0, inner - 3)] + "..."
106
- row_s = status_raw.ljust(inner)[:inner]
107
-
108
- box_w = inner + 2
109
- pad_b = max(0, (bw - box_w) // 2)
110
- bp = " " * pad_b
111
-
112
- def box_row(body: str) -> str:
113
- b = body[:inner].ljust(inner)
114
- return bp + "│" + b + "│"
115
-
116
- lines.append(bp + top)
117
- lines.append(box_row(row_p))
118
- lines.append(box_row(row_m))
119
- lines.append(box_row(row_e))
120
- lines.append(bp + mid)
121
- lines.append(box_row(row_s))
122
- lines.append(bp + bot)
123
- else:
124
- # Solid header: one rigid info line, no extra blocks.
125
- mode = "vertex" if vertex_env_active() else "cloud"
126
- info = f"{provider} · {model} · {endpoint} · {mode} ready — type /help"
127
- lines.append(_center(info, bw))
128
-
129
- ver = os.environ.get("GEMCODE_VERSION", get_version())
130
- lines.append(_center(f"gemcode v{ver}", bw))
131
-
132
- root = getattr(cfg, "project_root", None)
133
- if isinstance(root, Path):
134
- nt = narrow_workspace_tip(root)
135
- if nt:
136
- lines.append(_center(nt, bw))
137
- return "\n".join(lines)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes