tsugite-cli 0.14.3__tar.gz → 0.16.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.
Files changed (227) hide show
  1. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/AGENTS.md +17 -0
  2. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/PKG-INFO +3 -1
  3. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/README.md +2 -0
  4. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/pyproject.toml +3 -1
  5. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_inheritance.py +140 -38
  6. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_runner/__init__.py +12 -0
  7. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_runner/helpers.py +116 -1
  8. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_runner/runner.py +149 -22
  9. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_utils.py +36 -22
  10. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_agents/default.md +55 -0
  11. tsugite_cli-0.16.0/tsugite/builtin_agents/job_verifier.md +58 -0
  12. tsugite_cli-0.16.0/tsugite/builtin_agents/job_worker.md +93 -0
  13. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_skills/codebase-exploration/SKILL.md +1 -0
  14. tsugite_cli-0.16.0/tsugite/builtin_skills/external-agent-bridge/SKILL.md +73 -0
  15. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_skills/scheduling/SKILL.md +1 -0
  16. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_skills/skill-authoring/SKILL.md +1 -0
  17. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_skills/tsugite-agent-basics/SKILL.md +1 -0
  18. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_skills/tsugite-jinja-reference/SKILL.md +1 -0
  19. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_skills/tsugite-skill-basics/SKILL.md +4 -2
  20. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/__init__.py +2 -0
  21. tsugite_cli-0.16.0/tsugite/cli/exec.py +176 -0
  22. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/__init__.py +2 -1
  23. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/agent.py +75 -9
  24. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/claude_code.py +53 -11
  25. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/executor.py +58 -4
  26. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/subprocess_executor.py +4 -1
  27. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/adapters/base.py +125 -13
  28. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/adapters/http.py +470 -35
  29. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/adapters/scheduler_adapter.py +9 -0
  30. tsugite_cli-0.16.0/tsugite/daemon/commands.py +435 -0
  31. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/compaction_scheduler.py +15 -0
  32. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/config.py +57 -1
  33. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/gateway.py +147 -2
  34. tsugite_cli-0.16.0/tsugite/daemon/job_store.py +245 -0
  35. tsugite_cli-0.16.0/tsugite/daemon/jobs_orchestrator.py +1467 -0
  36. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/memory.py +36 -0
  37. tsugite_cli-0.16.0/tsugite/daemon/pty_manager.py +401 -0
  38. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/push.py +4 -1
  39. tsugite_cli-0.16.0/tsugite/daemon/record_store.py +146 -0
  40. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/scheduler.py +184 -56
  41. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/session_runner.py +43 -17
  42. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/session_store.py +66 -8
  43. tsugite_cli-0.16.0/tsugite/daemon/terminal_runtime.py +238 -0
  44. tsugite_cli-0.16.0/tsugite/daemon/terminal_store.py +132 -0
  45. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/css/console.css +18 -62
  46. tsugite_cli-0.16.0/tsugite/daemon/web/css/job-flows.css +91 -0
  47. tsugite_cli-0.16.0/tsugite/daemon/web/css/jobs-tab.css +158 -0
  48. tsugite_cli-0.16.0/tsugite/daemon/web/css/jobs.css +203 -0
  49. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/css/responsive.css +45 -11
  50. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/css/styles.css +6 -68
  51. tsugite_cli-0.16.0/tsugite/daemon/web/css/terminal.css +335 -0
  52. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/css/theme.css +4 -0
  53. tsugite_cli-0.16.0/tsugite/daemon/web/css/tsu-modal.css +298 -0
  54. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/index.html +963 -109
  55. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/app.js +51 -2
  56. tsugite_cli-0.16.0/tsugite/daemon/web/js/utils/tsu-modal.js +66 -0
  57. tsugite_cli-0.16.0/tsugite/daemon/web/js/utils/xterm-loader.js +183 -0
  58. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/utils.js +28 -0
  59. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/conversation/attachments.js +2 -3
  60. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/conversation/history.js +121 -6
  61. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/conversation/input.js +79 -11
  62. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/conversation/sessions.js +35 -10
  63. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/conversations.js +296 -26
  64. tsugite_cli-0.16.0/tsugite/daemon/web/js/views/jobs.js +367 -0
  65. tsugite_cli-0.16.0/tsugite/daemon/web/js/views/terminals.js +740 -0
  66. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/history/reconstruction.py +13 -0
  67. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/md_agents.py +5 -0
  68. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/models.py +22 -11
  69. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/options.py +2 -0
  70. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/providers/anthropic.py +21 -5
  71. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/providers/claude_code.py +14 -1
  72. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/providers/model_registry.py +10 -3
  73. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/providers/openai_compat.py +4 -3
  74. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/schemas/agent.schema.json +18 -0
  75. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/__init__.py +48 -1
  76. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/agents.py +37 -23
  77. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/fs.py +16 -14
  78. tsugite_cli-0.16.0/tsugite/tools/jobs.py +196 -0
  79. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/schedule.py +14 -14
  80. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/sessions.py +22 -11
  81. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/skills.py +25 -7
  82. tsugite_cli-0.16.0/tsugite/tools/terminal.py +319 -0
  83. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/repl_completer.py +5 -20
  84. tsugite_cli-0.14.3/tsugite/daemon/commands.py +0 -206
  85. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/.gitignore +0 -0
  86. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/CONTRIBUTING.md +0 -0
  87. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/LICENSE +0 -0
  88. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/__init__.py +0 -0
  89. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_preparation.py +0 -0
  90. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_runner/exec_directives.py +0 -0
  91. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_runner/exec_runner.py +0 -0
  92. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_runner/history_integration.py +0 -0
  93. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_runner/metrics.py +0 -0
  94. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_runner/models.py +0 -0
  95. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/agent_runner/validation.py +0 -0
  96. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/attachments/__init__.py +0 -0
  97. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/attachments/auto_context.py +0 -0
  98. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/attachments/base.py +0 -0
  99. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/attachments/file.py +0 -0
  100. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/attachments/inline.py +0 -0
  101. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/attachments/storage.py +0 -0
  102. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/attachments/url.py +0 -0
  103. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/attachments/youtube.py +0 -0
  104. {tsugite_cli-0.14.3/tsugite/builtin_skills → tsugite_cli-0.16.0/tsugite/builtin_agents}/.gitkeep +0 -0
  105. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_agents/code_searcher.md +0 -0
  106. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_agents/file_searcher.md +0 -0
  107. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_agents/onboard.md +0 -0
  108. {tsugite_cli-0.14.3/tsugite/builtin_agents → tsugite_cli-0.16.0/tsugite/builtin_skills}/.gitkeep +0 -0
  109. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_skills/python-math/SKILL.md +0 -0
  110. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/builtin_skills/response-patterns/SKILL.md +0 -0
  111. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cache.py +0 -0
  112. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/agents.py +0 -0
  113. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/attachments.py +0 -0
  114. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/cache.py +0 -0
  115. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/chat.py +0 -0
  116. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/config.py +0 -0
  117. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/daemon.py +0 -0
  118. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/helpers.py +0 -0
  119. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/history.py +0 -0
  120. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/init.py +0 -0
  121. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/models.py +0 -0
  122. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/plugins.py +0 -0
  123. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/render.py +0 -0
  124. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/run.py +0 -0
  125. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/secrets.py +0 -0
  126. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/skills.py +0 -0
  127. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/tools.py +0 -0
  128. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/usage.py +0 -0
  129. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/validate.py +0 -0
  130. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/cli/workspace.py +0 -0
  131. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/config.py +0 -0
  132. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/console.py +0 -0
  133. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/constants.py +0 -0
  134. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/content_blocks.py +0 -0
  135. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/memory.py +0 -0
  136. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/proxy.py +0 -0
  137. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/sandbox.py +0 -0
  138. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/state.py +0 -0
  139. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/core/tools.py +0 -0
  140. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/__init__.py +0 -0
  141. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/adapters/__init__.py +0 -0
  142. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/adapters/discord.py +0 -0
  143. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/auth.py +0 -0
  144. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/icons/icon-192.png +0 -0
  145. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/icons/icon-512-maskable.png +0 -0
  146. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/icons/icon-512.png +0 -0
  147. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/icons/screenshot-narrow.png +0 -0
  148. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/icons/screenshot-wide.png +0 -0
  149. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/api.js +0 -0
  150. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/vendor/marked.LICENSE.md +0 -0
  151. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/vendor/marked.esm.min.js +0 -0
  152. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/conversation/event_types.js +0 -0
  153. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/conversation/streaming.js +0 -0
  154. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/file-editor.js +0 -0
  155. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/schedules.js +0 -0
  156. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/usage.js +0 -0
  157. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/webhooks.js +0 -0
  158. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/js/views/workspace.js +0 -0
  159. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/manifest.json +0 -0
  160. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/web/sw.js +0 -0
  161. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/daemon/webhook_store.py +0 -0
  162. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/events/__init__.py +0 -0
  163. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/events/base.py +0 -0
  164. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/events/bus.py +0 -0
  165. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/events/events.py +0 -0
  166. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/events/helpers.py +0 -0
  167. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/exceptions.py +0 -0
  168. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/history/__init__.py +0 -0
  169. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/history/models.py +0 -0
  170. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/history/storage.py +0 -0
  171. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/hooks.py +0 -0
  172. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/interaction.py +0 -0
  173. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/plugins.py +0 -0
  174. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/providers/__init__.py +0 -0
  175. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/providers/base.py +0 -0
  176. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/providers/model_cache.py +0 -0
  177. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/providers/ollama.py +0 -0
  178. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/providers/openrouter.py +0 -0
  179. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/renderer.py +0 -0
  180. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/schemas/__init__.py +0 -0
  181. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/secrets/__init__.py +0 -0
  182. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/secrets/backend.py +0 -0
  183. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/secrets/env.py +0 -0
  184. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/secrets/exec.py +0 -0
  185. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/secrets/file.py +0 -0
  186. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/secrets/masking.py +0 -0
  187. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/secrets/registry.py +0 -0
  188. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/secrets/sqlite.py +0 -0
  189. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/shell_tool_config.py +0 -0
  190. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/skill_discovery.py +0 -0
  191. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/templates/AGENTS.md +0 -0
  192. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/templates/IDENTITY.md +0 -0
  193. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/templates/MEMORY.md +0 -0
  194. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/templates/USER.md +0 -0
  195. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/templates/personas/casual-technical.md +0 -0
  196. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/templates/personas/marvin.md +0 -0
  197. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/templates/personas/minimal.md +0 -0
  198. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/history.py +0 -0
  199. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/http.py +0 -0
  200. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/interactive.py +0 -0
  201. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/notify.py +0 -0
  202. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/scratchpad.py +0 -0
  203. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/secrets.py +0 -0
  204. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/shell.py +0 -0
  205. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/shell_tools.py +0 -0
  206. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tools/time.py +0 -0
  207. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/tsugite.py +0 -0
  208. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/__init__.py +0 -0
  209. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/base.py +0 -0
  210. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/chat.py +0 -0
  211. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/helpers.py +0 -0
  212. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/jsonl.py +0 -0
  213. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/live.py +0 -0
  214. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/plain.py +0 -0
  215. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/repl_chat.py +0 -0
  216. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/repl_commands.py +0 -0
  217. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui/repl_handler.py +0 -0
  218. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/ui_context.py +0 -0
  219. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/usage/__init__.py +0 -0
  220. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/usage/store.py +0 -0
  221. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/user_agent.py +0 -0
  222. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/utils.py +0 -0
  223. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/workspace/__init__.py +0 -0
  224. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/workspace/context.py +0 -0
  225. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/workspace/models.py +0 -0
  226. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/workspace/session.py +0 -0
  227. {tsugite_cli-0.14.3 → tsugite_cli-0.16.0}/tsugite/workspace/templates.py +0 -0
