gemcode 0.3.16__tar.gz → 0.3.17__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 (110) hide show
  1. {gemcode-0.3.16/src/gemcode.egg-info → gemcode-0.3.17}/PKG-INFO +2 -1
  2. {gemcode-0.3.16 → gemcode-0.3.17}/pyproject.toml +2 -1
  3. gemcode-0.3.17/src/gemcode/tui/input_handler.py +182 -0
  4. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/tui/scrollback.py +20 -4
  5. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/tui/welcome_rich.py +6 -1
  6. {gemcode-0.3.16 → gemcode-0.3.17/src/gemcode.egg-info}/PKG-INFO +2 -1
  7. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode.egg-info/SOURCES.txt +1 -0
  8. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode.egg-info/requires.txt +1 -0
  9. {gemcode-0.3.16 → gemcode-0.3.17}/LICENSE +0 -0
  10. {gemcode-0.3.16 → gemcode-0.3.17}/MANIFEST.in +0 -0
  11. {gemcode-0.3.16 → gemcode-0.3.17}/README.md +0 -0
  12. {gemcode-0.3.16 → gemcode-0.3.17}/setup.cfg +0 -0
  13. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/__init__.py +0 -0
  14. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/__main__.py +0 -0
  15. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/agent.py +0 -0
  16. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/audit.py +0 -0
  17. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/autocompact.py +0 -0
  18. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/callbacks.py +0 -0
  19. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/capability_routing.py +0 -0
  20. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/cli.py +0 -0
  21. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/compaction.py +0 -0
  22. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/computer_use/__init__.py +0 -0
  23. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/computer_use/browser_computer.py +0 -0
  24. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/config.py +0 -0
  25. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/context_budget.py +0 -0
  26. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/context_warning.py +0 -0
  27. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/credentials.py +0 -0
  28. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/hitl_session.py +0 -0
  29. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/interactions.py +0 -0
  30. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/invoke.py +0 -0
  31. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/kairos_daemon.py +0 -0
  32. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/limits.py +0 -0
  33. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/live_audio_engine.py +0 -0
  34. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/logging_config.py +0 -0
  35. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/mcp_loader.py +0 -0
  36. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/memory/__init__.py +0 -0
  37. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/memory/embedding_memory_service.py +0 -0
  38. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/memory/file_memory_service.py +0 -0
  39. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/modality_tools.py +0 -0
  40. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/model_errors.py +0 -0
  41. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/model_routing.py +0 -0
  42. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/paths.py +0 -0
  43. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/permissions.py +0 -0
  44. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/plugins/__init__.py +0 -0
  45. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
  46. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  47. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/prompt_suggestions.py +0 -0
  48. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/query/__init__.py +0 -0
  49. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/query/config.py +0 -0
  50. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/query/deps.py +0 -0
  51. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/query/engine.py +0 -0
  52. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/query/stop_hooks.py +0 -0
  53. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/query/token_budget.py +0 -0
  54. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/query/transitions.py +0 -0
  55. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/repl_commands.py +0 -0
  56. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/repl_slash.py +0 -0
  57. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/session_runtime.py +0 -0
  58. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/slash_commands.py +0 -0
  59. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/thinking.py +0 -0
  60. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/tool_prompt_manifest.py +0 -0
  61. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/tool_registry.py +0 -0
  62. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/tools/__init__.py +0 -0
  63. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/tools/bash.py +0 -0
  64. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/tools/edit.py +0 -0
  65. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/tools/filesystem.py +0 -0
  66. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/tools/search.py +0 -0
  67. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/tools/shell.py +0 -0
  68. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/tools/shell_gate.py +0 -0
  69. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/tools/todo.py +0 -0
  70. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/tools/web.py +0 -0
  71. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/tools_inspector.py +0 -0
  72. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/trust.py +0 -0
  73. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/tui/spinner.py +0 -0
  74. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/tui/welcome_banner.py +0 -0
  75. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/version.py +0 -0
  76. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/vertex.py +0 -0
  77. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/web/__init__.py +0 -0
  78. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/web/claude_sse_adapter.py +0 -0
  79. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/web/terminal_repl.py +0 -0
  80. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode/workspace_hints.py +0 -0
  81. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode.egg-info/dependency_links.txt +0 -0
  82. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode.egg-info/entry_points.txt +0 -0
  83. {gemcode-0.3.16 → gemcode-0.3.17}/src/gemcode.egg-info/top_level.txt +0 -0
  84. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_agent_instruction.py +0 -0
  85. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_autocompact.py +0 -0
  86. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_capability_routing.py +0 -0
  87. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_claude_web_adapter_sse.py +0 -0
  88. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_cli_init.py +0 -0
  89. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_computer_use_permissions.py +0 -0
  90. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_context_budget.py +0 -0
  91. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_context_warning.py +0 -0
  92. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_credentials.py +0 -0
  93. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_interactive_permission_ask.py +0 -0
  94. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_kairos_scheduler.py +0 -0
  95. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_modality_tools.py +0 -0
  96. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_model_error_retry.py +0 -0
  97. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_model_errors.py +0 -0
  98. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_model_routing.py +0 -0
  99. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_paths.py +0 -0
  100. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_permissions.py +0 -0
  101. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_prompt_suggestions.py +0 -0
  102. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_repl_commands.py +0 -0
  103. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_repl_slash.py +0 -0
  104. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_slash_commands.py +0 -0
  105. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_thinking_config.py +0 -0
  106. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_token_budget.py +0 -0
  107. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_tool_context_circulation.py +0 -0
  108. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_tools.py +0 -0
  109. {gemcode-0.3.16 → gemcode-0.3.17}/tests/test_tools_inspector.py +0 -0
  110. {gemcode-0.3.16 → gemcode-0.3.17}/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.16
