tunacode-cli 0.0.39__py3-none-any.whl → 0.0.41__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.

Potentially problematic release.


This version of tunacode-cli might be problematic. Click here for more details.

Files changed (33) hide show
  1. tunacode/cli/commands/__init__.py +2 -0
  2. tunacode/cli/commands/implementations/__init__.py +3 -0
  3. tunacode/cli/commands/implementations/debug.py +1 -1
  4. tunacode/cli/commands/implementations/todo.py +217 -0
  5. tunacode/cli/commands/registry.py +2 -0
  6. tunacode/cli/main.py +12 -5
  7. tunacode/cli/repl.py +197 -132
  8. tunacode/configuration/defaults.py +1 -0
  9. tunacode/configuration/models.py +6 -0
  10. tunacode/constants.py +32 -3
  11. tunacode/context.py +7 -3
  12. tunacode/core/agents/main.py +52 -9
  13. tunacode/core/setup/config_setup.py +5 -0
  14. tunacode/core/state.py +50 -1
  15. tunacode/core/token_usage/api_response_parser.py +44 -0
  16. tunacode/core/token_usage/cost_calculator.py +58 -0
  17. tunacode/core/token_usage/usage_tracker.py +98 -0
  18. tunacode/prompts/system.md +69 -5
  19. tunacode/tools/todo.py +343 -0
  20. tunacode/types.py +20 -1
  21. tunacode/ui/input.py +1 -1
  22. tunacode/ui/output.py +36 -0
  23. tunacode/utils/message_utils.py +17 -0
  24. tunacode/utils/text_utils.py +131 -25
  25. tunacode/utils/token_counter.py +78 -8
  26. {tunacode_cli-0.0.39.dist-info → tunacode_cli-0.0.41.dist-info}/METADATA +3 -1
  27. {tunacode_cli-0.0.39.dist-info → tunacode_cli-0.0.41.dist-info}/RECORD +31 -27
  28. tunacode/cli/textual_app.py +0 -420
  29. tunacode/cli/textual_bridge.py +0 -161
  30. {tunacode_cli-0.0.39.dist-info → tunacode_cli-0.0.41.dist-info}/WHEEL +0 -0
  31. {tunacode_cli-0.0.39.dist-info → tunacode_cli-0.0.41.dist-info}/entry_points.txt +0 -0
  32. {tunacode_cli-0.0.39.dist-info → tunacode_cli-0.0.41.dist-info}/licenses/LICENSE +0 -0
  33. {tunacode_cli-0.0.39.dist-info → tunacode_cli-0.0.41.dist-info}/top_level.txt +0 -0
@@ -1,23 +1,93 @@
1
- """Simple token counting utility for estimating message sizes."""
1
+ """Token counting utility using tiktoken for accurate, offline token estimation."""
2
2
 
3
+ import logging
4
+ from functools import lru_cache
5
+ from typing import Optional
3
6
 
4
- def estimate_tokens(text: str) -> int:
7
+ # Configure logging
8
+ logging.basicConfig(level=logging.INFO)
9
+ logger = logging.getLogger(__name__)
10
+
11
+ # Cache for tokenizer encodings
12
+ _encoding_cache = {}
13
+
14
+
15
+ @lru_cache(maxsize=8)
16
+ def get_encoding(model_name: str):
17
+ """Get the appropriate tiktoken encoding for a model.
18
+
19
+ Args:
20
+ model_name: The model name in format "provider:model"
21
+
22
+ Returns:
23
+ A tiktoken encoding instance
24
+ """
25
+ try:
26
+ import tiktoken
27
+ except ImportError:
28
+ logger.warning("tiktoken not available, falling back to character estimation")
29
+ return None
30
+
31
+ # Extract the model part from "provider:model" format
32
+ if ":" in model_name:
33
+ provider, model = model_name.split(":", 1)
34
+ else:
35
+ provider, model = "unknown", model_name
36
+
37
+ # Map common models to their tiktoken encodings
38
+ if provider == "openai":
39
+ if "gpt-4" in model:
40
+ encoding_name = "cl100k_base" # GPT-4 encoding
41
+ elif "gpt-3.5" in model:
42
+ encoding_name = "cl100k_base" # GPT-3.5-turbo encoding
43
+ else:
44
+ encoding_name = "cl100k_base" # Default for newer models
45
+ elif provider == "anthropic":
46
+ # Claude models use similar tokenization to GPT-4
47
+ encoding_name = "cl100k_base"
48
+ else:
49
+ # Default encoding for unknown models
50
+ encoding_name = "cl100k_base"
51
+
52
+ try:
53
+ return tiktoken.get_encoding(encoding_name)
54
+ except Exception as e:
55
+ logger.error(f"Error loading tiktoken encoding '{encoding_name}': {e}")
56
+ return None
57
+
58
+
59
+ def estimate_tokens(text: str, model_name: Optional[str] = None) -> int:
5
60
  """
6
- Estimate token count using a simple character-based approximation.
61
+ Estimate token count using tiktoken for accurate results.
7
62
 
8
- This is a rough estimate: ~4 characters per token on average.
9
- For more accurate counting, we would need tiktoken or similar.
63
+ Args:
64
+ text: The text to count tokens for.
65
+ model_name: Optional model name for model-specific tokenization.
66
+
67
+ Returns:
68
+ The estimated number of tokens.
10
69
  """
