gemcode 0.3.2__tar.gz → 0.3.4__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 (106) hide show
  1. {gemcode-0.3.2/src/gemcode.egg-info → gemcode-0.3.4}/PKG-INFO +1 -1
  2. {gemcode-0.3.2 → gemcode-0.3.4}/pyproject.toml +1 -1
  3. gemcode-0.3.4/src/gemcode/__init__.py +5 -0
  4. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/agent.py +25 -3
  5. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/config.py +6 -0
  6. gemcode-0.3.4/src/gemcode/logging_config.py +44 -0
  7. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/repl_commands.py +5 -1
  8. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/tui/app.py +38 -6
  9. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/tui/scrollback.py +125 -5
  10. gemcode-0.3.4/src/gemcode/version.py +15 -0
  11. gemcode-0.3.4/src/gemcode/workspace_hints.py +23 -0
  12. {gemcode-0.3.2 → gemcode-0.3.4/src/gemcode.egg-info}/PKG-INFO +1 -1
  13. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode.egg-info/SOURCES.txt +6 -1
  14. gemcode-0.3.4/tests/test_agent_instruction.py +13 -0
  15. gemcode-0.3.4/tests/test_workspace_hints.py +16 -0
  16. gemcode-0.3.2/src/gemcode/__init__.py +0 -3
  17. {gemcode-0.3.2 → gemcode-0.3.4}/LICENSE +0 -0
  18. {gemcode-0.3.2 → gemcode-0.3.4}/MANIFEST.in +0 -0
  19. {gemcode-0.3.2 → gemcode-0.3.4}/README.md +0 -0
  20. {gemcode-0.3.2 → gemcode-0.3.4}/setup.cfg +0 -0
  21. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/__main__.py +0 -0
  22. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/audit.py +0 -0
  23. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/autocompact.py +0 -0
  24. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/callbacks.py +0 -0
  25. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/capability_routing.py +0 -0
  26. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/cli.py +0 -0
  27. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/compaction.py +0 -0
  28. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/computer_use/__init__.py +0 -0
  29. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/computer_use/browser_computer.py +0 -0
  30. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/context_budget.py +0 -0
  31. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/context_warning.py +0 -0
  32. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/credentials.py +0 -0
  33. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/hitl_session.py +0 -0
  34. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/interactions.py +0 -0
  35. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/invoke.py +0 -0
  36. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/kairos_daemon.py +0 -0
  37. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/limits.py +0 -0
  38. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/live_audio_engine.py +0 -0
  39. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/mcp_loader.py +0 -0
  40. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/memory/__init__.py +0 -0
  41. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/memory/embedding_memory_service.py +0 -0
  42. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/memory/file_memory_service.py +0 -0
  43. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/modality_tools.py +0 -0
  44. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/model_errors.py +0 -0
  45. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/model_routing.py +0 -0
  46. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/paths.py +0 -0
  47. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/permissions.py +0 -0
  48. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/plugins/__init__.py +0 -0
  49. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
  50. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  51. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/prompt_suggestions.py +0 -0
  52. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/query/__init__.py +0 -0
  53. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/query/config.py +0 -0
  54. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/query/deps.py +0 -0
  55. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/query/engine.py +0 -0
  56. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/query/stop_hooks.py +0 -0
  57. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/query/token_budget.py +0 -0
  58. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/query/transitions.py +0 -0
  59. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/repl_slash.py +0 -0
  60. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/session_runtime.py +0 -0
  61. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/slash_commands.py +0 -0
  62. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/thinking.py +0 -0
  63. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/tool_prompt_manifest.py +0 -0
  64. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/tool_registry.py +0 -0
  65. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/tools/__init__.py +0 -0
  66. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/tools/edit.py +0 -0
  67. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/tools/filesystem.py +0 -0
  68. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/tools/search.py +0 -0
  69. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/tools/shell.py +0 -0
  70. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/tools/shell_gate.py +0 -0
  71. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/tools/todo.py +0 -0
  72. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/tools_inspector.py +0 -0
  73. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/trust.py +0 -0
  74. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/vertex.py +0 -0
  75. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/web/__init__.py +0 -0
  76. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/web/claude_sse_adapter.py +0 -0
  77. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode/web/terminal_repl.py +0 -0
  78. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode.egg-info/dependency_links.txt +0 -0
  79. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode.egg-info/entry_points.txt +0 -0
  80. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode.egg-info/requires.txt +0 -0
  81. {gemcode-0.3.2 → gemcode-0.3.4}/src/gemcode.egg-info/top_level.txt +0 -0
  82. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_autocompact.py +0 -0
  83. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_capability_routing.py +0 -0
  84. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_claude_web_adapter_sse.py +0 -0
  85. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_cli_init.py +0 -0
  86. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_computer_use_permissions.py +0 -0
  87. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_context_budget.py +0 -0
  88. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_context_warning.py +0 -0
  89. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_credentials.py +0 -0
  90. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_interactive_permission_ask.py +0 -0
  91. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_kairos_scheduler.py +0 -0
  92. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_modality_tools.py +0 -0
  93. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_model_error_retry.py +0 -0
  94. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_model_errors.py +0 -0
  95. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_model_routing.py +0 -0
  96. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_paths.py +0 -0
  97. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_permissions.py +0 -0
  98. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_prompt_suggestions.py +0 -0
  99. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_repl_commands.py +0 -0
  100. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_repl_slash.py +0 -0
  101. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_slash_commands.py +0 -0
  102. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_thinking_config.py +0 -0
  103. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_token_budget.py +0 -0
  104. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_tool_context_circulation.py +0 -0
  105. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_tools.py +0 -0
  106. {gemcode-0.3.2 → gemcode-0.3.4}/tests/test_tools_inspector.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.2
