gemcode 0.3.24__py3-none-any.whl → 0.3.26__py3-none-any.whl

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/tui/scrollback.py CHANGED
@@ -140,6 +140,43 @@ class _Ansi:
140
140
  return self.esc("38;5;69")
141
141
 
142
142
 
143
+ async def _read_permission_char(loop) -> bool:
144
+ """
145
+ Read a single character from stdin without requiring Enter.
146
+
147
+ Uses cbreak mode (Unix) so the user just presses 'y' — no Enter needed.
148
+ This sidesteps the prompt_toolkit raw-mode conflict entirely: after
149
+ prompt_async() returns, the terminal may still behave as if Enter sends
150
+ \\r instead of \\n, causing input()/readline() to block forever.
151
+ cbreak mode + read(1) works regardless of the terminal's current line-
152
+ discipline state.
153
+
154
+ Falls back to readline() on Windows or non-TTY (CI, piped input).
155
+ """
156
+ # ── Unix / macOS: cbreak + read(1) ───────────────────────────────────────
157
+ try:
158
+ import termios
159
+ import tty
160
+
161
+ fd = sys.stdin.fileno()
162
+ old_settings = termios.tcgetattr(fd)
163
+ try:
164
+ tty.setcbreak(fd) # single-char, no echo, signals still work
165
+ ch = await loop.run_in_executor(None, lambda: sys.stdin.read(1))
166
+ finally:
167
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
168
+ return ch.lower() == "y"
169
+ except Exception:
170
+ pass
171
+
172
+ # ── Fallback: readline in thread (Windows, non-TTY, termios error) ───────
173
+ try:
174
+ raw = await loop.run_in_executor(None, sys.stdin.readline)
175
+ return (raw or "").replace("\r", "").replace("\n", "").strip().lower() in ("y", "yes")
176
+ except EOFError:
177
+ return False
178
+
179
+
143
180
  def _term_width(default: int = 100) -> int:
144
181
  try:
145
182
  import shutil