11
70
  if not text:
12
71
  return 0
13
72
 
14
- # Simple approximation: ~4 characters per token
73
+ # Try tiktoken first if model is specified
74
+ if model_name:
75
+ encoding = get_encoding(model_name)
76
+ if encoding:
77
+ try:
78
+ return len(encoding.encode(text))
79
+ except Exception as e:
80
+ logger.error(f"Error counting tokens with tiktoken: {e}")
81
+
82
+ # Fallback to character-based estimation
15
83
  # This is roughly accurate for English text
16
84
  return len(text) // 4
17
85
 
18
86
 
19
87
  def format_token_count(count: int) -> str:
20
- """Format token count for display."""
21
- if count >= 1000:
88
+ """Format token count for display with full precision."""
89
+ if count >= 1_000_000:
90
+ return f"{count:,}"
91
+ elif count >= 1000:
22
92
  return f"{count:,}"
23
93
  return str(count)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tunacode-cli
3
- Version: 0.0.39
3
+ Version: 0.0.41
4
4
  Summary: Your agentic CLI developer.
5
5
  Author-email: larock22 <noreply@github.com>
6
6
  License-Expression: MIT
@@ -24,6 +24,7 @@ Requires-Dist: prompt_toolkit==3.0.51
24
24
  Requires-Dist: pydantic-ai[logfire]==0.2.6
25
25
  Requires-Dist: pygments==2.19.1
26
26
  Requires-Dist: rich==14.0.0
27
+ Requires-Dist: tiktoken>=0.5.2
27
28
  Provides-Extra: dev
28
29
  Requires-Dist: build; extra == "dev"
29
30
  Requires-Dist: ruff; extra == "dev"
@@ -31,6 +32,7 @@ Requires-Dist: pytest; extra == "dev"
31
32
  Requires-Dist: pytest-cov; extra == "dev"
32
33
  Requires-Dist: pytest-asyncio; extra == "dev"
33
34
  Requires-Dist: textual-dev; extra == "dev"
35
+ Requires-Dist: pre-commit; extra == "dev"
34
36
  Dynamic: license-file
35
37
 
36
38
  # TunaCode
@@ -1,34 +1,33 @@
1
1
  tunacode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- tunacode/constants.py,sha256=fIAgJ0_fPxJgXWyaF-pWyVbZ7VK1gZnjVETFTVKB2lU,4074
3
- tunacode/context.py,sha256=6sterdRvPOyG3LU0nEAXpBsEPZbO3qtPyTlJBi-_VXE,2612
2
+ tunacode/constants.py,sha256=gBTHjFJIlYIwg_85bzrFLBJFwJ10a6TUnyES7eBMZjI,4993
3
+ tunacode/context.py,sha256=_gXVCyjU052jlyRAl9tklZSwl5U_zI_EIX8XN87VVWE,2786
4
4
  tunacode/exceptions.py,sha256=mTWXuWyr1k16CGLWN2tsthDGi7lbx1JK0ekIqogYDP8,3105
5
5
  tunacode/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  tunacode/setup.py,sha256=XPt4eAK-qcIZQv64jGZ_ryxcImDwps9OmXjJfIS1xcs,1899
7
- tunacode/types.py,sha256=BciT-uxnQ44iC-4QiDY72OD23LOtqSyMOuK_N0ttlaA,7676
7
+ tunacode/types.py,sha256=Czq7jYXHq7fZQtyqkCN5_7eEu1wyYUcB50C6v3sTWDw,8188
8
8
  tunacode/cli/__init__.py,sha256=zgs0UbAck8hfvhYsWhWOfBe5oK09ug2De1r4RuQZREA,55