3
+ Version: 0.3.4
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.2"
7
+ version = "0.3.4"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -0,0 +1,5 @@
1
+ """GemCode: Gemini + ADK coding agent."""
2
+
3
+ from gemcode.version import get_version
4
+
5
+ __version__ = get_version()
@@ -56,11 +56,28 @@ def _load_gemini_md(project_root: Path) -> str:
56
56
  return ""
57
57
 
58
58
 
59
+ def _build_runtime_facts(cfg: GemCodeConfig) -> str:
60
+ """
61
+ Injected every session so the model does not hallucinate deployment, permissions,
62
+ or "how to switch Pro" the way a product-agnostic base prompt would.
63
+ """
64
+ root = cfg.project_root.resolve()
65
+ model = (getattr(cfg, "model", None) or "").strip() or "(default)"
66
+ return f"""## Runtime facts (authoritative for this session)
67
+ - **Project root** — every filesystem tool path is relative to: `{root}`
68
+ - **Model id in use:** `{model}`. Changing it requires restarting GemCode with `--model <id>` or env `GEMCODE_MODEL`, or using `/model` in the REPL for routing info. Shell: `gemcode list-models` lists candidate ids.
69
+ - **UI banner** phrases such as "GemCode Pro" are **terminal marketing**, not a separate API tier or model you enable from chat.
70
+ - **Env toggles** (`GEMCODE_ENABLE_COMPUTER_USE`, `GEMCODE_MODEL`, etc.) affect only the **OS process** that launched `gemcode`. Pasting `VAR=1` in chat does **not** reconfigure a running session—tell the user to export in their shell, use project `.env`, or restart the CLI.
71
+ - **Working in subfolders** — use tools: e.g. `list_directory("Desktop")`, `glob_files("**/query.ts")`, `read_file("testing/ai-edtech-app/src/app/page.tsx")`, or `run_command` with `cwd_subdir`. Never claim the sandbox cannot reach a subpath unless a tool returned an explicit error."""
72
+
73
+
59
74
  def build_instruction(cfg: GemCodeConfig) -> str:
60
75
  # Layered instructions mirror the *structure* of mature coding agents (scope,
61
76
  # task interpretation, tool choice, parallelism, risk)—not proprietary text.
62
- base = """You are GemCode, an expert software engineering agent.
63
- You operate only inside the user's project directory (current working directory).
77
+ base = f"""You are GemCode, an expert software engineering agent.
78
+ You run locally via the GemCode CLI and call **Google Gemini** through its API. You are the same agent stack the user launched—not a hosted "portal" you can reconfigure from inside the conversation.
79
+
80
+ {_build_runtime_facts(cfg)}
64
81
 
