gemcode 0.3.12__tar.gz → 0.3.14__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 (109) hide show
  1. {gemcode-0.3.12/src/gemcode.egg-info → gemcode-0.3.14}/PKG-INFO +2 -4
  2. {gemcode-0.3.12 → gemcode-0.3.14}/pyproject.toml +2 -2
  3. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/cli.py +11 -28
  4. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/tui/scrollback.py +10 -78
  5. gemcode-0.3.14/src/gemcode/tui/welcome_banner.py +127 -0
  6. gemcode-0.3.14/src/gemcode/tui/welcome_rich.py +40 -0
  7. {gemcode-0.3.12 → gemcode-0.3.14/src/gemcode.egg-info}/PKG-INFO +2 -4
  8. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode.egg-info/SOURCES.txt +2 -1
  9. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode.egg-info/requires.txt +1 -4
  10. gemcode-0.3.12/src/gemcode/tui/app.py +0 -968
  11. {gemcode-0.3.12 → gemcode-0.3.14}/LICENSE +0 -0
  12. {gemcode-0.3.12 → gemcode-0.3.14}/MANIFEST.in +0 -0
  13. {gemcode-0.3.12 → gemcode-0.3.14}/README.md +0 -0
  14. {gemcode-0.3.12 → gemcode-0.3.14}/setup.cfg +0 -0
  15. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/__init__.py +0 -0
  16. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/__main__.py +0 -0
  17. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/agent.py +0 -0
  18. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/audit.py +0 -0
  19. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/autocompact.py +0 -0
  20. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/callbacks.py +0 -0
  21. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/capability_routing.py +0 -0
  22. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/compaction.py +0 -0
  23. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/computer_use/__init__.py +0 -0
  24. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/computer_use/browser_computer.py +0 -0
  25. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/config.py +0 -0
  26. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/context_budget.py +0 -0
  27. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/context_warning.py +0 -0
  28. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/credentials.py +0 -0
  29. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/hitl_session.py +0 -0
  30. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/interactions.py +0 -0
  31. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/invoke.py +0 -0
  32. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/kairos_daemon.py +0 -0
  33. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/limits.py +0 -0
  34. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/live_audio_engine.py +0 -0
  35. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/logging_config.py +0 -0
  36. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/mcp_loader.py +0 -0
  37. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/memory/__init__.py +0 -0
  38. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/memory/embedding_memory_service.py +0 -0
  39. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/memory/file_memory_service.py +0 -0
  40. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/modality_tools.py +0 -0
  41. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/model_errors.py +0 -0
  42. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/model_routing.py +0 -0
  43. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/paths.py +0 -0
  44. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/permissions.py +0 -0
  45. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/plugins/__init__.py +0 -0
  46. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
  47. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  48. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/prompt_suggestions.py +0 -0
  49. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/query/__init__.py +0 -0
  50. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/query/config.py +0 -0
  51. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/query/deps.py +0 -0
  52. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/query/engine.py +0 -0
  53. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/query/stop_hooks.py +0 -0
  54. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/query/token_budget.py +0 -0
  55. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/query/transitions.py +0 -0
  56. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/repl_commands.py +0 -0
  57. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/repl_slash.py +0 -0
  58. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/session_runtime.py +0 -0
  59. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/slash_commands.py +0 -0
  60. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/thinking.py +0 -0
  61. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/tool_prompt_manifest.py +0 -0
  62. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/tool_registry.py +0 -0
  63. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/tools/__init__.py +0 -0
  64. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/tools/edit.py +0 -0
  65. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/tools/filesystem.py +0 -0
  66. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/tools/search.py +0 -0
  67. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/tools/shell.py +0 -0
  68. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/tools/shell_gate.py +0 -0
  69. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/tools/todo.py +0 -0
  70. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/tools_inspector.py +0 -0
  71. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/trust.py +0 -0
  72. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/tui/spinner.py +0 -0
  73. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/tui/startup_screen.py +0 -0
  74. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/version.py +0 -0
  75. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/vertex.py +0 -0
  76. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/web/__init__.py +0 -0
  77. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/web/claude_sse_adapter.py +0 -0
  78. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/web/terminal_repl.py +0 -0
  79. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode/workspace_hints.py +0 -0
  80. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode.egg-info/dependency_links.txt +0 -0
  81. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode.egg-info/entry_points.txt +0 -0
  82. {gemcode-0.3.12 → gemcode-0.3.14}/src/gemcode.egg-info/top_level.txt +0 -0
  83. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_agent_instruction.py +0 -0
  84. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_autocompact.py +0 -0
  85. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_capability_routing.py +0 -0
  86. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_claude_web_adapter_sse.py +0 -0
  87. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_cli_init.py +0 -0
  88. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_computer_use_permissions.py +0 -0
  89. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_context_budget.py +0 -0
  90. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_context_warning.py +0 -0
  91. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_credentials.py +0 -0
  92. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_interactive_permission_ask.py +0 -0
  93. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_kairos_scheduler.py +0 -0
  94. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_modality_tools.py +0 -0
  95. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_model_error_retry.py +0 -0
  96. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_model_errors.py +0 -0
  97. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_model_routing.py +0 -0
  98. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_paths.py +0 -0
  99. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_permissions.py +0 -0
  100. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_prompt_suggestions.py +0 -0
  101. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_repl_commands.py +0 -0
  102. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_repl_slash.py +0 -0
  103. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_slash_commands.py +0 -0
  104. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_thinking_config.py +0 -0
  105. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_token_budget.py +0 -0
  106. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_tool_context_circulation.py +0 -0
  107. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_tools.py +0 -0
  108. {gemcode-0.3.12 → gemcode-0.3.14}/tests/test_tools_inspector.py +0 -0
  109. {gemcode-0.3.12 → gemcode-0.3.14}/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.12