9
- tunacode/cli/main.py,sha256=PIcFnfmIoI_pmK2y-zB_ouJbzR5fbSI7zsKQNPB_J8o,2406
10
- tunacode/cli/repl.py,sha256=iNaWh1pCybicGq8GKcoujr9BsN-t1IapGtSlYV8LXrI,17507
11
- tunacode/cli/textual_app.py,sha256=14-Nt0IIETmyHBrNn9uwSF3EwCcutwTp6gdoKgNm0sY,12593
12
- tunacode/cli/textual_bridge.py,sha256=LvqiTtF0hu3gNujzpKaW9h-m6xzEP3OH2M8KL2pCwRc,6333
13
- tunacode/cli/commands/__init__.py,sha256=YMrLz7szrmseJCRZGGX6_TyO3dJU8_QDCOFEhRAztzo,1634
9
+ tunacode/cli/main.py,sha256=ypefhvSt9hXzNOv0WpR-PlkUOSGadvcFbUIRT13n9oo,2619
10
+ tunacode/cli/repl.py,sha256=Sg9qDbT9lVs4brWiLhHhGH8mt_5EQf9MdwgvhS1KbuI,18333
11
+ tunacode/cli/commands/__init__.py,sha256=zmE9JcJ9Qd2xJhgdS4jMDJOoZsrAZmL5MAFxbKkk7F8,1670
14
12
  tunacode/cli/commands/base.py,sha256=GxUuDsDSpz0iXryy8MrEw88UM3C3yxL__kDK1QhshoA,2517
15
- tunacode/cli/commands/registry.py,sha256=d2dSAKrz_c02zU2AKtWghqPmm-p7XG_L81yj9nI1qu8,8152
16
- tunacode/cli/commands/implementations/__init__.py,sha256=sRFG2aktjtWt-M0Co_GdeLjODiVjEFqHDcLyyVabj8M,917
13
+ tunacode/cli/commands/registry.py,sha256=XVuLpp5S4Fw7GfIZfLrVZFo4jMLMNmYNpYN7xWgXyOk,8223
14
+ tunacode/cli/commands/implementations/__init__.py,sha256=lMgLZRX9hnw-ftZu4ykqoJoHqkZ5Yu0lBvYuzHylm7Q,986
17
15
  tunacode/cli/commands/implementations/conversation.py,sha256=EsnsZB6yyVI_sbNNMvk37tCz3iAj4E85R9ev696qeqg,4683
18
- tunacode/cli/commands/implementations/debug.py,sha256=TdP72Dpd3Nq3lwwyj0qZEdbSjDDmRyFu-0t6ttPFNno,6769
16
+ tunacode/cli/commands/implementations/debug.py,sha256=hWr9DOIS-kn8z89IJZ6HuRkyN1tOsnFZg5qlB8YPbG8,6763
19
17
  tunacode/cli/commands/implementations/development.py,sha256=kZRdVgReVmGU0uijFxtPio2RYkTrYMufOwgI1Aj1_NU,2729
20
18
  tunacode/cli/commands/implementations/model.py,sha256=uthx6IX9KwgwywNTDklkJpqCbaTX9h1_p-eVmqL73WQ,2245
21
19
  tunacode/cli/commands/implementations/system.py,sha256=2cGw5iCJO3aNhXTFF28CgAIyLgslvHmpfyL2ZHVB6oQ,7903
20
+ tunacode/cli/commands/implementations/todo.py,sha256=Dtz5bgcuK2VXGPWEBBZQgnWUMYkRXNzTGf_qkVPLF2U,8125
22
21
  tunacode/configuration/__init__.py,sha256=MbVXy8bGu0yKehzgdgZ_mfWlYGvIdb1dY2Ly75nfuPE,17
23
- tunacode/configuration/defaults.py,sha256=lNeJUW1S8zj4-XTCkMP9UaDc-tHWXLff9K8t0uPA_oE,801
24
- tunacode/configuration/models.py,sha256=XPobkLM_TzKTuMIWhK-svJfGRGFT9r2LhKEM6rv6QHk,3756
22
+ tunacode/configuration/defaults.py,sha256=lK_qf3BexmoQh7lbtxYG_ef0Kq3WyiLGOYmiVDO_amQ,840
23
+ tunacode/configuration/models.py,sha256=buH8ZquvcYI3OQBDIZeJ08cu00rSCeNABtUwl3VQS0E,4103
25
24
  tunacode/configuration/settings.py,sha256=KoN0u6GG3Hh_TWt02D_wpRfbACYri3gCDTXHtJfHl2w,994
26
25
  tunacode/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
26
  tunacode/core/code_index.py,sha256=jgAx3lSWP_DwnyiP5Jkm1YvX4JJyI4teMzlNrJSpEOA,15661
28
- tunacode/core/state.py,sha256=d2xUa7sRYizVc1mkaQN4aU034VSeh9nxQadzpL-aRas,1810
27
+ tunacode/core/state.py,sha256=hOLuvojJksd3GEezNT6jLPAsCcI-mZKldUUvlP4IKjw,3641
29
28
  tunacode/core/tool_handler.py,sha256=BPjR013OOO0cLXPdLeL2FDK0ixUwOYu59FfHdcdFhp4,2277
30
29
  tunacode/core/agents/__init__.py,sha256=UUJiPYb91arwziSpjd7vIk7XNGA_4HQbsOIbskSqevA,149