65
82
  ## How to interpret requests
66
83
  - Treat every message as a **software engineering** task in this repo unless the user clearly wants something else. If the instruction is vague ("fix it", "rename that", "the config", "see codebase"), **infer intent from the repository**: search, read, then act—do not answer with abstract advice when concrete files exist.
@@ -79,6 +96,10 @@ You operate only inside the user's project directory (current working directory)
79
96
  - **Parallelize:** when you need several **independent** reads or searches (no output from one is required to form the next call), issue them together in one turn so the user gets answers faster. When step B depends on step A's result, run **sequentially**.
80
97
  - **Deletion:** use `delete_file` for a single file under the project root; reserve `rm` via `run_command` for unusual cases.
81
98
  - **Autonomy:** explore with `list_directory` ("."), `glob_files` (e.g. `**/*.md`, `**/*keyword*`), and `grep_content` before asking "which file?". Prefer widening your search over interrogating the user.
99
+ - **Workspace scope:** All file tools use paths **relative to the project root** (the current working directory GemCode was started in). That root may be the user's home folder—then subfolders like `Desktop`, `Desktop/code`, or `Documents` are **inside** the sandbox. **Call** `list_directory("Desktop")` or `glob_files("**/*name*.ts")` instead of assuming access is blocked. **Only** treat access as denied when a tool returns an `error` string—**do not** invent extra "security" or "permission" policies the runtime did not report.
100
+ - **Finding files:** For a basename like `query.ts`, try several globs in one turn when needed: `**/query.ts`, `**/*query.ts`, `**/*_query.ts`. If the user names a parent path (e.g. Desktop), **list that path** and narrow down. If a search fails, **change the pattern** (broader `**`, partial stem) before saying "not found".
101
+ - **Agentic turns:** One user message can include **many** model↔tool rounds (bounded by runtime). If the task is **not** done after the first tool (e.g. you only searched, or read one file), **keep going** with more tools in the same turn until you can answer or have a clear blocker—do not stop at the first tool call unless it fully satisfies the request.
102
+ - **Model output:** If a response is mostly **function calls** without prose, that is normal—execute tools, then synthesize a clear **text** answer for the user once you have enough information.
82
103
 
83
104
  ## Risk and permissions
84
105
  - Destructive or irreversible actions (deletes, force pushes, anything that wipes data) deserve a clear, honest description; the runtime may require explicit user approval. If the session uses **inline** approval, wait for it—do not instruct the user to "re-run with --yes" unless that is actually required by the environment.
@@ -86,7 +107,8 @@ You operate only inside the user's project directory (current working directory)
86
107
 
87
108
  ## Communication
88
109
  - Before the first tool call in a turn, give a **short** line on what you are about to do. Assume the user does not see raw tool internals—summarize outcomes in plain language.
89
- - Prefer small, testable edits and accurate reporting over breadth."""
110
+ - Prefer small, testable edits and accurate reporting over breadth.
111
+ - If the user pastes **UI copy** or noise (e.g. fragments of a webpage, marketing lines, or mixed headings), infer intent: they often want that clutter **removed or replaced** in source—read the file, then edit the real `page.tsx` (or relevant file), do not treat pasted UI strings as a dialogue prompt."""
90
112
 
91
113
  tool_manifest = build_tool_manifest(cfg)
92
114
 
@@ -295,3 +295,9 @@ def load_cli_environment() -> None:
295
295
  from gemcode.credentials import apply_saved_google_api_key_to_environ
296
296
 
297
297
  apply_saved_google_api_key_to_environ()
