petsitter 0.1.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.
@@ -0,0 +1,138 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ log.txt
3
+ __pycache__/
4
+ *.py[cod]
5
+ *$py.class
6
+
7
+ # C extensions
8
+ *.so
9
+
10
+ # Distribution / packaging
11
+ .Python
12
+ build/
13
+ develop-eggs/
14
+ dist/
15
+ downloads/
16
+ eggs/
17
+ .eggs/
18
+ lib/
19
+ lib64/
20
+ parts/
21
+ sdist/
22
+ var/
23
+ wheels/
24
+ pip-wheel-metadata/
25
+ share/python-wheels/
26
+ *.egg-info/
27
+ .installed.cfg
28
+ *.egg
29
+ MANIFEST
30
+
31
+ # PyInstaller
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+
53
+ # Translations
54
+ *.mo
55
+ *.pot
56
+
57
+ # Django stuff:
58
+ *.log
59
+ local_settings.py
60
+ db.sqlite3
61
+ db.sqlite3
62
+
63
+ # Flask stuff:
64
+ instance/
65
+ .webassets-cache
66
+
67
+ # Scrapy stuff:
68
+ .scrapy
69
+
70
+ # Sphinx documentation
71
+ docs/_build/
72
+
73
+ # PyBuilder
74
+ target/
75
+
76
+ # Jupyter Notebook
77
+ .ipynb_checkpoints
78
+
79
+ # IPython
80
+ profile_default/
81
+ ipython_config.py
82
+
83
+ # pyenv
84
+ .python-version
85
+
86
+ # pipenv
87
+ Pipfile.lock
88
+
89
+ # PEP 582
90
+ __pypackages__/
91
+
92
+ # Celery stuff
93
+ celerybeat-schedule
94
+ celerybeat.pid
95
+
96
+ # SageMath parsed files
97
+ *.sage.py
98
+
99
+ # Environments
100
+ .env
101
+ .venv
102
+ env/
103
+ venv/
104
+ ENV/
105
+ env.bak/
106
+ venv.bak/
107
+
108
+ # Spyder project settings
109
+ .spyderproject
110
+ .spyproject
111
+
112
+ # Rope project settings
113
+ .ropeproject
114
+
115
+ # mkdocs documentation
116
+ /site
117
+
118
+ # mypy
119
+ .mypy_cache/
120
+ .dmypy.json
121
+ dmypy.json
122
+
123
+ # Pyre type checker
124
+ .pyre/
125
+
126
+ # IDEs
127
+ .vscode/
128
+ .idea/
129
+ *.swp
130
+ *.swo
131
+ *~
132
+ .DS_Store
133
+
134
+ # PetSitter specific
135
+ *.log
136
+ petsitter.egg-info/
137
+ /harness/models/*/
138
+ !/harness/models/.gitkeep
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Chris McKenzie
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,280 @@
1
+ Metadata-Version: 2.4
2
+ Name: petsitter
3
+ Version: 0.1.0
4
+ Summary: OpenAI-compatible proxy that adds functionality to models through tricks
5
+ License-File: LICENSE.MIT
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: click>=8.1.0
8
+ Requires-Dist: httpx>=0.25.0
9
+ Requires-Dist: pydantic>=2.0.0
10
+ Requires-Dist: starlette>=0.34.0
11
+ Requires-Dist: uvicorn>=0.24.0
12
+ Provides-Extra: test
13
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'test'
14
+ Requires-Dist: pytest>=7.0.0; extra == 'test'
15
+ Description-Content-Type: text/markdown
16
+
17
+ # Petsitter
18
+
19
+ **Teach old models new tricks.**
20
+
21
+ Petsitter is an OpenAI-compatible proxy that layers smart harnesses on top of language models, giving them capabilities they don't natively have. Smaller models can't do tool calling? Petsitter tricks them into it. Need structured JSON output? Petsitter will loop until it gets it right.
22
+
23
+ But that's only the beginning. Cyclomatic complexity? Halstead metrics? Chidamber and Kemerer? Why not!
24
+
25
+ ## Who Is This For?
26
+
27
+ - **You run local models** (Ollama, llama.cpp, vllm, sglang) and miss OpenAI's features
28
+ - **You use small/cheap models** that lack tool calling or JSON mode
29
+ - **You build agentic systems** that need consistent capabilities across different models
30
+ - **You want to experiment** with prompt engineering tricks without changing your application code
31
+
32
+ ## What Does It Do?
33
+
34
+ Petsitter sits between your application and your model, intercepting requests and responses to apply "tricks" — pluggable transformations that add functionality through:
35
+
36
+ 1. **Prompt engineering** — Inject instructions and tool definitions
37
+ 2. **Context manipulation** — Modify messages before/after the model sees them
38
+ 3. **Retry loops** — Call the model again if output doesn't meet requirements
39
+ 4. **Response transformation** — Convert outputs to expected formats (e.g., OpenAI tool_calls)
40
+
41
+ ## Why Use It?
42
+
43
+ - **No model changes required** — Works with any OpenAI-compatible endpoint
44
+ - **Pluggable architecture** — Write your own tricks in Python
45
+ - **Transparent to your app** — Point your existing code at petsitter instead of the model
46
+ - **Mix and match** — Combine multiple tricks for compound effects
47
+
48
+ ---
49
+
50
+ ## Installation
51
+
52
+ ```bash
53
+ # Create virtual environment
54
+ uv venv
55
+
56
+ # Activate it
57
+ source .venv/bin/activate
58
+
59
+ # Install petsitter
60
+ pip install -e .
61
+ ```
62
+
63
+ ## Quick Start
64
+
65
+ ```bash
66
+ # Start your model backend (e.g., Ollama)
67
+ ollama serve
68
+
69
+ # Activate the virtual environment
70
+ source .venv/bin/activate
71
+
72
+ # Run petsitter with tricks
73
+ ./petsitter --model_url http://localhost:11434 \
74
+ --model_name llama3:8b \
75
+ --trick tricks/json_mode.py \
76
+ --trick tricks/tool_call.py \
77
+ --listen_on localhost:8080
78
+ ```
79
+
80
+ Now point your AI applications to `http://localhost:8080/v1`.
81
+
82
+ ## CLI Options
83
+
84
+ | Option | Required | Description |
85
+ |--------|----------|-------------|
86
+ | `--model_url` | Yes | Base URL of upstream model (e.g., `http://localhost:11434`) |
87
+ | `--model_name` | No | Model name (optional for vllm, sglang, llama.cpp) |
88
+ | `--api_key` | No | API key for upstream (if required) |
89
+ | `--trick` | No | Path to a trick module (can be repeated) |
90
+ | `--listen_on` | No | Host:port to listen on (default: `localhost:8080`) |
91
+
92
+ ## Built-in Tricks
93
+
94
+ ### JSON Mode (`tricks/json_mode.py`)
95
+
96
+ Enforces valid JSON output by:
97
+ - Adding formatting instructions to the system prompt
98
+ - Retrying with feedback if response isn't valid JSON
99
+ - Stripping markdown code blocks
100
+
101
+ ```bash
102
+ ./petsitter --model_url http://localhost:11434 --trick tricks/json_mode.py
103
+ ```
104
+
105
+ ### Tool Calling (`tricks/tool_call.py`)
106
+
107
+ Enables tool calling for models without native support:
108
+ - Injects tool definitions into prompts
109
+ - Parses JSONRPC-style tool call responses
110
+ - Converts to OpenAI `tool_calls` format
111
+
112
+ ```bash
113
+ ./petsitter --model_url http://localhost:11434 --trick tricks/tool_call.py
114
+ ```
115
+
116
+ ### List Files (`tricks/list_files.py`)
117
+
118
+ Test trick that provides a `list_files` tool. Useful for testing tool calling functionality.
119
+
120
+ ## Creating Custom Tricks
121
+
122
+ The `Trick` class has four hooks you can implement. Each hook is optional — only implement what you need.
123
+
124
+ ### `system_prompt(to_add: str) -> str`
125
+
126
+ **When:** Called once per request, before any messages are sent to the model.
127
+
128
+ **Purpose:** Append instructions to the system prompt. This is how you "prime" the model to behave a certain way.
129
+
130
+ **Example:**
131
+ ```python
132
+ def system_prompt(self, to_add: str) -> str:
133
+ return "IMPORTANT: Respond only in valid JSON. No markdown, no explanations."
134
+ ```
135
+
136
+ ### `pre_hook(context: list, params: dict) -> list`
137
+
138
+ **When:** Called after the system prompt is set, before the model receives the messages.
139
+
140
+ **Purpose:** Modify the conversation context. You can inject tool definitions, add few-shot examples, or restructure messages.
141
+
142
+ **Parameters:**
143
+ - `context`: List of message dicts (`[{"role": "user", "content": "..."}]`)
144
+ - `params`: Request parameters including `tools`, `temperature`, etc.
145
+
146
+ **Example:**
147
+ ```python
148
+ def pre_hook(self, context: list, params: dict) -> list:
149
+ if "tools" in params:
150
+ # Inject tool definitions into system prompt
151
+ tools_json = json.dumps(params["tools"])
152
+ context[0]["content"] += f"\n\nAvailable tools: {tools_json}"
153
+ return context
154
+ ```
155
+
156
+ ### `post_hook(context: list) -> list`
157
+
158
+ **When:** Called after the model responds, before the response goes back to your application.
159
+
160
+ **Purpose:** Validate, transform, or retry. This is where you can:
161
+ - Parse the response and convert it to a different format
162
+ - Detect when the model failed and call it again with feedback
163
+ - Extract tool calls from natural language
164
+
165
+ **Example (JSON validation with retry):**
166
+ ```python
167
+ def post_hook(self, context: list) -> list:
168
+ attempts = 3
169
+ while attempts > 0:
170
+ try:
171
+ json.loads(context[-1]["content"])
172
+ break # Valid JSON, we're done
173
+ except json.JSONDecodeError:
174
+ attempts -= 1
175
+ if attempts == 0:
176
+ break
177
+ # Retry with feedback
178
+ context = callmodel(context, "That wasn't valid JSON. Try again.")
179
+ return context
180
+ ```
181
+
182
+ **Example (Tool call detection):**
183
+ ```python
184
+ def post_hook(self, context: list) -> list:
185
+ content = context[-1]["content"]
186
+ if self._looks_like_tool_call(content):
187
+ # Convert to OpenAI tool_calls format
188
+ context[-1]["tool_calls"] = [self._parse_tool_call(content)]
189
+ context[-1]["content"] = None
190
+ return context
191
+ ```
192
+
193
+ ### `info(capabilities: dict) -> dict`
194
+
195
+ **When:** Called when building the response to your application.
196
+
197
+ **Purpose:** Declare what capabilities this trick provides. Some frameworks check for capabilities before using certain features.
198
+
199
+ **Example:**
200
+ ```python
201
+ def info(self, capabilities: dict) -> dict:
202
+ capabilities["json_mode"] = True
203
+ capabilities["tools_support"] = True
204
+ return capabilities
205
+ ```
206
+
207
+ ## Full Trick Example
208
+
209
+ Here's a trick that makes any model respond in haiku:
210
+
211
+ ```python
212
+ from src.trick import Trick
213
+
214
+ class HaikuTrick(Trick):
215
+ """Force the model to respond only in haiku."""
216
+
217
+ def system_prompt(self, to_add: str) -> str:
218
+ return (
219
+ "You must respond only in haiku (5-7-5 syllables). "
220
+ "No explanations, no extra text. Just haiku."
221
+ )
222
+
223
+ def post_hook(self, context: list) -> list:
224
+ # Could add syllable counting and retry here
225
+ return context
226
+
227
+ def info(self, capabilities: dict) -> dict:
228
+ capabilities["haiku_mode"] = True
229
+ return capabilities
230
+ ```
231
+
232
+ Use it:
233
+ ```bash
234
+ ./petsitter --model_url http://localhost:11434 --trick haiku.py
235
+ ```
236
+
237
+ ## API Endpoints
238
+
239
+ Petsitter exposes OpenAI-compatible endpoints:
240
+
241
+ - `POST /v1/chat/completions` - Chat completions (proxied + transformed)
242
+ - `GET /v1/models` - List available models (proxied)
243
+ - `GET /health` - Health check
244
+
245
+ ## Running Tests
246
+
247
+ ```bash
248
+ # Activate virtual environment
249
+ source .venv/bin/activate
250
+
251
+ # Install test dependencies
252
+ pip install -e ".[test]"
253
+
254
+ # Run tests
255
+ pytest tests/
256
+ ```
257
+
258
+ ## Example: Using with an Agentic Framework
259
+
260
+ ```python
261
+ from openai import OpenAI
262
+
263
+ # Point to petsitter instead of directly to the model
264
+ client = OpenAI(
265
+ base_url="http://localhost:8080/v1",
266
+ api_key="not-needed"
267
+ )
268
+
269
+ response = client.chat.completions.create(
270
+ model="any-model-name",
271
+ messages=[{"role": "user", "content": "List files in /tmp"}],
272
+ tools=[{"type": "function", "function": {"name": "list_files", ...}}]
273
+ )
274
+
275
+ # With tool_call trick, even small models can use tools!
276
+ ```
277
+
278
+ ## License
279
+
280
+ MIT
@@ -0,0 +1,264 @@
1
+ # Petsitter
2
+
3
+ **Teach old models new tricks.**
4
+
5
+ Petsitter is an OpenAI-compatible proxy that layers smart harnesses on top of language models, giving them capabilities they don't natively have. Smaller models can't do tool calling? Petsitter tricks them into it. Need structured JSON output? Petsitter will loop until it gets it right.
6
+
7
+ But that's only the beginning. Cyclomatic complexity? Halstead metrics? Chidamber and Kemerer? Why not!
8
+
9
+ ## Who Is This For?
10
+
11
+ - **You run local models** (Ollama, llama.cpp, vllm, sglang) and miss OpenAI's features
12
+ - **You use small/cheap models** that lack tool calling or JSON mode
13
+ - **You build agentic systems** that need consistent capabilities across different models
14
+ - **You want to experiment** with prompt engineering tricks without changing your application code
15
+
16
+ ## What Does It Do?
17
+
18
+ Petsitter sits between your application and your model, intercepting requests and responses to apply "tricks" — pluggable transformations that add functionality through:
19
+
20
+ 1. **Prompt engineering** — Inject instructions and tool definitions
21
+ 2. **Context manipulation** — Modify messages before/after the model sees them
22
+ 3. **Retry loops** — Call the model again if output doesn't meet requirements
23
+ 4. **Response transformation** — Convert outputs to expected formats (e.g., OpenAI tool_calls)
24
+
25
+ ## Why Use It?
26
+
27
+ - **No model changes required** — Works with any OpenAI-compatible endpoint
28
+ - **Pluggable architecture** — Write your own tricks in Python
29
+ - **Transparent to your app** — Point your existing code at petsitter instead of the model
30
+ - **Mix and match** — Combine multiple tricks for compound effects
31
+
32
+ ---
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ # Create virtual environment
38
+ uv venv
39
+
40
+ # Activate it
41
+ source .venv/bin/activate
42
+
43
+ # Install petsitter
44
+ pip install -e .
45
+ ```
46
+
47
+ ## Quick Start
48
+
49
+ ```bash
50
+ # Start your model backend (e.g., Ollama)
51
+ ollama serve
52
+
53
+ # Activate the virtual environment
54
+ source .venv/bin/activate
55
+
56
+ # Run petsitter with tricks
57
+ ./petsitter --model_url http://localhost:11434 \
58
+ --model_name llama3:8b \
59
+ --trick tricks/json_mode.py \
60
+ --trick tricks/tool_call.py \
61
+ --listen_on localhost:8080
62
+ ```
63
+
64
+ Now point your AI applications to `http://localhost:8080/v1`.
65
+
66
+ ## CLI Options
67
+
68
+ | Option | Required | Description |
69
+ |--------|----------|-------------|
70
+ | `--model_url` | Yes | Base URL of upstream model (e.g., `http://localhost:11434`) |
71
+ | `--model_name` | No | Model name (optional for vllm, sglang, llama.cpp) |
72
+ | `--api_key` | No | API key for upstream (if required) |
73
+ | `--trick` | No | Path to a trick module (can be repeated) |
74
+ | `--listen_on` | No | Host:port to listen on (default: `localhost:8080`) |
75
+
76
+ ## Built-in Tricks
77
+
78
+ ### JSON Mode (`tricks/json_mode.py`)
79
+
80
+ Enforces valid JSON output by:
81
+ - Adding formatting instructions to the system prompt
82
+ - Retrying with feedback if response isn't valid JSON
83
+ - Stripping markdown code blocks
84
+
85
+ ```bash
86
+ ./petsitter --model_url http://localhost:11434 --trick tricks/json_mode.py
87
+ ```
88
+
89
+ ### Tool Calling (`tricks/tool_call.py`)
90
+
91
+ Enables tool calling for models without native support:
92
+ - Injects tool definitions into prompts
93
+ - Parses JSONRPC-style tool call responses
94
+ - Converts to OpenAI `tool_calls` format
95
+
96
+ ```bash
97
+ ./petsitter --model_url http://localhost:11434 --trick tricks/tool_call.py
98
+ ```
99
+
100
+ ### List Files (`tricks/list_files.py`)
101
+
102
+ Test trick that provides a `list_files` tool. Useful for testing tool calling functionality.
103
+
104
+ ## Creating Custom Tricks
105
+
106
+ The `Trick` class has four hooks you can implement. Each hook is optional — only implement what you need.
107
+
108
+ ### `system_prompt(to_add: str) -> str`
109
+
110
+ **When:** Called once per request, before any messages are sent to the model.
111
+
112
+ **Purpose:** Append instructions to the system prompt. This is how you "prime" the model to behave a certain way.
113
+
114
+ **Example:**
115
+ ```python
116
+ def system_prompt(self, to_add: str) -> str:
117
+ return "IMPORTANT: Respond only in valid JSON. No markdown, no explanations."
118
+ ```
119
+
120
+ ### `pre_hook(context: list, params: dict) -> list`
121
+
122
+ **When:** Called after the system prompt is set, before the model receives the messages.
123
+
124
+ **Purpose:** Modify the conversation context. You can inject tool definitions, add few-shot examples, or restructure messages.
125
+
126
+ **Parameters:**
127
+ - `context`: List of message dicts (`[{"role": "user", "content": "..."}]`)
128
+ - `params`: Request parameters including `tools`, `temperature`, etc.
129
+
130
+ **Example:**
131
+ ```python
132
+ def pre_hook(self, context: list, params: dict) -> list:
133
+ if "tools" in params:
134
+ # Inject tool definitions into system prompt
135
+ tools_json = json.dumps(params["tools"])
136
+ context[0]["content"] += f"\n\nAvailable tools: {tools_json}"
137
+ return context
138
+ ```
139
+
140
+ ### `post_hook(context: list) -> list`
141
+
142
+ **When:** Called after the model responds, before the response goes back to your application.
143
+
144
+ **Purpose:** Validate, transform, or retry. This is where you can:
145
+ - Parse the response and convert it to a different format
146
+ - Detect when the model failed and call it again with feedback
147
+ - Extract tool calls from natural language
148
+
149
+ **Example (JSON validation with retry):**
150
+ ```python
151
+ def post_hook(self, context: list) -> list:
152
+ attempts = 3
153
+ while attempts > 0:
154
+ try:
155
+ json.loads(context[-1]["content"])
156
+ break # Valid JSON, we're done
157
+ except json.JSONDecodeError:
158
+ attempts -= 1
159
+ if attempts == 0:
160
+ break
161
+ # Retry with feedback
162
+ context = callmodel(context, "That wasn't valid JSON. Try again.")
163
+ return context
164
+ ```
165
+
166
+ **Example (Tool call detection):**
167
+ ```python
168
+ def post_hook(self, context: list) -> list:
169
+ content = context[-1]["content"]
170
+ if self._looks_like_tool_call(content):
171
+ # Convert to OpenAI tool_calls format
172
+ context[-1]["tool_calls"] = [self._parse_tool_call(content)]
173
+ context[-1]["content"] = None
174
+ return context
175
+ ```
176
+
177
+ ### `info(capabilities: dict) -> dict`
178
+
179
+ **When:** Called when building the response to your application.
180
+
181
+ **Purpose:** Declare what capabilities this trick provides. Some frameworks check for capabilities before using certain features.
182
+
183
+ **Example:**
184
+ ```python
185
+ def info(self, capabilities: dict) -> dict:
186
+ capabilities["json_mode"] = True
187
+ capabilities["tools_support"] = True
188
+ return capabilities
189
+ ```
190
+
191
+ ## Full Trick Example
192
+
193
+ Here's a trick that makes any model respond in haiku:
194
+
195
+ ```python
196
+ from src.trick import Trick
197
+
198
+ class HaikuTrick(Trick):
199
+ """Force the model to respond only in haiku."""
200
+
201
+ def system_prompt(self, to_add: str) -> str:
202
+ return (
203
+ "You must respond only in haiku (5-7-5 syllables). "
204
+ "No explanations, no extra text. Just haiku."
205
+ )
206
+
207
+ def post_hook(self, context: list) -> list:
208
+ # Could add syllable counting and retry here
209
+ return context
210
+
211
+ def info(self, capabilities: dict) -> dict:
212
+ capabilities["haiku_mode"] = True
213
+ return capabilities
214
+ ```
215
+
216
+ Use it:
217
+ ```bash
218
+ ./petsitter --model_url http://localhost:11434 --trick haiku.py
219
+ ```
220
+
221
+ ## API Endpoints
222
+
223
+ Petsitter exposes OpenAI-compatible endpoints:
224
+
225
+ - `POST /v1/chat/completions` - Chat completions (proxied + transformed)
226
+ - `GET /v1/models` - List available models (proxied)
227
+ - `GET /health` - Health check
228
+
229
+ ## Running Tests
230
+
231
+ ```bash
232
+ # Activate virtual environment
233
+ source .venv/bin/activate
234
+
235
+ # Install test dependencies
236
+ pip install -e ".[test]"
237
+
238
+ # Run tests
239
+ pytest tests/
240
+ ```
241
+
242
+ ## Example: Using with an Agentic Framework
243
+
244
+ ```python
245
+ from openai import OpenAI
246
+
247
+ # Point to petsitter instead of directly to the model
248
+ client = OpenAI(
249
+ base_url="http://localhost:8080/v1",
250
+ api_key="not-needed"
251
+ )
252
+
253
+ response = client.chat.completions.create(
254
+ model="any-model-name",
255
+ messages=[{"role": "user", "content": "List files in /tmp"}],
256
+ tools=[{"type": "function", "function": {"name": "list_files", ...}}]
257
+ )
258
+
259
+ # With tool_call trick, even small models can use tools!
260
+ ```
261
+
262
+ ## License
263
+
264
+ MIT
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env python3
2
+ """Petsitter CLI entry point."""
3
+
4
+ from src.server import cli
5
+
6
+ if __name__ == "__main__":
7
+ cli()