31
- tunacode/core/agents/main.py,sha256=BkAyqSr-xgENdyEmrPrx-E21ljclSU36EJh675Vhttk,41173
30
+ tunacode/core/agents/main.py,sha256=xDHA9lqs089jNmTOdIX9W9BEOh28HbilZtyyOYqHNGQ,43101
32
31
  tunacode/core/agents/utils.py,sha256=VaNsPB2l1dAP-VlS_QLRKvCb4NW0pXNRoxkh12AGXAg,10744
33
32
  tunacode/core/background/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
33
  tunacode/core/background/manager.py,sha256=rJdl3eDLTQwjbT7VhxXcJbZopCNR3M8ZGMbmeVnwwMc,1126
@@ -36,11 +35,14 @@ tunacode/core/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
36
35
  tunacode/core/setup/__init__.py,sha256=lzdpY6rIGf9DDlDBDGFvQZaSOQeFsNglHbkpq1-GtU8,376
37
36
  tunacode/core/setup/agent_setup.py,sha256=trELO8cPnWo36BBnYmXDEnDPdhBg0p-VLnx9A8hSSSQ,1401
38
37
  tunacode/core/setup/base.py,sha256=cbyT2-xK2mWgH4EO17VfM_OM2bj0kT895NW2jSXbe3c,968
39
- tunacode/core/setup/config_setup.py,sha256=xOQcjlFEL7HtGXH7wtpu83S6oA9K02wLYR1r-cWINpI,14449
38
+ tunacode/core/setup/config_setup.py,sha256=ctf9GuN7niqCsRK5rbEzCbzsN0MrAOeWzKtwOnyJJmY,14654
40
39
  tunacode/core/setup/coordinator.py,sha256=oVTN2xIeJERXitVJpkIk9tDGLs1D1bxIRmaogJwZJFI,2049
41
40
  tunacode/core/setup/environment_setup.py,sha256=n3IrObKEynHZSwtUJ1FddMg2C4sHz7ca42awemImV8s,2225
42
41
  tunacode/core/setup/git_safety_setup.py,sha256=CRIqrQt0QUJQRS344njty_iCqTorrDhHlXRuET7w0Tk,6714
43
- tunacode/prompts/system.md,sha256=YgcHMlksWHzi_R8XKGN7B7vz1rsqUZKHYq05Dlqewt0,13202
42
+ tunacode/core/token_usage/api_response_parser.py,sha256=CTtqGaFaxpkzkW3TEbe00QJzyRULpWN1EQxIYMleseg,1622
43
+ tunacode/core/token_usage/cost_calculator.py,sha256=oQPMphGhqIt7NKdOg1o5Zbo59_nwFWmRJMQ30ViiCWs,1835
44
+ tunacode/core/token_usage/usage_tracker.py,sha256=uKCpdZgmDfLauwsawSCifMu0kJE3lAnK7Sjd-0KYUgA,3894
45
+ tunacode/prompts/system.md,sha256=hXpjZ8Yiv2Acr2_6EmC2uOklP8FbmvyYR9oais-1KLk,16290
44
46
  tunacode/services/__init__.py,sha256=w_E8QK6RnvKSvU866eDe8BCRV26rAm4d3R-Yg06OWCU,19
45
47
  tunacode/services/mcp.py,sha256=R48X73KQjQ9vwhBrtbWHSBl-4K99QXmbIhh5J_1Gezo,3046
46
48
  tunacode/tools/__init__.py,sha256=ECBuUWWF1JjHW42CCceaPKgVTQyuljbz3RlhuA2fe2s,314
@@ -52,6 +54,7 @@ tunacode/tools/list_dir.py,sha256=1kNqzYCNlcA5rqXIEVqcjQy6QxlLZLj5AG6YIECfwio,72
52
54
  tunacode/tools/read_file.py,sha256=z2omev9xzj4-0GG9mRssD13rj-Aa1c-pszFi2Z7Hxvk,3268
53
55
  tunacode/tools/read_file_async_poc.py,sha256=2v2ckLQlwahgPGWGdE2c5Es37B35Y7zWdseZwT46E1E,6453
54
56
  tunacode/tools/run_command.py,sha256=7UvXjFQI1Av4vceXx48MbQCTrsFNj4PlygTAAhNDYIA,4376
57
+ tunacode/tools/todo.py,sha256=bVbohgwKqvvTe8efxXrMZDQU8vdk4E3jF9Cj38dRq7k,12727
55
58
  tunacode/tools/update_file.py,sha256=bW1MhTzRjBDjJzqQ6A1yCVEbkr1oIqtEC8uqcg_rfY4,3957
56
59
  tunacode/tools/write_file.py,sha256=prL6u8XOi9ZyPU-YNlG9YMLbSLrDJXDRuDX73ncXh-k,2699
57
60
  tunacode/ui/__init__.py,sha256=aRNE2pS50nFAX6y--rSGMNYwhz905g14gRd6g4BolYU,13
