tsugite-cli 0.12.2__tar.gz → 0.13.0__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.12.2 → tsugite_cli-0.13.0}/.github/copilot-instructions.md +3 -5
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/AGENTS.md +108 -15
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/PKG-INFO +4 -6
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/README.md +3 -4
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/docs/agents.md +70 -2
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/pyproject.toml +1 -2
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/scripts/update_model_registry.py +11 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/README.md +12 -16
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/conftest.py +39 -36
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/core/test_agent.py +148 -73
- tsugite_cli-0.13.0/tests/core/test_agent_event_loop.py +310 -0
- tsugite_cli-0.13.0/tests/core/test_agent_parser_triple_quotes.py +70 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/core/test_agent_ui_events.py +91 -27
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/core/test_content_blocks.py +12 -12
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/core/test_executor.py +133 -25
- tsugite_cli-0.13.0/tests/core/test_executor_escape_tripwire.py +35 -0
- tsugite_cli-0.13.0/tests/core/test_state.py +85 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/core/test_subprocess_executor.py +40 -11
- tsugite_cli-0.13.0/tests/daemon/test_chat_reentrancy.py +399 -0
- tsugite_cli-0.13.0/tests/daemon/test_compaction_command.py +247 -0
- tsugite_cli-0.13.0/tests/daemon/test_event_broadcast_scope.py +55 -0
- tsugite_cli-0.13.0/tests/daemon/test_final_answer_delivery.py +75 -0
- tsugite_cli-0.13.0/tests/daemon/test_http_adapter.py +797 -0
- tsugite_cli-0.13.0/tests/daemon/test_message_context_topic.py +207 -0
- tsugite_cli-0.13.0/tests/daemon/test_prompt_too_long_retry.py +144 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/daemon/test_session_metadata_api.py +28 -0
- tsugite_cli-0.13.0/tests/daemon/test_session_pinning_api.py +284 -0
- tsugite_cli-0.13.0/tests/daemon/test_session_store_pinning.py +194 -0
- tsugite_cli-0.13.0/tests/daemon/test_skill_trigger_loading.py +154 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/e2e/test_history_code_rendering.py +103 -7
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/e2e/test_markdown_rendering.py +12 -31
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/e2e/test_message_actions.py +26 -33
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/e2e/test_scroll_behavior.py +6 -12
- tsugite_cli-0.13.0/tests/integration/test_concurrent_workspace_cwd.py +88 -0
- tsugite_cli-0.13.0/tests/integration/test_nested_session_spawn.py +103 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/smoke_test.sh +3 -3
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_agent_inheritance.py +54 -20
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_agent_sessions.py +200 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_agent_skills.py +10 -30
- tsugite_cli-0.13.0/tests/test_anthropic_extended_thinking.py +198 -0
- tsugite_cli-0.13.0/tests/test_attachments.py +1062 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_background_tasks.py +103 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_builtin_agent_paths.py +1 -1
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_builtin_agents.py +1 -1
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_claude_code_attachments.py +20 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_claude_code_provider.py +216 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_cli.py +47 -6
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_cli_rendering.py +3 -4
- tsugite_cli-0.13.0/tests/test_cli_skills.py +85 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_cli_subcommands.py +0 -30
- tsugite_cli-0.13.0/tests/test_compaction_context_preservation.py +239 -0
- tsugite_cli-0.13.0/tests/test_compaction_event.py +113 -0
- tsugite_cli-0.13.0/tests/test_compaction_progress.py +162 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_daemon_unified_sessions.py +11 -24
- tsugite_cli-0.13.0/tests/test_event_storage.py +153 -0
- tsugite_cli-0.13.0/tests/test_events_to_messages.py +156 -0
- tsugite_cli-0.13.0/tests/test_fs_tool_workspace.py +89 -0
- tsugite_cli-0.13.0/tests/test_history_migrate.py +274 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_hooks.py +18 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_http_tools.py +62 -16
- tsugite_cli-0.13.0/tests/test_interaction_backend_isolation.py +191 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_interactive_context.py +0 -1
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_jsonl_ui.py +41 -0
- tsugite_cli-0.13.0/tests/test_live_event_recording.py +130 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_md_agents_json.py +1 -3
- tsugite_cli-0.13.0/tests/test_memory_sanitize.py +290 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_model_registry.py +11 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_multistep_agents.py +102 -0
- tsugite_cli-0.13.0/tests/test_multistep_retry_side_effects.py +108 -0
- tsugite_cli-0.13.0/tests/test_post_compaction_counters.py +257 -0
- tsugite_cli-0.13.0/tests/test_provider_turn_heartbeat.py +142 -0
- tsugite_cli-0.13.0/tests/test_reasoning_models.py +127 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_renderer.py +59 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_rendering_scenarios.py +103 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_scheduler_history_injection.py +10 -15
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_schema.py +4 -3
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_session_metadata.py +34 -0
- tsugite_cli-0.13.0/tests/test_shell_tool_cwd.py +82 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_skill_discovery.py +172 -4
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_skill_tools.py +103 -0
- tsugite_cli-0.13.0/tests/test_step_render_framework_flags.py +108 -0
- tsugite_cli-0.13.0/tests/test_unified_event_log.py +185 -0
- tsugite_cli-0.13.0/tests/test_user_agent.py +100 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/agent_inheritance.py +16 -20
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/agent_preparation.py +323 -38
- tsugite_cli-0.13.0/tsugite/agent_runner/history_integration.py +245 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/agent_runner/models.py +1 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/agent_runner/runner.py +224 -78
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/attachments/base.py +16 -1
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/attachments/url.py +6 -5
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/builtin_agents/default.md +42 -20
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/builtin_agents/file_searcher.md +1 -1
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/builtin_agents/onboard.md +1 -1
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/builtin_skills/tsugite-agent-basics/SKILL.md +22 -6
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/cli/__init__.py +2 -4
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/cli/helpers.py +28 -2
- tsugite_cli-0.13.0/tsugite/cli/history.py +715 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/cli/render.py +6 -3
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/cli/run.py +43 -21
- tsugite_cli-0.13.0/tsugite/cli/skills.py +77 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/config.py +1 -1
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/core/agent.py +453 -366
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/core/claude_code.py +26 -3
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/core/executor.py +162 -94
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/core/memory.py +3 -0
- tsugite_cli-0.13.0/tsugite/core/state.py +76 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/core/subprocess_executor.py +142 -110
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/core/tools.py +0 -1
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/adapters/base.py +284 -168
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/adapters/http.py +425 -285
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/adapters/scheduler_adapter.py +38 -31
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/commands.py +7 -5
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/compaction_scheduler.py +10 -2
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/gateway.py +9 -2
- tsugite_cli-0.13.0/tsugite/daemon/memory.py +507 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/session_runner.py +29 -1
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/session_store.py +328 -40
- tsugite_cli-0.13.0/tsugite/daemon/web/css/console.css +1704 -0
- tsugite_cli-0.13.0/tsugite/daemon/web/css/responsive.css +110 -0
- tsugite_cli-0.13.0/tsugite/daemon/web/css/styles.css +239 -0
- tsugite_cli-0.13.0/tsugite/daemon/web/css/theme.css +61 -0
- tsugite_cli-0.13.0/tsugite/daemon/web/index.html +1753 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/web/js/api.js +10 -2
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/web/js/app.js +32 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/web/js/utils.js +20 -0
- tsugite_cli-0.13.0/tsugite/daemon/web/js/views/conversation/event_types.js +71 -0
- tsugite_cli-0.13.0/tsugite/daemon/web/js/views/conversation/history.js +412 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/web/js/views/conversation/input.js +1 -1
- tsugite_cli-0.13.0/tsugite/daemon/web/js/views/conversation/sessions.js +457 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/web/js/views/conversation/streaming.js +110 -47
- tsugite_cli-0.13.0/tsugite/daemon/web/js/views/conversations.js +644 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/web/js/views/file-editor.js +3 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/web/js/views/usage.js +2 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/web/manifest.json +1 -1
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/events/__init__.py +2 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/events/base.py +3 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/events/events.py +10 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/exceptions.py +22 -0
- tsugite_cli-0.13.0/tsugite/history/__init__.py +24 -0
- tsugite_cli-0.13.0/tsugite/history/models.py +46 -0
- tsugite_cli-0.13.0/tsugite/history/reconstruction.py +136 -0
- tsugite_cli-0.13.0/tsugite/history/storage.py +224 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/hooks.py +20 -1
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/md_agents.py +84 -7
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/models.py +59 -6
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/options.py +1 -3
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/providers/anthropic.py +39 -14
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/providers/base.py +2 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/providers/claude_code.py +18 -7
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/providers/model_registry.py +5 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/providers/openai_compat.py +12 -8
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/renderer.py +16 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/schemas/__init__.py +12 -9
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/schemas/agent.schema.json +80 -24
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/skill_discovery.py +92 -24
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/tools/agents.py +27 -17
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/tools/fs.py +8 -7
- tsugite_cli-0.13.0/tsugite/tools/history.py +240 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/tools/http.py +25 -16
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/tools/interactive.py +8 -10
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/tools/secrets.py +10 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/tools/shell.py +11 -2
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/tools/skills.py +64 -2
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/tools/tmux.py +4 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/ui/jsonl.py +23 -2
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/ui/repl_commands.py +23 -21
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/user_agent.py +28 -3
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/utils.py +32 -1
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/workspace/models.py +10 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/workspace/session.py +9 -9
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/uv.lock +647 -882
- tsugite_cli-0.12.2/tests/daemon/test_http_adapter.py +0 -534
- tsugite_cli-0.12.2/tests/test_attachments.py +0 -448
- tsugite_cli-0.12.2/tests/test_cache_control.py +0 -181
- tsugite_cli-0.12.2/tests/test_continuation.py +0 -453
- tsugite_cli-0.12.2/tests/test_daemon_history_persistence.py +0 -514
- tsugite_cli-0.12.2/tests/test_daemon_memory.py +0 -481
- tsugite_cli-0.12.2/tests/test_daemon_session_isolation.py +0 -265
- tsugite_cli-0.12.2/tests/test_history.py +0 -414
- tsugite_cli-0.12.2/tests/test_history_integration.py +0 -715
- tsugite_cli-0.12.2/tests/test_history_models.py +0 -517
- tsugite_cli-0.12.2/tests/test_history_performance.py +0 -202
- tsugite_cli-0.12.2/tests/test_history_tools.py +0 -286
- tsugite_cli-0.12.2/tests/test_mcp_client.py +0 -378
- tsugite_cli-0.12.2/tests/test_mcp_server.py +0 -109
- tsugite_cli-0.12.2/tests/test_reasoning_models.py +0 -62
- tsugite_cli-0.12.2/tsugite/agent_runner/history_integration.py +0 -306
- tsugite_cli-0.12.2/tsugite/cli/history.py +0 -413
- tsugite_cli-0.12.2/tsugite/cli/mcp.py +0 -193
- tsugite_cli-0.12.2/tsugite/cli/serve.py +0 -41
- tsugite_cli-0.12.2/tsugite/daemon/memory.py +0 -333
- tsugite_cli-0.12.2/tsugite/daemon/web/css/responsive.css +0 -191
- tsugite_cli-0.12.2/tsugite/daemon/web/css/styles.css +0 -387
- tsugite_cli-0.12.2/tsugite/daemon/web/css/theme.css +0 -47
- tsugite_cli-0.12.2/tsugite/daemon/web/index.html +0 -1344
- tsugite_cli-0.12.2/tsugite/daemon/web/js/views/conversation/history.js +0 -184
- tsugite_cli-0.12.2/tsugite/daemon/web/js/views/conversation/sessions.js +0 -207
- tsugite_cli-0.12.2/tsugite/daemon/web/js/views/conversations.js +0 -345
- tsugite_cli-0.12.2/tsugite/history/__init__.py +0 -54
- tsugite_cli-0.12.2/tsugite/history/models.py +0 -147
- tsugite_cli-0.12.2/tsugite/history/reconstruction.py +0 -348
- tsugite_cli-0.12.2/tsugite/history/storage.py +0 -619
- tsugite_cli-0.12.2/tsugite/mcp_client.py +0 -221
- tsugite_cli-0.12.2/tsugite/mcp_config.py +0 -174
- tsugite_cli-0.12.2/tsugite/mcp_server.py +0 -144
- tsugite_cli-0.12.2/tsugite/tools/history.py +0 -190
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/.github/workflows/ci.yml +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/.github/workflows/docker-publish.yml +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/.github/workflows/pypi-publish.yml +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/.gitignore +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/CLAUDE.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/LICENSE +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/docs/plugin-hooks.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/docs/secrets.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/docs/test_agents/prefresh_readme.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/docs/test_agents/template_test.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/docs/test_agents/test_agent.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/docs/test_agents/test_steps.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/mise.toml +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/scripts/backfill_usage.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/scripts/regenerate_schema.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/__init__.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/core/__init__.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/core/test_agent_context_tokens.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/core/test_memory.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/core/test_proxy.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/core/test_sandbox.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/core/test_tools.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/daemon/__init__.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/e2e/__init__.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/e2e/conftest.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/e2e/test_auth.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/e2e/test_chat.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/e2e/test_context_bar.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/e2e/test_draft_persistence.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/e2e/test_history.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/e2e/test_page_load.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/e2e/test_prompt_inspector.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/e2e/test_sessions.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/e2e/test_sidebar_redesign.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/e2e/test_sse_metadata.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/events/test_event_consolidation.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_agent_file_hot_loading.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_agent_parser.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_agent_utils.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_agents_tool.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_attachment_deduplication.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_auto_context_handler.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_auto_discovery.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_background_task_status.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_chat_cli.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_chat_error_handling.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_cli_arguments.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_completion_callbacks.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_config.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_custom_shell_tools.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_custom_ui.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_daemon_auth.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_daemon_compaction_scheduler.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_daemon_config.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_daemon_push.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_daemon_scheduler.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_discord_progress.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_error_display.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_file_references.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_file_tools.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_interaction_backends.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_interactive_tool.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_list_agents_tool.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_models.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_multi_agent.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_orchestrator_heartbeat.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_plugins.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_prompt_snapshot.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_provider_registry.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_repl_commands.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_repl_completer.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_repl_handler.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_retry_system.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_run_if.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_schedule_model_override.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_secret_access_event.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_secrets.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_security_phase1.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_send_message.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_session_orchestrator_tools.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_stdin.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_subagent_subprocess.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_tmux_tools.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_tool_directives.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_tool_registry.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_workspace_auto_continue.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_workspace_cwd.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tests/test_workspace_discovery.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/__init__.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/agent_runner/__init__.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/agent_runner/helpers.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/agent_runner/metrics.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/agent_runner/validation.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/agent_utils.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/attachments/__init__.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/attachments/auto_context.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/attachments/file.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/attachments/inline.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/attachments/storage.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/attachments/youtube.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/builtin_agents/.gitkeep +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/builtin_agents/code_searcher.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/builtin_skills/.gitkeep +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/builtin_skills/codebase-exploration/SKILL.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/builtin_skills/python-math/SKILL.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/builtin_skills/response-patterns/SKILL.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/builtin_skills/scheduling/SKILL.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/builtin_skills/skill-authoring/SKILL.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/builtin_skills/tsugite-jinja-reference/SKILL.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/builtin_skills/tsugite-skill-basics/SKILL.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/cache.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/cli/agents.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/cli/attachments.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/cli/cache.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/cli/chat.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/cli/config.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/cli/daemon.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/cli/init.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/cli/models.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/cli/plugins.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/cli/secrets.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/cli/tools.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/cli/usage.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/cli/validate.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/cli/workspace.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/console.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/constants.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/core/__init__.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/core/content_blocks.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/core/proxy.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/core/sandbox.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/__init__.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/adapters/__init__.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/adapters/discord.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/auth.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/config.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/push.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/scheduler.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/web/icons/icon-192.png +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/web/icons/icon-512-maskable.png +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/web/icons/icon-512.png +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/web/icons/screenshot-narrow.png +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/web/icons/screenshot-wide.png +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/web/js/vendor/marked.LICENSE.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/web/js/vendor/marked.esm.min.js +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/web/js/views/conversation/attachments.js +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/web/js/views/schedules.js +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/web/js/views/webhooks.js +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/web/js/views/workspace.js +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/web/sw.js +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/daemon/webhook_store.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/events/bus.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/events/helpers.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/interaction.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/plugins.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/providers/__init__.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/providers/model_cache.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/providers/ollama.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/providers/openrouter.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/secrets/__init__.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/secrets/backend.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/secrets/env.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/secrets/exec.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/secrets/file.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/secrets/masking.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/secrets/registry.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/secrets/sqlite.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/shell_tool_config.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/templates/AGENTS.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/templates/IDENTITY.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/templates/MEMORY.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/templates/USER.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/templates/personas/casual-technical.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/templates/personas/marvin.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/templates/personas/minimal.md +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/tools/__init__.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/tools/notify.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/tools/schedule.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/tools/scratchpad.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/tools/sessions.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/tools/shell_tools.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/tsugite.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/ui/__init__.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/ui/base.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/ui/chat.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/ui/helpers.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/ui/plain.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/ui/repl_chat.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/ui/repl_completer.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/ui/repl_handler.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/ui_context.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/usage/__init__.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/usage/store.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/workspace/__init__.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/workspace/context.py +0 -0
- {tsugite_cli-0.12.2 → tsugite_cli-0.13.0}/tsugite/workspace/templates.py +0 -0
|
@@ -4,16 +4,16 @@ Tsugite is a micro-agent CLI: agents are Markdown + YAML frontmatter, rendered v
|
|
|
4
4
|
|
|
5
5
|
### Architecture
|
|
6
6
|
|
|
7
|
-
**Flow:** `Agent.md` (YAML + Jinja2) → `renderer.py` → `TsugiteAgent` →
|
|
7
|
+
**Flow:** `Agent.md` (YAML + Jinja2) → `renderer.py` → `TsugiteAgent` → Provider → Tools
|
|
8
8
|
|
|
9
9
|
**Key Modules:**
|
|
10
|
-
- `cli/__init__.py` - Typer CLI (run, chat, render, config,
|
|
10
|
+
- `cli/__init__.py` - Typer CLI (run, chat, render, config, tools, agents, attachments, cache)
|
|
11
11
|
- `md_agents.py` - Parse frontmatter + agent resolution + directives
|
|
12
12
|
- `builtin_agents/` - File-based built-in agent definitions (default.md, chat-assistant.md)
|
|
13
13
|
- `agent_inheritance.py` - Agent resolution pipeline + inheritance chain
|
|
14
14
|
- `chat.py` - Chat session management with history
|
|
15
15
|
- `agent_runner.py` - Execution orchestration, prefetch, tool wiring, multi-step
|
|
16
|
-
- `core/agent.py` -
|
|
16
|
+
- `core/agent.py` - agent loop + streaming
|
|
17
17
|
- `renderer.py` - Jinja2 rendering + helpers (now, today, slugify, env, file_exists, read_text, is_file, is_dir)
|
|
18
18
|
- `tools/` - Tool registry + implementations
|
|
19
19
|
- `shell_tool_config.py` + `tools/shell_tools.py` - Custom shell command wrappers
|
|
@@ -62,7 +62,6 @@ File-based agents distributed with the package in `tsugite/builtin_agents/` dire
|
|
|
62
62
|
**Merge rules:**
|
|
63
63
|
- Scalars (model, max_steps): Child overwrites
|
|
64
64
|
- Lists (tools): Merge + deduplicate
|
|
65
|
-
- Dicts (mcp_servers): Merge, child keys override
|
|
66
65
|
- Strings (instructions): Concatenate with `\n\n`
|
|
67
66
|
|
|
68
67
|
**Opt-out:** `extends: none` skips all inheritance.
|
|
@@ -236,7 +235,6 @@ assert tool is not None
|
|
|
236
235
|
|
|
237
236
|
**Safety:**
|
|
238
237
|
- Shell tool guard: Never relax dangerous patterns blocklist
|
|
239
|
-
- MCP: Requires `--trust-mcp-code` flag
|
|
240
238
|
- File ops: Use `Path.resolve()` to prevent traversal
|
|
241
239
|
|
|
242
240
|
**Testing:** Test-first workflow. Add minimal failing test, run target, widen to full suite before merge.
|
|
@@ -54,7 +54,7 @@ Tsugite is an agentic CLI that executes AI agents defined as markdown files with
|
|
|
54
54
|
1. **CLI Entry** (`tsugite/cli/__init__.py`)
|
|
55
55
|
- Typer-based CLI with subcommands
|
|
56
56
|
- Main commands: `run` (single-shot), `chat` (interactive), `render` (preview)
|
|
57
|
-
- Additional: `daemon`, `workspace`, `init`, `validate`, `
|
|
57
|
+
- Additional: `daemon`, `workspace`, `init`, `validate`, `agents`, `config`, `attachments`, `cache`, `tools`, `history`
|
|
58
58
|
|
|
59
59
|
2. **Agent Resolution & Inheritance** (`tsugite/agent_inheritance.py`)
|
|
60
60
|
- Resolves agent names to file paths using search order:
|
|
@@ -102,22 +102,20 @@ Tsugite is an agentic CLI that executes AI agents defined as markdown files with
|
|
|
102
102
|
- Integrates with history system for conversation continuity
|
|
103
103
|
|
|
104
104
|
7. **LLM Agent Loop** (`tsugite/core/agent.py`)
|
|
105
|
-
- `TsugiteAgent` - Custom agent using
|
|
105
|
+
- `TsugiteAgent` - Custom agent using tsugite's provider abstraction
|
|
106
106
|
- Think-Code-Observation loop (max_turns iterations)
|
|
107
107
|
- Supports reasoning models (o1, o3, Claude extended thinking)
|
|
108
108
|
- Direct control over model parameters (temperature, reasoning_effort)
|
|
109
109
|
- Code execution via `LocalExecutor`
|
|
110
110
|
|
|
111
111
|
8. **Tool System** (`tsugite/tools/`)
|
|
112
|
-
- Tool registry with built-in tools (fs, http, shell,
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
- MCP integration (`tsugite/mcp_client.py`)
|
|
116
|
-
- Tool expansion supports globs (`*_search`) and exclusions (`-delete_file`)
|
|
112
|
+
- Tool registry with built-in tools (fs, http, shell, agents, skills, history, interactive) plus optional categories (notify, schedule, scratchpad, sessions, secrets, tmux — gated by `_OPTIONAL_CATEGORIES` in `tools/__init__.py`)
|
|
113
|
+
- Custom shell tools (config-based command wrappers in `tools/shell_tools.py`)
|
|
114
|
+
- Tool expansion supports globs (`*_search`), categories (`@fs`), and exclusions (`-delete_file`)
|
|
117
115
|
|
|
118
116
|
9. **Event System** (`tsugite/events/`)
|
|
119
117
|
- Event-driven architecture for UI decoupling
|
|
120
|
-
-
|
|
118
|
+
- 28 event types: execution, LLM, meta, progress, skills
|
|
121
119
|
- `EventBus` dispatches to multiple handlers
|
|
122
120
|
- Handlers: Rich console, plain text, JSONL, chat, REPL
|
|
123
121
|
|
|
@@ -131,17 +129,16 @@ Tsugite is an agentic CLI that executes AI agents defined as markdown files with
|
|
|
131
129
|
- XDG-compliant paths: `~/.config/tsugite/` or `$XDG_CONFIG_HOME/tsugite/`
|
|
132
130
|
- JSON config with model aliases, default settings
|
|
133
131
|
- Attachment storage (reusable context)
|
|
134
|
-
- MCP server registration
|
|
135
132
|
- Auto-context discovery (CLAUDE.md, AGENTS.md, CONTEXT.md)
|
|
136
133
|
|
|
137
134
|
### Key Data Structures
|
|
138
135
|
|
|
139
|
-
**AgentConfig** (`md_agents.py:
|
|
136
|
+
**AgentConfig** (`md_agents.py:36`)
|
|
140
137
|
- Pydantic model for agent frontmatter
|
|
141
138
|
- Validates all fields (name, model, tools, max_turns, etc.)
|
|
142
139
|
- Schema exported to `tsugite/schemas/agent.schema.json`
|
|
143
140
|
|
|
144
|
-
**PreparedAgent** (`agent_preparation.py:
|
|
141
|
+
**PreparedAgent** (`agent_preparation.py:69`)
|
|
145
142
|
- Dataclass containing everything for execution/display
|
|
146
143
|
- Ensures `render` shows exactly what `run` executes
|
|
147
144
|
- Contains: agent, config, system_message, user_message, tools, context
|
|
@@ -241,7 +238,7 @@ Tsugite supports vision (images), audio, and document understanding through the
|
|
|
241
238
|
**How It Works:**
|
|
242
239
|
|
|
243
240
|
1. **URL Attachments** (Images/Documents):
|
|
244
|
-
-
|
|
241
|
+
- The provider fetches the URL directly (no download overhead — support varies by provider and content type; see `tsugite/attachments/url.py`)
|
|
245
242
|
- Example: `tsu run -f https://example.com/chart.png "Describe this chart"`
|
|
246
243
|
- Automatically detected via HTTP HEAD request
|
|
247
244
|
|
|
@@ -286,6 +283,63 @@ tsu run -f image1.jpg -f image2.jpg "Compare these images"
|
|
|
286
283
|
- YouTube handler: `tsugite/attachments/youtube.py` (fetches transcripts)
|
|
287
284
|
- LLM formatting: `tsugite/core/agent.py:_format_attachment()`
|
|
288
285
|
|
|
286
|
+
#### Frontmatter Attachment Specs
|
|
287
|
+
|
|
288
|
+
The `attachments:` field accepts either plain string paths (legacy) or dict-form `AttachmentSpec` entries. Both forms can be mixed in the same list.
|
|
289
|
+
|
|
290
|
+
**String form** (existing): rendered through Jinja, then loaded as a single file. Prefix with `-` to remove a workspace default (e.g., `-MEMORY.md`).
|
|
291
|
+
|
|
292
|
+
```yaml
|
|
293
|
+
attachments:
|
|
294
|
+
- MEMORY.md
|
|
295
|
+
- "{{ today() }}.md"
|
|
296
|
+
- -SKIP.md # remove a workspace default named SKIP.md
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Dict form** (`AttachmentSpec`): adds `assign:`, `mode: index`, glob expansion, and other knobs.
|
|
300
|
+
|
|
301
|
+
```yaml
|
|
302
|
+
attachments:
|
|
303
|
+
- path: MEMORY.md
|
|
304
|
+
assign: memory_content # bind file content as Jinja variable
|
|
305
|
+
|
|
306
|
+
- path: USER.md
|
|
307
|
+
assign: user_prefs
|
|
308
|
+
attach: false # bind variable but don't inject into prompt
|
|
309
|
+
|
|
310
|
+
- path: "memory/topics/*.md" # glob - one Attachment per matched file
|
|
311
|
+
assign: topic_files # bound as list[dict] of {path, content}
|
|
312
|
+
|
|
313
|
+
- path: "memory/topics/*.md"
|
|
314
|
+
mode: index # render path+heading list, not full content
|
|
315
|
+
name: topic_index # explicit attachment name (default derives from glob)
|
|
316
|
+
assign: topics # bound as list[dict] of {path, heading, size_bytes, mtime}
|
|
317
|
+
index_format: first_heading # path_only | first_line | first_heading | frontmatter
|
|
318
|
+
max_entries: 50 # cap, warns if exceeded
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Variable shapes** (when `assign:` is set):
|
|
322
|
+
|
|
323
|
+
| Spec | Shape |
|
|
324
|
+
|---|---|
|
|
325
|
+
| `mode: full`, single concrete file | `str` (file content) |
|
|
326
|
+
| `mode: full`, glob | `list[dict]` of `{path, content}` (binaries skipped) |
|
|
327
|
+
| `mode: index` | `list[dict]` of `{path, heading, size_bytes, mtime}` |
|
|
328
|
+
| Single file, missing | `None` |
|
|
329
|
+
| Empty glob | `[]` |
|
|
330
|
+
|
|
331
|
+
**Constraints (validated at parse time):**
|
|
332
|
+
- `assign:` must be a valid Python identifier.
|
|
333
|
+
- Two specs can't share the same `assign` value.
|
|
334
|
+
- `attach: false` requires `assign:` (otherwise the attachment loads for nothing).
|
|
335
|
+
- `path:` cannot start with `-` (use string form for removal).
|
|
336
|
+
|
|
337
|
+
**Index mode rendering** wraps the formatted index in an existing `<attachment>` tag with a `mode="index"` attribute, e.g. `<attachment name="topic_index" mode="index">...</attachment>`. Agents are expected to call `read_file(path=...)` to fetch individual entries on demand.
|
|
338
|
+
|
|
339
|
+
**Collisions:** if an `assign:` name shadows a built-in or prefetch variable, the assignment wins and a warning is logged.
|
|
340
|
+
|
|
341
|
+
See `examples/attachment_assign_demo.md` and `examples/attachment_index_demo.md` for runnable references.
|
|
342
|
+
|
|
289
343
|
## Development Patterns
|
|
290
344
|
|
|
291
345
|
### Adding a New Built-in Agent
|
|
@@ -315,14 +369,31 @@ tsu run -f image1.jpg -f image2.jpg "Compare these images"
|
|
|
315
369
|
|
|
316
370
|
1. Update `AgentConfig` Pydantic model (`md_agents.py`)
|
|
317
371
|
2. Regenerate schema: `uv run python scripts/regenerate_schema.py`
|
|
318
|
-
3. Update documentation in
|
|
372
|
+
3. Update relevant documentation in `AGENTS.md` and any user-facing doc under `docs/`
|
|
319
373
|
4. Add validation tests in `tests/test_agent_parser.py`
|
|
320
374
|
|
|
321
375
|
## Testing Strategy
|
|
322
376
|
|
|
323
|
-
|
|
377
|
+
### TDD is the default
|
|
378
|
+
|
|
379
|
+
For any non-trivial bug fix or feature, write a failing test **first**. Exceptions: typo fixes, docs, pure refactors, and changes the user has explicitly scoped as exploratory.
|
|
380
|
+
|
|
381
|
+
The workflow:
|
|
382
|
+
|
|
383
|
+
1. **Reproduce before fixing.** Write the smallest test that demonstrates the symptom as reported. Run it. If it passes on master, the hypothesis is wrong — do not write a fix. Investigate further, ask the user for more detail, or add logging.
|
|
384
|
+
2. **Implement the fix.** Keep the change minimal. Do not refactor or add adjacent cleanup in the same diff.
|
|
385
|
+
3. **Verify the test is load-bearing.** Temp-revert the production change and re-run. The test must go red again. If it still passes, the test isn't actually exercising the fix — rework it.
|
|
386
|
+
4. **Cover adjacent cases before declaring done.** When touching a parser, protocol boundary, or state machine, enumerate at least two adjacent input shapes and add tests for them. Narrow tests let bugs sneak in through the cases you didn't think about.
|
|
387
|
+
5. **Run the local regression suite.** At minimum, the test files next to the modules you changed. Faster than running everything, catches silent breakage of existing tests.
|
|
388
|
+
6. **Honest uncertainty.** If a hypothesis doesn't reproduce, say so. Don't ship a "probably-this" fix with no failing test to anchor it — that's how placebo fixes get merged.
|
|
389
|
+
|
|
390
|
+
Reproducing tests don't need to be elaborate. A 10-line test that flips red→green is worth more than a 100-line one that nobody understands.
|
|
391
|
+
|
|
392
|
+
### Test layers
|
|
393
|
+
|
|
324
394
|
- **Unit tests**: Individual functions and classes
|
|
325
|
-
- **Pipeline tests**: Mock only `TsugiteAgent
|
|
395
|
+
- **Pipeline tests**: Mock only `TsugiteAgent` / the provider's `acompletion` but exercise the full pipeline (parsing → rendering → preparation → tool expansion → execution). Most tests in the suite are this style.
|
|
396
|
+
- **Integration tests**: Live under `tests/integration/` (not collected by default — see `pyproject.toml` `norecursedirs`). Run explicitly with `uv run pytest tests/integration/`. Good for concurrency, cwd, and daemon-wiring tests that need real threading or a real workspace.
|
|
326
397
|
- **Smoke tests**: `tests/smoke_test.sh` hits a real LLM API (requires `OPENAI_API_KEY`, not run in CI)
|
|
327
398
|
- **Fixtures**: `conftest.py` provides shared test data
|
|
328
399
|
- **Mocking**: Use `@pytest.fixture` for LLM responses
|
|
@@ -348,6 +419,28 @@ tsu run -f image1.jpg -f image2.jpg "Compare these images"
|
|
|
348
419
|
6. **Test both sync and async paths**: Many tools support both execution modes
|
|
349
420
|
7. **Don't embed prompts in adapters/code**: Use context variables + conditional blocks in `default.md` instead. Add new context vars in `_build_agent_context()` (base adapter) and default them in `agent_preparation.py`, then use `{% if var %}` in the agent template. This keeps all prompt content in one place and leverages the existing rendering pipeline.
|
|
350
421
|
|
|
422
|
+
## Web UI
|
|
423
|
+
|
|
424
|
+
The daemon's web UI lives at `tsugite/daemon/web/`.
|
|
425
|
+
|
|
426
|
+
### Stack
|
|
427
|
+
|
|
428
|
+
- **Alpine.js 3** loaded from CDN. No build step. No React. Don't port this to a framework - the design handoff explicitly said match visuals, not internal structure.
|
|
429
|
+
- **Starlette** serves `index.html` plus `/static/{css,js,icons}` and the `/api/*` routes (`tsugite/daemon/adapters/http.py`).
|
|
430
|
+
- CSS lives in three files. Read all three before adding rules: `theme.css` (Catppuccin tokens), `console.css` (Console redesign - the real stylesheet), `styles.css` (legacy modals/auth/forms; ~120 lines, pruned aggressively after the redesign).
|
|
431
|
+
- View modules under `js/views/`: `conversations.js` (orchestrator) + `conversation/{sessions,history,streaming,input,attachments,event_types}.js` mixins, plus `workspace.js`, `schedules.js`, `webhooks.js`, `usage.js`, `file-editor.js`. Shared helpers: `js/api.js` (REST + SSE), `js/utils.js` (markdown, formatters, toast).
|
|
432
|
+
|
|
433
|
+
### Theme tokens
|
|
434
|
+
|
|
435
|
+
- Use **bare token names** - `var(--base)`, `var(--mantle)`, `var(--crust)`, `var(--surface0/1/2)`, `var(--text)`, `var(--lavender)`, `var(--blue)`, `var(--peach)`, etc. The legacy `--ctp-*` aliases were removed.
|
|
436
|
+
- **Visual depth is `crust < mantle < base`** (darker → lighter on Frappé/Mocha/Macchiato). For dark-on-dark panels, panels go `var(--mantle)` and inner cards `var(--surface0)` or `var(--crust)`. Don't put a brighter (`--bg`/`--base`) header over a `--mantle` panel - it inverts the depth on dark themes.
|
|
437
|
+
- Never hardcode hex colors. They won't theme-switch. Even in JS palette objects (e.g. `PI_COLORS` in `conversations.js`), use `var(--pink)` strings - they get embedded into `:style="background: ..."` and resolve through the CSS cascade.
|
|
438
|
+
- `<meta name="theme-color">` is synced to the active theme's `--crust` via an `Alpine.effect` in `app.js` so the PWA status bar / mobile chrome track the theme.
|
|
439
|
+
|
|
440
|
+
### Verification
|
|
441
|
+
|
|
442
|
+
Browser verification on UI changes is mandatory. The smoketest recipe - daemon spinup, Playwright wiring, and the post-redesign DOM selectors - is captured in the agent's project memory file `project_daemon_ui_smoketest.md`. There's also a `project_webui_console_hotzones.md` memory listing the load-bearing assumptions in the new UI (pulse signal, mid-turn reload guards, theme depth, PWA resume, mobile back-buttons) that bit during the redesign and should be re-read before touching session lifecycle, theme tokens, or PWA behaviour.
|
|
443
|
+
|
|
351
444
|
## Code Review Policy
|
|
352
445
|
|
|
353
446
|
- **Automatically run the `code-simplifier:code-simplifier` agent** (via the Task tool) after implementing any changes, without waiting for the user to ask. This catches duplication, unnecessary complexity, and keeps the codebase DRY.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tsugite-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: Micro-agent runner for task automation using markdown definitions
|
|
5
5
|
Author: Justyn Shull
|
|
6
6
|
License: GNU AFFERO GENERAL PUBLIC LICENSE
|
|
@@ -246,7 +246,6 @@ Requires-Dist: ddgs>=9.6.0
|
|
|
246
246
|
Requires-Dist: html2text>=2020.1.16
|
|
247
247
|
Requires-Dist: httpx>=0.25
|
|
248
248
|
Requires-Dist: jinja2>=3.1
|
|
249
|
-
Requires-Dist: mcp>=1.0
|
|
250
249
|
Requires-Dist: nest-asyncio>=1.5.0
|
|
251
250
|
Requires-Dist: pathspec>=0.11.0
|
|
252
251
|
Requires-Dist: portalocker>=2.8.0
|
|
@@ -334,8 +333,8 @@ tsu run +default "summarize the files in this directory"
|
|
|
334
333
|
# Run an agent file directly
|
|
335
334
|
tsu run my-agent.md "do the thing"
|
|
336
335
|
|
|
337
|
-
# Start the web UI
|
|
338
|
-
tsu
|
|
336
|
+
# Start the daemon (web UI, Discord/Telegram bots)
|
|
337
|
+
tsu daemon
|
|
339
338
|
```
|
|
340
339
|
|
|
341
340
|
## Features
|
|
@@ -347,7 +346,6 @@ tsu serve
|
|
|
347
346
|
- **Skills** directory-based knowledge modules (mostly) following the [agentskills.io](https://agentskills.io/) SKILL.md format
|
|
348
347
|
- **Hooks** that fire shell commands on lifecycle events (post-tool, pre-message, pre/post-compact)
|
|
349
348
|
- **Sandbox** (linux only) via bubblewrap with filesystem and network isolation
|
|
350
|
-
- **MCP** integration for connecting to MCP servers
|
|
351
349
|
|
|
352
350
|
## Agents in more detail
|
|
353
351
|
|
|
@@ -395,7 +393,7 @@ All paths follow [XDG Base Directory](https://specifications.freedesktop.org/bas
|
|
|
395
393
|
|
|
396
394
|
| Path | Default | Contents |
|
|
397
395
|
|----------------------------------------|--------------------------------------|----------------------------------------------|
|
|
398
|
-
| `$XDG_CONFIG_HOME/tsugite/` | `~/.config/tsugite/` | `config.json`, `
|
|
396
|
+
| `$XDG_CONFIG_HOME/tsugite/` | `~/.config/tsugite/` | `config.json`, `daemon.yaml` |
|
|
399
397
|
| `$XDG_DATA_HOME/tsugite/history/` | `~/.local/share/tsugite/history/` | Session history (JSONL per session) |
|
|
400
398
|
| `$XDG_DATA_HOME/tsugite/daemon/` | `~/.local/share/tsugite/daemon/` | Daemon state |
|
|
401
399
|
| `$XDG_DATA_HOME/tsugite/secrets/` | `~/.local/share/tsugite/secrets/` | Encrypted secrets (`secrets.db`) |
|
|
@@ -63,8 +63,8 @@ tsu run +default "summarize the files in this directory"
|
|
|
63
63
|
# Run an agent file directly
|
|
64
64
|
tsu run my-agent.md "do the thing"
|
|
65
65
|
|
|
66
|
-
# Start the web UI
|
|
67
|
-
tsu
|
|
66
|
+
# Start the daemon (web UI, Discord/Telegram bots)
|
|
67
|
+
tsu daemon
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
## Features
|
|
@@ -76,7 +76,6 @@ tsu serve
|
|
|
76
76
|
- **Skills** directory-based knowledge modules (mostly) following the [agentskills.io](https://agentskills.io/) SKILL.md format
|
|
77
77
|
- **Hooks** that fire shell commands on lifecycle events (post-tool, pre-message, pre/post-compact)
|
|
78
78
|
- **Sandbox** (linux only) via bubblewrap with filesystem and network isolation
|
|
79
|
-
- **MCP** integration for connecting to MCP servers
|
|
80
79
|
|
|
81
80
|
## Agents in more detail
|
|
82
81
|
|
|
@@ -124,7 +123,7 @@ All paths follow [XDG Base Directory](https://specifications.freedesktop.org/bas
|
|
|
124
123
|
|
|
125
124
|
| Path | Default | Contents |
|
|
126
125
|
|----------------------------------------|--------------------------------------|----------------------------------------------|
|
|
127
|
-
| `$XDG_CONFIG_HOME/tsugite/` | `~/.config/tsugite/` | `config.json`, `
|
|
126
|
+
| `$XDG_CONFIG_HOME/tsugite/` | `~/.config/tsugite/` | `config.json`, `daemon.yaml` |
|
|
128
127
|
| `$XDG_DATA_HOME/tsugite/history/` | `~/.local/share/tsugite/history/` | Session history (JSONL per session) |
|
|
129
128
|
| `$XDG_DATA_HOME/tsugite/daemon/` | `~/.local/share/tsugite/daemon/` | Daemon state |
|
|
130
129
|
| `$XDG_DATA_HOME/tsugite/secrets/` | `~/.local/share/tsugite/secrets/` | Encrypted secrets (`secrets.db`) |
|
|
@@ -46,7 +46,6 @@ All fields are optional except `name`.
|
|
|
46
46
|
| `auto_load_skills` | list | `[]` | Skills to load automatically at startup |
|
|
47
47
|
| `skill_paths` | list | `[]` | Extra directories to search for skills |
|
|
48
48
|
| `prefetch` | list | `[]` | Tools to run before execution, results available in templates |
|
|
49
|
-
| `mcp_servers` | dict | `{}` | MCP server connections |
|
|
50
49
|
| `custom_tools` | list | `[]` | Inline tool definitions |
|
|
51
50
|
| `reasoning_effort` | str | none | For reasoning models: `low`, `medium`, `high` |
|
|
52
51
|
| `disable_history` | bool | `false` | Don't save this agent's sessions |
|
|
@@ -186,7 +185,57 @@ attachments:
|
|
|
186
185
|
- "{{ WORKSPACE_DIR }}/notes.md"
|
|
187
186
|
```
|
|
188
187
|
|
|
189
|
-
Supports text files, PDFs, images, and URLs. Jinja2 works in attachment paths. All attachments are injected as context.
|
|
188
|
+
Supports text files, PDFs, images, and URLs. Jinja2 works in attachment paths. All attachments are injected as context. Prefix with `-` to remove a workspace default (e.g. `-MEMORY.md`).
|
|
189
|
+
|
|
190
|
+
### Attachment specs (dict form)
|
|
191
|
+
|
|
192
|
+
Use the dict form to bind attachment content to a Jinja variable, render an on-demand index instead of full content, or expand globs:
|
|
193
|
+
|
|
194
|
+
```yaml
|
|
195
|
+
attachments:
|
|
196
|
+
# Bind file content as a variable usable in body/instructions templates
|
|
197
|
+
- path: MEMORY.md
|
|
198
|
+
assign: memory_content
|
|
199
|
+
|
|
200
|
+
# Bind without injecting (the LLM doesn't see it; the template does)
|
|
201
|
+
- path: USER.md
|
|
202
|
+
assign: user_prefs
|
|
203
|
+
attach: false
|
|
204
|
+
|
|
205
|
+
# Glob - one attachment per matched file; assign binds list[dict] of {path, content}
|
|
206
|
+
- path: "notes/*.md"
|
|
207
|
+
assign: notes
|
|
208
|
+
|
|
209
|
+
# Index mode - emits a single <attachment mode="index"> with path+heading bullets,
|
|
210
|
+
# no full file content. Agents read individual entries via read_file().
|
|
211
|
+
- path: "memory/topics/*.md"
|
|
212
|
+
mode: index
|
|
213
|
+
name: topic_index # optional override; default derives from glob
|
|
214
|
+
assign: topics # list[dict] of {path, heading, size_bytes, mtime}
|
|
215
|
+
index_format: first_heading # path_only | first_line | first_heading | frontmatter
|
|
216
|
+
max_entries: 50
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Variable shapes when `assign:` is set:
|
|
220
|
+
|
|
221
|
+
| Spec | Variable shape |
|
|
222
|
+
|---|---|
|
|
223
|
+
| `mode: full`, single concrete file | `str` (file content) |
|
|
224
|
+
| `mode: full`, glob | `list[dict]` of `{path, content}` (binaries skipped) |
|
|
225
|
+
| `mode: index` | `list[dict]` of `{path, heading, size_bytes, mtime}` |
|
|
226
|
+
| Single file, missing | `None` |
|
|
227
|
+
| Empty glob | `[]` |
|
|
228
|
+
|
|
229
|
+
Validation rules (enforced at parse time):
|
|
230
|
+
|
|
231
|
+
- `assign:` must be a valid Python identifier.
|
|
232
|
+
- Two specs cannot share the same `assign:` value.
|
|
233
|
+
- `attach: false` requires `assign:`.
|
|
234
|
+
- `path:` cannot start with `-` (use string form for removal).
|
|
235
|
+
|
|
236
|
+
Collisions between an `assign:` name and a built-in/prefetch variable resolve in favor of the attachment binding, with a warning logged.
|
|
237
|
+
|
|
238
|
+
See `examples/attachment_assign_demo.md` and `examples/attachment_index_demo.md` for full working examples.
|
|
190
239
|
|
|
191
240
|
## Multi-step agents
|
|
192
241
|
|
|
@@ -227,6 +276,7 @@ The variable `findings` is available in Python.
|
|
|
227
276
|
| `repeat_while` | str | Jinja2 expression, loop while truthy |
|
|
228
277
|
| `repeat_until` | str | Jinja2 expression, loop until truthy |
|
|
229
278
|
| `max_iterations` | int | Loop safety limit (default 10) |
|
|
279
|
+
| `agent` | str | Path to agent file; runs the step via `spawn_agent` instead of inline |
|
|
230
280
|
|
|
231
281
|
### Variable passing
|
|
232
282
|
|
|
@@ -235,6 +285,24 @@ When a step has `assign="var_name"`, the result is available in later steps:
|
|
|
235
285
|
- In Jinja2 templates: `{{ var_name }}`
|
|
236
286
|
- In Python code execution: just use `var_name` directly
|
|
237
287
|
|
|
288
|
+
### Step context isolation
|
|
289
|
+
|
|
290
|
+
Each `tsu:step` already runs as an independent agent invocation: a fresh system prompt, no LLM-level conversation history from prior steps. The only thing that flows between steps is variables captured with `assign=`, substituted into later steps via Jinja.
|
|
291
|
+
|
|
292
|
+
If you need a step to run with a fully separate identity (its own model defaults, attachments, skills, instructions), use `agent="path/to/other.md"`. The step content (after Jinja rendering) becomes the spawned agent's prompt. The spawned agent does not see the parent's `step_context` — only the rendered prompt — so any data the spawned agent needs must already be substituted in.
|
|
293
|
+
|
|
294
|
+
```markdown
|
|
295
|
+
<!-- tsu:step name="implement" assign="diff" -->
|
|
296
|
+
Write a fix for: {{ user_prompt }}. Return only the diff.
|
|
297
|
+
|
|
298
|
+
<!-- tsu:step name="review" agent="agents/code-reviewer.md" assign="verdict" -->
|
|
299
|
+
Review this diff:
|
|
300
|
+
|
|
301
|
+
{{ diff }}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Path resolution follows the standard agent search order (workspace, project-local `agents/`, builtin agents, global config). Unresolvable `agent=` paths fail before any step runs, so a typo in step 2 won't waste step 1's work.
|
|
305
|
+
|
|
238
306
|
## Other directives
|
|
239
307
|
|
|
240
308
|
### tsu:tool
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tsugite-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.13.0"
|
|
4
4
|
description = "Micro-agent runner for task automation using markdown definitions"
|
|
5
5
|
authors = [{ name = "Justyn Shull" }]
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -13,7 +13,6 @@ dependencies = [
|
|
|
13
13
|
"pyyaml>=6.0",
|
|
14
14
|
"httpx>=0.25",
|
|
15
15
|
"rich>=13.0",
|
|
16
|
-
"mcp>=1.0",
|
|
17
16
|
"ddgs>=9.6.0",
|
|
18
17
|
"questionary>=2.1.1",
|
|
19
18
|
"youtube-transcript-api>=0.6.0",
|
|
@@ -51,6 +51,13 @@ SKIP_PREFIXES = (
|
|
|
51
51
|
# Reasoning model name patterns (for supports_reasoning flag)
|
|
52
52
|
REASONING_PATTERNS = re.compile(r"^(o1|o3|o4)(-mini|-preview|-pro)?")
|
|
53
53
|
|
|
54
|
+
# Reasoning models that accept the reasoning_effort parameter.
|
|
55
|
+
# Excludes o1-mini (unsupported by OpenAI).
|
|
56
|
+
EFFORT_LEVELS_PATTERNS = re.compile(r"^(o1(-pro)?|o3(-mini|-pro)?|o4(-mini|-pro)?)(-\d{4}-\d{2}-\d{2})?$")
|
|
57
|
+
|
|
58
|
+
# Anthropic models that support extended thinking (Claude 4+ families).
|
|
59
|
+
ANTHROPIC_THINKING_PATTERNS = re.compile(r"^claude-(opus|sonnet|haiku)-4")
|
|
60
|
+
|
|
54
61
|
|
|
55
62
|
def fetch_litellm_data() -> dict:
|
|
56
63
|
print(f"Fetching {LITELLM_URL}...")
|
|
@@ -96,6 +103,10 @@ def entry_to_model_info(key: str, entry: dict, provider: str) -> str:
|
|
|
96
103
|
parts.append("supports_vision=True")
|
|
97
104
|
if reasoning:
|
|
98
105
|
parts.append("supports_reasoning=True")
|
|
106
|
+
if EFFORT_LEVELS_PATTERNS.match(key):
|
|
107
|
+
parts.append('supported_effort_levels=["low", "medium", "high"]')
|
|
108
|
+
elif provider == "anthropic" and ANTHROPIC_THINKING_PATTERNS.match(key):
|
|
109
|
+
parts.append('supported_effort_levels=["low", "medium", "high", "max"]')
|
|
99
110
|
|
|
100
111
|
return f"ModelInfo({', '.join(parts)})"
|
|
101
112
|
|
|
@@ -26,7 +26,7 @@ uv run pytest --cov=tsugite --cov-report=html
|
|
|
26
26
|
|
|
27
27
|
### Smoke Tests
|
|
28
28
|
|
|
29
|
-
**Smoke tests are NOT run automatically** to avoid API costs. They test real
|
|
29
|
+
**Smoke tests are NOT run automatically** to avoid API costs. They test real provider integration with actual API calls.
|
|
30
30
|
|
|
31
31
|
#### Prerequisites
|
|
32
32
|
|
|
@@ -42,7 +42,7 @@ bash tests/smoke_test.sh
|
|
|
42
42
|
|
|
43
43
|
#### What Smoke Tests Verify
|
|
44
44
|
|
|
45
|
-
1. **Real
|
|
45
|
+
1. **Real provider integration** - Tests actual API calls, not mocks
|
|
46
46
|
2. **Async context detection** - Catches issues like missing asyncio imports
|
|
47
47
|
3. **Tool integration** - Verifies tools work in real execution
|
|
48
48
|
4. **New agents** - Tests recently added agents end-to-end
|
|
@@ -50,7 +50,7 @@ bash tests/smoke_test.sh
|
|
|
50
50
|
#### When to Run Smoke Tests
|
|
51
51
|
|
|
52
52
|
- Before releases
|
|
53
|
-
- After upgrading
|
|
53
|
+
- After upgrading provider dependencies
|
|
54
54
|
- After major refactors to agent execution
|
|
55
55
|
- When debugging integration issues
|
|
56
56
|
- After adding new agents
|
|
@@ -61,7 +61,7 @@ bash tests/smoke_test.sh
|
|
|
61
61
|
|
|
62
62
|
- **`core/`** - Tests for core agent implementation (TsugiteAgent, executor, tools)
|
|
63
63
|
- **`test_*.py`** - Feature-specific tests (CLI, rendering, parsing, etc.)
|
|
64
|
-
- All unit tests mock
|
|
64
|
+
- All unit tests mock the provider to avoid API calls
|
|
65
65
|
|
|
66
66
|
### Integration Tests
|
|
67
67
|
|
|
@@ -69,24 +69,20 @@ Currently implemented as smoke tests (see above).
|
|
|
69
69
|
|
|
70
70
|
## Writing Tests
|
|
71
71
|
|
|
72
|
-
### Mocking
|
|
72
|
+
### Mocking the provider
|
|
73
73
|
|
|
74
|
-
When writing unit tests that use `TsugiteAgent
|
|
74
|
+
When writing unit tests that use `TsugiteAgent`, use the `mock_provider` and
|
|
75
|
+
`mock_completion_response` fixtures from `tests/conftest.py`:
|
|
75
76
|
|
|
76
77
|
```python
|
|
77
|
-
from unittest.mock import AsyncMock, patch
|
|
78
|
-
|
|
79
78
|
@pytest.mark.asyncio
|
|
80
|
-
async def test_agent_example(
|
|
81
|
-
|
|
82
|
-
mock_litellm.acompletion = AsyncMock(
|
|
83
|
-
return_value=mock_litellm_response("test response")
|
|
84
|
-
)
|
|
79
|
+
async def test_agent_example(mock_provider, mock_completion_response):
|
|
80
|
+
mock_provider.acompletion.return_value = mock_completion_response("test response")
|
|
85
81
|
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
agent = TsugiteAgent(...)
|
|
83
|
+
result = await agent.run("test task")
|
|
88
84
|
|
|
89
|
-
|
|
85
|
+
assert result == expected
|
|
90
86
|
```
|
|
91
87
|
|
|
92
88
|
### Testing Agents
|
|
@@ -50,17 +50,46 @@ def clear_agent_context():
|
|
|
50
50
|
This prevents test pollution where one test sets current_agent
|
|
51
51
|
and affects another test running in the same worker.
|
|
52
52
|
"""
|
|
53
|
-
from tsugite.agent_runner.helpers import clear_allowed_agents, clear_current_agent
|
|
53
|
+
from tsugite.agent_runner.helpers import clear_allowed_agents, clear_current_agent, set_allowed_secrets
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
def _reset():
|
|
56
|
+
clear_current_agent()
|
|
57
|
+
clear_allowed_agents()
|
|
58
|
+
set_allowed_secrets(None)
|
|
58
59
|
|
|
60
|
+
_reset()
|
|
59
61
|
yield
|
|
62
|
+
_reset()
|
|
60
63
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
|
|
65
|
+
@pytest.fixture
|
|
66
|
+
def secret_backend(tmp_path):
|
|
67
|
+
"""File-backed secrets rooted under tmp_path. Wires the backend, registers a
|
|
68
|
+
test agent, clears the masking registry, and yields the backend so callers
|
|
69
|
+
can populate via backend.set(name, value)."""
|
|
70
|
+
from tsugite.agent_runner.helpers import set_current_agent
|
|
71
|
+
from tsugite.secrets import set_backend
|
|
72
|
+
from tsugite.secrets.file import FileSecretBackend
|
|
73
|
+
from tsugite.secrets.registry import get_registry
|
|
74
|
+
|
|
75
|
+
backend = FileSecretBackend({"path": str(tmp_path / "secrets")})
|
|
76
|
+
set_backend(backend)
|
|
77
|
+
set_current_agent("test-agent")
|
|
78
|
+
get_registry().clear()
|
|
79
|
+
return backend
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@pytest.fixture(autouse=True)
|
|
83
|
+
def reset_workspace_dir_cv():
|
|
84
|
+
"""Clear the workspace ContextVar between tests so that state from one test
|
|
85
|
+
doesn't leak into the next (the CV persists across sync tests otherwise)."""
|
|
86
|
+
from tsugite.cli.helpers import _workspace_dir_cv, set_workspace_dir
|
|
87
|
+
|
|
88
|
+
token = set_workspace_dir(None)
|
|
89
|
+
try:
|
|
90
|
+
yield
|
|
91
|
+
finally:
|
|
92
|
+
_workspace_dir_cv.reset(token)
|
|
64
93
|
|
|
65
94
|
|
|
66
95
|
@pytest.fixture
|
|
@@ -287,12 +316,12 @@ def spawn_agent_tool(reset_tool_registry, request):
|
|
|
287
316
|
def interactive_tools(reset_tool_registry):
|
|
288
317
|
"""Register interactive tools for testing."""
|
|
289
318
|
from tsugite.tools import tool
|
|
290
|
-
from tsugite.tools.interactive import ask_user, ask_user_batch,
|
|
319
|
+
from tsugite.tools.interactive import ask_user, ask_user_batch, react_to_message, return_value, send_message
|
|
291
320
|
|
|
292
321
|
# Re-register the tools after registry reset
|
|
293
322
|
tool(ask_user)
|
|
294
323
|
tool(ask_user_batch)
|
|
295
|
-
tool(
|
|
324
|
+
tool(return_value)
|
|
296
325
|
tool(send_message)
|
|
297
326
|
tool(react_to_message)
|
|
298
327
|
|
|
@@ -403,33 +432,7 @@ tools: {tools_str}"""
|
|
|
403
432
|
|
|
404
433
|
|
|
405
434
|
@pytest.fixture
|
|
406
|
-
def
|
|
407
|
-
"""Factory for creating MCP server configurations."""
|
|
408
|
-
|
|
409
|
-
def _create_config(server_type="stdio", name="test-server", **kwargs):
|
|
410
|
-
"""Create an MCP server config dict.
|
|
411
|
-
|
|
412
|
-
Args:
|
|
413
|
-
server_type: "stdio" or "http"
|
|
414
|
-
name: Server name
|
|
415
|
-
**kwargs: Additional config fields (command, args, env, url, etc)
|
|
416
|
-
"""
|
|
417
|
-
if server_type == "stdio":
|
|
418
|
-
return {
|
|
419
|
-
"command": kwargs.get("command", "npx"),
|
|
420
|
-
"args": kwargs.get("args", ["-y", "test-server"]),
|
|
421
|
-
"env": kwargs.get("env", {}),
|
|
422
|
-
}
|
|
423
|
-
elif server_type == "http":
|
|
424
|
-
return {"url": kwargs.get("url", "http://localhost:8000/mcp"), "type": "http"}
|
|
425
|
-
else:
|
|
426
|
-
raise ValueError(f"Unknown server type: {server_type}")
|
|
427
|
-
|
|
428
|
-
return _create_config
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
@pytest.fixture
|
|
432
|
-
def mock_litellm_response():
|
|
435
|
+
def mock_completion_response():
|
|
433
436
|
"""Create a mock provider CompletionResponse for agent tests.
|
|
434
437
|
|
|
435
438
|
This fixture provides a factory function that creates mock LLM responses
|