298
+
299
+ from gemcode.logging_config import apply_gemcode_logging_filters
300
+ from gemcode.version import get_version
301
+
302
+ os.environ.setdefault("GEMCODE_VERSION", get_version())
303
+ apply_gemcode_logging_filters()
@@ -0,0 +1,44 @@
1
+ """
2
+ Tune third-party loggers for interactive CLI/TUI (expected Gemini function-call noise).
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import logging
8
+ import os
9
+
10
+
11
+ def apply_gemcode_logging_filters() -> None:
12
+ """
13
+ google.genai logs logger.warning when .text strips non-text parts (normal with tools).
14
+
15
+ That uses the **logging** module, not warnings.warn — filterwarnings cannot silence it.
16
+ Set GEMCODE_VERBOSE_GENAI=1 to keep those lines.
17
+ """
18
+ if os.environ.get("GEMCODE_VERBOSE_GENAI", "").lower() in (
19
+ "1",
20
+ "true",
21
+ "yes",
22
+ "on",
23
+ ):
24
+ return
25
+
26
+ class _Filter(logging.Filter):
27
+ def filter(self, record: logging.LogRecord) -> bool:
28
+ try:
29
+ msg = record.getMessage()
30
+ except Exception:
31
+ return True
32
+ if "non-text parts in the response" in msg:
33
+ return False
34
+ if "multiple candidates in the response" in msg:
35
+ return False
36
+ return True
37
+
38
+ f = _Filter()
39
+ for name in (
40
+ "google_genai.types",
41
+ "google_genai",
42
+ "google.genai.types",
43
+ ):
44
+ logging.getLogger(name).addFilter(f)
@@ -14,6 +14,7 @@ from typing import Any, Iterable
14
14
 
15
15
  from gemcode.config import GemCodeConfig
16
16
  from gemcode.trust import is_trusted_root
17
+ from gemcode.version import get_version
17
18
 
18
19
 
19
20
  def _is_executable(p: Path) -> bool:
@@ -52,7 +53,9 @@ def format_doctor_lines(cfg: GemCodeConfig) -> list[str]:
52
53
  except Exception as e:
53
54
  lines.append(f" project_root: ERROR {e}")
54
55
  lines.append(f" folder_trusted: {is_trusted_root(cfg.project_root)}")
55
- lines.append(f" GEMCODE_VERSION: {os.environ.get('GEMCODE_VERSION', '(unset)')}")
56
+ lines.append(
57
+ f" gemcode_version: {os.environ.get('GEMCODE_VERSION', get_version())}"
58
+ )
56
59
  return lines
57
60
 
58
61
 
@@ -157,6 +160,7 @@ def format_tools_lines(
157
160
  def slash_help_lines() -> list[str]:
158
161
  return [
159
162
  "Slash commands:",
163
+ " (CLI) gemcode -C DIR Use a project folder as root (recommended vs. ~ )",
160
164
  " (CLI) gemcode login Save or change API key (~/.gemcode/credentials.json)",
161
165
  " /help Show this help",
162
166
  " /status Show current session/model info",
@@ -8,8 +8,11 @@ import warnings
8
8
  from datetime import datetime
9
9
 
10
10
  from gemcode.capability_routing import apply_capability_routing
11
+ from gemcode.config import load_cli_environment
11
12
  from gemcode.model_routing import pick_effective_model
12
13
  from gemcode.repl_slash import process_repl_slash
14
+ from gemcode.tui.scrollback import format_tool_call_extras
15
+ from gemcode.version import get_version
13
16
 
14
17
 
15
18
  async def run_gemcode_tui(
@@ -26,6 +29,7 @@ async def run_gemcode_tui(
26
29
  ``extra_tools`` matches the runner (e.g. MCP toolsets when ``--mcp``) so
27
30
  ``/tools`` lists the same inventory as the agent.
28
31
  """
32
+ load_cli_environment()
29
33
  session_state = {"id": session_id}
30
34
 
31
35
  from prompt_toolkit.application import Application
@@ -65,7 +69,7 @@ async def run_gemcode_tui(
65
69
  height=D(weight=1),
66
70
  )
