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.
- {gemcode-0.3.14/src/gemcode.egg-info → gemcode-0.3.15}/PKG-INFO +1 -1
- {gemcode-0.3.14 → gemcode-0.3.15}/pyproject.toml +1 -1
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/cli.py +1 -4
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tui/scrollback.py +6 -10
- gemcode-0.3.15/src/gemcode/tui/welcome_rich.py +135 -0
- {gemcode-0.3.14 → gemcode-0.3.15/src/gemcode.egg-info}/PKG-INFO +1 -1
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode.egg-info/SOURCES.txt +0 -1
- gemcode-0.3.14/src/gemcode/tui/startup_screen.py +0 -196
- gemcode-0.3.14/src/gemcode/tui/welcome_rich.py +0 -40
- {gemcode-0.3.14 → gemcode-0.3.15}/LICENSE +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/MANIFEST.in +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/README.md +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/setup.cfg +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/__init__.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/__main__.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/agent.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/audit.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/autocompact.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/callbacks.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/capability_routing.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/compaction.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/computer_use/__init__.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/computer_use/browser_computer.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/config.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/context_budget.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/context_warning.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/credentials.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/hitl_session.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/interactions.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/invoke.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/kairos_daemon.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/limits.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/live_audio_engine.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/logging_config.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/mcp_loader.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/memory/__init__.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/memory/embedding_memory_service.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/memory/file_memory_service.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/modality_tools.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/model_errors.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/model_routing.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/paths.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/permissions.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/plugins/__init__.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/prompt_suggestions.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/query/__init__.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/query/config.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/query/deps.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/query/engine.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/query/stop_hooks.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/query/token_budget.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/query/transitions.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/repl_commands.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/repl_slash.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/session_runtime.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/slash_commands.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/thinking.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tool_prompt_manifest.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tool_registry.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tools/__init__.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tools/edit.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tools/filesystem.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tools/search.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tools/shell.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tools/shell_gate.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tools/todo.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tools_inspector.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/trust.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tui/spinner.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/tui/welcome_banner.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/version.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/vertex.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/web/__init__.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/web/claude_sse_adapter.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/web/terminal_repl.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode/workspace_hints.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode.egg-info/dependency_links.txt +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode.egg-info/entry_points.txt +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode.egg-info/requires.txt +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/src/gemcode.egg-info/top_level.txt +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_agent_instruction.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_autocompact.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_capability_routing.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_claude_web_adapter_sse.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_cli_init.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_computer_use_permissions.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_context_budget.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_context_warning.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_credentials.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_interactive_permission_ask.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_kairos_scheduler.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_modality_tools.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_model_error_retry.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_model_errors.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_model_routing.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_paths.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_permissions.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_prompt_suggestions.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_repl_commands.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_repl_slash.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_slash_commands.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_thinking_config.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_token_budget.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_tool_context_circulation.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_tools.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_tools_inspector.py +0 -0
- {gemcode-0.3.14 → gemcode-0.3.15}/tests/test_workspace_hints.py +0 -0
|
@@ -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
|
-
#
|
|
121
|
-
return self.esc("38;5;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}"))
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|