tsugite-cli 0.3.3__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.
- tsugite_cli-0.3.3/.github/copilot-instructions.md +258 -0
- tsugite_cli-0.3.3/.github/workflows/ci.yml +74 -0
- tsugite_cli-0.3.3/.github/workflows/docker-publish.yml +61 -0
- tsugite_cli-0.3.3/.github/workflows/pypi-publish.yml +37 -0
- tsugite_cli-0.3.3/.gitignore +169 -0
- tsugite_cli-0.3.3/AGENTS.md +1356 -0
- tsugite_cli-0.3.3/CLAUDE.md +1 -0
- tsugite_cli-0.3.3/Dockerfile.runtime +11 -0
- tsugite_cli-0.3.3/LICENSE +235 -0
- tsugite_cli-0.3.3/PKG-INFO +325 -0
- tsugite_cli-0.3.3/README.md +64 -0
- tsugite_cli-0.3.3/benchmarks/README.md +14 -0
- tsugite_cli-0.3.3/benchmarks/basic/echo.md +27 -0
- tsugite_cli-0.3.3/benchmarks/basic/echo.test.md +33 -0
- tsugite_cli-0.3.3/benchmarks/basic/hello_world.md +27 -0
- tsugite_cli-0.3.3/benchmarks/basic/hello_world.test.md +33 -0
- tsugite_cli-0.3.3/benchmarks/basic/json_formatter.md +38 -0
- tsugite_cli-0.3.3/benchmarks/basic/json_formatter.test.md +36 -0
- tsugite_cli-0.3.3/benchmarks/basic/list_maker.md +33 -0
- tsugite_cli-0.3.3/benchmarks/basic/list_maker.test.md +39 -0
- tsugite_cli-0.3.3/benchmarks/basic/simple_math.md +32 -0
- tsugite_cli-0.3.3/benchmarks/basic/simple_math.test.md +33 -0
- tsugite_cli-0.3.3/benchmarks/complex/creative_writing.md +35 -0
- tsugite_cli-0.3.3/benchmarks/complex/creative_writing.test.md +60 -0
- tsugite_cli-0.3.3/benchmarks/tools/file_read_tool.md +12 -0
- tsugite_cli-0.3.3/benchmarks/tools/file_read_tool.test.md +33 -0
- tsugite_cli-0.3.3/benchmarks/tools/file_write_read_tool.md +14 -0
- tsugite_cli-0.3.3/benchmarks/tools/file_write_read_tool.test.md +33 -0
- tsugite_cli-0.3.3/benchmarks/tools/file_write_tool.md +12 -0
- tsugite_cli-0.3.3/benchmarks/tools/file_write_tool.test.md +39 -0
- tsugite_cli-0.3.3/benchmarks/tools/fixtures/config.json +1 -0
- tsugite_cli-0.3.3/benchmarks/tools/fixtures/data.txt +3 -0
- tsugite_cli-0.3.3/benchmarks/tools/fixtures/sample.txt +2 -0
- tsugite_cli-0.3.3/benchmarks/tools/shell_command_tool.md +12 -0
- tsugite_cli-0.3.3/benchmarks/tools/shell_command_tool.test.md +32 -0
- tsugite_cli-0.3.3/docs/examples/agents/morning_brief.md +86 -0
- tsugite_cli-0.3.3/docs/examples/agents/research_coordinator.md +57 -0
- tsugite_cli-0.3.3/docs/examples/agents/system_monitor.md +54 -0
- tsugite_cli-0.3.3/docs/examples/agents/web_searcher.md +49 -0
- tsugite_cli-0.3.3/docs/test_agents/prefresh_readme.md +16 -0
- tsugite_cli-0.3.3/docs/test_agents/template_test.md +14 -0
- tsugite_cli-0.3.3/docs/test_agents/test_agent.md +15 -0
- tsugite_cli-0.3.3/docs/test_agents/test_steps.md +8 -0
- tsugite_cli-0.3.3/pyproject.toml +108 -0
- tsugite_cli-0.3.3/tests/README.md +127 -0
- tsugite_cli-0.3.3/tests/__init__.py +1 -0
- tsugite_cli-0.3.3/tests/conftest.py +398 -0
- tsugite_cli-0.3.3/tests/core/__init__.py +1 -0
- tsugite_cli-0.3.3/tests/core/test_agent.py +729 -0
- tsugite_cli-0.3.3/tests/core/test_agent_ui_events.py +506 -0
- tsugite_cli-0.3.3/tests/core/test_executor.py +313 -0
- tsugite_cli-0.3.3/tests/core/test_memory.py +165 -0
- tsugite_cli-0.3.3/tests/core/test_tools.py +372 -0
- tsugite_cli-0.3.3/tests/events/test_event_consolidation.py +241 -0
- tsugite_cli-0.3.3/tests/fixtures/agents/failing.md +13 -0
- tsugite_cli-0.3.3/tests/fixtures/agents/nested.md +12 -0
- tsugite_cli-0.3.3/tests/fixtures/agents/simple.md +12 -0
- tsugite_cli-0.3.3/tests/fixtures/agents/slow.md +12 -0
- tsugite_cli-0.3.3/tests/smoke_test.sh +87 -0
- tsugite_cli-0.3.3/tests/test_agent_attachments.py +127 -0
- tsugite_cli-0.3.3/tests/test_agent_composition.py +189 -0
- tsugite_cli-0.3.3/tests/test_agent_inheritance.py +567 -0
- tsugite_cli-0.3.3/tests/test_agent_parser.py +501 -0
- tsugite_cli-0.3.3/tests/test_agent_utils.py +246 -0
- tsugite_cli-0.3.3/tests/test_agents_tool.py +219 -0
- tsugite_cli-0.3.3/tests/test_attachment_deduplication.py +228 -0
- tsugite_cli-0.3.3/tests/test_attachment_resolution.py +184 -0
- tsugite_cli-0.3.3/tests/test_attachments.py +196 -0
- tsugite_cli-0.3.3/tests/test_auto_context_handler.py +431 -0
- tsugite_cli-0.3.3/tests/test_auto_discovery.py +231 -0
- tsugite_cli-0.3.3/tests/test_benchmark_core.py +301 -0
- tsugite_cli-0.3.3/tests/test_benchmark_evaluators.py +333 -0
- tsugite_cli-0.3.3/tests/test_benchmark_metrics.py +265 -0
- tsugite_cli-0.3.3/tests/test_benchmark_reports.py +347 -0
- tsugite_cli-0.3.3/tests/test_builtin_agent_paths.py +154 -0
- tsugite_cli-0.3.3/tests/test_builtin_agents.py +296 -0
- tsugite_cli-0.3.3/tests/test_cache_control.py +157 -0
- tsugite_cli-0.3.3/tests/test_chat_cli.py +168 -0
- tsugite_cli-0.3.3/tests/test_cli.py +761 -0
- tsugite_cli-0.3.3/tests/test_cli_arguments.py +132 -0
- tsugite_cli-0.3.3/tests/test_cli_rendering.py +365 -0
- tsugite_cli-0.3.3/tests/test_cli_subcommands.py +205 -0
- tsugite_cli-0.3.3/tests/test_config.py +140 -0
- tsugite_cli-0.3.3/tests/test_continuation.py +510 -0
- tsugite_cli-0.3.3/tests/test_custom_shell_tools.py +340 -0
- tsugite_cli-0.3.3/tests/test_custom_ui.py +375 -0
- tsugite_cli-0.3.3/tests/test_docker_delegation.py +236 -0
- tsugite_cli-0.3.3/tests/test_edit_strategies.py +293 -0
- tsugite_cli-0.3.3/tests/test_error_display.py +85 -0
- tsugite_cli-0.3.3/tests/test_file_references.py +249 -0
- tsugite_cli-0.3.3/tests/test_file_tools.py +636 -0
- tsugite_cli-0.3.3/tests/test_history.py +637 -0
- tsugite_cli-0.3.3/tests/test_history_integration.py +422 -0
- tsugite_cli-0.3.3/tests/test_history_models.py +346 -0
- tsugite_cli-0.3.3/tests/test_http_tools.py +109 -0
- tsugite_cli-0.3.3/tests/test_interactive_context.py +275 -0
- tsugite_cli-0.3.3/tests/test_interactive_tool.py +483 -0
- tsugite_cli-0.3.3/tests/test_jsonl_ui.py +181 -0
- tsugite_cli-0.3.3/tests/test_list_agents_tool.py +252 -0
- tsugite_cli-0.3.3/tests/test_llm_evaluator.py +344 -0
- tsugite_cli-0.3.3/tests/test_mcp_client.py +378 -0
- tsugite_cli-0.3.3/tests/test_models.py +56 -0
- tsugite_cli-0.3.3/tests/test_multistep_agents.py +1195 -0
- tsugite_cli-0.3.3/tests/test_reasoning_models.py +75 -0
- tsugite_cli-0.3.3/tests/test_renderer.py +673 -0
- tsugite_cli-0.3.3/tests/test_rendering_scenarios.py +387 -0
- tsugite_cli-0.3.3/tests/test_retry_system.py +176 -0
- tsugite_cli-0.3.3/tests/test_stdin.py +243 -0
- tsugite_cli-0.3.3/tests/test_subagent_subprocess.py +119 -0
- tsugite_cli-0.3.3/tests/test_task_tracking.py +362 -0
- tsugite_cli-0.3.3/tests/test_textual_chat.py +428 -0
- tsugite_cli-0.3.3/tests/test_tool_directives.py +433 -0
- tsugite_cli-0.3.3/tests/test_tool_registry.py +420 -0
- tsugite_cli-0.3.3/tsugite/__init__.py +6 -0
- tsugite_cli-0.3.3/tsugite/agent_composition.py +163 -0
- tsugite_cli-0.3.3/tsugite/agent_inheritance.py +479 -0
- tsugite_cli-0.3.3/tsugite/agent_preparation.py +236 -0
- tsugite_cli-0.3.3/tsugite/agent_runner/__init__.py +45 -0
- tsugite_cli-0.3.3/tsugite/agent_runner/helpers.py +106 -0
- tsugite_cli-0.3.3/tsugite/agent_runner/history_integration.py +248 -0
- tsugite_cli-0.3.3/tsugite/agent_runner/metrics.py +100 -0
- tsugite_cli-0.3.3/tsugite/agent_runner/runner.py +1879 -0
- tsugite_cli-0.3.3/tsugite/agent_runner/validation.py +70 -0
- tsugite_cli-0.3.3/tsugite/agent_utils.py +167 -0
- tsugite_cli-0.3.3/tsugite/attachments/__init__.py +65 -0
- tsugite_cli-0.3.3/tsugite/attachments/auto_context.py +199 -0
- tsugite_cli-0.3.3/tsugite/attachments/base.py +34 -0
- tsugite_cli-0.3.3/tsugite/attachments/file.py +51 -0
- tsugite_cli-0.3.3/tsugite/attachments/inline.py +31 -0
- tsugite_cli-0.3.3/tsugite/attachments/storage.py +178 -0
- tsugite_cli-0.3.3/tsugite/attachments/url.py +59 -0
- tsugite_cli-0.3.3/tsugite/attachments/youtube.py +101 -0
- tsugite_cli-0.3.3/tsugite/benchmark/__init__.py +62 -0
- tsugite_cli-0.3.3/tsugite/benchmark/config.py +183 -0
- tsugite_cli-0.3.3/tsugite/benchmark/core.py +292 -0
- tsugite_cli-0.3.3/tsugite/benchmark/discovery.py +377 -0
- tsugite_cli-0.3.3/tsugite/benchmark/evaluators.py +671 -0
- tsugite_cli-0.3.3/tsugite/benchmark/execution.py +657 -0
- tsugite_cli-0.3.3/tsugite/benchmark/metrics.py +204 -0
- tsugite_cli-0.3.3/tsugite/benchmark/reports.py +420 -0
- tsugite_cli-0.3.3/tsugite/benchmark/utils.py +288 -0
- tsugite_cli-0.3.3/tsugite/builtin_agents/chat-assistant.md +53 -0
- tsugite_cli-0.3.3/tsugite/builtin_agents/default.md +140 -0
- tsugite_cli-0.3.3/tsugite/builtin_agents.py +5 -0
- tsugite_cli-0.3.3/tsugite/cache.py +195 -0
- tsugite_cli-0.3.3/tsugite/cli/__init__.py +1042 -0
- tsugite_cli-0.3.3/tsugite/cli/agents.py +148 -0
- tsugite_cli-0.3.3/tsugite/cli/attachments.py +193 -0
- tsugite_cli-0.3.3/tsugite/cli/benchmark.py +663 -0
- tsugite_cli-0.3.3/tsugite/cli/cache.py +113 -0
- tsugite_cli-0.3.3/tsugite/cli/config.py +272 -0
- tsugite_cli-0.3.3/tsugite/cli/helpers.py +534 -0
- tsugite_cli-0.3.3/tsugite/cli/history.py +193 -0
- tsugite_cli-0.3.3/tsugite/cli/init.py +387 -0
- tsugite_cli-0.3.3/tsugite/cli/mcp.py +193 -0
- tsugite_cli-0.3.3/tsugite/cli/tools.py +419 -0
- tsugite_cli-0.3.3/tsugite/config.py +204 -0
- tsugite_cli-0.3.3/tsugite/console.py +48 -0
- tsugite_cli-0.3.3/tsugite/constants.py +21 -0
- tsugite_cli-0.3.3/tsugite/core/__init__.py +19 -0
- tsugite_cli-0.3.3/tsugite/core/agent.py +774 -0
- tsugite_cli-0.3.3/tsugite/core/executor.py +300 -0
- tsugite_cli-0.3.3/tsugite/core/memory.py +67 -0
- tsugite_cli-0.3.3/tsugite/core/tools.py +271 -0
- tsugite_cli-0.3.3/tsugite/docker_cli.py +270 -0
- tsugite_cli-0.3.3/tsugite/events/__init__.py +55 -0
- tsugite_cli-0.3.3/tsugite/events/base.py +46 -0
- tsugite_cli-0.3.3/tsugite/events/bus.py +62 -0
- tsugite_cli-0.3.3/tsugite/events/events.py +224 -0
- tsugite_cli-0.3.3/tsugite/exceptions.py +40 -0
- tsugite_cli-0.3.3/tsugite/history/__init__.py +29 -0
- tsugite_cli-0.3.3/tsugite/history/index.py +210 -0
- tsugite_cli-0.3.3/tsugite/history/models.py +106 -0
- tsugite_cli-0.3.3/tsugite/history/storage.py +157 -0
- tsugite_cli-0.3.3/tsugite/mcp_client.py +219 -0
- tsugite_cli-0.3.3/tsugite/mcp_config.py +174 -0
- tsugite_cli-0.3.3/tsugite/md_agents.py +751 -0
- tsugite_cli-0.3.3/tsugite/models.py +257 -0
- tsugite_cli-0.3.3/tsugite/renderer.py +151 -0
- tsugite_cli-0.3.3/tsugite/shell_tool_config.py +265 -0
- tsugite_cli-0.3.3/tsugite/templates/assistant.md +14 -0
- tsugite_cli-0.3.3/tsugite/tools/__init__.py +265 -0
- tsugite_cli-0.3.3/tsugite/tools/agents.py +312 -0
- tsugite_cli-0.3.3/tsugite/tools/edit_strategies.py +393 -0
- tsugite_cli-0.3.3/tsugite/tools/fs.py +329 -0
- tsugite_cli-0.3.3/tsugite/tools/http.py +239 -0
- tsugite_cli-0.3.3/tsugite/tools/interactive.py +430 -0
- tsugite_cli-0.3.3/tsugite/tools/shell.py +129 -0
- tsugite_cli-0.3.3/tsugite/tools/shell_tools.py +214 -0
- tsugite_cli-0.3.3/tsugite/tools/tasks.py +339 -0
- tsugite_cli-0.3.3/tsugite/tsugite.py +7 -0
- tsugite_cli-0.3.3/tsugite/ui/__init__.py +46 -0
- tsugite_cli-0.3.3/tsugite/ui/base.py +638 -0
- tsugite_cli-0.3.3/tsugite/ui/chat.py +265 -0
- tsugite_cli-0.3.3/tsugite/ui/chat.tcss +92 -0
- tsugite_cli-0.3.3/tsugite/ui/chat_history.py +286 -0
- tsugite_cli-0.3.3/tsugite/ui/helpers.py +102 -0
- tsugite_cli-0.3.3/tsugite/ui/jsonl.py +125 -0
- tsugite_cli-0.3.3/tsugite/ui/live_template.py +529 -0
- tsugite_cli-0.3.3/tsugite/ui/plain.py +419 -0
- tsugite_cli-0.3.3/tsugite/ui/textual_chat.py +642 -0
- tsugite_cli-0.3.3/tsugite/ui/textual_handler.py +225 -0
- tsugite_cli-0.3.3/tsugite/ui/widgets/__init__.py +6 -0
- tsugite_cli-0.3.3/tsugite/ui/widgets/base_scroll_log.py +27 -0
- tsugite_cli-0.3.3/tsugite/ui/widgets/message_list.py +121 -0
- tsugite_cli-0.3.3/tsugite/ui/widgets/thought_log.py +80 -0
- tsugite_cli-0.3.3/tsugite/ui_context.py +90 -0
- tsugite_cli-0.3.3/tsugite/utils.py +367 -0
- tsugite_cli-0.3.3/tsugite/xdg.py +104 -0
- tsugite_cli-0.3.3/uv.lock +2767 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
## Tsugite: AI Contributor Guide
|
|
2
|
+
|
|
3
|
+
Tsugite is a micro-agent CLI: agents are Markdown + YAML frontmatter, rendered via Jinja2, and executed by `TsugiteAgent` with a lightweight tool registry.
|
|
4
|
+
|
|
5
|
+
### Architecture
|
|
6
|
+
|
|
7
|
+
**Flow:** `Agent.md` (YAML + Jinja2) → `renderer.py` → `TsugiteAgent` → LiteLLM → Tools
|
|
8
|
+
|
|
9
|
+
**Key Modules:**
|
|
10
|
+
- `cli/__init__.py` - Typer CLI (run, chat, render, config, mcp, tools, agents, attachments, cache, benchmark)
|
|
11
|
+
- `md_agents.py` - Parse frontmatter + agent resolution + directives
|
|
12
|
+
- `builtin_agents/` - File-based built-in agent definitions (default.md, chat-assistant.md)
|
|
13
|
+
- `agent_inheritance.py` - Agent resolution pipeline + inheritance chain
|
|
14
|
+
- `chat.py` - Chat session management with history
|
|
15
|
+
- `ui/textual_chat.py` - Textual TUI for interactive chat
|
|
16
|
+
- `agent_runner.py` - Execution orchestration, prefetch, tool wiring, multi-step
|
|
17
|
+
- `core/agent.py` - LiteLLM agent loop + streaming
|
|
18
|
+
- `renderer.py` - Jinja2 rendering + helpers (now, today, slugify, env, file_exists, read_text, is_file, is_dir)
|
|
19
|
+
- `tools/` - Tool registry + implementations
|
|
20
|
+
- `shell_tool_config.py` + `tools/shell_tools.py` - Custom shell command wrappers
|
|
21
|
+
- `models.py` - Model string parsing + provider dispatch
|
|
22
|
+
- `config.py` - Configuration (default_model, model_aliases, default_base_agent, chat_theme)
|
|
23
|
+
|
|
24
|
+
**Development:**
|
|
25
|
+
```bash
|
|
26
|
+
uv sync --dev
|
|
27
|
+
uv run pytest tests/test_specific.py -v
|
|
28
|
+
uv run black . && uv run ruff check .
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Agent Resolution Order:**
|
|
32
|
+
1. `.tsugite/{name}.md` - project-local shared
|
|
33
|
+
2. `agents/{name}.md` - project convention
|
|
34
|
+
3. `./{name}.md` - current directory
|
|
35
|
+
4. Package agents directory (`tsugite/builtin_agents/`)
|
|
36
|
+
5. Global agent directories (`~/.config/tsugite/agents/`, etc.)
|
|
37
|
+
|
|
38
|
+
**Model String:** `provider:model[:variant]` (e.g., `ollama:qwen2.5-coder:7b`, `openai:gpt-4o`)
|
|
39
|
+
|
|
40
|
+
### Built-in Agents System
|
|
41
|
+
|
|
42
|
+
File-based agents distributed with the package in `tsugite/builtin_agents/` directory.
|
|
43
|
+
|
|
44
|
+
**Current Built-ins:**
|
|
45
|
+
1. `default.md` - Base agent with task tracking, spawn_agent, and file tools
|
|
46
|
+
2. `chat-assistant.md` - Chat agent with tools (read_file, write_file, list_files, web_search, fetch_text, run), text_mode enabled
|
|
47
|
+
|
|
48
|
+
**Location:** `tsugite/builtin_agents/{name}.md`
|
|
49
|
+
|
|
50
|
+
**Resolution:** Built-in agents are resolved as part of the normal agent resolution order (step 4). Project agents can override them by creating agents with the same name in project directories.
|
|
51
|
+
|
|
52
|
+
**Adding New Built-ins:**
|
|
53
|
+
1. Create `tsugite/builtin_agents/new_agent.md` with frontmatter and template
|
|
54
|
+
2. Agent is automatically discovered - no code changes needed
|
|
55
|
+
3. Users reference it with `+new_agent` or `new_agent`
|
|
56
|
+
4. Ensure `pyproject.toml` includes builtin_agents directory in package data
|
|
57
|
+
|
|
58
|
+
### Agent Inheritance
|
|
59
|
+
|
|
60
|
+
**Chain:** Default base → Extended → Current (highest precedence)
|
|
61
|
+
|
|
62
|
+
**Merge rules:**
|
|
63
|
+
- Scalars (model, max_steps): Child overwrites
|
|
64
|
+
- Lists (tools): Merge + deduplicate
|
|
65
|
+
- Dicts (mcp_servers): Merge, child keys override
|
|
66
|
+
- Strings (instructions): Concatenate with `\n\n`
|
|
67
|
+
|
|
68
|
+
**Opt-out:** `extends: none` skips all inheritance.
|
|
69
|
+
|
|
70
|
+
### Chat Mode
|
|
71
|
+
|
|
72
|
+
**Components:**
|
|
73
|
+
- `chat.py:ChatManager` - Session management, history (max_history), turn execution
|
|
74
|
+
- `ui/textual_chat.py:ChatApp` - Textual TUI
|
|
75
|
+
- `ui/widgets/` - MessageList, ThoughtLog
|
|
76
|
+
|
|
77
|
+
**Default Agent:** `chat-assistant`. Override with `.tsugite/chat-assistant.md`.
|
|
78
|
+
|
|
79
|
+
**Flow:**
|
|
80
|
+
1. User input → `ChatManager.run_turn()`
|
|
81
|
+
2. Inject `chat_history` context (list of ChatTurn objects)
|
|
82
|
+
3. Call `agent_runner.run_agent()` with `force_text_mode=True`
|
|
83
|
+
4. Store result in ChatTurn (timestamp, messages, token_count, cost)
|
|
84
|
+
5. Prune history if `> max_history`
|
|
85
|
+
|
|
86
|
+
**Text Mode:** `text_mode: true` allows LLM responses without code blocks.
|
|
87
|
+
|
|
88
|
+
### Directives
|
|
89
|
+
|
|
90
|
+
**Types:**
|
|
91
|
+
1. `<!-- tsu:ignore -->...<!-- /tsu:ignore -->` - Strip developer docs before LLM
|
|
92
|
+
2. `<!-- tsu:tool name="..." args={...} assign="..." -->` - Execute tool during render
|
|
93
|
+
3. `<!-- tsu:step name="..." assign="..." -->` - Multi-step boundaries
|
|
94
|
+
|
|
95
|
+
**Parsing:** `md_agents.py:extract_step_directives()`, `extract_tool_directives()`
|
|
96
|
+
|
|
97
|
+
**Execution:**
|
|
98
|
+
- Ignore blocks: `renderer.py` strips before Jinja2
|
|
99
|
+
- Tool directives: `agent_runner.py:execute_tool_directives()` runs before rendering
|
|
100
|
+
- Step directives: `agent_runner.py:run_multistep_agent()` executes sequentially
|
|
101
|
+
|
|
102
|
+
**Scoping:**
|
|
103
|
+
- Preamble directives: Execute once, available to all steps
|
|
104
|
+
- Step directives: Execute per step, scoped to that step
|
|
105
|
+
- Variables from previous steps: Always available in later steps
|
|
106
|
+
|
|
107
|
+
### Development Workflows
|
|
108
|
+
|
|
109
|
+
**Adding Tools:**
|
|
110
|
+
```python
|
|
111
|
+
# In tools/{category}.py
|
|
112
|
+
from tsugite.tools import tool
|
|
113
|
+
|
|
114
|
+
@tool
|
|
115
|
+
def my_tool(param: str, optional: int = 10) -> dict:
|
|
116
|
+
"""Brief description.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
param: What it does
|
|
120
|
+
optional: Optional parameter
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Result dictionary
|
|
124
|
+
"""
|
|
125
|
+
if not param:
|
|
126
|
+
raise ValueError("param required")
|
|
127
|
+
return {"output": do_work(param, optional)}
|
|
128
|
+
|
|
129
|
+
# Add to tools/__init__.py:
|
|
130
|
+
# from .category import my_tool # noqa: F401
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Error conventions:**
|
|
134
|
+
- `ValueError` → bad inputs
|
|
135
|
+
- `RuntimeError` → execution failures
|
|
136
|
+
- Return dict/str; exceptions surfaced as strings to agent
|
|
137
|
+
|
|
138
|
+
**Safety:** `tools/shell.py` blocks dangerous patterns (`rm -rf /`, `sudo rm`, `dd if=`). Never relax.
|
|
139
|
+
|
|
140
|
+
**Creating Agents:**
|
|
141
|
+
|
|
142
|
+
Basic:
|
|
143
|
+
```markdown
|
|
144
|
+
---
|
|
145
|
+
name: my_agent
|
|
146
|
+
model: ollama:qwen2.5-coder:7b
|
|
147
|
+
max_steps: 5
|
|
148
|
+
tools: [read_file, write_file]
|
|
149
|
+
---
|
|
150
|
+
Task: {{ user_prompt }}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Chat:
|
|
154
|
+
```markdown
|
|
155
|
+
---
|
|
156
|
+
name: my_chat
|
|
157
|
+
model: openai:gpt-4o
|
|
158
|
+
text_mode: true
|
|
159
|
+
tools: [read_file, web_search]
|
|
160
|
+
---
|
|
161
|
+
{% if chat_history %}
|
|
162
|
+
{% for turn in chat_history %}
|
|
163
|
+
**User:** {{ turn.user_message }}
|
|
164
|
+
**Assistant:** {{ turn.agent_response }}
|
|
165
|
+
{% endfor %}
|
|
166
|
+
{% endif %}
|
|
167
|
+
{{ user_prompt }}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Multi-step:
|
|
171
|
+
```markdown
|
|
172
|
+
---
|
|
173
|
+
name: researcher
|
|
174
|
+
max_steps: 10
|
|
175
|
+
tools: [web_search, write_file]
|
|
176
|
+
---
|
|
177
|
+
Topic: {{ user_prompt }}
|
|
178
|
+
|
|
179
|
+
<!-- tsu:step name="research" assign="data" -->
|
|
180
|
+
Research {{ user_prompt }}.
|
|
181
|
+
|
|
182
|
+
<!-- tsu:step name="summarize" assign="summary" -->
|
|
183
|
+
Summarize: {{ data }}
|
|
184
|
+
|
|
185
|
+
<!-- tsu:step name="save" -->
|
|
186
|
+
Write {{ summary }} to file.
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Testing
|
|
190
|
+
|
|
191
|
+
**Commands:**
|
|
192
|
+
```bash
|
|
193
|
+
uv run pytest # All tests
|
|
194
|
+
uv run pytest tests/test_file.py::test_x # Specific test
|
|
195
|
+
uv run pytest --cov=tsugite # Coverage
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Patterns:**
|
|
199
|
+
```python
|
|
200
|
+
from tsugite.md_agents import parse_agent_file
|
|
201
|
+
from tsugite.agent_inheritance import find_agent_file, get_builtin_agents_path
|
|
202
|
+
from tsugite.chat import ChatManager
|
|
203
|
+
from tsugite.tools import get_tool
|
|
204
|
+
|
|
205
|
+
# Agent parsing
|
|
206
|
+
agent = parse_agent_file(Path("agent.md"))
|
|
207
|
+
assert agent.config.name == "expected"
|
|
208
|
+
|
|
209
|
+
# Built-in agents location
|
|
210
|
+
builtin_path = get_builtin_agents_path()
|
|
211
|
+
default_agent = builtin_path / "default.md"
|
|
212
|
+
assert default_agent.exists()
|
|
213
|
+
|
|
214
|
+
# Resolution
|
|
215
|
+
path = find_agent_file("default", Path.cwd())
|
|
216
|
+
assert path == builtin_path / "default.md"
|
|
217
|
+
|
|
218
|
+
# Chat
|
|
219
|
+
manager = ChatManager(Path("agent.md"), max_history=10)
|
|
220
|
+
response = manager.run_turn("Hello")
|
|
221
|
+
assert len(manager.conversation_history) == 1
|
|
222
|
+
|
|
223
|
+
# Tools
|
|
224
|
+
tool = get_tool("read_file")
|
|
225
|
+
assert tool is not None
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Code Standards
|
|
229
|
+
|
|
230
|
+
**Python 3.12+**, type hints on public APIs
|
|
231
|
+
|
|
232
|
+
**Style:**
|
|
233
|
+
- Imports: stdlib → third-party → local (blank lines between)
|
|
234
|
+
- Line length: 88 characters (Black)
|
|
235
|
+
- Docstrings: `Args:` / `Returns:` for public functions
|
|
236
|
+
|
|
237
|
+
**Errors:**
|
|
238
|
+
- `ValueError` → invalid input/config
|
|
239
|
+
- `RuntimeError` → execution failure (include component name)
|
|
240
|
+
|
|
241
|
+
**Safety:**
|
|
242
|
+
- Shell tool guard: Never relax dangerous patterns blocklist
|
|
243
|
+
- MCP: Requires `--trust-mcp-code` flag
|
|
244
|
+
- File ops: Use `Path.resolve()` to prevent traversal
|
|
245
|
+
|
|
246
|
+
**Testing:** Test-first workflow. Add minimal failing test, run target, widen to full suite before merge.
|
|
247
|
+
|
|
248
|
+
### Rendering & Context
|
|
249
|
+
|
|
250
|
+
**Jinja2:** Undefined variables raise immediately. Use prefetch or tool directives to ensure variables exist.
|
|
251
|
+
|
|
252
|
+
**Helpers:** `now()`, `today()`, `slugify()`, `file_exists()`, `read_text()`, `is_file()`, `is_dir()`, `env.*`
|
|
253
|
+
|
|
254
|
+
**Prefetch:** Reduces token use by fetching data before LLM sees prompt. Failures assign `None`—guard with `{% if var %}`.
|
|
255
|
+
|
|
256
|
+
**Tool Directives:** More flexible than prefetch for multi-step. Scoped per-step, can access step variables.
|
|
257
|
+
|
|
258
|
+
**Best practice:** Prefetch for initial setup, tool directives for dynamic per-step data.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master, development]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master]
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
lint:
|
|
15
|
+
name: Lint
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Set up Python
|
|
21
|
+
uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.12"
|
|
24
|
+
|
|
25
|
+
- name: Install uv
|
|
26
|
+
uses: astral-sh/setup-uv@v5
|
|
27
|
+
with:
|
|
28
|
+
enable-cache: true
|
|
29
|
+
|
|
30
|
+
- name: Install dependencies
|
|
31
|
+
run: uv sync --dev
|
|
32
|
+
|
|
33
|
+
- name: Run ruff check
|
|
34
|
+
run: uv run ruff check --show-fixes .
|
|
35
|
+
|
|
36
|
+
- name: Run ruff format
|
|
37
|
+
run: uv run ruff format --check
|
|
38
|
+
|
|
39
|
+
test:
|
|
40
|
+
name: Test (Python ${{ matrix.python-version }})
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
strategy:
|
|
43
|
+
fail-fast: false
|
|
44
|
+
matrix:
|
|
45
|
+
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
46
|
+
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v4
|
|
49
|
+
|
|
50
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
51
|
+
uses: actions/setup-python@v5
|
|
52
|
+
with:
|
|
53
|
+
python-version: ${{ matrix.python-version }}
|
|
54
|
+
|
|
55
|
+
- name: Install uv
|
|
56
|
+
uses: astral-sh/setup-uv@v5
|
|
57
|
+
with:
|
|
58
|
+
enable-cache: true
|
|
59
|
+
|
|
60
|
+
- name: Install dependencies
|
|
61
|
+
run: uv sync --dev
|
|
62
|
+
|
|
63
|
+
- name: Run tests
|
|
64
|
+
run: uv run pytest -v
|
|
65
|
+
|
|
66
|
+
- name: Upload coverage reports
|
|
67
|
+
if: matrix.python-version == '3.12'
|
|
68
|
+
uses: actions/upload-artifact@v4
|
|
69
|
+
with:
|
|
70
|
+
name: coverage-report
|
|
71
|
+
path: |
|
|
72
|
+
coverage.xml
|
|
73
|
+
htmlcov/
|
|
74
|
+
retention-days: 30
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: Publish Docker Image
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
release:
|
|
8
|
+
types: [published]
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
env:
|
|
12
|
+
REGISTRY: ghcr.io
|
|
13
|
+
IMAGE_NAME: ${{ github.repository }}
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
build-and-push:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
packages: write
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- name: Checkout repository
|
|
24
|
+
uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- name: Set up QEMU
|
|
27
|
+
uses: docker/setup-qemu-action@v3
|
|
28
|
+
|
|
29
|
+
- name: Set up Docker Buildx
|
|
30
|
+
uses: docker/setup-buildx-action@v3
|
|
31
|
+
|
|
32
|
+
- name: Log in to GitHub Container Registry
|
|
33
|
+
uses: docker/login-action@v3
|
|
34
|
+
with:
|
|
35
|
+
registry: ${{ env.REGISTRY }}
|
|
36
|
+
username: ${{ github.actor }}
|
|
37
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
38
|
+
|
|
39
|
+
- name: Extract metadata (tags, labels)
|
|
40
|
+
id: meta
|
|
41
|
+
uses: docker/metadata-action@v5
|
|
42
|
+
with:
|
|
43
|
+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
44
|
+
tags: |
|
|
45
|
+
type=semver,pattern={{version}}
|
|
46
|
+
type=semver,pattern={{major}}.{{minor}}
|
|
47
|
+
type=semver,pattern={{major}}
|
|
48
|
+
type=ref,event=branch
|
|
49
|
+
type=sha
|
|
50
|
+
|
|
51
|
+
- name: Build and push Docker image
|
|
52
|
+
uses: docker/build-push-action@v5
|
|
53
|
+
with:
|
|
54
|
+
context: .
|
|
55
|
+
file: ./Dockerfile.runtime
|
|
56
|
+
platforms: linux/amd64,linux/arm64
|
|
57
|
+
push: true
|
|
58
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
59
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
60
|
+
cache-from: type=gha
|
|
61
|
+
cache-to: type=gha,mode=max
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
release:
|
|
8
|
+
types: [published]
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build-and-publish:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
id-token: write # Required for trusted publishing
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout repository
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Set up Python
|
|
23
|
+
uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: "3.12"
|
|
26
|
+
|
|
27
|
+
- name: Install uv
|
|
28
|
+
uses: astral-sh/setup-uv@v5
|
|
29
|
+
|
|
30
|
+
- name: Build package
|
|
31
|
+
run: uv build
|
|
32
|
+
|
|
33
|
+
- name: Publish to PyPI
|
|
34
|
+
# Uses trusted publishing (no API token needed)
|
|
35
|
+
# For API token method, add with: {password: ${{ secrets.PYPI_API_TOKEN }}}
|
|
36
|
+
# Package name on PyPI: tsugite-cli
|
|
37
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# ---> Python
|
|
2
|
+
# Byte-compiled / optimized / DLL files
|
|
3
|
+
__pycache__/
|
|
4
|
+
*.py[cod]
|
|
5
|
+
*$py.class
|
|
6
|
+
|
|
7
|
+
# C extensions
|
|
8
|
+
*.so
|
|
9
|
+
|
|
10
|
+
# Distribution / packaging
|
|
11
|
+
.Python
|
|
12
|
+
build/
|
|
13
|
+
develop-eggs/
|
|
14
|
+
dist/
|
|
15
|
+
downloads/
|
|
16
|
+
eggs/
|
|
17
|
+
.eggs/
|
|
18
|
+
lib/
|
|
19
|
+
lib64/
|
|
20
|
+
parts/
|
|
21
|
+
sdist/
|
|
22
|
+
var/
|
|
23
|
+
wheels/
|
|
24
|
+
share/python-wheels/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.installed.cfg
|
|
27
|
+
*.egg
|
|
28
|
+
MANIFEST
|
|
29
|
+
|
|
30
|
+
# PyInstaller
|
|
31
|
+
# Usually these files are written by a python script from a template
|
|
32
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
33
|
+
*.manifest
|
|
34
|
+
*.spec
|
|
35
|
+
|
|
36
|
+
# Installer logs
|
|
37
|
+
pip-log.txt
|
|
38
|
+
pip-delete-this-directory.txt
|
|
39
|
+
|
|
40
|
+
# Unit test / coverage reports
|
|
41
|
+
htmlcov/
|
|
42
|
+
.tox/
|
|
43
|
+
.nox/
|
|
44
|
+
.coverage
|
|
45
|
+
.coverage.*
|
|
46
|
+
.cache
|
|
47
|
+
nosetests.xml
|
|
48
|
+
coverage.xml
|
|
49
|
+
*.cover
|
|
50
|
+
*.py,cover
|
|
51
|
+
.hypothesis/
|
|
52
|
+
.pytest_cache/
|
|
53
|
+
cover/
|
|
54
|
+
|
|
55
|
+
# Translations
|
|
56
|
+
*.mo
|
|
57
|
+
*.pot
|
|
58
|
+
|
|
59
|
+
# Django stuff:
|
|
60
|
+
*.log
|
|
61
|
+
local_settings.py
|
|
62
|
+
db.sqlite3
|
|
63
|
+
db.sqlite3-journal
|
|
64
|
+
|
|
65
|
+
# Flask stuff:
|
|
66
|
+
instance/
|
|
67
|
+
.webassets-cache
|
|
68
|
+
|
|
69
|
+
# Scrapy stuff:
|
|
70
|
+
.scrapy
|
|
71
|
+
|
|
72
|
+
# Sphinx documentation
|
|
73
|
+
docs/_build/
|
|
74
|
+
|
|
75
|
+
# PyBuilder
|
|
76
|
+
.pybuilder/
|
|
77
|
+
target/
|
|
78
|
+
|
|
79
|
+
# Jupyter Notebook
|
|
80
|
+
.ipynb_checkpoints
|
|
81
|
+
|
|
82
|
+
# IPython
|
|
83
|
+
profile_default/
|
|
84
|
+
ipython_config.py
|
|
85
|
+
|
|
86
|
+
# pyenv
|
|
87
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
88
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
89
|
+
# .python-version
|
|
90
|
+
|
|
91
|
+
# pipenv
|
|
92
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
93
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
94
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
95
|
+
# install all needed dependencies.
|
|
96
|
+
#Pipfile.lock
|
|
97
|
+
|
|
98
|
+
# poetry
|
|
99
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
100
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
101
|
+
# commonly ignored for libraries.
|
|
102
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
103
|
+
#poetry.lock
|
|
104
|
+
|
|
105
|
+
# pdm
|
|
106
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
107
|
+
#pdm.lock
|
|
108
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
109
|
+
# in version control.
|
|
110
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
111
|
+
.pdm.toml
|
|
112
|
+
.pdm-python
|
|
113
|
+
.pdm-build/
|
|
114
|
+
|
|
115
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
116
|
+
__pypackages__/
|
|
117
|
+
|
|
118
|
+
# Celery stuff
|
|
119
|
+
celerybeat-schedule
|
|
120
|
+
celerybeat.pid
|
|
121
|
+
|
|
122
|
+
# SageMath parsed files
|
|
123
|
+
*.sage.py
|
|
124
|
+
|
|
125
|
+
# Environments
|
|
126
|
+
.env
|
|
127
|
+
.venv
|
|
128
|
+
env/
|
|
129
|
+
venv/
|
|
130
|
+
ENV/
|
|
131
|
+
env.bak/
|
|
132
|
+
venv.bak/
|
|
133
|
+
|
|
134
|
+
# Spyder project settings
|
|
135
|
+
.spyderproject
|
|
136
|
+
.spyproject
|
|
137
|
+
|
|
138
|
+
# Rope project settings
|
|
139
|
+
.ropeproject
|
|
140
|
+
|
|
141
|
+
# mkdocs documentation
|
|
142
|
+
/site
|
|
143
|
+
|
|
144
|
+
# mypy
|
|
145
|
+
.mypy_cache/
|
|
146
|
+
.dmypy.json
|
|
147
|
+
dmypy.json
|
|
148
|
+
|
|
149
|
+
# Pyre type checker
|
|
150
|
+
.pyre/
|
|
151
|
+
|
|
152
|
+
# pytype static type analyzer
|
|
153
|
+
.pytype/
|
|
154
|
+
|
|
155
|
+
# Cython debug symbols
|
|
156
|
+
cython_debug/
|
|
157
|
+
|
|
158
|
+
# PyCharm
|
|
159
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
160
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
161
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
162
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
163
|
+
#.idea/
|
|
164
|
+
|
|
165
|
+
.env
|
|
166
|
+
.env
|
|
167
|
+
benchmark_results/
|
|
168
|
+
test_output/
|
|
169
|
+
.claude/settings.local.json
|