@@ -24,6 +24,9 @@ uv run pytest --cov=tsugite --cov-report=html
24
24
  uv run black .
25
25
  uv run ruff check .
26
26
  uv run pylint tsugite
27
+
28
+ # One-time: enable repo pre-commit hook (lint + format on commit)
29
+ git config core.hooksPath .githooks
27
30
  ```
28
31
 
29
32
  ### Testing Agents
@@ -41,6 +44,20 @@ uv run tsu render agent.md "task"
41
44
  uv run tsu validate agents/*.md
42
45
  ```
43
46
 
47
+ ### Reusing tsugite from external coding agents
48
+
49
+ `tsu exec` runs a Python snippet in tsugite's tool namespace, so a tsugite skill's code
50
+ (which calls `read_file`, `http_request`, `get_secret`, ...) runs without the full agent
51
+ loop. Secrets are allowlisted + masked; `--no-network` / `--sandbox` isolate the run. See
52
+ [docs/external-agent-integration.md](docs/external-agent-integration.md) for reusing skills
53
+ and agents from Claude Code, Cursor, or any agent that runs shell commands.
54
+
55
+ ```bash
56
+ echo 'read_file(path="README.md")' | tsu exec -
57
+ tsu exec snippet.py --tools @fs,@http --allow-secret gh-token
58
+ tsu run +<agent> "task" # or reuse a whole agent
59
+ ```
60
+
44
61
  ### Schema Management