67
71
  input_box = TextArea(
68
- prompt="> ",
72
+ prompt=" ",
69
73
  multiline=True,
70
74
  wrap_lines=True,
71
75
  height=D(min=3, max=6, preferred=3),
@@ -178,9 +182,9 @@ async def run_gemcode_tui(
178
182
 
179
183
  def _set_input_prompt() -> None:
180
184
  if pending_confirm.get("future") is not None:
181
- input_box.prompt = "perm> "
185
+ input_box.prompt = "perm "
182
186
  else:
183
- input_box.prompt = "> "
187
+ input_box.prompt = " "
184
188
 
185
189
  def _input_help_text():
186
190
  if pending_confirm.get("future") is not None:
@@ -272,7 +276,10 @@ async def run_gemcode_tui(
272
276
  return s[: max(0, w - 1)] + "…"
273
277
  return s + (" " * (w - len(s)))
274
278
 
275
- mid_title = "│" + pad(f" GemCode v{os.environ.get('GEMCODE_VERSION', '0.1.0')}", width - 2) + "│"
279
+ mid_title = "│" + pad(
280
+ f" GemCode v{os.environ.get('GEMCODE_VERSION', get_version())}",
281
+ width - 2,
282
+ ) + "│"
276
283
 
277
284
  welcome = f"Welcome back {_uname()}!"
278
285
  bot = [
@@ -571,7 +578,11 @@ async def run_gemcode_tui(
571
578
  name = getattr(fc, "name", "") or ""
572
579
  if name == REQUEST_CONFIRMATION_FC:
573
580
  continue
574
- _box("tool", [name])
581
+ extra = format_tool_call_extras(fc)
582
+ if extra:
583
+ _box("tool", [name, extra])
584
+ else:
585
+ _box("tool", [name])
575
586
 
576
587
  # Token-budget reset matches invoke.run_turn behavior.
577
588
  state_delta = None
@@ -639,7 +650,7 @@ async def run_gemcode_tui(
639
650
  if not confirmation_fcs:
640
651
  # Now that we know no confirmation is needed, render buffered text.
641
652
  if buffered:
642
- append_inline("GemCode: ")
653
+ append_inline("GemCode: ")
643
654
  await typewrite("".join(buffered))
644
655
  break
645
656
 
@@ -690,6 +701,27 @@ async def run_gemcode_tui(
690
701
  if not assistant_started:
691
702
  append_inline("(no text output)")
692
703
  append("") # newline after assistant turn
704
+ if os.environ.get("GEMCODE_TUI_TURN_FOOTER", "1").lower() in (
705
+ "1",
706
+ "true",
707
+ "yes",
708
+ "on",
709
+ ):
710
+ sid = session_state["id"]
711
+ sid_short = sid[:8] if len(sid) >= 8 else sid
712
+ model = getattr(cfg, "model", "") or ""
713
+ append(f"\033[2m · {model} · session {sid_short}\033[0m")
714
+ if os.environ.get("GEMCODE_TUI_TURN_RULE", "1").lower() in (
715
+ "1",
716
+ "true",
717
+ "yes",
718
+ "on",
719
+ ):
720
+ try:
721
+ cw = app.output.get_size().columns
722
+ except Exception:
723
+ cw = 80
724
+ append("\033[2m" + ("─" * max(40, min(cw - 2, 200))) + "\033[0m")
693
725
  except Exception as e:
694
726
  append(f"GemCode: error: {e}\n")
695
727
 
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import json
4
5
  import os
5
6
  import sys
6
7
  from dataclasses import dataclass
@@ -9,8 +10,88 @@ from google.adk.agents.run_config import RunConfig
9
10
  from google.genai import types
10
11
 
11
12
  from gemcode.capability_routing import apply_capability_routing
13
+ from gemcode.config import load_cli_environment
12
14
  from gemcode.model_routing import pick_effective_model
13
15
  from gemcode.repl_slash import process_repl_slash
16
+ from gemcode.version import get_version
17
+ from gemcode.workspace_hints import narrow_workspace_tip
18
+
19
+ _ADK_REQUEST_CONFIRMATION = "adk_request_confirmation"
20
+
21
+
22
+ def format_tool_call_extras(fc) -> str:
23
+ """
24
+ One-line summary of tool arguments for Claude-style ``[tool] name …`` lines.
25
+
26
+ Parses ``FunctionCall.args`` / nested ``originalFunctionCall.args`` when present.
27
+ """
28
+ try:
29
+ raw = getattr(fc, "args", None)
30
+ if raw is None:
31
+ return ""
32
+ if isinstance(raw, str):
33
+ try:
34
+ raw = json.loads(raw)
35
+ except Exception:
36
+ return ""
37
+ if not isinstance(raw, dict):
38
+ return ""
39
+ inner: dict = {}
40
+ orig = raw.get("originalFunctionCall")
41
+ if isinstance(orig, dict):
42
+ a = orig.get("args")
43
+ if isinstance(a, dict):
44
+ inner = a
45
+ elif isinstance(a, str):
46
+ try:
47
+ inner = json.loads(a) if a.strip() else {}
48
+ except Exception:
49
+ inner = {}
50
+ if not inner:
51
+ inner = {
52
+ k: v
53
+ for k, v in raw.items()
54
+ if k not in ("originalFunctionCall", "toolConfirmation")
55
+ }
56
+ if not isinstance(inner, dict) or not inner:
57
+ return ""
58
+ for key in (
59
+ "path",
60
+ "glob_pattern",
61
+ "pattern",
62
+ "command",
63
+ "query",
64
+ "url",
65
+ "file_path",
66
+ "target_file",
67
+ ):
68
+ if key in inner and inner[key] not in (None, ""):
69
+ v = str(inner[key])
70
+ if len(v) > 80:
71
+ v = v[:77] + "..."
72
+ return f"{key}={v}"
73
+ parts: list[str] = []
74
+ for k, v in list(inner.items())[:4]:
75
+ if k in ("originalFunctionCall",):
76
+ continue
77
+ sv = str(v)
78
+ if len(sv) > 40:
79
+ sv = sv[:37] + "..."
80
+ parts.append(f"{k}={sv}")
81
+ return " ".join(parts) if parts else ""
82
+ except Exception:
83
+ return ""
84
+
85
+
86
+ def _events_had_non_confirmation_tools(events: list) -> bool:
87
+ for ev in events:
88
+ try:
89
+ for fc in ev.get_function_calls() or []:
90
+ if getattr(fc, "name", "") != _ADK_REQUEST_CONFIRMATION:
91
+ return True
92
+ except Exception:
93
+ continue
94
+ return False
14
95
 
15
96
 
16
97
  @dataclass(frozen=True)
@@ -72,7 +153,7 @@ def _hr(ch: str = "─") -> str:
72
153
 
73
154
  def _dashboard(cfg) -> str:
74
155
  w = _term_width()
75
- title = f" GemCode v{os.environ.get('GEMCODE_VERSION', '0.1.0')} "
156
+ title = f" GemCode v{os.environ.get('GEMCODE_VERSION', get_version())} "
76
157
  left_w = (w - 4) * 2 // 3
77
158
  right_w = (w - 4) - left_w
78
159
 
@@ -116,6 +197,9 @@ def _dashboard(cfg) -> str:
116
197
  lines.append(
117
198
  "│ " + pad(left[i], left_w) + " │ " + pad(right[i], right_w) + " │"
118
199
  )
200
+ nt = narrow_workspace_tip(getattr(cfg, "project_root"))
201
+ if nt:
202
+ lines.append("│" + pad(f" {nt}", w - 2) + "│")
119
203
  lines.append(box_bot)
120
204
  lines.append("")
121
205
  lines.append(" ↑ GemCode Pro now supports larger contexts · faster streaming")
@@ -134,6 +218,7 @@ async def run_gemcode_scrollback_tui(
134
218
  - Tool calls are shown as a short "internal state" block.
135
219
  - Permission prompts are inline: type y/n at the prompt.
136
220
  """
221
+ load_cli_environment()
137
222
  os.environ["GEMCODE_TUI_ACTIVE"] = "1"
138
223
 
139
224
  ansi = _Ansi(
@@ -179,14 +264,14 @@ async def run_gemcode_scrollback_tui(
179
264
  sys.stdout.flush()
180
265
  await asyncio.sleep(char_delay_ms / 1000.0)
181
266
 
182
- REQUEST_CONFIRMATION_FC = "adk_request_confirmation"
267
+ REQUEST_CONFIRMATION_FC = _ADK_REQUEST_CONFIRMATION
183
268
 
184
269
  def _get_confirmation_fcs(events: list) -> list[types.FunctionCall]:
185
270
  out: list[types.FunctionCall] = []
186
271
  for ev in events:
187
272
  try:
188
273
  for fc in ev.get_function_calls() or []:
189
- if getattr(fc, "name", None) == REQUEST_CONFIRMATION_FC:
274
+ if getattr(fc, "name", None) == _ADK_REQUEST_CONFIRMATION:
190
275
  out.append(fc)
191
276
  except Exception:
192
277
  continue
@@ -212,9 +297,16 @@ async def run_gemcode_scrollback_tui(
212
297
  fcs = []
213
298
  for fc in fcs:
214
299
  name = getattr(fc, "name", "") or ""
215
- if name == REQUEST_CONFIRMATION_FC:
300
+ if name == _ADK_REQUEST_CONFIRMATION:
216
301
  continue
217
- print(f" ⎿ {ansi.blue_tool}[tool]{ansi.reset} {ansi.bold}{name}{ansi.reset}")
302
+ extra = format_tool_call_extras(fc)
303
+ if extra:
304
+ print(
305
+ f" ⎿ {ansi.blue_tool}[tool]{ansi.reset} {ansi.bold}{name}{ansi.reset} "
306
+ f"{ansi.dim}{extra}{ansi.reset}"
307
+ )
308
+ else:
309
+ print(f" ⎿ {ansi.blue_tool}[tool]{ansi.reset} {ansi.bold}{name}{ansi.reset}")
218
310
 
219
311
  run_config = (
220
312
  RunConfig(max_llm_calls=cfg.max_llm_calls)
@@ -263,6 +355,7 @@ async def run_gemcode_scrollback_tui(
263
355
 
264
356
  while True:
265
357
  events: list = []
358
+ assistant_wrote_text = False
266
359
  kwargs = dict(
267
360
  user_id="local", session_id=current_session_id, new_message=current_message
268
361
  )
@@ -281,10 +374,17 @@ async def run_gemcode_scrollback_tui(
281
374
  for part in ev.content.parts:
282
375
  delta = getattr(part, "text", None)
283
376
  if delta:
377
+ assistant_wrote_text = True
284
378
  await typewrite(delta)
285
379
  except Exception:
286
380
  continue
287
381
 
382
+ if not assistant_wrote_text and _events_had_non_confirmation_tools(events):
383
+ await typewrite(
384
+ f"{ansi.dim}(Tools ran without a text reply in this step; "
385
+ f"the run may continue in the background. Ask a follow-up if you need more.){ansi.reset}"
386
+ )
387
+
288
388
  confirmation_fcs = _get_confirmation_fcs(events)
289
389
  if not confirmation_fcs:
290
390
  break
@@ -331,5 +431,25 @@ async def run_gemcode_scrollback_tui(
331
431
  do_reset = False
332
432
 
333
433
  print("")
434
+ if os.environ.get("GEMCODE_TUI_TURN_FOOTER", "1").lower() in (
435
+ "1",
436
+ "true",
437
+ "yes",
438
+ "on",
439
+ ):
440
+ sid = (
441
+ current_session_id[:8]
442
+ if len(current_session_id) >= 8
443
+ else current_session_id
444
+ )
445
+ model = getattr(cfg, "model", "") or ""
446
+ print(f"{ansi.dim} · {model} · session {sid}{ansi.reset}")
447
+ if os.environ.get("GEMCODE_TUI_TURN_RULE", "1").lower() in (
448
+ "1",
449
+ "true",
450
+ "yes",
451
+ "on",
452
+ ):
453
+ print(f"{ansi.dim}{_hr(ch='─')}{ansi.reset}")
334
454
  print("")
335
455
 
@@ -0,0 +1,15 @@
1
+ """Installed package version (PyPI / wheel metadata)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ try:
6
+ from importlib.metadata import PackageNotFoundError, version
7
+ except ImportError: # pragma: no cover
8
+ from importlib_metadata import PackageNotFoundError, version
9
+
10
+
11
+ def get_version() -> str:
12
+ try:
13
+ return version("gemcode")
14
+ except PackageNotFoundError:
15
+ return "0.0.0"
@@ -0,0 +1,23 @@
1
+ """UX hints when the project root is unusually broad (e.g. user home)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+
8
+ def project_root_is_user_home(project_root: Path) -> bool:
9
+ try:
10
+ return project_root.resolve() == Path.home().resolve()
11
+ except OSError:
12
+ return False
13
+
14
+
15
+ def narrow_workspace_tip(project_root: Path) -> str | None:
16
+ """
17
+ One-line suggestion when GemCode is anchored at ~ so searches span the whole account.
18
+ """
19
+ if not project_root_is_user_home(project_root):
20
+ return None
21
+ return (
22
+ "Tip: narrow the workspace — restart with: gemcode -C /path/to/your/repo"
23
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -21,6 +21,7 @@ src/gemcode/invoke.py
21
21
  src/gemcode/kairos_daemon.py
22
22
  src/gemcode/limits.py
23
23
  src/gemcode/live_audio_engine.py
24
+ src/gemcode/logging_config.py
24
25
  src/gemcode/mcp_loader.py
25
26
  src/gemcode/modality_tools.py
26
27
  src/gemcode/model_errors.py
@@ -37,7 +38,9 @@ src/gemcode/tool_prompt_manifest.py
37
38
  src/gemcode/tool_registry.py
38
39
  src/gemcode/tools_inspector.py
39
40
  src/gemcode/trust.py
41
+ src/gemcode/version.py
40
42
  src/gemcode/vertex.py
43
+ src/gemcode/workspace_hints.py
41
44
  src/gemcode.egg-info/PKG-INFO
42
45
  src/gemcode.egg-info/SOURCES.txt
43
46
  src/gemcode.egg-info/dependency_links.txt
@@ -71,6 +74,7 @@ src/gemcode/tui/scrollback.py
71
74
  src/gemcode/web/__init__.py
72
75
  src/gemcode/web/claude_sse_adapter.py
73
76
  src/gemcode/web/terminal_repl.py
77
+ tests/test_agent_instruction.py
74
78
  tests/test_autocompact.py
75
79
  tests/test_capability_routing.py
76
80
  tests/test_claude_web_adapter_sse.py
@@ -95,4 +99,5 @@ tests/test_thinking_config.py
95
99
  tests/test_token_budget.py
96
100
  tests/test_tool_context_circulation.py
97
101
  tests/test_tools.py
98
- tests/test_tools_inspector.py
102
+ tests/test_tools_inspector.py
103
+ tests/test_workspace_hints.py
@@ -0,0 +1,13 @@
1
+ from pathlib import Path
2
+
3
+ from gemcode.agent import build_instruction
4
+ from gemcode.config import GemCodeConfig
5
+
6
+
7
+ def test_instruction_includes_runtime_facts(tmp_path: Path) -> None:
8
+ cfg = GemCodeConfig(project_root=tmp_path, model="gemini-2.5-flash")
9
+ text = build_instruction(cfg)
10
+ assert str(tmp_path.resolve()) in text
11
+ assert "gemini-2.5-flash" in text
12
+ assert "list-models" in text
13
+ assert "GEMCODE_MODEL" in text
@@ -0,0 +1,16 @@
1
+ from pathlib import Path
2
+
3
+ from gemcode import workspace_hints
4
+
5
+
6
+ def test_narrow_tip_none_for_non_home(tmp_path: Path) -> None:
7
+ assert workspace_hints.narrow_workspace_tip(tmp_path) is None
8
+ assert workspace_hints.project_root_is_user_home(tmp_path) is False
9
+
10
+
11
+ def test_narrow_tip_for_home_directory() -> None:
12
+ home = Path.home()
13
+ assert workspace_hints.project_root_is_user_home(home) is True
14
+ tip = workspace_hints.narrow_workspace_tip(home)
15
+ assert tip is not None
16
+ assert "gemcode -C" in tip
@@ -1,3 +0,0 @@
1
- """GemCode: Gemini + ADK coding agent."""
2
-
3
- __version__ = "0.1.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
File without changes