@@ -59,10 +62,10 @@ tunacode/ui/completers.py,sha256=Jx1zyCESwdm_4ZopvCBtb0bCJF-bRy8aBWG2yhPQtDc,487
59
62
  tunacode/ui/console.py,sha256=YXNFlnV7n4wyaIy-VohzIMJJ71C7fzgcjuLheNIO-QU,2079
60
63
  tunacode/ui/constants.py,sha256=A76B_KpM8jCuBYRg4cPmhi8_j6LLyWttO7_jjv47r3w,421
61
64
  tunacode/ui/decorators.py,sha256=e2KM-_pI5EKHa2M045IjUe4rPkTboxaKHXJT0K3461g,1914
62
- tunacode/ui/input.py,sha256=6LlEwKIXYXusNDI2PD0DDjRymQgu5mf2v06TsHbUln0,2957
65
+ tunacode/ui/input.py,sha256=E_zAJqNYoAVFA-j4xE9Qgs22y-GrdSZNqiseX-Or0ho,2955
63
66
  tunacode/ui/keybindings.py,sha256=h0MlD73CW_3i2dQzb9EFSPkqy0raZ_isgjxUiA9u6ts,691
64
67
  tunacode/ui/lexers.py,sha256=tmg4ic1enyTRLzanN5QPP7D_0n12YjX_8ZhsffzhXA4,1340
65
- tunacode/ui/output.py,sha256=kjVklUZumGwjV8LeB7aX1WjcwAbURnuudZZY1t4qsN4,4499
68
+ tunacode/ui/output.py,sha256=9BnjBa9iCFqMK7FleGyAjVIi0FVIlO2ku5RXPj2Gn2s,5549
66
69
  tunacode/ui/panels.py,sha256=IZpiWBb7jVXaycH5BPAnqTCs2-_ccJYq2V55MxkVHzQ,8199
67
70
  tunacode/ui/prompt_manager.py,sha256=U2cntB34vm-YwOj3gzFRUK362zccrz8pigQfpxr5sv8,4650
68
71
  tunacode/ui/tool_ui.py,sha256=S5-k1HwRlSqiQ8shGQ_QYGXQbuzb6Pg7u3CTqZwffdQ,6533
@@ -73,15 +76,16 @@ tunacode/utils/bm25.py,sha256=yq7KFWP3g_zIsjUV7l2hFPXYCzXyNQUInLU7u4qsc_4,1909
73
76
  tunacode/utils/diff_utils.py,sha256=V9QqQ0q4MfabVTnWptF3IXDp3estnfOKcJtDe_Sj14I,2372
74
77
  tunacode/utils/file_utils.py,sha256=AXiAJ_idtlmXEi9pMvwtfPy9Ys3yK-F4K7qb_NpwonU,923
75
78
  tunacode/utils/import_cache.py,sha256=q_xjJbtju05YbFopLDSkIo1hOtCx3DOTl3GQE5FFDgs,295
79
+ tunacode/utils/message_utils.py,sha256=kM6VSS2Dudjplie009khHgmIRjDoBUzv6tvHcYNDAAE,586
76
80
  tunacode/utils/ripgrep.py,sha256=AXUs2FFt0A7KBV996deS8wreIlUzKOlAHJmwrcAr4No,583
77
81
  tunacode/utils/security.py,sha256=e_zo9VmcOKFjgFMr9GOBIFhAmND4PBlJZgY7zqnsGjI,6548
78
82
  tunacode/utils/system.py,sha256=FSoibTIH0eybs4oNzbYyufIiV6gb77QaeY2yGqW39AY,11381
79
- tunacode/utils/text_utils.py,sha256=IiRviMqz5uoAbid8emkRXxgvQz6KE27ZeQom-qh9ymI,2984
80
- tunacode/utils/token_counter.py,sha256=nGCWwrHHFbKywqeDCEuJnADCkfJuzysWiB6cCltJOKI,648
83
+ tunacode/utils/text_utils.py,sha256=6YBD9QfkDO44-6jxnwRWIpmfIifPG-NqMzy_O2NAouc,7277
84
+ tunacode/utils/token_counter.py,sha256=l5KemYLfsypAtAF_YrDtVKFtBEghydS_MA8c-8mpPvM,2721
81
85
  tunacode/utils/user_configuration.py,sha256=Ilz8dpGVJDBE2iLWHAPT0xR8D51VRKV3kIbsAz8Bboc,3275