45
62
  ```bash
46
63
  # Regenerate JSON schema after modifying AgentConfig
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tsugite-cli
3
- Version: 0.14.3
3
+ Version: 0.16.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
@@ -400,6 +400,8 @@ tsu run +default "task" --sandbox --no-network
400
400
 
401
401
  Filesystem access is limited to the workspace. Network goes through a filtering proxy that only allows domains you specify.
402
402
 
403
+ The daemon can also sandbox its agents (off by default, configured in `daemon.yaml`). See [docs/sandbox.md](docs/sandbox.md).
404
+
403
405
  ## Config and Data Directories
404
406
 
405
407
  All paths follow [XDG Base Directory](https://specifications.freedesktop.org/basedir-spec/latest/) conventions and can be overridden with the standard environment variables.
@@ -129,6 +129,8 @@ tsu run +default "task" --sandbox --no-network
129
129
 
130
130
  Filesystem access is limited to the workspace. Network goes through a filtering proxy that only allows domains you specify.
131
131
 
132
+ The daemon can also sandbox its agents (off by default, configured in `daemon.yaml`). See [docs/sandbox.md](docs/sandbox.md).
133
+
132
134
  ## Config and Data Directories
133
135
 
134
136
  All paths follow [XDG Base Directory](https://specifications.freedesktop.org/basedir-spec/latest/) conventions and can be overridden with the standard environment variables.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tsugite-cli"
3
- version = "0.14.3"
3
+ version = "0.16.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"
@@ -67,6 +67,7 @@ members = ["plugins/*"]
67
67
  tsugite-cli = { workspace = true }
68
68
  tsugite-tmux = { workspace = true }
69
69
  tsugite-acp = { workspace = true }
70
+ tsugite-codex-cli = { workspace = true }
70
71
 
71
72
  [tool.ruff]
72
73
  target-version = "py311"
@@ -142,4 +143,5 @@ dev = [
142
143
  "vulture>=2.14",
143
144
  "tsugite-tmux",
144
145
  "tsugite-acp",
146
+ "tsugite-codex-cli",
145
147
  ]
@@ -1,11 +1,38 @@
1
1
  """Agent inheritance resolution for Tsugite."""
2
2
 
3
+ import importlib.metadata
4
+ import importlib.util
5
+ import logging
3
6
  import os
7
+ from dataclasses import dataclass
8
+ from enum import Enum
9
+ from functools import lru_cache
4
10
  from pathlib import Path
5
11
  from typing import Any, Dict, List, Optional, Set
6
12
 
7
13
  from tsugite.utils import ensure_file_exists
8
14
 
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class AgentDirSource(Enum):
19
+ """Origin of an agent search directory."""
20
+
21
+ WORKSPACE = "workspace" # caller-passed workspace agents/ (mutable)
22
+ PROJECT = "project" # caller-passed project agents/ (mutable; today only http.py)
23
+ BUILTIN = "builtin" # tsugite/builtin_agents/ (read-only)
24
+ PLUGIN = "plugin" # plugin <pkg>/agents/ via tsugite.plugins entry-points (read-only)
25
+ GLOBAL = "global" # XDG global agents/ (mutable)
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class AgentDir:
30
+ """A directory that may contain agent .md files."""
31
+
32
+ path: Path
33
+ source: AgentDirSource
34
+ readonly: bool
35
+
9
36
 
10
37
  def get_builtin_agents_path() -> Path:
11
38
  """Get the built-in agents directory path.
@@ -37,6 +64,113 @@ def get_global_agents_paths() -> List[Path]:
37
64
  return paths
38
65
 
39
66
 
67
+ @lru_cache(maxsize=1)
68
+ def get_plugin_agents_paths() -> List[Path]:
69
+ """Get agent directory paths contributed by installed plugins.
70
+
71
+ Scans the ``tsugite.plugins`` entry-point group; each plugin's package
72
+ may ship an ``agents/`` directory next to its top-level module. Plugin
73
+ agent dirs are treated as read-only.
74
+
75
+ Returns:
76
+ List of plugin-supplied agent directories (deduped, only existing dirs)
77
+ """
78
+ from tsugite.plugins import GROUP_PLUGINS
79
+
80
+ paths: List[Path] = []
81
+ seen: set[Path] = set()
82
+ try:
83
+ eps = importlib.metadata.entry_points(group=GROUP_PLUGINS)
84
+ except Exception as e: # pragma: no cover - defensive
85
+ logger.debug("Failed to scan plugin entry points: %s", e)
86
+ return paths
87
+
88
+ for ep in eps:
89
+ module_name = ep.value.split(":")[0].strip()
90
+ top_level = module_name.split(".")[0]
91
+ try:
92
+ spec = importlib.util.find_spec(top_level)
93
+ except Exception:
94
+ spec = None
95
+ if spec is None or not spec.origin:
96
+ continue
97
+ agents_dir = Path(spec.origin).parent / "agents"
98
+ resolved = agents_dir.resolve()
99
+ if resolved in seen:
100
+ continue
101
+ if not agents_dir.is_dir():
102
+ continue
103
+ seen.add(resolved)
104
+ paths.append(agents_dir)
105
+ return paths
106
+
107
+
108
+ def iter_agent_search_paths(
109
+ *,
110
+ current_agent_dir: Optional[Path] = None,
111
+ workspace: Optional[Any] = None,
112
+ extra_project_dirs: Optional[List[Path]] = None,
113
+ include_local_roots: bool = True,
114
+ ) -> List[AgentDir]:
115
+ """Yield the canonical agent-search order, deduped by resolved path.
116
+
117
+ Order:
118
+
119
+ 1. workspace.agents_dir (if workspace given)
120
+ 2. current_agent_dir / ".tsugite"
121
+ 3. current_agent_dir / ".tsugite" / "agents"
122
+ 4. current_agent_dir / "agents"
123
+ 5. current_agent_dir itself (for `<name>.md` directly in cwd)
124
+ 6. caller-supplied extra project dirs (HTTP adapter feeds per-agent dirs here)
125
+ 7. builtin agents
126
+ 8. XDG global agents (user agents beat plugin agents of the same name)
127
+ 9. plugin-supplied agents
128
+
129
+ include_local_roots: when False, skip entries 2 and 5 (the bare .tsugite/
130
+ and cwd roots). Name-resolution callers want them so `extends: foo` finds a
131
+ sibling foo.md; discovery callers that glob *.md must skip them, otherwise
132
+ every frontmattered note in a docs/notes workspace lists as an agent.
133
+
134
+ Callers that only care about a subset filter on ``AgentDir.source``.
135
+ """
136
+ results: List[AgentDir] = []
137
+ seen: set[Path] = set()
138
+
139
+ def _add(path: Path, source: AgentDirSource, readonly: bool) -> None:
140
+ try:
141
+ resolved = path.resolve()
142
+ except (OSError, RuntimeError):
143
+ return
144
+ if resolved in seen:
145
+ return
146
+ seen.add(resolved)
147
+ results.append(AgentDir(path=path, source=source, readonly=readonly))
148
+
149
+ if workspace is not None and hasattr(workspace, "agents_dir"):
150
+ _add(workspace.agents_dir, AgentDirSource.WORKSPACE, readonly=False)
151
+
152
+ if current_agent_dir is not None:
153
+ if include_local_roots:
154
+ _add(current_agent_dir / ".tsugite", AgentDirSource.PROJECT, readonly=False)
155
+ _add(current_agent_dir / ".tsugite" / "agents", AgentDirSource.PROJECT, readonly=False)
156
+ _add(current_agent_dir / "agents", AgentDirSource.PROJECT, readonly=False)
157
+ if include_local_roots:
158
+ _add(current_agent_dir, AgentDirSource.PROJECT, readonly=False)
159
+
160
+ for extra in extra_project_dirs or []:
161
+ _add(extra, AgentDirSource.PROJECT, readonly=False)
162
+
163
+ _add(get_builtin_agents_path(), AgentDirSource.BUILTIN, readonly=True)
164
+
165
+ for global_dir in get_global_agents_paths():
166
+ _add(global_dir, AgentDirSource.GLOBAL, readonly=False)
167
+
168
+ for plugin_path in get_plugin_agents_paths():
169
+ _add(plugin_path, AgentDirSource.PLUGIN, readonly=True)
170
+
171
+ return results
172
+
173
+
40
174
  def find_agent_file(
41
175
  agent_ref: str,
42
176
  current_agent_dir: Path,
@@ -52,14 +186,8 @@ def find_agent_file(
52
186
  Returns:
53
187
  Path to agent file if found, None otherwise
54
188
 
55
- Search order:
56
- 1. If path-like (contains / or .md), resolve relative to current agent
57
- 2. Workspace agents directory (if workspace provided)
58
- 3. .tsugite/{name}.md (project-local shared)
59
- 4. ./agents/{name}.md (project convention)
60
- 5. ./{name}.md (current directory)
61
- 6. Built-in agents directory (tsugite/builtin_agents/)
62
- 7. Global agent directories (XDG order)
189
+ Search order matches `iter_agent_search_paths`: workspace agents,
190
+ project-local (.tsugite/, agents/, current dir), builtin, global, plugin.
63
191
  """
64
192
  # If it looks like a path, resolve it relative to current agent
65
193
  if "/" in agent_ref or agent_ref.endswith(".md"):
@@ -71,38 +199,12 @@ def find_agent_file(
71
199
  return abs_path.resolve()
72
200
  return None
73
201
 
74
- # Ensure .md extension for name-based lookup
75
202
  agent_name = agent_ref if agent_ref.endswith(".md") else f"{agent_ref}.md"
76
203
 
77
- search_paths = []
78
-
79
- # Workspace agents directory (highest priority)
80
- if workspace and hasattr(workspace, "agents_dir"):
81
- workspace_agents = workspace.agents_dir
82
- if workspace_agents.exists():
83
- search_paths.append(workspace_agents / agent_name)
84
-
85
- # Project-local locations
86
- search_paths.extend(
87
- [
88
- current_agent_dir / ".tsugite" / agent_name,
89
- current_agent_dir / "agents" / agent_name,
90
- current_agent_dir / agent_name,
91
- ]
92
- )
93
-
94
- # Built-in agents directory
95
- builtin_path = get_builtin_agents_path() / agent_name
96
- search_paths.append(builtin_path)
97
-
98
- # Global locations
99
- for global_dir in get_global_agents_paths():
100
- search_paths.append(global_dir / agent_name)
101
-
102
- # Return first existing path
103
- for path in search_paths:
104
- if path.exists():
105
- return path.resolve()
204
+ for entry in iter_agent_search_paths(current_agent_dir=current_agent_dir, workspace=workspace):
205
+ candidate = entry.path / agent_name
206
+ if candidate.exists():
207
+ return candidate.resolve()
106
208
 
107
209
  return None
108
210
 
@@ -3,16 +3,22 @@
3
3
  from tsugite.agent_runner.exec_directives import execute_exec_directives # noqa: F401
4
4
  from tsugite.agent_runner.exec_runner import ExecBlockResult, run_python_block # noqa: F401
5
5
  from tsugite.agent_runner.helpers import ( # noqa: F401
6
+ SandboxContext,
7
+ SandboxToolDeniedError,
6
8
  clear_allowed_agents,
7
9
  clear_current_agent,
10
+ clear_sandbox_context,
8
11
  get_allowed_agents,
9
12
  get_allowed_secrets,
10
13
  get_current_agent,
11
14
  get_display_console,
15
+ get_sandbox_context,
12
16
  get_ui_handler,
17
+ sandbox_context_to_override,
13
18
  set_allowed_agents,
14
19
  set_allowed_secrets,
15
20
  set_current_agent,
21
+ set_sandbox_context,
16
22
  )
17
23
  from tsugite.agent_runner.metrics import StepMetrics, display_step_metrics # noqa: F401
18
24
  from tsugite.agent_runner.models import AgentExecutionResult # noqa: F401
@@ -57,4 +63,10 @@ __all__ = [
57
63
  "set_allowed_secrets",
58
64
  "get_display_console",
59
65
  "get_ui_handler",
66
+ "SandboxContext",
67
+ "SandboxToolDeniedError",
68
+ "get_sandbox_context",
69
+ "set_sandbox_context",
70
+ "clear_sandbox_context",
71
+ "sandbox_context_to_override",
60
72
  ]
@@ -1,18 +1,133 @@
1
1
  """Shared helper functions for agent execution."""
2
2
 
3
3
  import threading
4
- from typing import Any, List, Optional
4
+ from dataclasses import dataclass, field
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING, Any, List, Optional
5
7
 
6
8
  from rich.console import Console
7
9
 
8
10
  from tsugite.console import get_stderr_console
9
11
 
12
+ if TYPE_CHECKING:
13
+ from tsugite.options import ExecutionOptions
14
+
10
15
  # Console for warnings and debug output (stderr)
11
16
  _stderr_console = get_stderr_console()
12
17
 
13
18
  # Thread-local storage for tracking currently executing agent
14
19
  _current_agent_context = threading.local()
15
20
 
21
+ # Thread-local storage for the active sandbox policy. Set per-run (in the same
22
+ # thread as the agent loop and its parent-only tool dispatch) when an agent runs
23
+ # sandboxed; read by host-exec/spawn tools to inherit the sandbox or be denied.
24
+ # Thread-local (like _current_agent_context) so concurrent daemon sessions don't
25
+ # clobber each other.
26
+ _sandbox_context = threading.local()
27
+
28
+
29
+ @dataclass
30
+ class SandboxContext:
31
+ """Effective sandbox policy for the currently executing agent.
32
+
33
+ Presence of a SandboxContext means the agent is running sandboxed; tools read
34
+ it to propagate the same isolation to anything they spawn.
35
+ """
36
+
37
+ allow_domains: List[str] = field(default_factory=list)
38
+ no_network: bool = False
39
+ extra_ro_binds: List[Path] = field(default_factory=list)
40
+ extra_rw_binds: List[Path] = field(default_factory=list)
41
+ workspace_dir: Optional[Path] = None
42
+
43
+
44
+ class SandboxToolDeniedError(RuntimeError):
45
+ """Raised when a host-exec tool is refused because the agent runs sandboxed
46
+ (see the deny_when_sandboxed decorator)."""
47
+
48
+
49
+ def set_sandbox_context(ctx: Optional["SandboxContext"]) -> None:
50
+ """Set (or clear, with None) the active sandbox policy for this thread."""
51
+ _sandbox_context.value = ctx
52
+
53
+
54
+ def get_sandbox_context() -> Optional["SandboxContext"]:
55
+ """Return the active sandbox policy, or None when not running sandboxed."""
56
+ return getattr(_sandbox_context, "value", None)
57
+
58
+
59
+ def clear_sandbox_context() -> None:
60
+ """Clear the active sandbox policy from thread-local storage."""
61
+ if hasattr(_sandbox_context, "value"):
62
+ delattr(_sandbox_context, "value")
63
+
64
+
65
+ def sandbox_context_to_override() -> Optional[dict]:
66
+ """Serialize the active sandbox policy as a metadata override dict, or None.
67
+
68
+ Spawn tools stamp this onto the records they create (sessions, jobs,
69
+ schedules) so the spawned daemon run inherits the same sandbox when it later
70
+ reaches the adapter chokepoint. The shape matches SandboxSettings so it can
71
+ be validated back there; paths are stringified to survive JSON metadata.
72
+ """
73
+ ctx = get_sandbox_context()
74
+ if ctx is None:
75
+ return None
76
+ return {
77
+ "enabled": True,
78
+ "no_network": ctx.no_network,
79
+ "allow_domains": list(ctx.allow_domains),
80
+ "extra_ro_binds": [str(p) for p in ctx.extra_ro_binds],
81
+ "extra_rw_binds": [str(p) for p in ctx.extra_rw_binds],
82
+ }
83
+
84
+
85
+ def build_sandbox_policy(
86
+ exec_options: "ExecutionOptions",
87
+ *,
88
+ workspace_dir: Optional[Path] = None,
89
+ agent_config: Any = None,
90
+ ):
91
+ """Resolve the effective sandbox policy into (SandboxConfig, SandboxContext).
92
+
93
+ Returns (None, None) when the sandbox is off. Shared by the agent runner and
94
+ `tsu exec` so the two never drift. Agent frontmatter (network/sandbox) can only
95
+ tighten the CLI/daemon ceiling, never loosen it.
96
+
97
+ Raises RuntimeError if the sandbox is requested but bwrap is unavailable.
98
+ """
99
+ from tsugite.agent_runner.runner import resolve_effective_sandbox
100
+ from tsugite.core.sandbox import BubblewrapSandbox, SandboxConfig
101
+
102
+ sandbox_on, allow_domains, no_network = resolve_effective_sandbox(
103
+ daemon_enabled=exec_options.sandbox,
104
+ daemon_domains=list(exec_options.allow_domains),
105
+ daemon_no_network=exec_options.no_network,
106
+ fm_network=getattr(agent_config, "network", None),
107
+ fm_sandbox=getattr(agent_config, "sandbox", None),
108
+ )
109
+ if not sandbox_on:
110
+ return None, None
111
+
112
+ if not BubblewrapSandbox.check_available():
113
+ raise RuntimeError("bwrap not found. Install bubblewrap or use --no-sandbox.")
114
+
115
+ ctx = SandboxContext(
116
+ allow_domains=allow_domains,
117
+ no_network=no_network,
118
+ extra_ro_binds=list(exec_options.extra_ro_binds),
119
+ extra_rw_binds=list(exec_options.extra_rw_binds),
120
+ workspace_dir=workspace_dir,
121
+ )
122
+ config = SandboxConfig(
123
+ allowed_domains=ctx.allow_domains,
124
+ no_network=ctx.no_network,
125
+ extra_ro_binds=ctx.extra_ro_binds,
126
+ extra_rw_binds=ctx.extra_rw_binds,
127
+ )
128
+ return config, ctx
129
+
130
+
16
131
  # Module-level storage for allowed agents (single-threaded CLI execution)
17
132
  # Subagents run in separate processes, so this doesn't need to be thread-local
18
133
  _allowed_agents: Optional[List[str]] = None