3
+ Version: 0.3.14
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -178,14 +178,12 @@ License-File: LICENSE
178
178
  Requires-Dist: google-adk>=1.0.0
179
179
  Requires-Dist: google-genai>=1.0.0
180
180
  Requires-Dist: python-dotenv>=1.0.0
181
+ Requires-Dist: rich>=13.0.0
181
182
  Provides-Extra: dev
182
183
  Requires-Dist: pytest>=8.0.0; extra == "dev"
183
184
  Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
184
185
  Provides-Extra: mcp
185
186
  Requires-Dist: mcp>=1.0.0; extra == "mcp"
186
- Provides-Extra: tui
187
- Requires-Dist: prompt_toolkit>=3.0.0; extra == "tui"
188
- Requires-Dist: rich>=13.0.0; extra == "tui"
189
187
  Dynamic: license-file
190
188
 
191
189
  # GemCode
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gemcode"
7
- version = "0.3.12"
7
+ version = "0.3.14"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -30,6 +30,7 @@ dependencies = [
30
30
  "google-adk>=1.0.0",
31
31
  "google-genai>=1.0.0",
32
32
  "python-dotenv>=1.0.0",
33
+ "rich>=13.0.0",
33
34
  ]
34
35
 
35
36
  [project.urls]
@@ -40,7 +41,6 @@ Issues = "https://github.com/spiderdev27/GemCode/issues"
40
41
  [project.optional-dependencies]
41
42
  dev = ["pytest>=8.0.0", "pytest-asyncio>=0.24.0"]
42
43
  mcp = ["mcp>=1.0.0"]
43
- tui = ["prompt_toolkit>=3.0.0", "rich>=13.0.0"]
44
44
 
45
45
  [project.scripts]
46
46
  gemcode = "gemcode.cli:main"
@@ -229,11 +229,10 @@ async def _run_repl(cfg: GemCodeConfig, session_id: str, *, use_mcp: bool) -> No
229
229
  except EOFError:
230
230
  pass
231
231
 
