gemcode 0.3.51__tar.gz → 0.3.53__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.51/src/gemcode.egg-info → gemcode-0.3.53}/PKG-INFO +1 -1
  2. {gemcode-0.3.51 → gemcode-0.3.53}/pyproject.toml +1 -1
  3. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/session_runtime.py +31 -4
  4. gemcode-0.3.53/src/gemcode/tui/welcome_banner.py +137 -0
  5. {gemcode-0.3.51 → gemcode-0.3.53/src/gemcode.egg-info}/PKG-INFO +1 -1
  6. gemcode-0.3.51/src/gemcode/tui/welcome_banner.py +0 -127
  7. {gemcode-0.3.51 → gemcode-0.3.53}/LICENSE +0 -0
  8. {gemcode-0.3.51 → gemcode-0.3.53}/MANIFEST.in +0 -0
  9. {gemcode-0.3.51 → gemcode-0.3.53}/README.md +0 -0
  10. {gemcode-0.3.51 → gemcode-0.3.53}/setup.cfg +0 -0
  11. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/__init__.py +0 -0
  12. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/__main__.py +0 -0
  13. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/agent.py +0 -0
  14. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/audit.py +0 -0
  15. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/autocompact.py +0 -0
  16. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/callbacks.py +0 -0
  17. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/capability_routing.py +0 -0
  18. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/cli.py +0 -0
  19. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/compaction.py +0 -0
  20. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/computer_use/__init__.py +0 -0
  21. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/computer_use/browser_computer.py +0 -0
  22. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/config.py +0 -0
  23. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/context_budget.py +0 -0
  24. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/context_warning.py +0 -0
  25. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/credentials.py +0 -0
  26. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/hitl_session.py +0 -0
  27. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/hooks.py +0 -0
  28. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/intent_classifier.py +0 -0
  29. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/interactions.py +0 -0
  30. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/invoke.py +0 -0
  31. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/kairos_daemon.py +0 -0
  32. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/limits.py +0 -0
  33. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/live_audio_engine.py +0 -0
  34. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/logging_config.py +0 -0
  35. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/mcp_loader.py +0 -0
  36. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/memory/__init__.py +0 -0
  37. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/memory/embedding_memory_service.py +0 -0
  38. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/memory/file_memory_service.py +0 -0
  39. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/modality_tools.py +0 -0
  40. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/model_errors.py +0 -0
  41. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/model_routing.py +0 -0
  42. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/openapi_loader.py +0 -0
  43. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/paths.py +0 -0
  44. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/permissions.py +0 -0
  45. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/plugins/__init__.py +0 -0
  46. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
  47. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  48. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/pricing.py +0 -0
  49. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/prompt_suggestions.py +0 -0
  50. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/query/__init__.py +0 -0
  51. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/query/config.py +0 -0
  52. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/query/deps.py +0 -0
  53. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/query/engine.py +0 -0
  54. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/query/stop_hooks.py +0 -0
  55. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/query/token_budget.py +0 -0
  56. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/query/transitions.py +0 -0
  57. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/refine.py +0 -0
  58. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/repl_commands.py +0 -0
  59. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/repl_slash.py +0 -0
  60. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/review_agent.py +0 -0
  61. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/session_store.py +0 -0
  62. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/slash_commands.py +0 -0
  63. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/thinking.py +0 -0
  64. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/tool_prompt_manifest.py +0 -0
  65. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/tool_registry.py +0 -0
  66. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/tools/__init__.py +0 -0
  67. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/tools/bash.py +0 -0
  68. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/tools/browser.py +0 -0
  69. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/tools/edit.py +0 -0
  70. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/tools/filesystem.py +0 -0
  71. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/tools/notes.py +0 -0
  72. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/tools/search.py +0 -0
  73. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/tools/shell.py +0 -0
  74. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/tools/shell_gate.py +0 -0
  75. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/tools/subtask.py +0 -0
  76. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/tools/think.py +0 -0
  77. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/tools/todo.py +0 -0
  78. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/tools/web.py +0 -0
  79. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/tools_inspector.py +0 -0
  80. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/trust.py +0 -0
  81. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/tui/input_handler.py +0 -0
  82. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/tui/scrollback.py +0 -0
  83. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/tui/spinner.py +0 -0
  84. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/tui/welcome_rich.py +0 -0
  85. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/version.py +0 -0
  86. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/vertex.py +0 -0
  87. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/web/__init__.py +0 -0
  88. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/web/claude_sse_adapter.py +0 -0
  89. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/web/terminal_repl.py +0 -0
  90. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode/workspace_hints.py +0 -0
  91. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode.egg-info/SOURCES.txt +0 -0
  92. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode.egg-info/dependency_links.txt +0 -0
  93. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode.egg-info/entry_points.txt +0 -0
  94. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode.egg-info/requires.txt +0 -0
  95. {gemcode-0.3.51 → gemcode-0.3.53}/src/gemcode.egg-info/top_level.txt +0 -0
  96. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_agent_instruction.py +0 -0
  97. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_autocompact.py +0 -0
  98. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_capability_routing.py +0 -0
  99. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_claude_web_adapter_sse.py +0 -0
  100. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_cli_init.py +0 -0
  101. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_computer_use_permissions.py +0 -0
  102. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_context_budget.py +0 -0
  103. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_context_warning.py +0 -0
  104. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_credentials.py +0 -0
  105. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_interactive_permission_ask.py +0 -0
  106. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_kairos_scheduler.py +0 -0
  107. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_modality_tools.py +0 -0
  108. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_model_error_retry.py +0 -0
  109. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_model_errors.py +0 -0
  110. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_model_routing.py +0 -0
  111. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_paths.py +0 -0
  112. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_permissions.py +0 -0
  113. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_prompt_suggestions.py +0 -0
  114. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_repl_commands.py +0 -0
  115. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_repl_slash.py +0 -0
  116. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_slash_commands.py +0 -0
  117. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_thinking_config.py +0 -0
  118. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_token_budget.py +0 -0
  119. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_tool_context_circulation.py +0 -0
  120. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_tools.py +0 -0
  121. {gemcode-0.3.51 → gemcode-0.3.53}/tests/test_tools_inspector.py +0 -0
  122. {gemcode-0.3.51 → gemcode-0.3.53}/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.51
