gemcode 0.3.9__tar.gz → 0.3.11__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 (105) hide show
  1. {gemcode-0.3.9/src/gemcode.egg-info → gemcode-0.3.11}/PKG-INFO +1 -1
  2. {gemcode-0.3.9 → gemcode-0.3.11}/pyproject.toml +1 -1
  3. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/tui/app.py +85 -12
  4. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/tui/scrollback.py +15 -2
  5. {gemcode-0.3.9 → gemcode-0.3.11/src/gemcode.egg-info}/PKG-INFO +1 -1
  6. {gemcode-0.3.9 → gemcode-0.3.11}/LICENSE +0 -0
  7. {gemcode-0.3.9 → gemcode-0.3.11}/MANIFEST.in +0 -0
  8. {gemcode-0.3.9 → gemcode-0.3.11}/README.md +0 -0
  9. {gemcode-0.3.9 → gemcode-0.3.11}/setup.cfg +0 -0
  10. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/__init__.py +0 -0
  11. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/__main__.py +0 -0
  12. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/agent.py +0 -0
  13. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/audit.py +0 -0
  14. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/autocompact.py +0 -0
  15. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/callbacks.py +0 -0
  16. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/capability_routing.py +0 -0
  17. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/cli.py +0 -0
  18. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/compaction.py +0 -0
  19. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/computer_use/__init__.py +0 -0
  20. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/computer_use/browser_computer.py +0 -0
  21. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/config.py +0 -0
  22. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/context_budget.py +0 -0
  23. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/context_warning.py +0 -0
  24. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/credentials.py +0 -0
  25. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/hitl_session.py +0 -0
  26. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/interactions.py +0 -0
  27. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/invoke.py +0 -0
  28. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/kairos_daemon.py +0 -0
  29. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/limits.py +0 -0
  30. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/live_audio_engine.py +0 -0
  31. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/logging_config.py +0 -0
  32. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/mcp_loader.py +0 -0
  33. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/memory/__init__.py +0 -0
  34. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/memory/embedding_memory_service.py +0 -0
  35. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/memory/file_memory_service.py +0 -0
  36. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/modality_tools.py +0 -0
  37. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/model_errors.py +0 -0
  38. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/model_routing.py +0 -0
  39. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/paths.py +0 -0
  40. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/permissions.py +0 -0
  41. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/plugins/__init__.py +0 -0
  42. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
  43. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  44. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/prompt_suggestions.py +0 -0
  45. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/query/__init__.py +0 -0
  46. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/query/config.py +0 -0
  47. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/query/deps.py +0 -0
  48. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/query/engine.py +0 -0
  49. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/query/stop_hooks.py +0 -0
  50. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/query/token_budget.py +0 -0
  51. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/query/transitions.py +0 -0
  52. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/repl_commands.py +0 -0
  53. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/repl_slash.py +0 -0
  54. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/session_runtime.py +0 -0
  55. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/slash_commands.py +0 -0
  56. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/thinking.py +0 -0
  57. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/tool_prompt_manifest.py +0 -0
  58. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/tool_registry.py +0 -0
  59. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/tools/__init__.py +0 -0
  60. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/tools/edit.py +0 -0
  61. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/tools/filesystem.py +0 -0
  62. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/tools/search.py +0 -0
  63. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/tools/shell.py +0 -0
  64. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/tools/shell_gate.py +0 -0
  65. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/tools/todo.py +0 -0
  66. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/tools_inspector.py +0 -0
  67. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/trust.py +0 -0
  68. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/version.py +0 -0
  69. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/vertex.py +0 -0
  70. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/web/__init__.py +0 -0
  71. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/web/claude_sse_adapter.py +0 -0
  72. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/web/terminal_repl.py +0 -0
  73. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode/workspace_hints.py +0 -0
  74. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode.egg-info/SOURCES.txt +0 -0
  75. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode.egg-info/dependency_links.txt +0 -0
  76. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode.egg-info/entry_points.txt +0 -0
  77. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode.egg-info/requires.txt +0 -0
  78. {gemcode-0.3.9 → gemcode-0.3.11}/src/gemcode.egg-info/top_level.txt +0 -0
  79. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_agent_instruction.py +0 -0
  80. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_autocompact.py +0 -0
  81. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_capability_routing.py +0 -0
  82. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_claude_web_adapter_sse.py +0 -0
  83. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_cli_init.py +0 -0
  84. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_computer_use_permissions.py +0 -0
  85. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_context_budget.py +0 -0
  86. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_context_warning.py +0 -0
  87. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_credentials.py +0 -0
  88. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_interactive_permission_ask.py +0 -0
  89. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_kairos_scheduler.py +0 -0
  90. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_modality_tools.py +0 -0
  91. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_model_error_retry.py +0 -0
  92. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_model_errors.py +0 -0
  93. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_model_routing.py +0 -0
  94. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_paths.py +0 -0
  95. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_permissions.py +0 -0
  96. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_prompt_suggestions.py +0 -0
  97. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_repl_commands.py +0 -0
  98. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_repl_slash.py +0 -0
  99. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_slash_commands.py +0 -0
  100. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_thinking_config.py +0 -0
  101. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_token_budget.py +0 -0
  102. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_tool_context_circulation.py +0 -0
  103. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_tools.py +0 -0
  104. {gemcode-0.3.9 → gemcode-0.3.11}/tests/test_tools_inspector.py +0 -0
  105. {gemcode-0.3.9 → gemcode-0.3.11}/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.9