232
- # Optional TUI. Claude-like default is "scrollback" (no internal scrolling).
232
+ # Optional terminal UI: single scrollback-style REPL (terminal history, no alt-screen app).
233
233
  tui_enabled = os.environ.get("GEMCODE_TUI", "1").lower() in ("1", "true", "yes", "on")
234
234
  if tui_enabled:
235
235
  term = (os.environ.get("TERM") or "").strip().lower()
236
- # Guardrails: Prompt Toolkit needs a real interactive terminal.
237
236
  if not sys.stdin.isatty() or not sys.stdout.isatty() or term in ("", "dumb", "unknown"):
238
237
  print(
239
238
  f"[tui] disabled (stdin/stdout isatty={sys.stdin.isatty()}/{sys.stdout.isatty()}, TERM={term or '<unset>'}); using plain REPL",
@@ -241,35 +240,19 @@ async def _run_repl(cfg: GemCodeConfig, session_id: str, *, use_mcp: bool) -> No
241
240
  )
242
241
  else:
243
242
  try:
244
- # Default to the full-screen TUI (OpenClaude-like). Scrollback is still
245
- # available for terminals that can't handle alternate screen UIs.
246
- style = os.environ.get("GEMCODE_TUI_STYLE", "full").strip().lower()
247
- if style in ("scrollback", "claude", "claude-like"):
248
- from gemcode.tui.scrollback import run_gemcode_scrollback_tui
249
-
250
- await run_gemcode_scrollback_tui(
251
- cfg=cfg,
252
- runner=runner,
253
- session_id=session_id,
254
- extra_tools=extra or None,
255
- )
256
- else:
257
- from gemcode.tui.app import run_gemcode_tui
258
-
259
- await run_gemcode_tui(
260
- cfg=cfg,
261
- runner=runner,
262
- session_id=session_id,
263
- extra_tools=extra or None,
264
- )
243
+ from gemcode.tui.scrollback import run_gemcode_scrollback_tui
244
+
245
+ await run_gemcode_scrollback_tui(
246
+ cfg=cfg,
247
+ runner=runner,
248
+ session_id=session_id,
249
+ extra_tools=extra or None,
250
+ )
265
251
  return
266
252
  except Exception as e:
267
- # Dependency missing or terminal doesn't support full-screen.
268
- # Print one line so users know how to fix it.
269
253
  print(
270
- f"[tui] failed to start: {type(e).__name__}: {e} (falling back to plain REPL). "
271
- "Install extras with: pip install 'gemcode[tui]'",
272
- file=sys.stderr,
254
+ f"[tui] failed to start: {type(e).__name__}: {e} (falling back to plain REPL).",
255
+ file=sys.stderr,
273
256
  )
274
257
 