3
+ Version: 0.3.53
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.51"
7
+ version = "0.3.53"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -77,6 +77,18 @@ def _make_safe_computer_toolset(computer):
77
77
  file=sys.stderr,
78
78
  )
79
79
 
80
+ # ── BaseToolset required attribute ──────────────────────────────────────
81
+ @property
82
+ def tool_name_prefix(self) -> str:
83
+ if self._inner is not None:
84
+ try:
85
+ return self._inner.tool_name_prefix
86
+ except Exception:
87
+ pass
88
+ return ""
89
+
90
+ # ── Core protocol methods ────────────────────────────────────────────────
91
+
80
92
  async def process_llm_request(self, *, tool_context, llm_request) -> None:
81
93
  if self._broken or self._inner is None:
82
94
  return
@@ -85,8 +97,9 @@ def _make_safe_computer_toolset(computer):
85
97
  tool_context=tool_context, llm_request=llm_request
86
98
  )
87
99
  except Exception as exc:
88
- self._broken = True
89
- self._warn_once(exc)
100
+ if not self._broken:
101
+ self._broken = True
102
+ self._warn_once(exc)
90
103
 
91
104
  async def get_tools(self, readonly_context=None):
92
105
  if self._broken or self._inner is None:
@@ -94,8 +107,9 @@ def _make_safe_computer_toolset(computer):
94
107
  try:
95
108
  return await self._inner.get_tools(readonly_context)
96
109
  except Exception as exc:
97
- self._broken = True
98
- self._warn_once(exc)
110
+ if not self._broken:
111
+ self._broken = True
112
+ self._warn_once(exc)
99
113
  return []
100
114
 
101
115
  async def close(self) -> None:
@@ -105,6 +119,19 @@ def _make_safe_computer_toolset(computer):
105
119
  except Exception:
106
120
  pass
107
121
 
122
+ def __getattr__(self, name: str):
123
+ """Proxy any other BaseToolset attributes ADK needs to the inner toolset."""
124
+ # Guard against infinite recursion for our own private attrs.
125
+ if name.startswith("_"):
126
+ raise AttributeError(name)
127
+ inner = object.__getattribute__(self, "_inner")
128
+ if inner is not None:
129
+ try:
130
+ return getattr(inner, name)
131
+ except AttributeError:
132
+ pass
133
+ raise AttributeError(f"'_SafeComputerUseToolset' has no attribute '{name}'")
134
+
108
135
  return _SafeComputerUseToolset()
109
136
 
110
137
 
@@ -0,0 +1,137 @@
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.51
3
+ Version: 0.3.53
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -1,127 +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
- 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)
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