@@ -563,27 +600,59 @@ async def run_gemcode_scrollback_tui(
563
600
  if not interactive_enabled:
564
601
  print("")
565
602
  print(
566
- f" ⎿ {ansi.blue_warn}{ansi.bold}Permission needed{ansi.reset} for {ansi.bold}{tool_name}{ansi.reset} "
567
- f"but perm mode is not ask. Denying."
603
+ f" ⎿ {ansi.blue_warn}{ansi.bold}Permission needed{ansi.reset} for "
604
+ f"{ansi.bold}{tool_name}{ansi.reset} auto-denying "
605
+ f"(run with --yes or /computer on to allow)."
568
606
  )
569
607
  ok = False
570
608
  else:
571
609
  print("")
572
610
  if hint:
573
611
  print(
574
- f" ⎿ {ansi.blue}{ansi.bold}Permission needed{ansi.reset} for {ansi.bold}{tool_name}{ansi.reset}: {hint}"
612
+ f" ⎿ {ansi.blue}{ansi.bold}Permission needed{ansi.reset} "
613
+ f"for {ansi.bold}{tool_name}{ansi.reset}: {hint}"
575
614
  )
576
615
  else:
577
616
  print(
578
- f" ⎿ {ansi.blue}{ansi.bold}Permission needed{ansi.reset} for {ansi.bold}{tool_name}{ansi.reset}."
617
+ f" ⎿ {ansi.blue}{ansi.bold}Permission needed{ansi.reset} "
618
+ f"for {ansi.bold}{tool_name}{ansi.reset}."
579
619
  )
580
- try:
581
- ans = input(
582
- f" ⎿ Allow? ({ansi.blue_ok}y{ansi.reset}/{ansi.dim}N{ansi.reset}) "
583
- ).strip().lower()
584
- except EOFError:
585
- ans = ""
586
- ok = ans in ("y", "yes")
620
+ sys.stdout.flush()
621
+ # The core issue: prompt_toolkit puts the terminal in raw mode
622
+ # while reading user input. After prompt_async() returns, the
623
+ # terminal may still be in (or close to) raw mode. In raw mode
624
+ # pressing Enter sends \r instead of \n. Both input() and
625
+ # readline() wait for \n — so they block forever.
626
+ #
627
+ # Fix: read a SINGLE CHARACTER using cbreak mode (no Enter needed).
628
+ # - tty.setcbreak() disables line-buffering + echo but keeps
629
+ # signal keys (Ctrl+C etc.) working.
630
+ # - read(1) returns immediately after any key is pressed.
631
+ # - The user only needs to press "y" — no Enter required.
632
+ # - On Windows (no termios) we fall back to readline().
633
+ prompt_str = (
634
+ f" ⎿ Allow? "
635
+ f"[{ansi.blue_ok}y{ansi.reset} = yes "
636
+ f"{ansi.dim}any other key = no{ansi.reset}] "
637
+ )
638
+ sys.stdout.write(prompt_str)
639
+ sys.stdout.flush()
640
+ ok = await _read_permission_char(asyncio.get_running_loop())
641
+ # Echo the answer and move to next line
642
+ sys.stdout.write(("y" if ok else "n") + "\n")
643
+ sys.stdout.flush()
644
+
645
+ # Explicit visual feedback — user knows their answer was received.
646
+ if ok:
647
+ print(
648
+ f" ⎿ {ansi.blue_ok}{ansi.bold}Approved{ansi.reset} — "
649
+ f"continuing…"
650
+ )
651
+ else:
652
+ print(
653
+ f" ⎿ {ansi.dim}Denied — skipping {tool_name}.{ansi.reset}"
654
+ )
655
+ sys.stdout.flush()
587
656
 
588
657
  parts.append(
589
658
  types.Part(
@@ -596,6 +665,9 @@ async def run_gemcode_scrollback_tui(
596
665
  )
597
666
  current_message = types.Content(role="user", parts=parts)
598
667
  do_reset = False
668
+ # Restart the spinner so the user knows the agent is working again
669
+ # after they answered the permission prompt.
670
+ _start_anim("Working\u2026")
599
671
 
600
672
  print("")
601
673
  if os.environ.get("GEMCODE_TUI_TURN_FOOTER", "1").lower() in (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.24
3
+ Version: 0.3.26
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -65,16 +65,16 @@ gemcode/tools/think.py,sha256=WrNATR-bi97aLkbSsOFOYYAGxbzihe9AnPDZfw3z5-Q,1704
65
65
  gemcode/tools/todo.py,sha256=d9aXiyT04r1RFZIk6qdVif17-_Oc3oi4ymDnsPBRg68,3143
66
66
  gemcode/tools/web.py,sha256=ULg1e3inG4FjPSUCYI8dVBzTrcCHINNRo76SIU9qw-A,4489
67
67
  gemcode/tui/input_handler.py,sha256=rCSzV5Ucc-nZTBbY8KKTDS9einNrgMzvI_NGcMls9e8,8147
68
- gemcode/tui/scrollback.py,sha256=ujdYDfyVAofmPDlxpClJ4vFwgMphqricNJDxQlVbkCM,20396
68
+ gemcode/tui/scrollback.py,sha256=f14MSfDM4dryDWWG3vfTuzkV4mJfFbz2Yd7gF9S4pXs,23455
69
69
  gemcode/tui/spinner.py,sha256=AJrApG5od-Sh40-5uWcNM9RHb5ax7gr-NbgAZmTbIYY,4848
70
70
  gemcode/tui/welcome_banner.py,sha256=aocl1lnoyLIM6RN4f65g3i0wRA71RqUlgPrGsXeVLW4,4387
71
71
  gemcode/tui/welcome_rich.py,sha256=8FEZzLXrzqly5JWiDgV9ooRV1LNXDk-CXV1a7K6ua-U,4048
72
72
  gemcode/web/__init__.py,sha256=EysmUAWs6g-lmMk4VFljKfaHVrEgb_FiIzwQmBdORJc,40
73
73
  gemcode/web/claude_sse_adapter.py,sha256=HcNp0Lh4DdBZBLOpstsqa-VzfqAUrRngZ6FSuJ-mIMg,8609
74
74
  gemcode/web/terminal_repl.py,sha256=k2irvFGbCY8gDm_pbirR7b_cakaeafcctoTIvnJkVXk,3902
75
- gemcode-0.3.24.dist-info/licenses/LICENSE,sha256=TD4524qn-W8Z07GTDnag-9jJPFutFZNB0a1WbMHPC54,8388
76
- gemcode-0.3.24.dist-info/METADATA,sha256=tZ68L0CHQC965BXc_Et_1ir7AH-2VM9iSEAo0eCTre8,23695
77
- gemcode-0.3.24.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
78
- gemcode-0.3.24.dist-info/entry_points.txt,sha256=cZdLTLDiHbks7OSUCuxCh66dCWeQdpLR8BozoqfEjV4,45
79
- gemcode-0.3.24.dist-info/top_level.txt,sha256=UYrjULLBY2bcgK6KI6flomJWmsbDXu7n0rvW2SWFrbo,8
80
- gemcode-0.3.24.dist-info/RECORD,,
75
+ gemcode-0.3.26.dist-info/licenses/LICENSE,sha256=TD4524qn-W8Z07GTDnag-9jJPFutFZNB0a1WbMHPC54,8388
76
+ gemcode-0.3.26.dist-info/METADATA,sha256=gvuLPIdccTr2APbK1N6Zjp9MwO9Uw7k6sS6C71ckrXU,23695
77
+ gemcode-0.3.26.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
78
+ gemcode-0.3.26.dist-info/entry_points.txt,sha256=cZdLTLDiHbks7OSUCuxCh66dCWeQdpLR8BozoqfEjV4,45
79
+ gemcode-0.3.26.dist-info/top_level.txt,sha256=UYrjULLBY2bcgK6KI6flomJWmsbDXu7n0rvW2SWFrbo,8
80
+ gemcode-0.3.26.dist-info/RECORD,,