275
258
  print(
@@ -8,13 +8,13 @@ from dataclasses import dataclass
8
8
 
9
9
  from google.adk.agents.run_config import RunConfig
10
10
  from google.genai import types
11
+ from rich.console import Console
11
12
 
12
13
  from gemcode.capability_routing import apply_capability_routing
13
14
  from gemcode.config import load_cli_environment
14
15
  from gemcode.model_routing import pick_effective_model
15
16
  from gemcode.repl_slash import process_repl_slash
16
- from gemcode.version import get_version
17
- from gemcode.workspace_hints import narrow_workspace_tip
17
+ from gemcode.tui.welcome_rich import print_shortcuts_hint, print_welcome_dashboard
18
18
 
19
19
  _ADK_REQUEST_CONFIRMATION = "adk_request_confirmation"
20
20
 
@@ -154,64 +154,6 @@ def _hr(ch: str = "─") -> str:
154
154
  return ch * _term_width()
155
155
 
156
156
 
157
- def _dashboard(cfg) -> str:
158
- w = _term_width()
159
- title = f" GemCode v{os.environ.get('GEMCODE_VERSION', get_version())} "
160
- left_w = (w - 4) * 2 // 3
161
- right_w = (w - 4) - left_w
162
-
163
- def pad(s: str, ww: int) -> str:
164
- s = s.replace("\n", " ")
165
- if len(s) > ww:
166
- return s[: ww - 1] + "…"
167
- return s + (" " * (ww - len(s)))
168
-
169
- user = (os.environ.get("USER") or os.environ.get("LOGNAME") or "there").strip()
170
- model = getattr(cfg, "model", "") or ""
171
- root = str(getattr(cfg, "project_root", "") or "")
172
-
173
- box_top = "╭" + ("─" * (w - 2)) + "╮"
174
- box_bot = "╰" + ("─" * (w - 2)) + "╯"
175
- lines: list[str] = [box_top]
176
- lines.append("│" + pad(title, w - 2) + "│")
177
- lines.append("│" + (" " * (w - 2)) + "│")
178
- left = [
179
- "",
180
- f"Welcome back {user}!",
181
- "",
182
- " ▄▄▄▄▄▄▄",
183
- " ▐█████████▌",
184
- " ▐█████████▌",
185
- " ▀▀▀▀▀▀▀",
186
- "",
187
- f"{model or 'Gemini 2.0 Flash'} · Local session",
188
- root,
189
- ]
190
- right = [
191
- "Tips for getting started",
192
- "First run creates .gemcode/ (trust + API key)",
193
- "Use /help to see available commands",
194
- "",
195
- "Recent activity",
196
- "No recent activity",
197
- ]
198
- h = max(len(left), len(right))
199
- left += [""] * (h - len(left))
200
- right += [""] * (h - len(right))
201
- for i in range(h):
202
- lines.append(
203
- "│ " + pad(left[i], left_w) + " │ " + pad(right[i], right_w) + " │"
204
- )
205
- nt = narrow_workspace_tip(getattr(cfg, "project_root"))
206
- if nt:
207
- lines.append("│" + pad(f" {nt}", w - 2) + "│")
208
- lines.append(box_bot)
209
- lines.append("")
210
- lines.append(" ↑ GemCode now supports larger contexts · faster streaming · better tools")
211
- lines.append("")
212
- return "\n".join(lines)
213
-
214
-
215
157
  async def run_gemcode_scrollback_tui(
216
158
  *, cfg, runner, session_id: str, extra_tools=None
217
159
  ) -> None:
@@ -234,25 +176,15 @@ async def run_gemcode_scrollback_tui(
234
176
  )
235
177
  )
236
178
 
179
+ console = Console(
180
+ width=_term_width(),
181
+ force_terminal=bool(sys.stdout.isatty()),
182
+ no_color=not ansi.enabled,
183
+ highlight=False,
184
+ )
237
185
  if os.environ.get("GEMCODE_TUI_SHOW_DASHBOARD", "1").lower() in ("1", "true", "yes", "on"):
238
- dash = _dashboard(cfg)
239
- if ansi.enabled:
240
- # Color title + the ASCII mark.
241
- lines = dash.splitlines()
242
- if len(lines) >= 2:
243
- lines[1] = (
244
- lines[1]
245
- .replace("GemCode", f"{ansi.blue}{ansi.bold}GemCode{ansi.reset}")
246
- .replace("v", f"{ansi.dim}v{ansi.reset}")
247
- )
248
- for i, ln in enumerate(lines):
249
- if "▐▛███▜▌" in ln or "▝▜█████▛▘" in ln or "▘▘ ▝▝" in ln:
250
- lines[i] = f"{ansi.blue2}{ln}{ansi.reset}"
251
- dash = "\n".join(lines)
252
- print(dash)
253
-
254
- print(f"{ansi.dim} ? for shortcuts{ansi.reset}")
255
- print(f"{ansi.dim} (Tip: set GEMCODE_TUI_STYLE=full for spinner/status bar){ansi.reset}")
186
+ print_welcome_dashboard(cfg, console=console)
187
+ print_shortcuts_hint(console=console)
256
188
  print("")