3
+ Version: 0.3.17
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -179,6 +179,7 @@ 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
181
  Requires-Dist: rich>=13.0.0
182
+ Requires-Dist: prompt_toolkit>=3.0.0
182
183
  Provides-Extra: dev
183
184
  Requires-Dist: pytest>=8.0.0; extra == "dev"
184
185
  Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gemcode"
7
- version = "0.3.16"
7
+ version = "0.3.17"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -31,6 +31,7 @@ dependencies = [
31
31
  "google-genai>=1.0.0",
32
32
  "python-dotenv>=1.0.0",
33
33
  "rich>=13.0.0",
34
+ "prompt_toolkit>=3.0.0",
34
35
  ]
35
36
 
36
37
  [project.urls]
@@ -0,0 +1,182 @@
1
+ """
2
+ Interactive input handler for the GemCode REPL.
3
+
4
+ Provides (when prompt_toolkit is available):
5
+ - Slash command autocomplete — type / and see all commands with descriptions;
6
+ use ↑ / ↓ arrows to navigate the list, Tab or Enter to accept, Esc to dismiss.
7
+ - Conversation history — ↑ / ↓ arrows cycle through previous messages (when
8
+ the completion popup is not open).
9
+ - Status bar — shows the active model and session ID while typing.
10
+ - Styled ❯ prompt in GemCode blue.
11
+
12
+ Falls back gracefully to plain input() when prompt_toolkit is unavailable or
13
+ stdout is not a TTY (e.g. piped input, CI).
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import sys
19
+ from typing import Callable
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # Availability guard — import everything inside try so we never hard-crash
23
+ # ---------------------------------------------------------------------------
24
+ _PT_AVAILABLE = False
25
+ try:
26
+ from prompt_toolkit import PromptSession
27
+ from prompt_toolkit.completion import Completer, Completion
28
+ from prompt_toolkit.document import Document # noqa: F401 (type hint only)
29
+ from prompt_toolkit.formatted_text import HTML
30
+ from prompt_toolkit.history import InMemoryHistory
31
+ from prompt_toolkit.key_binding import KeyBindings
32
+ from prompt_toolkit.styles import Style
33
+
34
+ _PT_AVAILABLE = True
35
+ except ImportError:
36
+ pass
37
+
38
+ # ---------------------------------------------------------------------------
39
+ # Slash command registry
40
+ # (name without /, description shown in the autocomplete popup)
41
+ # ---------------------------------------------------------------------------
42
+ SLASH_COMMANDS: list[tuple[str, str]] = [
43
+ ("help", "List all available commands"),
44
+ ("clear", "Start a fresh session — clears conversation history"),
45
+ ("model", "View or switch model · /model use <id> · /model list"),
46
+ ("status", "Show session ID, model, and current settings"),
47
+ ("context", "Show context window usage and token counts"),
48
+ ("tools", "List all tools and their permission categories"),
49
+ ("compact", "Summarise conversation history to reclaim context space"),
50
+ ("config", "Show active configuration and environment variables"),
51
+ ("permissions", "Show current permission mode (default / strict / yes)"),
52
+ ("audit", "Show recent audit log · /audit [N lines]"),
53
+ ("memory", "Show memory / storage settings"),
54
+ ("doctor", "Run diagnostics and validate the environment"),
55
+ ("hooks", "Show hooks configuration"),
56
+ ("version", "Show installed GemCode version"),
57
+ ("exit", "Exit GemCode"),
58
+ ]
59
+
60
+
61
+ # ---------------------------------------------------------------------------
62
+ # Completer
63
+ # ---------------------------------------------------------------------------
64
+ if _PT_AVAILABLE:
65
+ class _SlashCommandCompleter(Completer):
66
+ """
67
+ Activates when the input starts with '/'.
68
+ Supports prefix matching and substring matching so typing '/mo' shows
69
+ /model, '/compact', etc.
70
+ """
71
+
72
+ def get_completions(self, document, complete_event):
73
+ text = document.text_before_cursor
74
+ if not text.startswith("/"):
75
+ return
76
+ query = text[1:].lower()
77
+ for name, desc in SLASH_COMMANDS:
78
+ if not query or name.startswith(query) or query in name:
79
+ yield Completion(
80
+ "/" + name,
81
+ start_position=-len(text),
82
+ display=HTML(f"<ansiblue>/<b>{name}</b></ansiblue>"),
83
+ display_meta=desc,
84
+ )
85
+
86
+
87
+ # ---------------------------------------------------------------------------
88
+ # Public class
89
+ # ---------------------------------------------------------------------------
90
+ class GemCodeInputHandler:
91
+ """
92
+ Wraps prompt_toolkit PromptSession with GemCode-specific styling and slash
93
+ command autocomplete. Use ``await handler.prompt_async()`` in the async
94
+ REPL loop.
95
+
96
+ When prompt_toolkit is not installed (or stdout is not a TTY) every call
97
+ transparently delegates to ``input()``.
98
+ """
99
+
100
+ def __init__(
101
+ self,
102
+ *,
103
+ ansi_enabled: bool = True,
104
+ get_model: Callable[[], str] | None = None,
105
+ get_session_id: Callable[[], str] | None = None,
106
+ ) -> None:
107
+ self._ansi = ansi_enabled
108
+ self._get_model = get_model or (lambda: "gemini")
109
+ self._get_session_id = get_session_id or (lambda: "")
110
+ self._session: "PromptSession | None" = None
111
+
112
+ if _PT_AVAILABLE and sys.stdin.isatty() and sys.stdout.isatty():
113
+ self._build_session()
114
+
115
+ def _build_session(self) -> None:
116
+ style = Style.from_dict(
117
+ {
118
+ # The ❯ prompt glyph
119
+ "prompt": "#5fafd7 bold",
120
+ # Completion popup rows
121
+ "completion-menu.completion": "bg:#0d2035 fg:#87cefa",
122
+ "completion-menu.completion.current": "bg:#0087d7 fg:#ffffff bold",
123
+ # Meta (description) column
124
+ "completion-menu.meta.completion": "bg:#0d2035 fg:#5a7a9a",
125
+ "completion-menu.meta.completion.current": "bg:#0065a0 fg:#cce8ff",
126
+ "completion-menu.border": "#0087d7",
127
+ # Bottom toolbar
128
+ "bottom-toolbar": "bg:#0d1f2d fg:#4a7a9a",
129
+ }
130
+ )
131
+
132
+ get_model = self._get_model
133
+ get_session_id = self._get_session_id
134
+
135
+ def bottom_toolbar() -> HTML:
136
+ model = get_model() or "gemini"
137
+ sid = get_session_id()
138
+ sid_short = sid[:8] if len(sid) >= 8 else sid
139
+ return HTML(
140
+ f'<style bg="#0d1f2d" fg="#5fafd7"><b> ◆ {model}</b></style>'
141
+ f'<style bg="#0d1f2d" fg="#3d6080"> · session {sid_short}'
142
+ f' · / for commands</style>'
143
+ )
144
+
145
+ kb = KeyBindings()
146
+
147
+ @kb.add("c-c")
148
+ def _ctrl_c(event):
149
+ """Ctrl+C clears current line (like a real shell)."""
150
+ event.app.current_buffer.reset()
151
+
152
+ self._session = PromptSession(
153
+ history=InMemoryHistory(),
154
+ completer=_SlashCommandCompleter(),
155
+ complete_while_typing=True,
156
+ style=style,
157
+ bottom_toolbar=bottom_toolbar,
158
+ key_bindings=kb,
159
+ mouse_support=False,
160
+ complete_in_thread=True,
161
+ reserve_space_for_menu=10,
162
+ # Show completion descriptions to the right (like VS Code)
163
+ complete_style="COLUMN",
164
+ )
165
+
166
+ def is_interactive(self) -> bool:
167
+ return self._session is not None
168
+
169
+ async def prompt_async(self) -> str:
170
+ """
171
+ Async prompt — returns stripped user input.
172
+ Raises EOFError on Ctrl+D (caller should handle gracefully).
173
+ """
174
+ if self._session is not None:
175
+ from prompt_toolkit.formatted_text import HTML as _HTML
176
+
177
+ result = await self._session.prompt_async(
178
+ _HTML('<style fg="#5fafd7"><b>❯</b></style> '),
179
+ )
180
+ return (result or "").strip()
181
+ else:
182
+ return input("❯ ").strip()
@@ -14,6 +14,7 @@ from gemcode.capability_routing import apply_capability_routing
14
14
  from gemcode.config import load_cli_environment
15
15
  from gemcode.model_routing import pick_effective_model
16
16
  from gemcode.repl_slash import process_repl_slash
17
+ from gemcode.tui.input_handler import GemCodeInputHandler
17
18
  from gemcode.tui.welcome_rich import print_shortcuts_hint, print_welcome_dashboard
18
19
 
19
20
  _ADK_REQUEST_CONFIRMATION = "adk_request_confirmation"
@@ -185,6 +186,17 @@ async def run_gemcode_scrollback_tui(
185
186
 
186
187
  char_delay_ms = int(os.environ.get("GEMCODE_TUI_CHAR_DELAY_MS", "0") or "0")
187
188
 
189
+ # Build the interactive input handler (prompt_toolkit when available, plain
190
+ # input() otherwise). Callables let the handler always read the *current*
191
+ # model and session id even after /model or /clear commands.
192
+ _current_session_id_holder = [session_id]
193
+
194
+ input_handler = GemCodeInputHandler(
195
+ ansi_enabled=ansi.enabled,
196
+ get_model=lambda: getattr(cfg, "model", "gemini") or "gemini",
197
+ get_session_id=lambda: _current_session_id_holder[0],
198
+ )
199
+
188
200
  async def typewrite(text: str) -> None:
189
201
  if not text:
190
202
  return
@@ -252,7 +264,7 @@ async def run_gemcode_scrollback_tui(
252
264
 
253
265
  while True:
254
266
  try:
255
- prompt = input(f"{ansi.bold}❯{ansi.reset} ").strip()
267
+ prompt = await input_handler.prompt_async()
256
268
  except EOFError:
257
269
  print("")
258
270
  return
@@ -276,6 +288,7 @@ async def run_gemcode_scrollback_tui(
276
288
  return
277
289
  if slash.new_session_id is not None:
278
290
  current_session_id = slash.new_session_id
291
+ _current_session_id_holder[0] = current_session_id
279
292
  if slash.skip_model_turn:
280
293
  # Runner binds the model at creation time (LlmAgent(model=...)),
281
294
  # so rebuild it when the user overrides the model mid-session.
@@ -400,9 +413,12 @@ async def run_gemcode_scrollback_tui(
400
413
  print(
401
414
  f" ⎿ {ansi.blue}{ansi.bold}Permission needed{ansi.reset} for {ansi.bold}{tool_name}{ansi.reset}."
402
415
  )
403
- ans = input(
404
- f" ⎿ Allow? ({ansi.blue_ok}y{ansi.reset}/{ansi.dim}N{ansi.reset}) "
405
- ).strip().lower()
416
+ try:
417
+ ans = input(
418
+ f" ⎿ Allow? ({ansi.blue_ok}y{ansi.reset}/{ansi.dim}N{ansi.reset}) "
419
+ ).strip().lower()
420
+ except EOFError:
421
+ ans = ""
406
422
  ok = ans in ("y", "yes")
407
423
 
408
424
  parts.append(
@@ -132,4 +132,9 @@ def print_welcome_dashboard(cfg, *, console: Console) -> None:
132
132
  def print_shortcuts_hint(*, console: Console) -> None:
133
133
  from rich.text import Text
134
134
 
135
- console.print(Text(" ? for shortcuts", style=f"dim {_LABEL_GREY}"))
135
+ console.print(
136
+ Text(
137
+ " Type a message to begin · / for commands · ↑↓ history",
138
+ style=f"dim {_LABEL_GREY}",
139
+ )
140
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.16
3
+ Version: 0.3.17
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -179,6 +179,7 @@ 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
181
  Requires-Dist: rich>=13.0.0
182
+ Requires-Dist: prompt_toolkit>=3.0.0
182
183
  Provides-Extra: dev
183
184
  Requires-Dist: pytest>=8.0.0; extra == "dev"
184
185
  Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
@@ -71,6 +71,7 @@ src/gemcode/tools/shell.py
71
71
  src/gemcode/tools/shell_gate.py
72
72
  src/gemcode/tools/todo.py
73
73
  src/gemcode/tools/web.py
74
+ src/gemcode/tui/input_handler.py
74
75
  src/gemcode/tui/scrollback.py
75
76
  src/gemcode/tui/spinner.py
76
77
  src/gemcode/tui/welcome_banner.py
@@ -2,6 +2,7 @@ google-adk>=1.0.0
2
2
  google-genai>=1.0.0
3
3
  python-dotenv>=1.0.0
4
4
  rich>=13.0.0
5
+ prompt_toolkit>=3.0.0
5
6
 
6
7
  [dev]
7
8
  pytest>=8.0.0
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