82
- tunacode_cli-0.0.39.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
83
- tunacode_cli-0.0.39.dist-info/METADATA,sha256=WZEtECuHPPI7pnDuhnxR5K4LwFT-2ReaMPFeG_GKF5g,5064
84
- tunacode_cli-0.0.39.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
85
- tunacode_cli-0.0.39.dist-info/entry_points.txt,sha256=hbkytikj4dGu6rizPuAd_DGUPBGF191RTnhr9wdhORY,51
86
- tunacode_cli-0.0.39.dist-info/top_level.txt,sha256=lKy2P6BWNi5XSA4DHFvyjQ14V26lDZctwdmhEJrxQbU,9
87
- tunacode_cli-0.0.39.dist-info/RECORD,,
86
+ tunacode_cli-0.0.41.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
87
+ tunacode_cli-0.0.41.dist-info/METADATA,sha256=kkUdWvJ3JjXnCCF_Jr1zdgi9lSYEo8CJuibfVNau_U0,5137
88
+ tunacode_cli-0.0.41.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
89
+ tunacode_cli-0.0.41.dist-info/entry_points.txt,sha256=hbkytikj4dGu6rizPuAd_DGUPBGF191RTnhr9wdhORY,51
90
+ tunacode_cli-0.0.41.dist-info/top_level.txt,sha256=lKy2P6BWNi5XSA4DHFvyjQ14V26lDZctwdmhEJrxQbU,9
91
+ tunacode_cli-0.0.41.dist-info/RECORD,,
@@ -1,420 +0,0 @@
1
- """
2
- Modern Textual-based TUI for TunaCode.
3
-
4
- Provides a rich, interactive terminal user interface with:
5
- - Split-pane layout with chat history and input
6
- - Sidebar with model info and commands
7
- - Modern dialog boxes for tool confirmations
8
- - Real-time status updates
9
- """
10
-
11
- import asyncio
12
- from typing import Optional
13
-
14
- from textual import on
15
- from textual.app import App, ComposeResult
16
- from textual.binding import Binding
17
- from textual.containers import Container, Horizontal, Vertical, VerticalScroll
18
- from textual.message import Message
19
- from textual.widgets import Button, Footer, Header, Static, TextArea
20
-
21
- from tunacode.core.state import StateManager
22
- from tunacode.setup import setup
23
- from tunacode.utils.system import check_for_updates
24
-
25
-
26
- class ChatMessage(Static):
27
- """A single chat message widget."""
28
-
29
- def __init__(self, sender: str, content: str, message_type: str = "user"):
30
- super().__init__()
31
- self.sender = sender
32
- self.content = content
33
- self.message_type = message_type
34
-
35
- def compose(self) -> ComposeResult:
36
- """Compose the chat message."""
37
- if self.message_type == "user":
38
- yield Static(f"[bold cyan]❯ You[/bold cyan]\n{self.content}", classes="user-message")
39
- elif self.message_type == "agent":
40
- yield Static(
41
- f"[bold green]🤖 TunaCode[/bold green]\n{self.content}", classes="agent-message"
42
- )
43
- elif self.message_type == "system":
44
- yield Static(
45
- f"[bold yellow]⚠️ System[/bold yellow]\n{self.content}", classes="system-message"
46
- )
47
- elif self.message_type == "tool":
48
- yield Static(
49
- f"[bold magenta]🔧 Tool[/bold magenta]\n{self.content}", classes="tool-message"
50
- )
51
-
52
-
53
- class Sidebar(Container):
54
- """Sidebar with model info and commands."""
55
-
56
- def __init__(self, state_manager: StateManager):
57
- super().__init__()
58
- self.state_manager = state_manager
59
-
60
- def compose(self) -> ComposeResult:
61
- """Compose the sidebar."""
62
- yield Static("[bold]TunaCode[/bold]", classes="sidebar-title")
63
- yield Static(f"Model: {self.state_manager.session.current_model}", id="current-model")
64
- yield Static("", classes="spacer")
65
-
66
- yield Static("[bold]Commands[/bold]", classes="section-title")
67
- yield Static("/help - Show help", classes="command-item")
68
- yield Static("/clear - Clear chat", classes="command-item")
69
- yield Static("/model - Switch model", classes="command-item")
70
- yield Static("/yolo - Toggle confirmations", classes="command-item")
71
- yield Static("", classes="spacer")
72
-
73
- yield Static("[bold]Status[/bold]", classes="section-title")
74
- yield Static("● Ready", id="status", classes="status-ready")
75
-
76
-
77
- class ChatHistory(VerticalScroll):
78
- """Scrollable chat history container."""
79
-
80
- def add_message(self, sender: str, content: str, message_type: str = "user") -> None:
81
- """Add a new message to the chat history."""
82
- message = ChatMessage(sender, content, message_type)
83
- self.mount(message)
84
- self.scroll_end(animate=True)
85
-
86
-
87
- class InputArea(Container):
88
- """Input area with text area and send button."""
89
-
90
- class SendMessage(Message):
91
- """Message sent when user submits input."""
92
-
93
- def __init__(self, content: str) -> None:
94
- self.content = content
95
- super().__init__()
96
-
97
- def compose(self) -> ComposeResult:
98
- """Compose the input area."""
99
- with Horizontal():
100
- yield TextArea(id="message-input")
101
- yield Button("Send", id="send-button", variant="primary")
102
-
103
- @on(Button.Pressed, "#send-button")
104
- def send_message(self) -> None:
105
- """Send the current message."""
106
- text_area = self.query_one("#message-input", TextArea)
107
- content = text_area.text.strip()
108
- if content:
109
- self.post_message(self.SendMessage(content))
110
- text_area.clear()
111
-
112
- def on_key(self, event) -> None:
113
- """Handle key events."""
114
- if event.key == "ctrl+enter":
115
- self.send_message()
116
-
117
-
118
- class TunaCodeApp(App):
119
- """Main TunaCode Textual application."""
120
-
121
- CSS = """
122
- Sidebar {
123
- width: 30;
124
- background: $surface;
125
- border: thick $primary;
126
- padding: 1;
127
- }
128
-
129
- .sidebar-title {
130
- text-align: center;
131
- color: $primary;
132
- margin-bottom: 1;
133
- }
134
-
135
- .section-title {
136
- color: $accent;
137
- margin: 1 0;
138
- }
139
-
140
- .command-item {
141
- color: $text-muted;
142
- margin-left: 1;
143
- }
144
-
145
- .status-ready {
146
- color: $success;
147
- }
148
-
149
- .status-busy {
150
- color: $warning;
151
- }
152
-
153
- .status-error {
154
- color: $error;
155
- }
156
-
157
- ChatHistory {
158
- border: thick $primary;
159
- padding: 1;
160
- height: 1fr;
161
- }
162
-
163
- .user-message {
164
- background: $surface;
165
- border-left: thick $primary;
166
- padding: 1;
167
- margin: 1 0;
168
- }
169
-
170
- .agent-message {
171
- background: $surface;
172
- border-left: thick $success;
173
- padding: 1;
174
- margin: 1 0;
175
- }
176
-
177
- .system-message {
178
- background: $surface;
179
- border-left: thick $warning;
180
- padding: 1;
181
- margin: 1 0;
182
- }
183
-
184
- .tool-message {
185
- background: $surface;
186
- border-left: thick $accent;
187
- padding: 1;
188
- margin: 1 0;
189
- }
190
-
191
- InputArea {
192
- height: 5;
193
- padding: 1;
194
- }
195
-
196
- #message-input {
197
- height: 3;
198
- }
199
-
200
- #send-button {
201
- width: 10;
202
- margin-left: 1;
203
- }
204
- """
205
-
206
- BINDINGS = [
207
- Binding("ctrl+c", "quit", "Quit"),
208
- Binding("ctrl+l", "clear_chat", "Clear"),
209
- Binding("f1", "help", "Help"),
210
- Binding("f2", "model_info", "Model"),
211
- ]
212
-
213
- def __init__(self, state_manager: StateManager):
214
- super().__init__()
215
- self.state_manager = state_manager
216
- self.chat_history: Optional[ChatHistory] = None
217
- self.sidebar: Optional[Sidebar] = None
218
- self.input_area: Optional[InputArea] = None
219
-
220
- def compose(self) -> ComposeResult:
221
- """Compose the main application layout."""
222
- yield Header()
223
-
224
- with Horizontal():
225
- self.sidebar = Sidebar(self.state_manager)
226
- yield self.sidebar
227
-
228
- with Vertical():
229
- self.chat_history = ChatHistory()
230
- yield self.chat_history
231
-
232
- self.input_area = InputArea()
233
- yield self.input_area
234
-
235
- yield Footer()
236
-
237
- def on_mount(self) -> None:
238
- """Called when the app is mounted."""
239
- # Add welcome messages
240
- self.chat_history.add_message(
241
- "System", "Welcome to TunaCode v0.11 - Your AI-powered development assistant", "system"
242
- )
243
- self.chat_history.add_message(
244
- "System", f"Current model: {self.state_manager.session.current_model}", "system"
245
- )
246
- self.chat_history.add_message(
247
- "System",
248
- "⚠️ IMPORTANT: Always use git branches before making major changes\n"
249
- "Type '/help' for available commands",
250
- "system",
251
- )
252
-
253
- @on(InputArea.SendMessage)
254
- async def handle_message(self, message: InputArea.SendMessage) -> None:
255
- """Handle incoming messages from the input area."""
256
- content = message.content
257
-
258
- # Add user message to chat
259
- self.chat_history.add_message("You", content, "user")
260
-
261
- # Update status
262
- status_widget = self.sidebar.query_one("#status", Static)
263
- status_widget.update("● Processing...")
264
- status_widget.classes = "status-busy"
265
-
266
- if content.startswith("/"):
267
- await self.handle_command(content)
268
- else:
269
- await self.handle_user_input(content)
270
-
271
- # Reset status
272
- status_widget.update("● Ready")
273
- status_widget.classes = "status-ready"
274
-
275
- async def handle_command(self, command: str) -> None:
276
- """Handle slash commands."""
277
- if command == "/help":
278
- help_text = """Available Commands:
279
-
280
- /help - Show this help message
281
- /clear - Clear chat history
282
- /model - Show current model info
283
- /yolo - Toggle confirmation prompts
284
- /quit - Exit the application
285
-
286
- Keyboard Shortcuts:
287
- Ctrl+C - Quit
288
- Ctrl+L - Clear chat
289
- F1 - Help
290
- F2 - Model info
291
- Ctrl+Enter - Send message"""
292
- self.chat_history.add_message("System", help_text, "system")
293
-
294
- elif command == "/clear":
295
- await self.action_clear_chat()
296
-
297
- elif command == "/model":
298
- model_info = f"Current model: {self.state_manager.session.current_model}"
299
- self.chat_history.add_message("System", model_info, "system")
300
-
301
- elif command == "/yolo":
302
- # Toggle yolo mode
303
- current_state = getattr(self.state_manager.session, "yolo_mode", False)
304
- self.state_manager.session.yolo_mode = not current_state
305
- new_state = "enabled" if not current_state else "disabled"
306
- self.chat_history.add_message("System", f"Confirmation prompts {new_state}", "system")
307
-
308
- elif command == "/quit":
309
- await self.action_quit()
310
-
311
- else:
312
- self.chat_history.add_message("System", f"Unknown command: {command}", "system")
313
-
314
- async def handle_user_input(self, text: str) -> None:
315
- """Handle regular user input."""
316
- try:
317
- # Use the bridge to process the input
318
- if not hasattr(self, "_bridge"):
319
- from tunacode.cli.textual_bridge import TextualAgentBridge
320
-
321
- self._bridge = TextualAgentBridge(self.state_manager, self._bridge_message_callback)
322
-
323
- # Process the request
324
- response = await self._bridge.process_user_input(text)
325
-
326
- # Add the agent's response to chat
327
- self.chat_history.add_message("TunaCode", response, "agent")
328
-
329
- except Exception as e:
330
- self.chat_history.add_message("System", f"Error: {str(e)}", "system")
331
-
332
- async def _bridge_message_callback(self, message_type: str, content: str) -> None:
333
- """Callback for bridge to send messages to the UI."""
334
- self.chat_history.add_message("System", content, message_type)
335
-
336
- def action_clear_chat(self) -> None:
337
- """Clear the chat history."""
338
- self.chat_history.remove_children()
339
- self.chat_history.add_message("System", "Chat cleared", "system")
340
-
341
- def action_help(self) -> None:
342
- """Show help information."""
343
- self.handle_command("/help")
344
-
345
- def action_model_info(self) -> None:
346
- """Show model information."""
347
- self.handle_command("/model")
348
-
349
-
350
- async def run_textual_app(state_manager: StateManager) -> None:
351
- """Run the Textual application."""
352
- app = TunaCodeApp(state_manager)
353
- await app.run_async()
354
-
355
-
356
- def main():
357
- """Main entry point for the Textual app."""
358
- import sys
359
-
360
- # Handle command line arguments
361
- version_flag = "--version" in sys.argv or "-v" in sys.argv
362
- if version_flag:
363
- from tunacode.constants import APP_VERSION
364
-
365
- print(f"TunaCode v{APP_VERSION}")
366
- return
367
-
368
- # Initialize state manager
369
- state_manager = StateManager()
370
- # Show banner
371
- print("🐟 TunaCode - Modern AI Development Assistant")
372
- print("=" * 50)
373
-
374
- # Check for updates
375
- has_update, latest_version = check_for_updates()
376
- if has_update:
377
- print(f"📦 Update available: v{latest_version}")
378
- print("Run: pip install --upgrade tunacode-cli")
379
- print()
380
-
381
- # Parse CLI arguments for configuration
382
- cli_config = {}
383
- args = sys.argv[1:]
384
- i = 0
385
- while i < len(args):
386
- if args[i] == "--model" and i + 1 < len(args):
387
- cli_config["model"] = args[i + 1]
388
- i += 2
389
- elif args[i] == "--key" and i + 1 < len(args):
390
- cli_config["key"] = args[i + 1]
391
- i += 2
392
- elif args[i] == "--baseurl" and i + 1 < len(args):
393
- cli_config["baseurl"] = args[i + 1]
394
- i += 2
395
- elif args[i] == "--setup":
396
- cli_config["setup"] = True
397
- i += 1
398
- else:
399
- i += 1
400
-
401
- async def run_app():
402
- try:
403
- # Run setup
404
- run_setup = cli_config.get("setup", False)
405
- await setup(run_setup, state_manager, cli_config)
406
-
407
- # Run the Textual app
408
- await run_textual_app(state_manager)
409
-
410
- except KeyboardInterrupt:
411
- print("\n👋 Goodbye!")
412
- except Exception as e:
413
- print(f"❌ Error: {e}")
414
-
415
- # Run the async app
416
- asyncio.run(run_app())
417
-
418
-
419
- if __name__ == "__main__":
420
- main()