tsugite-cli 0.3.3__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.
Files changed (101) hide show
  1. tsugite/__init__.py +6 -0
  2. tsugite/agent_composition.py +163 -0
  3. tsugite/agent_inheritance.py +479 -0
  4. tsugite/agent_preparation.py +236 -0
  5. tsugite/agent_runner/__init__.py +45 -0
  6. tsugite/agent_runner/helpers.py +106 -0
  7. tsugite/agent_runner/history_integration.py +248 -0
  8. tsugite/agent_runner/metrics.py +100 -0
  9. tsugite/agent_runner/runner.py +1879 -0
  10. tsugite/agent_runner/validation.py +70 -0
  11. tsugite/agent_utils.py +167 -0
  12. tsugite/attachments/__init__.py +65 -0
  13. tsugite/attachments/auto_context.py +199 -0
  14. tsugite/attachments/base.py +34 -0
  15. tsugite/attachments/file.py +51 -0
  16. tsugite/attachments/inline.py +31 -0
  17. tsugite/attachments/storage.py +178 -0
  18. tsugite/attachments/url.py +59 -0
  19. tsugite/attachments/youtube.py +101 -0
  20. tsugite/benchmark/__init__.py +62 -0
  21. tsugite/benchmark/config.py +183 -0
  22. tsugite/benchmark/core.py +292 -0
  23. tsugite/benchmark/discovery.py +377 -0
  24. tsugite/benchmark/evaluators.py +671 -0
  25. tsugite/benchmark/execution.py +657 -0
  26. tsugite/benchmark/metrics.py +204 -0
  27. tsugite/benchmark/reports.py +420 -0
  28. tsugite/benchmark/utils.py +288 -0
  29. tsugite/builtin_agents/chat-assistant.md +53 -0
  30. tsugite/builtin_agents/default.md +140 -0
  31. tsugite/builtin_agents.py +5 -0
  32. tsugite/cache.py +195 -0
  33. tsugite/cli/__init__.py +1042 -0
  34. tsugite/cli/agents.py +148 -0
  35. tsugite/cli/attachments.py +193 -0
  36. tsugite/cli/benchmark.py +663 -0
  37. tsugite/cli/cache.py +113 -0
  38. tsugite/cli/config.py +272 -0
  39. tsugite/cli/helpers.py +534 -0
  40. tsugite/cli/history.py +193 -0
  41. tsugite/cli/init.py +387 -0
  42. tsugite/cli/mcp.py +193 -0
  43. tsugite/cli/tools.py +419 -0
  44. tsugite/config.py +204 -0
  45. tsugite/console.py +48 -0
  46. tsugite/constants.py +21 -0
  47. tsugite/core/__init__.py +19 -0
  48. tsugite/core/agent.py +774 -0
  49. tsugite/core/executor.py +300 -0
  50. tsugite/core/memory.py +67 -0
  51. tsugite/core/tools.py +271 -0
  52. tsugite/docker_cli.py +270 -0
  53. tsugite/events/__init__.py +55 -0
  54. tsugite/events/base.py +46 -0
  55. tsugite/events/bus.py +62 -0
  56. tsugite/events/events.py +224 -0
  57. tsugite/exceptions.py +40 -0
  58. tsugite/history/__init__.py +29 -0
  59. tsugite/history/index.py +210 -0
  60. tsugite/history/models.py +106 -0
  61. tsugite/history/storage.py +157 -0
  62. tsugite/mcp_client.py +219 -0
  63. tsugite/mcp_config.py +174 -0
  64. tsugite/md_agents.py +751 -0
  65. tsugite/models.py +257 -0
  66. tsugite/renderer.py +151 -0
  67. tsugite/shell_tool_config.py +265 -0
  68. tsugite/templates/assistant.md +14 -0
  69. tsugite/tools/__init__.py +265 -0
  70. tsugite/tools/agents.py +312 -0
  71. tsugite/tools/edit_strategies.py +393 -0
  72. tsugite/tools/fs.py +329 -0
  73. tsugite/tools/http.py +239 -0
  74. tsugite/tools/interactive.py +430 -0
  75. tsugite/tools/shell.py +129 -0
  76. tsugite/tools/shell_tools.py +214 -0
  77. tsugite/tools/tasks.py +339 -0
  78. tsugite/tsugite.py +7 -0
  79. tsugite/ui/__init__.py +46 -0
  80. tsugite/ui/base.py +638 -0
  81. tsugite/ui/chat.py +265 -0
  82. tsugite/ui/chat.tcss +92 -0
  83. tsugite/ui/chat_history.py +286 -0
  84. tsugite/ui/helpers.py +102 -0
  85. tsugite/ui/jsonl.py +125 -0
  86. tsugite/ui/live_template.py +529 -0
  87. tsugite/ui/plain.py +419 -0
  88. tsugite/ui/textual_chat.py +642 -0
  89. tsugite/ui/textual_handler.py +225 -0
  90. tsugite/ui/widgets/__init__.py +6 -0
  91. tsugite/ui/widgets/base_scroll_log.py +27 -0
  92. tsugite/ui/widgets/message_list.py +121 -0
  93. tsugite/ui/widgets/thought_log.py +80 -0
  94. tsugite/ui_context.py +90 -0
  95. tsugite/utils.py +367 -0
  96. tsugite/xdg.py +104 -0
  97. tsugite_cli-0.3.3.dist-info/METADATA +325 -0
  98. tsugite_cli-0.3.3.dist-info/RECORD +101 -0
  99. tsugite_cli-0.3.3.dist-info/WHEEL +4 -0
  100. tsugite_cli-0.3.3.dist-info/entry_points.txt +5 -0
  101. tsugite_cli-0.3.3.dist-info/licenses/LICENSE +235 -0