3
+ Version: 0.3.11
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.9"
7
+ version = "0.3.11"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -180,6 +180,12 @@ async def run_gemcode_tui(
180
180
  # Non-modal permission prompt state. Modal dialogs can corrupt a full-screen TUI.
181
181
  pending_confirm: dict[str, object] = {"future": None, "tool": "", "hint": ""}
182
182
  assistant_busy: dict[str, bool] = {"value": False}
183
+ # Claude-style: hide thinking by default, reveal via Ctrl+O.
184
+ show_thinking: dict[str, bool] = {"value": False}
185
+ thinking_active: dict[str, bool] = {"value": False}
186
+ thinking_start_ts: dict[str, float] = {"value": 0.0}
187
+ pending_thinking_text: dict[str, str | None] = {"value": None}
188
+ pending_thinking_insert_pos: dict[str, int | None] = {"value": None}
183
189
  spinner_idx: dict[str, int] = {"value": 0}
184
190
 
185
191
  def _set_input_prompt() -> None:
@@ -203,7 +209,7 @@ async def run_gemcode_tui(
203
209
  ]
204
210
  return [
205
211
  ("class:muted", " "),
206
- ("class:muted", "Type your message below. Enter=send · Ctrl+J=newline · Ctrl+O=home"),
212
+ ("class:muted", "Type your message below. Enter=send · Ctrl+J=newline · Ctrl+O=show thinking"),
207
213
  ]
208
214
 
209
215
  def _status_text():
@@ -221,11 +227,22 @@ async def run_gemcode_tui(
221
227
  ("class:muted", "(Esc cancels)"),
222
228
  ]
223
229
  if assistant_busy.get("value"):
230
+ if not show_thinking.get("value") and thinking_active.get("value"):
231
+ elapsed_s = max(1, int(time.time() - float(thinking_start_ts.get("value", 0.0) or 0.0)))
232
+ return [
233
+ ("class:muted", " "),
234
+ ("class:pill", f"Thought for {elapsed_s}s (ctrl+o to show thinking)"),
235
+ ("class:muted", " "),
236
+ ("class:muted", "Tip: Esc=interrupt"),
237
+ ]
224
238
  frames = ["|", "/", "-", "\\"]
225
- fr = frames[spinner_idx.get("value", 0) % len(frames)]
239
+ verbs = ["Thinking", "Analyzing", "Planning", "Writing", "Checking", "Reviewing"]
240
+ i = int(spinner_idx.get("value", 0) or 0)
241
+ fr = frames[i % len(frames)]
242
+ verb = verbs[i % len(verbs)]
226
243
  return [
227
244
  ("class:muted", " "),
228
- ("class:pill", f"thinking {fr}"),
245
+ ("class:pill", f"{verb} {fr}"),
229
246
  ("class:muted", " "),
230
247
  ("class:muted", "Tip: Esc=interrupt"),
231
248
  ]
@@ -243,7 +260,7 @@ async def run_gemcode_tui(
243
260
  frames = ["|", "/", "-", "\\"]
244
261
  i = 0
245
262
  while assistant_busy.get("value"):
246
- spinner_idx["value"] = i % len(frames)
263
+ spinner_idx["value"] = i
247
264
  i += 1
248
265
  try:
249
266
  app.invalidate()
@@ -413,8 +430,46 @@ async def run_gemcode_tui(
413
430
 
414
431
  # Note: do NOT bind y/n globally. Permission answers are typed into the
415
432
  # input field (perm>) and submitted with Enter, Claude-style.
433
+ async def _flush_pending_thinking() -> None:
434
+ # Insert hidden thinking back into the transcript (before the already-rendered final),
435
+ # so ctrl+o behaves like Claude's "expand" (best-effort).
436
+ if not show_thinking.get("value"):
437
+ return
438
+ t = pending_thinking_text.get("value")
439
+ pos = pending_thinking_insert_pos.get("value")
440
+ if not t or pos is None:
441
+ return
442
+ try:
443
+ output.buffer.cursor_position = int(pos)
444
+ output.buffer.insert_text(t)
445
+ output.buffer.insert_text("\n\n")
446
+ output.buffer.cursor_position = len(output.text)
447
+ pending_thinking_text["value"] = None
448
+ pending_thinking_insert_pos["value"] = None
449
+ try:
450
+ app.invalidate()
451
+ except Exception:
452
+ pass
453
+ except Exception:
454
+ return
455
+
416
456
  @kb.add("c-o")
457
+ def _toggle_thinking(event) -> None:
458
+ show_thinking["value"] = not show_thinking.get("value")
459
+ try:
460
+ event.app.invalidate()
461
+ except Exception:
462
+ pass
463
+ # If we just turned thinking on, flush the most recent pending thinking.
464
+ if show_thinking.get("value") and pending_thinking_text.get("value"):
465
+ try:
466
+ event.app.create_background_task(_flush_pending_thinking())
467
+ except Exception:
468
+ pass
469
+
470
+ @kb.add("c-k")
417
471
  def _toggle_home(event) -> None:
472
+ # Repurposed Ctrl+O for thinking reveal; Ctrl+K keeps the home dashboard toggle.
418
473
  show_home["value"] = not show_home["value"]
419
474
  try:
420
475
  event.app.invalidate()
@@ -570,6 +625,11 @@ async def run_gemcode_tui(
570
625
  cfg.model = pick_effective_model(cfg, prompt)
571
626
 
572
627
  assistant_busy["value"] = True
628
+ spinner_idx["value"] = 0
629
+ try:
630
+ app.invalidate()
631
+ except Exception:
632
+ pass
573
633
  spinner_task = asyncio.create_task(_spin_status())
574
634
 
575
635
  try:
@@ -665,6 +725,12 @@ async def run_gemcode_tui(
665
725
  # into a separate buffer.
666
726
  buffered_thought: list[str] = []
667
727
  buffered_final: list[str] = []
728
+ # Claude-like thinking header is implicit in the spinner; we only
729
+ # print the full thinking block when the user asks for it via Ctrl+O.
730
+ thinking_active["value"] = True
731
+ thinking_start_ts["value"] = time.time()
732
+ pending_thinking_text["value"] = None
733
+ pending_thinking_insert_pos["value"] = None
668
734
  kwargs = dict(
669
735
  user_id="local",
670
736
  session_id=session_state["id"],
@@ -709,21 +775,26 @@ async def run_gemcode_tui(
709
775
  confirmation_fcs = _get_confirmation_fcs(events)
710
776
  if not confirmation_fcs:
711
777
  # Now that we know no confirmation is needed, render buffered
712
- # thinking + final response separately.
778
+ # thinking + final response. By default we keep thinking hidden
779
+ # (Claude-style); Ctrl+O can later expand it from the buffer.
713
780
  thought_text = "".join(buffered_thought)
714
781
  final_text = "".join(buffered_final)
715
782
  if buffered_thought:
716
783
  # If Gemini returns the same content for both "thought" and
717
784
  # final text, don't repeat it (Claude typically doesn't).
718
785
  if buffered_final and _normalize_ws(thought_text) == _normalize_ws(final_text):
719
- append_inline(" GemCode (thinking): ")
720
- await typewrite("(omitted: identical to final response)")
721
- append("")
786
+ pending_thinking_text["value"] = "(omitted: identical to final response)"
787
+ else:
788
+ pending_thinking_text["value"] = thought_text
789
+ # If the user already asked to see thinking, insert it now;
790
+ # otherwise remember the insertion point so Ctrl+O can expand.
791
+ if show_thinking.get("value"):
792
+ insert_pos = len(output.text)
793
+ pending_thinking_insert_pos["value"] = insert_pos
794
+ await _flush_pending_thinking()
722
795
  else:
723
- append_inline(" GemCode (thinking): ")
724
- await typewrite(thought_text)
725
- # Ensure visual separation before the final response section.
726
- append("")
796
+ pending_thinking_insert_pos["value"] = len(output.text)
797
+ thinking_active["value"] = False
727
798
  if buffered_final:
728
799
  append_inline("⎿ GemCode: ")
729
800
  await typewrite("".join(buffered_final))
@@ -731,6 +802,8 @@ async def run_gemcode_tui(
731
802
 
732
803
  interactive_enabled = bool(getattr(cfg, "interactive_permission_ask", False))
733
804
  parts: list[types.Part] = []
805
+ await typewrite("(thinking paused — tool confirmation requested)")
806
+ append("") # newline after paused thinking label
734
807
  for fc in confirmation_fcs:
735
808
  tool_name, hint = _extract_tool_and_hint(fc)
736
809
  if interactive_enabled:
@@ -417,14 +417,27 @@ async def run_gemcode_scrollback_tui(
417
417
  final_text = "".join(buffered_final)
418
418
  if buffered_thought:
419
419
  if buffered_final and _normalize_ws(thought_text) == _normalize_ws(final_text):
420
- print(f" ⎿ {ansi.dim}{ansi.bold}GemCode{ansi.reset} (thinking): {ansi.reset}(omitted: identical to final response)")
420
+ print(
421
+ f" ⎿ {ansi.dim}{ansi.bold}\u2234 Thinking{ansi.reset}: "
422
+ f"{ansi.reset}(omitted: identical to final response)"
423
+ )
421
424
  print("")
422
425
  else:
423
- sys.stdout.write(f" ⎿ {ansi.dim}{ansi.bold}GemCode{ansi.reset} (thinking): ")
426
+ sys.stdout.write(
427
+ f" ⎿ {ansi.dim}{ansi.bold}\u2234 Thinking{ansi.reset}: "
428
+ )
424
429
  sys.stdout.flush()
425
430
  await typewrite(thought_text)
426
431
  sys.stdout.write("\n")
427
432
  sys.stdout.flush()
433
+ else:
434
+ # Keep thinking visible even if the model produced no separate
435
+ # "thought" channel output.
436
+ print(
437
+ f" ⎿ {ansi.dim}{ansi.bold}\u2234 Thinking{ansi.reset}: "
438
+ "(no thinking output)"
439
+ )
440
+ print("")
428
441
 
429
442
  if buffered_final:
430
443
  sys.stdout.write(f" ⎿ {ansi.bold}GemCode{ansi.reset}: ")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.9
3
+ Version: 0.3.11
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
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