stirrup 0.1.2__py3-none-any.whl → 0.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,8 @@
2
2
 
3
3
  from pathlib import Path
4
4
 
5
+ from pydantic import ValidationError
6
+
5
7
  try:
6
8
  from e2b import InvalidArgumentException, TimeoutException
7
9
  from e2b.sandbox.filesystem.filesystem import FileType
@@ -13,7 +15,7 @@ except ImportError as e:
13
15
 
14
16
  import logging
15
17
 
16
- from stirrup.constants import SUBMISSION_SANDBOX_TIMEOUT
18
+ from stirrup.constants import SANDBOX_REQUEST_TIMEOUT, SANDBOX_TIMEOUT
17
19
  from stirrup.core.models import ImageContentBlock, Tool, ToolUseCountMetadata
18
20
 
19
21
  from .base import (
@@ -51,7 +53,8 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
51
53
  def __init__(
52
54
  self,
53
55
  *,
54
- timeout: int = SUBMISSION_SANDBOX_TIMEOUT,
56
+ timeout: int = SANDBOX_TIMEOUT,
57
+ request_timeout: int = SANDBOX_REQUEST_TIMEOUT,
55
58
  template: str | None = None,
56
59
  allowed_commands: list[str] | None = None,
57
60
  ) -> None:
@@ -67,6 +70,7 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
67
70
  """
68
71
  super().__init__(allowed_commands=allowed_commands)
69
72
  self._timeout = timeout
73
+ self._request_timeout = request_timeout
70
74
  self._template = template
71
75
  self._sbx: AsyncSandbox | None = None
72
76
 
@@ -109,7 +113,7 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
109
113
  if not await self._sbx.files.exists(path):
110
114
  raise FileNotFoundError(f"File not found: {path}")
111
115
 
112
- file_bytes = await self._sbx.files.read(path, format="bytes")
116
+ file_bytes = await self._sbx.files.read(path, format="bytes", request_timeout=self._request_timeout)
113
117
  return bytes(file_bytes)
114
118
 
115
119
  async def write_file_bytes(self, path: str, content: bytes) -> None:
@@ -126,7 +130,7 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
126
130
  if self._sbx is None:
127
131
  raise RuntimeError("ExecutionEnvironment not started.")
128
132
 
129
- await self._sbx.files.write(path, content)
133
+ await self._sbx.files.write(path, content, request_timeout=self._request_timeout)
130
134
 
131
135
  async def run_command(self, cmd: str, *, timeout: int = SHELL_TIMEOUT) -> CommandResult:
132
136
  """Execute command in E2B execution environment, returning raw CommandResult."""
@@ -146,7 +150,7 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
146
150
  )
147
151
 
148
152
  try:
149
- r = await self._sbx.commands.run(cmd, timeout=timeout)
153
+ r = await self._sbx.commands.run(cmd, timeout=timeout, request_timeout=self._request_timeout)
150
154
 
151
155
  return CommandResult(
152
156
  exit_code=getattr(r, "exit_code", 0),
@@ -231,7 +235,7 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
231
235
  continue
232
236
 
233
237
  # Read file content from execution environment
234
- file_bytes = await self._sbx.files.read(env_path, format="bytes")
238
+ file_bytes = await self._sbx.files.read(env_path, format="bytes", request_timeout=self._request_timeout)
235
239
  content = bytes(file_bytes)
236
240
 
237
241
  # Save with original filename directly in output_dir
@@ -297,6 +301,7 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
297
301
  result = UploadFilesResult()
298
302
 
299
303
  for source in paths:
304
+ original_name = Path(source).name # Get name BEFORE resolve
300
305
  source = Path(source).resolve()
301
306
 
302
307
  if not source.exists():
@@ -306,9 +311,10 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
306
311
 
307
312
  try:
308
313
  if source.is_file():
309
- dest = f"{dest_base}/{source.name}"
314
+ source = Path(source).resolve()
315
+ dest = f"{dest_base}/{original_name}"
310
316
  content = source.read_bytes()
311
- await self._sbx.files.write(dest, content)
317
+ await self._sbx.files.write(dest, content, request_timeout=self._request_timeout)
312
318
  result.uploaded.append(
313
319
  UploadedFile(
314
320
  source_path=source,
@@ -327,7 +333,7 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
327
333
  relative = file_path.relative_to(source)
328
334
  dest = f"{dest_base}/{relative}" if dest_dir else f"{dest_base}/{source.name}/{relative}"
329
335
  content = file_path.read_bytes()
330
- await self._sbx.files.write(dest, content)
336
+ await self._sbx.files.write(dest, content, request_timeout=self._request_timeout)
331
337
  result.uploaded.append(
332
338
  UploadedFile(
333
339
  source_path=file_path,
@@ -360,5 +366,13 @@ class E2BCodeExecToolProvider(CodeExecToolProvider):
360
366
  FileNotFoundError: If file does not exist.
361
367
 
362
368
  """
363
- file_bytes = await self.read_file_bytes(path)
364
- return ImageContentBlock(data=file_bytes)
369
+ if not path.lower().endswith((".png", ".jpg", ".jpeg")):
370
+ raise ValueError(f"Unsupported image type for `{path}`. Only .png, .jpg, or .jpeg are allowed.")
371
+
372
+ try:
373
+ file_bytes = await self.read_file_bytes(path)
374
+ image = ImageContentBlock(data=file_bytes)
375
+ except ValidationError as e:
376
+ raise ValueError("You submitted a corrupt/unsupported image file.") from e
377
+
378
+ return image
@@ -122,7 +122,7 @@ class LocalCodeExecToolProvider(CodeExecToolProvider):
122
122
  if self._temp_base_dir:
123
123
  self._temp_base_dir.mkdir(parents=True, exist_ok=True)
124
124
  self._temp_dir = Path(tempfile.mkdtemp(prefix="local_exec_env_", dir=self._temp_base_dir))
125
- logger.info("Created local execution environment temp directory: %s", self._temp_dir)
125
+ logger.debug("Created local execution environment temp directory: %s", self._temp_dir)
126
126
  return self.get_code_exec_tool(description=self._description)
127
127
 
128
128
  async def __aexit__(
@@ -359,7 +359,7 @@ class LocalCodeExecToolProvider(CodeExecToolProvider):
359
359
 
360
360
  # Move file (overwrites if exists)
361
361
  shutil.move(str(source_path), str(dest_path))
362
- logger.info("Moved file: %s -> %s", source_path, dest_path)
362
+ logger.debug("Moved file: %s -> %s", source_path, dest_path)
363
363
 
364
364
  result.saved.append(
365
365
  SavedFile(
stirrup/tools/finish.py CHANGED
@@ -19,5 +19,5 @@ SIMPLE_FINISH_TOOL: Tool[FinishParams, ToolUseCountMetadata] = Tool[FinishParams
19
19
  name=FINISH_TOOL_NAME,
20
20
  description="Signal task completion with a reason. Use when the task is finished or cannot proceed further. Note that you will need a separate turn to finish.",
21
21
  parameters=FinishParams,
22
- executor=lambda params: ToolResult(content=params.reason, metadata=ToolUseCountMetadata()),
22
+ executor=lambda params: ToolResult(content=params.reason, metadata=ToolUseCountMetadata(), success=True),
23
23
  )
@@ -0,0 +1,130 @@
1
+ """User input tool for interactive clarification during agent execution.
2
+
3
+ This module provides the user_input tool that allows agents to ask questions
4
+ and receive text responses from users during task execution.
5
+
6
+ Example usage:
7
+ from stirrup.clients.chat_completions_client import ChatCompletionsClient
8
+ from stirrup.tools import DEFAULT_TOOLS, USER_INPUT_TOOL
9
+
10
+ client = ChatCompletionsClient(model="gpt-5")
11
+ agent = Agent(
12
+ client=client,
13
+ name="assistant",
14
+ tools=[*DEFAULT_TOOLS, USER_INPUT_TOOL],
15
+ )
16
+
17
+ async with agent.session() as session:
18
+ await session.run("Help me configure my project")
19
+ """
20
+
21
+ from typing import Annotated, Literal
22
+
23
+ from pydantic import BaseModel, Field
24
+ from rich.panel import Panel
25
+ from rich.prompt import Confirm, Prompt
26
+
27
+ from stirrup.core.models import Tool, ToolResult, ToolUseCountMetadata
28
+ from stirrup.utils.logging import AgentLoggerBase, console
29
+
30
+ __all__ = ["USER_INPUT_TOOL", "UserInputParams"]
31
+
32
+
33
+ class UserInputParams(BaseModel):
34
+ """Parameters for asking the user a single question.
35
+
36
+ Supports three question types:
37
+ - "text": Free-form text input (default)
38
+ - "choice": Multiple choice from a list of options
39
+ - "confirm": Yes/no confirmation
40
+ """
41
+
42
+ question: Annotated[str, Field(description="A single question to ask the user (*not* multiple questions)")]
43
+ question_type: Annotated[
44
+ Literal["text", "choice", "confirm"],
45
+ Field(
46
+ default="text",
47
+ description="Type of question: 'text' for free-form, 'choice' for multiple choice, 'confirm' for yes/no",
48
+ ),
49
+ ]
50
+ choices: Annotated[
51
+ list[str] | None,
52
+ Field(default=None, description="List of valid choices (required when question_type is 'choice')"),
53
+ ]
54
+ default: Annotated[
55
+ str,
56
+ Field(default="", description="Default value if user presses Enter without input"),
57
+ ]
58
+
59
+
60
+ def _get_logger() -> "AgentLoggerBase | None":
61
+ """Get the current session's logger for pause/resume.
62
+
63
+ Returns the logger from SessionState if available, None otherwise.
64
+ """
65
+ from stirrup.core.agent import _SESSION_STATE
66
+
67
+ state = _SESSION_STATE.get(None)
68
+ return state.logger if state else None
69
+
70
+
71
+ def user_input_executor(params: UserInputParams) -> ToolResult[ToolUseCountMetadata]:
72
+ """Prompt the user for input and return their response."""
73
+ logger = _get_logger()
74
+
75
+ # Pause spinner before prompting
76
+ if logger:
77
+ logger.pause_live()
78
+
79
+ try:
80
+ # Print newline to separate from spinner, then display question in a styled panel
81
+ console.print()
82
+ panel = Panel(
83
+ params.question,
84
+ title="[bold cyan]🤔 Agent Question[/]",
85
+ title_align="left",
86
+ border_style="cyan",
87
+ padding=(0, 1),
88
+ )
89
+ console.print(panel)
90
+
91
+ # Get user input based on question type
92
+ if params.question_type == "confirm":
93
+ # Yes/no confirmation
94
+ default_bool = params.default.lower() in ("yes", "y", "true", "1") if params.default else False
95
+ result = Confirm.ask("[bold]Your answer[/]", default=default_bool, console=console)
96
+ answer = "yes" if result else "no"
97
+
98
+ elif params.question_type == "choice" and params.choices:
99
+ # Multiple choice
100
+ answer = Prompt.ask(
101
+ "[bold]Your answer[/]",
102
+ choices=params.choices,
103
+ default=params.default,
104
+ console=console,
105
+ )
106
+
107
+ else:
108
+ # Free-form text (default)
109
+ answer = Prompt.ask("[bold]Your answer[/]", default=params.default or "", console=console)
110
+
111
+ return ToolResult(content=answer, metadata=ToolUseCountMetadata())
112
+
113
+ finally:
114
+ # Always resume spinner, even if an exception occurs
115
+ if logger:
116
+ logger.resume_live()
117
+
118
+
119
+ USER_INPUT_TOOL: Tool[UserInputParams, ToolUseCountMetadata] = Tool(
120
+ name="user_input",
121
+ description=(
122
+ "Ask the user a question when you need clarification or are uncertain. "
123
+ "Supports three types: 'text' for free-form input, 'choice' for multiple choice "
124
+ "(provide choices list), 'confirm' for yes/no questions. Returns the user's response."
125
+ "There should only EVER be one question per call to this tool."
126
+ "If you need to ask multiple questions, you should call this tool multiple times."
127
+ ),
128
+ parameters=UserInputParams,
129
+ executor=user_input_executor,
130
+ )
stirrup/tools/web.py CHANGED
@@ -125,6 +125,7 @@ def _get_fetch_web_page_tool(client: httpx.AsyncClient | None = None) -> Tool[Fe
125
125
  return ToolResult(
126
126
  content=f"<web_fetch><url>{params.url}</url><error>"
127
127
  f"{truncate_msg(str(exc), MAX_LENGTH_WEB_FETCH_HTML)}</error></web_fetch>",
128
+ success=False,
128
129
  metadata=WebFetchMetadata(pages_fetched=[params.url]),
129
130
  )
130
131
 
stirrup/utils/logging.py CHANGED
@@ -27,6 +27,7 @@ from stirrup.core.models import AssistantMessage, ToolMessage, UserMessage, _agg
27
27
  __all__ = [
28
28
  "AgentLogger",
29
29
  "AgentLoggerBase",
30
+ "console",
30
31
  ]
31
32
 
32
33
  # Shared console instance
@@ -247,6 +248,12 @@ class AgentLoggerBase(ABC):
247
248
  """Log an error message."""
248
249
  ...
249
250
 
251
+ def pause_live(self) -> None: # noqa: B027
252
+ """Pause live display (e.g., spinner) before user interaction."""
253
+
254
+ def resume_live(self) -> None: # noqa: B027
255
+ """Resume live display after user interaction."""
256
+
250
257
 
251
258
  class AgentLogger(AgentLoggerBase):
252
259
  """Rich console logger for agent workflows.
@@ -655,6 +662,23 @@ class AgentLogger(AgentLoggerBase):
655
662
  if self._live:
656
663
  self._live.update(self._make_spinner())
657
664
 
665
+ def pause_live(self) -> None:
666
+ """Pause the live spinner display.
667
+
668
+ Call this before prompting for user input to prevent the spinner
669
+ from interfering with the input prompt.
670
+ """
671
+ if self._live is not None:
672
+ self._live.stop()
673
+
674
+ def resume_live(self) -> None:
675
+ """Resume the live spinner display.
676
+
677
+ Call this after user input is complete to restart the spinner.
678
+ """
679
+ if self._live is not None:
680
+ self._live.start()
681
+
658
682
  def set_level(self, level: int) -> None:
659
683
  """Set the logging level."""
660
684
  self._level = level
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: stirrup
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: The lightweight foundation for building agents
5
5
  Keywords: ai,agent,llm,openai,anthropic,tools,framework
6
6
  Author: Artificial Analysis, Inc.
@@ -0,0 +1,36 @@
1
+ stirrup/__init__.py,sha256=4p5Rw7f_wdxVu1FJTgJROe0aTlnc8tOsainBEzDRGEY,1905
2
+ stirrup/clients/__init__.py,sha256=oO8VMHmbUhoxFyC0JLQs_kUFNSRlvTj5xz0FgzBb98E,405
3
+ stirrup/clients/chat_completions_client.py,sha256=P0VOGFQcfLIVf7zCGYbopQDjNVEJlpeLBeGvB87sQQg,7390
4
+ stirrup/clients/litellm_client.py,sha256=2ZrZKKAEV2dEFs6ze4qBl3it5VwiGH8s7wHVuHdw-uY,5507
5
+ stirrup/clients/utils.py,sha256=Z_8KiENDZVD9fChUm7PA-RLhvoChswHVQsjrHXlIfkg,5684
6
+ stirrup/constants.py,sha256=WpVPm2jRN2AqYMyoMYeJimiggoquP7M3IrcHNpduFF4,644
7
+ stirrup/core/__init__.py,sha256=ReBVl7B9h_FNkZ77vCx2xlfuK1JuQ0yTSXrEgc4tONU,39
8
+ stirrup/core/agent.py,sha256=2KxjuCy3scLRc59habPSZyYDCHL9a9--UcdFKYV-86w,56907
9
+ stirrup/core/cache.py,sha256=lAbbBJzgYInewoBBPMzNooroU2Q7JbF21riggfdIDa8,16697
10
+ stirrup/core/exceptions.py,sha256=CzLVAi7Ns-t9BWSkqQUCB7ypVHAesV2s4a09-i0NXyQ,213
11
+ stirrup/core/models.py,sha256=qMmpecRIl8sKdUePtoVChKA3rBHxzG8lI4ZvuLFHNzo,22553
12
+ stirrup/prompts/__init__.py,sha256=e4bpTktBaFPuO_bIW5DelGNWtT6_NIUqnD2lRv8n89I,796
13
+ stirrup/prompts/base_system_prompt.txt,sha256=D_UlDWEnG2yaPCMFrE7IqMHI8VCzi4BZ-GnuL3qs5q4,288
14
+ stirrup/prompts/message_summarizer.txt,sha256=uQoTxreMuC42rTGSZmoH1Dnj06WrEQb0gLkDvVMhosQ,1173
15
+ stirrup/prompts/message_summarizer_bridge.txt,sha256=sWbfnHtI6RWemBIyQsnqHMGpnU-E6FTbfUC6rvkEHLY,372
16
+ stirrup/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ stirrup/skills/__init__.py,sha256=BEcmdSskfBzx_QK4eKXECucndIKRjHXzzwwwsaez8k4,700
18
+ stirrup/skills/skills.py,sha256=qhA3HI55kaRqLyvn_56Cs71833Xacg-8qP7muHrwruE,4282
19
+ stirrup/tools/__init__.py,sha256=l8bKHZwPBZfBPgxQfLzOKyyzdfoF86NrgdmxhjYW09s,2790
20
+ stirrup/tools/calculator.py,sha256=Cckt-8TtltxtuyY_Hh0wOr8efUzBZzg7rG4dBpvpuRM,1293
21
+ stirrup/tools/code_backends/__init__.py,sha256=O3Rs76r0YcQ27voTrx_zuhIEFawK3b1TQdKi70MORG8,987
22
+ stirrup/tools/code_backends/base.py,sha256=5mQxbHoOvtTdiF1y1aVe4csodaCOGvHaVN5viRnUlUE,17250
23
+ stirrup/tools/code_backends/docker.py,sha256=Xx4aBZ1uXVznP0qV4tXL2PMMs-8QEPw1bIPvgPasEGk,30281
24
+ stirrup/tools/code_backends/e2b.py,sha256=4I5Aqas-4PVhYgMhIAQh3xcwl_0kGiUpo64UU9-awfE,14921
25
+ stirrup/tools/code_backends/local.py,sha256=snzbWHnZkVu1ibLLRyRVmi6U9-8407_A0sKbdDNyTe4,19600
26
+ stirrup/tools/finish.py,sha256=zdljSs77zMVkA2AGUfV05QmeLxz6JrwJF_ar02yvsVA,950
27
+ stirrup/tools/mcp.py,sha256=4wWYae95y8Bs7e36hHwnxRfVVj0PABrsRStw492lLaw,18749
28
+ stirrup/tools/user_input.py,sha256=XwK14FvRQly3vGwgNzPVGoSXfbco0WWaSVpTDyjV09E,4508
29
+ stirrup/tools/view_image.py,sha256=zazCpZMtLOD6lplLPYGNQ8JeYfc0oUDJoUUyVAp3AMU,3126
30
+ stirrup/tools/web.py,sha256=B-zp5i1WhjOOMAlYtnvU3N5hNYnJYm8qVXAtNx_ZaRw,12292
31
+ stirrup/utils/__init__.py,sha256=4kcuExrphSXqgxRgu1q8_Z6Rrb9aAZpIo4Xq4S9Twuk,230
32
+ stirrup/utils/logging.py,sha256=48rCzv7B15jiVM1JlKzwTSnGodbqGSKLlsZi9xRMmVM,35045
33
+ stirrup/utils/text.py,sha256=3lGlcXFzQ-Mclsbu7wJciG3CcHvQ_Sk98tqOZxYLlGw,479
34
+ stirrup-0.1.3.dist-info/WHEEL,sha256=KSLUh82mDPEPk0Bx0ScXlWL64bc8KmzIPNcpQZFV-6E,79
35
+ stirrup-0.1.3.dist-info/METADATA,sha256=EyJEJTRFzLoSXnkBPauFvskBgMwJDJ8O2Vm8HdJPdsI,12862
36
+ stirrup-0.1.3.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.18
2
+ Generator: uv 0.9.22
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,34 +0,0 @@
1
- stirrup/__init__.py,sha256=Dol0Uui-gBslpplfEUrgxkqqJWr1ucZJej5finNvCeI,1869
2
- stirrup/clients/__init__.py,sha256=oO8VMHmbUhoxFyC0JLQs_kUFNSRlvTj5xz0FgzBb98E,405
3
- stirrup/clients/chat_completions_client.py,sha256=p_EeXqRuo6mWnzgMhy22SWdHE6_OXIRyABvCKgHdnu4,7586
4
- stirrup/clients/litellm_client.py,sha256=J-HDv7ZZTkNYC-aeSNyd7xTDd_5r8DEeXOPz9eQMC7A,4985
5
- stirrup/clients/utils.py,sha256=Yyeh6unQSvqgDTDhjpD5DoRu_wP_nWfsNv9DGXQwgo8,5452
6
- stirrup/constants.py,sha256=h3NzsePJ4FKpImTpV5xtFeJarKb67jR_6n89tNOkQYs,523
7
- stirrup/core/__init__.py,sha256=ReBVl7B9h_FNkZ77vCx2xlfuK1JuQ0yTSXrEgc4tONU,39
8
- stirrup/core/agent.py,sha256=tt1V564B6n_C0ffyH24LuC6PhE4EJA8NOolQCxDG9iw,50836
9
- stirrup/core/exceptions.py,sha256=CzLVAi7Ns-t9BWSkqQUCB7ypVHAesV2s4a09-i0NXyQ,213
10
- stirrup/core/models.py,sha256=KAyjJIoqbhgy_MN0sPwGI8XWxSiziPnyndMD05ylj_U,21121
11
- stirrup/prompts/__init__.py,sha256=e4bpTktBaFPuO_bIW5DelGNWtT6_NIUqnD2lRv8n89I,796
12
- stirrup/prompts/base_system_prompt.txt,sha256=KZ2_JhJ91u4oMqRZvhuAp99nb6ZkXkJdVbIRN6drVME,348
13
- stirrup/prompts/message_summarizer.txt,sha256=uQoTxreMuC42rTGSZmoH1Dnj06WrEQb0gLkDvVMhosQ,1173
14
- stirrup/prompts/message_summarizer_bridge.txt,sha256=sWbfnHtI6RWemBIyQsnqHMGpnU-E6FTbfUC6rvkEHLY,372
15
- stirrup/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- stirrup/skills/__init__.py,sha256=BEcmdSskfBzx_QK4eKXECucndIKRjHXzzwwwsaez8k4,700
17
- stirrup/skills/skills.py,sha256=qhA3HI55kaRqLyvn_56Cs71833Xacg-8qP7muHrwruE,4282
18
- stirrup/tools/__init__.py,sha256=ohyeMvXb6oURiAyoHi0VmC9ksZSRyGleT341VNzHCy4,2714
19
- stirrup/tools/calculator.py,sha256=JkuGmGZJtaKbC4vHVrIph4aTjlGcFMhhv5MB1ntqgv4,1278
20
- stirrup/tools/code_backends/__init__.py,sha256=O3Rs76r0YcQ27voTrx_zuhIEFawK3b1TQdKi70MORG8,987
21
- stirrup/tools/code_backends/base.py,sha256=Nx0tTDX4GKoBWQK2F953vSsFgWCcOd_1WNtYCA4FG4o,17021
22
- stirrup/tools/code_backends/docker.py,sha256=Xx4aBZ1uXVznP0qV4tXL2PMMs-8QEPw1bIPvgPasEGk,30281
23
- stirrup/tools/code_backends/e2b.py,sha256=7wV1SOu4S5g5uCtnipC1xNg8kBzCrudyIEOIkf-JCkE,14072
24
- stirrup/tools/code_backends/local.py,sha256=WV-MMcPY5ooKPhOwd3JUUz718Ht8eRyYklGAZ0gkrx4,19598
25
- stirrup/tools/finish.py,sha256=K_NxwOwdvncT2QTua2A_8lZ9MwK4WQQ5FL2gdUrE29c,936
26
- stirrup/tools/mcp.py,sha256=4wWYae95y8Bs7e36hHwnxRfVVj0PABrsRStw492lLaw,18749
27
- stirrup/tools/view_image.py,sha256=zazCpZMtLOD6lplLPYGNQ8JeYfc0oUDJoUUyVAp3AMU,3126
28
- stirrup/tools/web.py,sha256=2yfBJsu8GgFI7Oh1dFlXwNaXth6WQfGpbJFU-rV-yuI,12261
29
- stirrup/utils/__init__.py,sha256=4kcuExrphSXqgxRgu1q8_Z6Rrb9aAZpIo4Xq4S9Twuk,230
30
- stirrup/utils/logging.py,sha256=3Li6MhjLJWwaXHDZ06EjgW42XDL6V3ZDt9rccV_ZYZ4,34292
31
- stirrup/utils/text.py,sha256=3lGlcXFzQ-Mclsbu7wJciG3CcHvQ_Sk98tqOZxYLlGw,479
32
- stirrup-0.1.2.dist-info/WHEEL,sha256=ZyFSCYkV2BrxH6-HRVRg3R9Fo7MALzer9KiPYqNxSbo,79
33
- stirrup-0.1.2.dist-info/METADATA,sha256=eIK1F1yXhCgFspDO8q5J48B7P3Jt16QZez3ZBVFU5n8,12862
34
- stirrup-0.1.2.dist-info/RECORD,,