tsugite/config.py ADDED
@@ -0,0 +1,204 @@
1
+ """Tsugite configuration management."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Dict, List, Optional
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+
9
+ from .xdg import get_xdg_config_path
10
+
11
+
12
+ class Config(BaseModel):
13
+ """Tsugite configuration."""
14
+
15
+ model_config = ConfigDict(
16
+ extra="allow",
17
+ str_strip_whitespace=True,
18
+ )
19
+
20
+ default_model: Optional[str] = None
21
+ model_aliases: Dict[str, str] = Field(default_factory=dict)
22
+ default_base_agent: Optional[str] = None
23
+ chat_theme: str = "gruvbox"
24
+ history_enabled: bool = True
25
+ history_dir: Optional[Path] = None
26
+ machine_name: Optional[str] = None
27
+ max_history_days: Optional[int] = None
28
+ auto_context_enabled: bool = True
29
+ auto_context_files: List[str] = Field(default_factory=lambda: [".tsugite/CONTEXT.md", "AGENTS.md", "CLAUDE.md"])
30
+ auto_context_include_global: bool = True
31
+
32
+
33
+ def get_config_path() -> Path:
34
+ return get_xdg_config_path("config.json")
35
+
36
+
37
+ def load_config(path: Optional[Path] = None) -> Config:
38
+ """Load Tsugite configuration from JSON file.
39
+
40
+ Args:
41
+ path: Path to config.json file. If None, uses default path
42
+
43
+ Returns:
44
+ Config object with loaded settings. Returns default config if file doesn't exist.
45
+ """
46
+ if path is None:
47
+ path = get_config_path()
48
+
49
+ if not path.exists():
50
+ return Config()
51
+
52
+ try:
53
+ with open(path, "r", encoding="utf-8") as f:
54
+ data = json.load(f)
55
+
56
+ # Convert history_dir string to Path if present
57
+ if "history_dir" in data and data["history_dir"]:
58
+ data["history_dir"] = Path(data["history_dir"])
59
+
60
+ # Use Pydantic's model_validate for validation and construction
61
+ return Config.model_validate(data)
62
+
63
+ except json.JSONDecodeError as e:
64
+ print(f"Warning: Failed to parse config at {path}: {e}")
65
+ return Config()
66
+ except Exception as e:
67
+ print(f"Warning: Failed to load config from {path}: {e}")
68
+ return Config()
69
+
70
+
71
+ def save_config(config: Config, path: Optional[Path] = None) -> None:
72
+ """Save Tsugite configuration to JSON file.
73
+
74
+ Args:
75
+ config: Config object to save
76
+ path: Path to config.json file. If None, uses default path
77
+
78
+ Raises:
79
+ IOError: If file cannot be written
80
+ """
81
+ if path is None:
82
+ path = get_config_path()
83
+
84
+ path.parent.mkdir(parents=True, exist_ok=True)
85
+
86
+ # Use Pydantic's model_dump to serialize, excluding None values for cleaner output
87
+ config_data = config.model_dump(exclude_none=True, mode="json")
88
+
89
+ # Convert Path to string for JSON serialization
90
+ if "history_dir" in config_data and config_data["history_dir"]:
91
+ config_data["history_dir"] = str(config_data["history_dir"])
92
+
93
+ # Remove empty model_aliases dict for cleaner output
94
+ if "model_aliases" in config_data and not config_data["model_aliases"]:
95
+ del config_data["model_aliases"]
96
+
97
+ with open(path, "w", encoding="utf-8") as f:
98
+ json.dump(config_data, f, indent=2)
99
+
100
+
101
+ def set_default_model(model: str, path: Optional[Path] = None) -> None:
102
+ """Set the default model in configuration.
103
+
104
+ Args:
105
+ model: Model string (e.g., "ollama:qwen2.5-coder:7b")
106
+ path: Path to config.json file. If None, uses default path
107
+ """
108
+ config = load_config(path)
109
+ config.default_model = model
110
+ save_config(config, path)
111
+
112
+
113
+ def add_model_alias(alias: str, model: str, path: Optional[Path] = None) -> None:
114
+ """Add or update a model alias in configuration.
115
+
116
+ Args:
117
+ alias: Alias name (e.g., "cheap")
118
+ model: Model string (e.g., "openai:gpt-4o-mini")
119
+ path: Path to config.json file. If None, uses default path
120
+ """
121
+ config = load_config(path)
122
+ config.model_aliases[alias] = model
123
+ save_config(config, path)
124
+
125
+
126
+ def remove_model_alias(alias: str, path: Optional[Path] = None) -> bool:
127
+ """Remove a model alias from configuration.
128
+
129
+ Args:
130
+ alias: Alias name to remove
131
+ path: Path to config.json file. If None, uses default path
132
+
133
+ Returns:
134
+ True if alias was removed, False if it didn't exist
135
+ """
136
+ config = load_config(path)
137
+ if alias in config.model_aliases:
138
+ del config.model_aliases[alias]
139
+ save_config(config, path)
140
+ return True
141
+ return False
142
+
143
+
144
+ def get_model_alias(alias: str, path: Optional[Path] = None) -> Optional[str]:
145
+ """Get the model string for an alias.
146
+
147
+ Args:
148
+ alias: Alias name to look up
149
+ path: Path to config.json file. If None, uses default path
150
+
151
+ Returns:
152
+ Model string if alias exists, None otherwise
153
+ """
154
+ config = load_config(path)
155
+ return config.model_aliases.get(alias)
156
+
157
+
158
+ def set_default_base_agent(base_agent: Optional[str], path: Optional[Path] = None) -> None:
159
+ """Set the default base agent in configuration.
160
+
161
+ Args:
162
+ base_agent: Base agent name (e.g., "default") or None to disable
163
+ path: Path to config.json file. If None, uses default path
164
+ """
165
+ config = load_config(path)
166
+ config.default_base_agent = base_agent
167
+ save_config(config, path)
168
+
169
+
170
+ def set_chat_theme(theme: str, path: Optional[Path] = None) -> None:
171
+ """Set the chat UI theme in configuration.
172
+
173
+ Args:
174
+ theme: Theme name (e.g., "gruvbox", "nord", "tokyo-night")
175
+ path: Path to config.json file. If None, uses default path
176
+ """
177
+ config = load_config(path)
178
+ config.chat_theme = theme
179
+ save_config(config, path)
180
+
181
+
182
+ def set_auto_context_enabled(enabled: bool, path: Optional[Path] = None) -> None:
183
+ """Set whether auto-context is enabled in configuration.
184
+
185
+ Args:
186
+ enabled: True to enable auto-context, False to disable
187
+ path: Path to config.json file. If None, uses default path
188
+ """
189
+ config = load_config(path)
190
+ config.auto_context_enabled = enabled
191
+ save_config(config, path)
192
+
193
+
194
+ def get_chat_theme(path: Optional[Path] = None) -> str:
195
+ """Get the chat UI theme from configuration.
196
+
197
+ Args:
198
+ path: Path to config.json file. If None, uses default path
199
+
200
+ Returns:
201
+ Theme name (defaults to "gruvbox")
202
+ """
203
+ config = load_config(path)
204
+ return config.chat_theme
tsugite/console.py ADDED
@@ -0,0 +1,48 @@
1
+ """Centralized console creation utilities."""
2
+
3
+ import sys
4
+
5
+ from rich.console import Console
6
+
7
+
8
+ def get_stderr_console(no_color: bool = False) -> Console:
9
+ """Get console for error/warning output to stderr.
10
+
11
+ Args:
12
+ no_color: Disable color output
13
+
14
+ Returns:
15
+ Console instance configured for stderr
16
+ """
17
+ return Console(file=sys.stderr, no_color=no_color)
18
+
19
+
20
+ def get_stdout_console(no_color: bool = False, force_terminal: bool = False) -> Console:
21
+ """Get console for standard output to stdout.
22
+
23
+ Args:
24
+ no_color: Disable color output
25
+ force_terminal: Force terminal mode even if TTY not detected
26
+
27
+ Returns:
28
+ Console instance configured for stdout
29
+ """
30
+ return Console(file=sys.stdout, no_color=no_color, force_terminal=force_terminal)
31
+
32
+
33
+ def get_output_console() -> Console:
34
+ """Get console for standard output (alias for get_stdout_console with no_color=True)."""
35
+ return get_stdout_console(no_color=True)
36
+
37
+
38
+ def get_error_console(headless: bool, console: Console) -> Console:
39
+ """Get console for error output based on mode.
40
+
41
+ Args:
42
+ headless: Whether running in headless mode
43
+ console: Default console to use in non-headless mode
44
+
45
+ Returns:
46
+ Console for error output (no-color stderr in headless, console otherwise)
47
+ """
48
+ return get_stderr_console(no_color=True) if headless else console
tsugite/constants.py ADDED
@@ -0,0 +1,21 @@
1
+ """Constants for Tsugite CLI."""
2
+
3
+ TSUGITE_LOGO_NARROW = """
4
+ ╔╦╗╔═╗╦ ╦╔═╗╦╔╦╗╔═╗
5
+ ║ ╚═╗║ ║║ ╦║ ║ ║╣
6
+ ╩ ╚═╝╚═╝╚═╝╩ ╩ ╚═╝
7
+ """
8
+
9
+ TSUGITE_LOGO_WIDE = """
10
+ ███████████ ███ █████
11
+ ░█░░░███░░░█ ░░░ ░░███
12
+ ░ ░███ ░ █████ █████ ████ ███████ ████ ███████ ██████
13
+ ░███ ███░░ ░░███ ░███ ███░░███░░███ ░░░███░ ███░░███
14
+ ░███ ░░█████ ░███ ░███ ░███ ░███ ░███ ░███ ░███████
15
+ ░███ ░░░░███ ░███ ░███ ░███ ░███ ░███ ░███ ███░███░░░
16
+ █████ ██████ ░░████████░░███████ █████ ░░█████ ░░██████
17
+ ░░░░░ ░░░░░░ ░░░░░░░░ ░░░░░███░░░░░ ░░░░░ ░░░░░░
18
+ ███ ░███
19
+ ░░██████ Tsugite
20
+ ░░░░░░
21
+ """
@@ -0,0 +1,19 @@
1
+ """Core agent implementation with direct LiteLLM integration."""
2
+
3
+ from .agent import AgentResult, TsugiteAgent
4
+ from .executor import CodeExecutor, ExecutionResult, LocalExecutor
5
+ from .memory import AgentMemory, StepResult
6
+ from .tools import Tool, create_tool_from_function, create_tool_from_tsugite
7
+
8
+ __all__ = [
9
+ "TsugiteAgent",
10
+ "AgentResult",
11
+ "CodeExecutor",
12
+ "LocalExecutor",
13
+ "ExecutionResult",
14
+ "AgentMemory",
15
+ "StepResult",
16
+ "Tool",
17
+ "create_tool_from_function",
18
+ "create_tool_from_tsugite",
19
+ ]