257
189
 
258
190
  char_delay_ms = int(os.environ.get("GEMCODE_TUI_CHAR_DELAY_MS", "0") or "0")
@@ -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)
@@ -0,0 +1,40 @@
1
+ """Rich renderables for the scrollback welcome screen."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from gemcode.tui.welcome_banner import format_welcome_banner
8
+
9
+ if TYPE_CHECKING:
10
+ from rich.console import Console
11
+
12
+
13
+ def print_welcome_dashboard(cfg, *, console: Console) -> None:
14
+ from rich.console import Group
15
+ from rich.text import Text
16
+
17
+ tw = max(60, console.width or 80)
18
+ raw = format_welcome_banner(cfg, term_width=tw)
19
+ rows: list[Text] = []
20
+ for ln in raw.splitlines():
21
+ low = ln.lower()
22
+ if "█" in ln:
23
+ rows.append(Text(ln, style="bold #3BA8E8"))
24
+ elif any(c in ln for c in "╔╗║╚╠╣═│") and "█" not in ln:
25
+ rows.append(Text(ln, style="bright_blue"))
26
+ elif "gemcode v" in low:
27
+ rows.append(Text(ln, style="bold dim"))
28
+ elif "✦" in ln:
29
+ rows.append(Text(ln, style="dim italic"))
30
+ elif ln.strip().startswith("Tip:"):
31
+ rows.append(Text(ln, style="dim"))
32
+ else:
33
+ rows.append(Text(ln))
34
+ console.print(Group(*rows))
35
+
36
+
37
+ def print_shortcuts_hint(*, console: Console) -> None:
38
+ from rich.text import Text
39
+
40
+ console.print(Text(" ? for shortcuts", style="dim"))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.12
3
+ Version: 0.3.14
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -178,14 +178,12 @@ License-File: LICENSE
178
178
  Requires-Dist: google-adk>=1.0.0
179
179
  Requires-Dist: google-genai>=1.0.0
180
180
  Requires-Dist: python-dotenv>=1.0.0
181
+ Requires-Dist: rich>=13.0.0
181
182
  Provides-Extra: dev
182
183
  Requires-Dist: pytest>=8.0.0; extra == "dev"
183
184
  Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
184
185
  Provides-Extra: mcp
185
186
  Requires-Dist: mcp>=1.0.0; extra == "mcp"
186
- Provides-Extra: tui
187
- Requires-Dist: prompt_toolkit>=3.0.0; extra == "tui"
188
- Requires-Dist: rich>=13.0.0; extra == "tui"
189
187
  Dynamic: license-file
190
188
 
191
189
  # GemCode
@@ -69,10 +69,11 @@ src/gemcode/tools/search.py
69
69
  src/gemcode/tools/shell.py
70
70
  src/gemcode/tools/shell_gate.py
71
71
  src/gemcode/tools/todo.py
72
- src/gemcode/tui/app.py
73
72
  src/gemcode/tui/scrollback.py
74
73
  src/gemcode/tui/spinner.py
75
74
  src/gemcode/tui/startup_screen.py
75
+ src/gemcode/tui/welcome_banner.py
76
+ src/gemcode/tui/welcome_rich.py
76
77
  src/gemcode/web/__init__.py
77
78
  src/gemcode/web/claude_sse_adapter.py
78
79
  src/gemcode/web/terminal_repl.py
@@ -1,6 +1,7 @@
1
1
  google-adk>=1.0.0
2
2
  google-genai>=1.0.0
3
3
  python-dotenv>=1.0.0
4
+ rich>=13.0.0
4
5
 
5
6
  [dev]
6
7
  pytest>=8.0.0
@@ -8,7 +9,3 @@ pytest-asyncio>=0.24.0
8
9
 
9
10
  [mcp]
10
11
  mcp>=1.0.0
11
-
12
- [tui]
13
- prompt_toolkit>=3.0.0
14
- rich>=13.0.0