gemcode 0.3.14__tar.gz → 0.3.15__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.14/src/gemcode.egg-info → gemcode-0.3.15}/PKG-INFO +1 -1
  2. {gemcode-0.3.14 → gemcode-0.3.15}/pyproject.toml +1 -1
  3. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/cli.py +1 -4
  4. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tui/scrollback.py +6 -10
  5. gemcode-0.3.15/src/gemcode/tui/welcome_rich.py +135 -0
  6. {gemcode-0.3.14 → gemcode-0.3.15/src/gemcode.egg-info}/PKG-INFO +1 -1
  7. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode.egg-info/SOURCES.txt +0 -1
  8. gemcode-0.3.14/src/gemcode/tui/startup_screen.py +0 -196
  9. gemcode-0.3.14/src/gemcode/tui/welcome_rich.py +0 -40
  10. {gemcode-0.3.14 → gemcode-0.3.15}/LICENSE +0 -0
  11. {gemcode-0.3.14 → gemcode-0.3.15}/MANIFEST.in +0 -0
  12. {gemcode-0.3.14 → gemcode-0.3.15}/README.md +0 -0
  13. {gemcode-0.3.14 → gemcode-0.3.15}/setup.cfg +0 -0
  14. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/__init__.py +0 -0
  15. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/__main__.py +0 -0
  16. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/agent.py +0 -0
  17. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/audit.py +0 -0
  18. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/autocompact.py +0 -0
  19. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/callbacks.py +0 -0
  20. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/capability_routing.py +0 -0
  21. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/compaction.py +0 -0
  22. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/computer_use/__init__.py +0 -0
  23. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/computer_use/browser_computer.py +0 -0
  24. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/config.py +0 -0
  25. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/context_budget.py +0 -0
  26. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/context_warning.py +0 -0
  27. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/credentials.py +0 -0
  28. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/hitl_session.py +0 -0
  29. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/interactions.py +0 -0
  30. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/invoke.py +0 -0
  31. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/kairos_daemon.py +0 -0
  32. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/limits.py +0 -0
  33. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/live_audio_engine.py +0 -0
  34. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/logging_config.py +0 -0
  35. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/mcp_loader.py +0 -0
  36. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/memory/__init__.py +0 -0
  37. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/memory/embedding_memory_service.py +0 -0
  38. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/memory/file_memory_service.py +0 -0
  39. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/modality_tools.py +0 -0
  40. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/model_errors.py +0 -0
  41. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/model_routing.py +0 -0
  42. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/paths.py +0 -0
  43. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/permissions.py +0 -0
  44. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/plugins/__init__.py +0 -0
  45. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
  46. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  47. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/prompt_suggestions.py +0 -0
  48. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/query/__init__.py +0 -0
  49. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/query/config.py +0 -0
  50. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/query/deps.py +0 -0
  51. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/query/engine.py +0 -0
  52. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/query/stop_hooks.py +0 -0
  53. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/query/token_budget.py +0 -0
  54. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/query/transitions.py +0 -0
  55. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/repl_commands.py +0 -0
  56. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/repl_slash.py +0 -0
  57. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/session_runtime.py +0 -0
  58. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/slash_commands.py +0 -0
  59. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/thinking.py +0 -0
  60. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tool_prompt_manifest.py +0 -0
  61. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tool_registry.py +0 -0
  62. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tools/__init__.py +0 -0
  63. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tools/edit.py +0 -0
  64. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tools/filesystem.py +0 -0
  65. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tools/search.py +0 -0
  66. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tools/shell.py +0 -0
  67. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tools/shell_gate.py +0 -0
  68. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tools/todo.py +0 -0
  69. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tools_inspector.py +0 -0
  70. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/trust.py +0 -0
  71. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tui/spinner.py +0 -0
  72. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tui/welcome_banner.py +0 -0
  73. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/version.py +0 -0
  74. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/vertex.py +0 -0
  75. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/web/__init__.py +0 -0
  76. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/web/claude_sse_adapter.py +0 -0
  77. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/web/terminal_repl.py +0 -0
  78. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/workspace_hints.py +0 -0
  79. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode.egg-info/dependency_links.txt +0 -0
  80. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode.egg-info/entry_points.txt +0 -0
  81. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode.egg-info/requires.txt +0 -0
  82. {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode.egg-info/top_level.txt +0 -0
  83. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_agent_instruction.py +0 -0
  84. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_autocompact.py +0 -0
  85. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_capability_routing.py +0 -0
  86. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_claude_web_adapter_sse.py +0 -0
  87. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_cli_init.py +0 -0
  88. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_computer_use_permissions.py +0 -0
  89. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_context_budget.py +0 -0
  90. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_context_warning.py +0 -0
  91. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_credentials.py +0 -0
  92. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_interactive_permission_ask.py +0 -0
  93. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_kairos_scheduler.py +0 -0
  94. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_modality_tools.py +0 -0
  95. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_model_error_retry.py +0 -0
  96. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_model_errors.py +0 -0
  97. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_model_routing.py +0 -0
  98. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_paths.py +0 -0
  99. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_permissions.py +0 -0
  100. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_prompt_suggestions.py +0 -0
  101. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_repl_commands.py +0 -0
  102. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_repl_slash.py +0 -0
  103. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_slash_commands.py +0 -0
  104. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_thinking_config.py +0 -0
  105. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_token_budget.py +0 -0
  106. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_tool_context_circulation.py +0 -0
  107. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_tools.py +0 -0
  108. {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_tools_inspector.py +0 -0
  109. {gemcode-0.3.14 → gemcode-0.3.15}/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.14
3
+ Version: 0.3.15
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.14"
7
+ version = "0.3.15"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -183,10 +183,7 @@ async def _run_repl(cfg: GemCodeConfig, session_id: str, *, use_mcp: bool) -> No
183
183
  _maybe_prompt_google_api_key()
184
184
  require_google_api_key()
185
185
  _initialize_gemcode_project(cfg)
186
-
187
- # Show beautiful startup screen before TUI loads
188
- from gemcode.tui.startup_screen import print_startup_screen
189
- print_startup_screen()
186
+
190
187
  extra: list = []
191
188
  if use_mcp:
192
189
  from gemcode.mcp_loader import load_mcp_toolsets
@@ -117,28 +117,24 @@ class _Ansi:
117
117
 
118
118
  @property
119
119
  def blue(self) -> str:
120
- # Sunset orange/warm color instead of blue
121
- return self.esc("38;5;215")
120
+ # ANSI 256-color bright-ish blue
121
+ return self.esc("38;5;75")
122
122
 
123
123
  @property
124
124
  def blue2(self) -> str:
125
- # Deeper warm color
126
- return self.esc("38;5;209")
125
+ return self.esc("38;5;33")
127
126
 
128
127
  @property
129
128
  def blue_ok(self) -> str:
130
- # Warm green for success
131
- return self.esc("38;5;150")
129
+ return self.esc("38;5;81")
132
130
 
133
131
  @property
134
132
  def blue_warn(self) -> str:
135
- # Warm amber for warnings
136
- return self.esc("38;5;221")
133
+ return self.esc("38;5;39")
137
134
 
138
135
  @property
139
136
  def blue_tool(self) -> str:
140
- # Warm tool color
141
- return self.esc("38;5;180")
137
+ return self.esc("38;5;69")
142
138
 
143
139
 
144
140
  def _term_width(default: int = 100) -> int:
@@ -0,0 +1,135 @@
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
+ # Classic terminal blues (xterm 75 / 33); gradient stays in this range (no violet).
10
+ _BLUE_MAIN = "#5fafd7"
11
+ _BLUE_DEEP = "#0087d7"
12
+ _BLUE_HI = "#87d7ff" # lighter edge of gradient
13
+ # Info box: slate grey frame + labels; cyan-blue values (not saturated “blue” that reads purple).
14
+ _BORDER_GREY = "#64748b"
15
+ _LABEL_GREY = "#94a3b8"
16
+ _VALUE_BLUE = "#5fafd7"
17
+
18
+ if TYPE_CHECKING:
19
+ from rich.console import Console
20
+
21
+
22
+ def _hex_rgb(h: str) -> tuple[int, int, int]:
23
+ h = h.removeprefix("#")
24
+ return int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)
25
+
26
+
27
+ def _lerp_rgb(
28
+ a: tuple[int, int, int], b: tuple[int, int, int], t: float
29
+ ) -> tuple[int, int, int]:
30
+ t = max(0.0, min(1.0, t))
31
+ return (
32
+ round(a[0] + (b[0] - a[0]) * t),
33
+ round(a[1] + (b[1] - a[1]) * t),
34
+ round(a[2] + (b[2] - a[2]) * t),
35
+ )
36
+
37
+
38
+ def _gradient_logo_line(line: str, *, line_t: float) -> "Text":
39
+ """Horizontal + mild vertical blend (same idea as OpenClaude startup gradient)."""
40
+ from rich.text import Text
41
+
42
+ lo = _hex_rgb(_BLUE_DEEP)
43
+ hi = _hex_rgb(_BLUE_HI)
44
+ n = len(line)
45
+ out = Text()
46
+ for i, ch in enumerate(line):
47
+ if ch == " ":
48
+ out.append(" ")
49
+ continue
50
+ t_h = i / (n - 1) if n > 1 else 0.0
51
+ blend = line_t * 0.45 + t_h * 0.55
52
+ r, g, b = _lerp_rgb(lo, hi, blend)
53
+ out.append(ch, style=f"bold #{r:02x}{g:02x}{b:02x}")
54
+ return out
55
+
56
+
57
+ _BOX_DRAW = frozenset("╔╗║╚╠╣═")
58
+
59
+
60
+ def _info_box_line(line: str) -> "Text":
61
+ """Grey borders/labels, blue values — avoids an all-blue/violet panel."""
62
+ from rich.text import Text
63
+
64
+ t = Text()
65
+ first = line.find("│")
66
+ last = line.rfind("│")
67
+ if first != -1 and last > first:
68
+ t.append(line[:first])
69
+ t.append("│", style=f"dim {_BORDER_GREY}")
70
+ inner = line[first + 1:last]
71
+ stripped = inner.lstrip()
72
+ if stripped.startswith("●") and "Ready" in inner:
73
+ ready_at = inner.find("Ready")
74
+ if ready_at > 0:
75
+ t.append(inner[:ready_at], style=f"dim {_LABEL_GREY}")
76
+ t.append(inner[ready_at:], style=_VALUE_BLUE)
77
+ else:
78
+ t.append(inner, style=_VALUE_BLUE)
79
+ elif stripped.startswith(("Provider", "Model", "Endpoint")):
80
+ t.append(inner[:1])
81
+ t.append(inner[1:11], style=f"dim {_LABEL_GREY}")
82
+ t.append(inner[11:], style=_VALUE_BLUE)
83
+ else:
84
+ t.append(inner, style=_VALUE_BLUE)
85
+ t.append("│", style=f"dim {_BORDER_GREY}")
86
+ t.append(line[last + 1 :])
87
+ return t
88
+
89
+ i = 0
90
+ while i < len(line) and line[i] == " ":
91
+ t.append(line[i])
92
+ i += 1
93
+ while i < len(line):
94
+ c = line[i]
95
+ if c in _BOX_DRAW:
96
+ t.append(c, style=f"dim {_BORDER_GREY}")
97
+ else:
98
+ t.append(c)
99
+ i += 1
100
+ return t
101
+
102
+
103
+ def print_welcome_dashboard(cfg, *, console: Console) -> None:
104
+ from rich.console import Group
105
+ from rich.text import Text
106
+
107
+ tw = max(60, console.width or 80)
108
+ raw = format_welcome_banner(cfg, term_width=tw)
109
+ lines = raw.splitlines()
110
+ art_total = max(1, sum(1 for x in lines if "█" in x))
111
+ art_i = 0
112
+ rows: list[Text] = []
113
+ for ln in lines:
114
+ low = ln.lower()
115
+ if "█" in ln:
116
+ line_t = art_i / max(art_total - 1, 1)
117
+ rows.append(_gradient_logo_line(ln, line_t=line_t))
118
+ art_i += 1
119
+ elif any(c in ln for c in "╔╗║╚╠╣═│") and "█" not in ln:
120
+ rows.append(_info_box_line(ln))
121
+ elif "gemcode v" in low:
122
+ rows.append(Text(ln, style=f"bold {_BLUE_MAIN}"))
123
+ elif "✦" in ln:
124
+ rows.append(Text(ln, style=f"dim italic {_LABEL_GREY}"))
125
+ elif ln.strip().startswith("Tip:"):
126
+ rows.append(Text(ln, style=f"dim {_LABEL_GREY}"))
127
+ else:
128
+ rows.append(Text(ln))
129
+ console.print(Group(*rows))
130
+
131
+
132
+ def print_shortcuts_hint(*, console: Console) -> None:
133
+ from rich.text import Text
134
+
135
+ console.print(Text(" ? for shortcuts", style=f"dim {_LABEL_GREY}"))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.14
3
+ Version: 0.3.15
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -71,7 +71,6 @@ src/gemcode/tools/shell_gate.py
71
71
  src/gemcode/tools/todo.py
72
72
  src/gemcode/tui/scrollback.py
73
73
  src/gemcode/tui/spinner.py
74
- src/gemcode/tui/startup_screen.py
75
74
  src/gemcode/tui/welcome_banner.py
76
75
  src/gemcode/tui/welcome_rich.py
77
76
  src/gemcode/web/__init__.py
@@ -1,196 +0,0 @@
1
- """
2
- GemCode startup screen with gradient ASCII art logo.
3
- Inspired by OpenClaude's beautiful startup experience.
4
- """
5
-
6
- from __future__ import annotations
7
-
8
- import os
9
- import sys
10
- from typing import NamedTuple
11
-
12
-
13
- class RGB(NamedTuple):
14
- r: int
15
- g: int
16
- b: int
17
-
18
-
19
- ESC = "\x1b["
20
- RESET = f"{ESC}0m"
21
- DIM = f"{ESC}2m"
22
-
23
-
24
- def rgb(r: int, g: int, b: int) -> str:
25
- return f"{ESC}38;2;{r};{g};{b}m"
26
-
27
-
28
- def lerp(a: RGB, b: RGB, t: float) -> RGB:
29
- return RGB(
30
- round(a.r + (b.r - a.r) * t),
31
- round(a.g + (b.g - a.g) * t),
32
- round(a.b + (b.b - a.b) * t),
33
- )
34
-
35
-
36
- def grad_at(stops: list[RGB], t: float) -> RGB:
37
- c = max(0.0, min(1.0, t))
38
- s = c * (len(stops) - 1)
39
- i = int(s)
40
- if i >= len(stops) - 1:
41
- return stops[-1]
42
- return lerp(stops[i], stops[i + 1], s - i)
43
-
44
-
45
- def paint_line(text: str, stops: list[RGB], line_t: float) -> str:
46
- out = ""
47
- for i, ch in enumerate(text):
48
- t = line_t * 0.5 + (i / (len(text) - 1)) * 0.5 if len(text) > 1 else line_t
49
- color = grad_at(stops, t)
50
- out += f"{rgb(color.r, color.g, color.b)}{ch}"
51
- return out + RESET
52
-
53
-
54
- # Color palette - sunset gradient
55
- SUNSET_GRAD: list[RGB] = [
56
- RGB(255, 180, 100),
57
- RGB(240, 140, 80),
58
- RGB(217, 119, 87),
59
- RGB(193, 95, 60),
60
- RGB(160, 75, 55),
61
- RGB(130, 60, 50),
62
- ]
63
-
64
- ACCENT = RGB(240, 148, 100)
65
- CREAM = RGB(220, 195, 170)
66
- DIMCOL = RGB(120, 100, 82)
67
- BORDER = RGB(100, 80, 65)
68
-
69
- # Filled block text logo
70
- LOGO_GEM = [
71
- " ██████╗ ███████╗███╗ ███╗",
72
- " ██╔════╝ ██╔════╝████╗ ████║",
73
- " ██║ ███╗█████╗ ██╔████╔██║",
74
- " ██║ ██║██╔══╝ ██║╚██╔╝██║",
75
- " ╚██████╔╝███████╗██║ ╚═╝ ██║",
76
- " ╚═════╝ ╚══════╝╚═╝ ╚═╝",
77
- ]
78
-
79
- LOGO_CODE = [
80
- " ██████╗ ██████╗ ██████╗ ███████╗",
81
- " ██╔════╝██╔═══██╗██╔══██╗██╔════╝",
82
- " ██║ ██║ ██║██║ ██║█████╗ ",
83
- " ██║ ██║ ██║██║ ██║██╔══╝ ",
84
- " ╚██████╗╚██████╔╝██████╔╝███████╗",
85
- " ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝",
86
- ]
87
-
88
-
89
- def detect_provider() -> dict[str, str | bool]:
90
- """Detect current provider configuration."""
91
- # Check for Gemini (default for GemCode)
92
- api_key = os.environ.get("GOOGLE_API_KEY") or os.environ.get("GEMINI_API_KEY")
93
- model = os.environ.get("GEMINI_MODEL") or os.environ.get("GEMCODE_MODEL") or "gemini-2.0-flash-exp"
94
- base_url = os.environ.get("GEMINI_BASE_URL") or "https://generativelanguage.googleapis.com"
95
-
96
- return {
97
- "name": "Google Gemini",
98
- "model": model,
99
- "base_url": base_url,
100
- "is_local": False,
101
- "has_key": bool(api_key),
102
- }
103
-
104
-
105
- def box_row(content: str, width: int, raw_len: int) -> str:
106
- """Create a box row with proper padding."""
107
- pad = max(0, width - 2 - raw_len)
108
- return f"{rgb(BORDER.r, BORDER.g, BORDER.b)}│{RESET}{content}{' ' * pad}{rgb(BORDER.r, BORDER.g, BORDER.b)}│{RESET}"
109
-
110
-
111
- def print_startup_screen() -> None:
112
- """Print the beautiful gradient startup screen."""
113
- # Skip in non-interactive / CI environments
114
- if os.environ.get("CI") or not sys.stdout.isatty():
115
- return
116
-
117
- # Skip if explicitly disabled
118
- if os.environ.get("GEMCODE_NO_STARTUP_SCREEN", "").lower() in ("1", "true", "yes", "on"):
119
- return
120
-
121
- p = detect_provider()
122
- W = 62
123
- out: list[str] = []
124
-
125
- out.append("")
126
-
127
- # Gradient logo
128
- all_logo = LOGO_GEM + [""] + LOGO_CODE
129
- total = len(all_logo)
130
- for i, line in enumerate(all_logo):
131
- t = i / (total - 1) if total > 1 else 0
132
- if line == "":
133
- out.append("")
134
- else:
135
- out.append(paint_line(line, SUNSET_GRAD, t))
136
-
137
- out.append("")
138
-
139
- # Tagline
140
- out.append(
141
- f" {rgb(ACCENT.r, ACCENT.g, ACCENT.b)}✦{RESET} "
142
- f"{rgb(CREAM.r, CREAM.g, CREAM.b)}Gemini-powered coding agent. Fast. Capable. Local.{RESET} "
143
- f"{rgb(ACCENT.r, ACCENT.g, ACCENT.b)}✦{RESET}"
144
- )
145
- out.append("")
146
-
147
- # Provider info box
148
- out.append(f"{rgb(BORDER.r, BORDER.g, BORDER.b)}╔{'═' * (W - 2)}╗{RESET}")
149
-
150
- def lbl(k: str, v: str, c: RGB = CREAM) -> tuple[str, int]:
151
- pad_k = k.ljust(9)
152
- return (
153
- f" {DIM}{rgb(DIMCOL.r, DIMCOL.g, DIMCOL.b)}{pad_k}{RESET} {rgb(c.r, c.g, c.b)}{v}{RESET}",
154
- len(f" {pad_k} {v}"),
155
- )
156
-
157
- prov_c = RGB(130, 175, 130) if p["is_local"] else ACCENT
158
- r, l = lbl("Provider", str(p["name"]), prov_c)
159
- out.append(box_row(r, W, l))
160
-
161
- r, l = lbl("Model", str(p["model"]))
162
- out.append(box_row(r, W, l))
163
-
164
- ep = str(p["base_url"])
165
- if len(ep) > 38:
166
- ep = ep[:35] + "..."
167
- r, l = lbl("Endpoint", ep)
168
- out.append(box_row(r, W, l))
169
-
170
- out.append(f"{rgb(BORDER.r, BORDER.g, BORDER.b)}╠{'═' * (W - 2)}╣{RESET}")
171
-
172
- # Status line
173
- s_c = RGB(130, 175, 130) if p["is_local"] else ACCENT
174
- s_l = "local" if p["is_local"] else "cloud"
175
- status_text = (
176
- f" {rgb(s_c.r, s_c.g, s_c.b)}●{RESET} "
177
- f"{DIM}{rgb(DIMCOL.r, DIMCOL.g, DIMCOL.b)}{s_l}{RESET} "
178
- f"{DIM}{rgb(DIMCOL.r, DIMCOL.g, DIMCOL.b)}Ready — type {RESET}"
179
- f"{rgb(ACCENT.r, ACCENT.g, ACCENT.b)}/help{RESET}"
180
- f"{DIM}{rgb(DIMCOL.r, DIMCOL.g, DIMCOL.b)} to begin{RESET}"
181
- )
182
- s_len = len(f" ● {s_l} Ready — type /help to begin")
183
- out.append(box_row(status_text, W, s_len))
184
-
185
- out.append(f"{rgb(BORDER.r, BORDER.g, BORDER.b)}╚{'═' * (W - 2)}╝{RESET}")
186
-
187
- # Version
188
- from gemcode.version import get_version
189
- version = os.environ.get("GEMCODE_VERSION", get_version())
190
- out.append(
191
- f" {DIM}{rgb(DIMCOL.r, DIMCOL.g, DIMCOL.b)}gemcode {RESET}"
192
- f"{rgb(ACCENT.r, ACCENT.g, ACCENT.b)}v{version}{RESET}"
193
- )
194
- out.append("")
195
-
196
- print("\n".join(out), flush=True)
@@ -1,40 +0,0 @@
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"))
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