botable-dev 0.3.1__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.
- botable/__init__.py +3 -0
- botable/__main__.py +5 -0
- botable/api_client.py +203 -0
- botable/cli.py +781 -0
- botable/config.py +193 -0
- botable/errors.py +53 -0
- botable/generate.py +288 -0
- botable/mcp_install.py +72 -0
- botable/prompts.py +137 -0
- botable/runner.py +333 -0
- botable/scaffold.py +104 -0
- botable/ui.py +48 -0
- botable_dev-0.3.1.dist-info/METADATA +186 -0
- botable_dev-0.3.1.dist-info/RECORD +19 -0
- botable_dev-0.3.1.dist-info/WHEEL +4 -0
- botable_dev-0.3.1.dist-info/entry_points.txt +3 -0
- botable_dev-0.3.1.dist-info/licenses/LICENSE +21 -0
- botable_mcp/__init__.py +3 -0
- botable_mcp/server.py +227 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: botable-dev
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: Botable CLI: build, deploy and run Telegram bots from your terminal, Claude Code or Codex
|
|
5
|
+
Project-URL: Homepage, https://botable.dev
|
|
6
|
+
Author-email: Botable Team <hello@botable.dev>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: ai,aiogram,bot,chatbot,cli,generator,telegram
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Framework :: AsyncIO
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Communications :: Chat
|
|
20
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: httpx>=0.27
|
|
23
|
+
Requires-Dist: openai>=1.0
|
|
24
|
+
Requires-Dist: python-dotenv>=1.0
|
|
25
|
+
Requires-Dist: rich>=13.0
|
|
26
|
+
Requires-Dist: tomli-w>=1.0
|
|
27
|
+
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
28
|
+
Requires-Dist: typer>=0.15
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: build>=1.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: ruff>=0.11; extra == 'dev'
|
|
34
|
+
Requires-Dist: twine>=6.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: watchfiles>=1.0; extra == 'dev'
|
|
36
|
+
Provides-Extra: mcp
|
|
37
|
+
Requires-Dist: mcp<2,>=1.0; extra == 'mcp'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# ⚡ Botable CLI
|
|
41
|
+
|
|
42
|
+
> **Generate Telegram bots with AI — from idea to running bot in 3 minutes.**
|
|
43
|
+
|
|
44
|
+
[](https://pypi.org/project/botable-dev/)
|
|
45
|
+
[](https://pypi.org/project/botable-dev/)
|
|
46
|
+
[](LICENSE)
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install "botable-dev[mcp]"
|
|
52
|
+
botable login
|
|
53
|
+
botable mcp install
|
|
54
|
+
|
|
55
|
+
# Create a project
|
|
56
|
+
botable init my-bot
|
|
57
|
+
cd my-bot
|
|
58
|
+
|
|
59
|
+
# Add your keys to .env
|
|
60
|
+
echo "BOT_TOKEN=your-telegram-token" >> .env
|
|
61
|
+
echo "OPENAI_API_KEY=sk-..." >> .env
|
|
62
|
+
|
|
63
|
+
# Generate a bot from a description
|
|
64
|
+
botable generate "a FAQ bot for my restaurant with menu and hours"
|
|
65
|
+
|
|
66
|
+
# Run locally
|
|
67
|
+
botable dev
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**That's it.** Your bot is running in under 3 minutes.
|
|
71
|
+
|
|
72
|
+
## Commands
|
|
73
|
+
|
|
74
|
+
### Local Development
|
|
75
|
+
|
|
76
|
+
| Command | Description |
|
|
77
|
+
|---------|-------------|
|
|
78
|
+
| `botable init [name]` | Scaffold a new bot project |
|
|
79
|
+
| `botable generate "description"` | Generate bot from natural language |
|
|
80
|
+
| `botable dev` | Run bot locally with live output |
|
|
81
|
+
| `botable dev --watch` | Run with hot reload on file changes |
|
|
82
|
+
| `botable version` | Show CLI version and effective API URL |
|
|
83
|
+
|
|
84
|
+
### Cloud Deployment
|
|
85
|
+
|
|
86
|
+
| Command | Description |
|
|
87
|
+
|---------|-------------|
|
|
88
|
+
| `botable login [--no-browser]` | Sign in through the browser |
|
|
89
|
+
| `botable login --api-key KEY [--api-url URL]` | Save an existing API key and API URL |
|
|
90
|
+
| `botable mcp install [--target claude\|codex\|all] [--scope user\|project]` | Configure agent MCP clients |
|
|
91
|
+
| `botable logout` | Remove stored authentication |
|
|
92
|
+
| `botable deploy` | Deploy to Botable cloud |
|
|
93
|
+
| `botable logs [project_id]` | Show the current bot status snapshot |
|
|
94
|
+
| `botable bots list` | List all deployed bots |
|
|
95
|
+
| `botable bots stop {id}` | Stop a running bot |
|
|
96
|
+
|
|
97
|
+
## Deploy to Cloud
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
botable login
|
|
101
|
+
|
|
102
|
+
# Deploy your bot
|
|
103
|
+
botable deploy
|
|
104
|
+
|
|
105
|
+
# Check status
|
|
106
|
+
botable bots list
|
|
107
|
+
botable logs
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Configuration
|
|
111
|
+
|
|
112
|
+
Botable uses a 3-layer config system:
|
|
113
|
+
|
|
114
|
+
| File | Scope | Purpose |
|
|
115
|
+
|------|-------|---------|
|
|
116
|
+
| `botable.toml` | Project | Bot name, template, branding (committed to git) |
|
|
117
|
+
| `.env` | Project | Secrets: BOT_TOKEN, API keys (NOT committed) |
|
|
118
|
+
| `~/.config/botable/config.toml` | User | Global defaults, auth token |
|
|
119
|
+
|
|
120
|
+
`botable login --api-url URL` saves the API URL. `BOTABLE_API_URL` overrides it for the current process; otherwise the default is `https://api-staging.botable.dev` (staging, until the CLI routes reach production). Run `botable version` to see the effective URL.
|
|
121
|
+
|
|
122
|
+
## Use Botable from Claude Code / Codex
|
|
123
|
+
|
|
124
|
+
In Botable's **Code** tab, export your bot project. The export contains `botable.toml` with the project ID. Edit the files locally, then run `botable deploy` from that directory to deploy the same project. The CLI deployment routes currently work on staging, which is the default API URL:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
botable login
|
|
128
|
+
botable mcp install
|
|
129
|
+
botable version
|
|
130
|
+
cd path/to/exported-bot
|
|
131
|
+
botable deploy
|
|
132
|
+
botable bots list
|
|
133
|
+
botable logs PROJECT_ID
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**`botable deploy` uploads the project's `.env` file**, including any secrets in it. Check its contents before deployment. `botable logs` currently returns a status snapshot; runtime log lines and live streaming are not available through the CLI API yet.
|
|
137
|
+
|
|
138
|
+
`botable mcp install` registers the installed `botable-mcp` executable with Claude Code and Codex. Use `--target` to select one client. Claude uses its `claude mcp add` command when installed; without Claude, `--scope project` writes project `.mcp.json` (the default user scope requires Claude). Codex configuration goes in `~/.codex/config.toml` with a backup of an existing file. Check with `claude mcp list` or `codex mcp list`.
|
|
139
|
+
|
|
140
|
+
The MCP server exposes project creation, generation, templates, local development, deployment, bot listing, status, and the available status snapshot through its logs tool. Its former SSH staging status tool was removed because it requires operator access and is unrelated to customer bots. The MCP server uses the API URL and key saved by `botable login` (or the `BOTABLE_API_URL` environment override).
|
|
141
|
+
|
|
142
|
+
### Branding
|
|
143
|
+
|
|
144
|
+
Every generated bot includes a `# Built with Botable` comment. Customize or disable it:
|
|
145
|
+
|
|
146
|
+
```toml
|
|
147
|
+
# In botable.toml (per-project)
|
|
148
|
+
[branding]
|
|
149
|
+
enabled = true
|
|
150
|
+
text = "Built with Botable (botable.dev)"
|
|
151
|
+
style = "comment" # "comment" | "docstring" | "none"
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
To disable globally:
|
|
155
|
+
|
|
156
|
+
```toml
|
|
157
|
+
# In ~/.config/botable/config.toml
|
|
158
|
+
[branding]
|
|
159
|
+
enabled = false
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## How It Works
|
|
163
|
+
|
|
164
|
+
1. **`botable init`** — creates project structure with `botable.toml` + `.env`
|
|
165
|
+
2. **`botable generate`** — calls OpenAI to generate aiogram bot code, validates syntax, writes `main.py` + `requirements.txt`
|
|
166
|
+
3. **`botable dev`** — creates a venv, installs deps, runs your bot with live output
|
|
167
|
+
4. **`botable deploy`** — packages project, uploads to Botable cloud, deploys automatically
|
|
168
|
+
|
|
169
|
+
Generated bots use [aiogram](https://docs.aiogram.dev/) (MIT license) — async-native, production-ready.
|
|
170
|
+
|
|
171
|
+
## Development
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
git clone https://github.com/Botable-dev/cli.git
|
|
175
|
+
cd cli
|
|
176
|
+
pip install -e ".[dev]"
|
|
177
|
+
pytest
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
MIT — see [LICENSE](LICENSE) for details.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
⚡ Built with [Botable](https://botable.dev)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
botable_mcp/__init__.py,sha256=TKpuc88IMV3Q00TGXxy1YC4I8osp25e1yMO7zG_jNy8,110
|
|
2
|
+
botable_mcp/server.py,sha256=hW8ScxWiiFkZTlQZEVLDvzCOKk51dRflwiym2bPHvMU,7076
|
|
3
|
+
botable/__init__.py,sha256=ksjdI3KL6Mta5q4HTkFYnM7ZU0-cd_OzeSQLZbp7LWI,77
|
|
4
|
+
botable/__main__.py,sha256=CzzADKdeKBZI0C_blAPwBozHX2toCZ-6CXqn5NJCR48,80
|
|
5
|
+
botable/api_client.py,sha256=zPmoqrqESLtmcpSpTutUQu1cQ266tANS0p3yISjMpLE,7759
|
|
6
|
+
botable/cli.py,sha256=JNFKf6NUfwkwkVTaIEFvdKZ7ck0O40qWLftFrHFtod0,28399
|
|
7
|
+
botable/config.py,sha256=iBeDGIrkXtEOT3o_swSvFcBTAzRVUdNOKl5MrYGH7ew,6125
|
|
8
|
+
botable/errors.py,sha256=4O6fktllLnLh--_j_mhZ4abvGZHGeV96WK2vb1kWE8Q,1582
|
|
9
|
+
botable/generate.py,sha256=Hguvv-3Ymetsh70XNvde-LJb3n8GTrORjWj4kOS1toE,10383
|
|
10
|
+
botable/mcp_install.py,sha256=GNudNSi_55CmTnFVONisKzm2NiVGfNeCfpEw_AZCgyw,3173
|
|
11
|
+
botable/prompts.py,sha256=kXufqo5dOK0rmuWm-rlAPIvA9Gz3WScO3H3JKJM6wgw,4334
|
|
12
|
+
botable/runner.py,sha256=Jey6ybaOo0OxTJA6el_OY5iEVIkY5wyL3TP4Iue5om4,10915
|
|
13
|
+
botable/scaffold.py,sha256=-r3eDzh7uDlBEbOmWlOoemMnghjEmFBHJWhF66oJdlQ,2161
|
|
14
|
+
botable/ui.py,sha256=zRE0dFuDgI7ycBqSrMMfkjS2FsXvLwK9awbjP9W5WKY,1161
|
|
15
|
+
botable_dev-0.3.1.dist-info/METADATA,sha256=e4MKKstKSYruGmCjsEt30oc1ibzdVBa1IpPwFnzLA6c,6722
|
|
16
|
+
botable_dev-0.3.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
17
|
+
botable_dev-0.3.1.dist-info/entry_points.txt,sha256=Pa6hdEApZYHGfTJEOc8switJQpwm5j3fAWbKyGXGGdM,82
|
|
18
|
+
botable_dev-0.3.1.dist-info/licenses/LICENSE,sha256=EXuJ5sHNeoVo7syFduoLoze4FE3EfQx1O8uXCy3lFE4,1067
|
|
19
|
+
botable_dev-0.3.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 HabLab LLP
|
|
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.
|
botable_mcp/__init__.py
ADDED
botable_mcp/server.py
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Botable MCP Server
|
|
4
|
+
|
|
5
|
+
Exposes Botable CLI tools over the Model Context Protocol (stdio transport)
|
|
6
|
+
for integration with Claude Code and other MCP-aware clients.
|
|
7
|
+
|
|
8
|
+
Tools:
|
|
9
|
+
- botable_init Create a new bot project
|
|
10
|
+
- botable_generate Generate bot code from a description
|
|
11
|
+
- botable_templates List available templates
|
|
12
|
+
- botable_dev Run a bot locally (background process)
|
|
13
|
+
- botable_deploy Deploy a local bot project
|
|
14
|
+
- botable_bots_list List deployed bots
|
|
15
|
+
- botable_bot_status Get a bot's status snapshot
|
|
16
|
+
- botable_bot_logs Get the same snapshot (runtime logs unavailable)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import importlib.util
|
|
22
|
+
import os
|
|
23
|
+
import shutil
|
|
24
|
+
import subprocess
|
|
25
|
+
import sys
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
from mcp.server.fastmcp import FastMCP
|
|
30
|
+
except ModuleNotFoundError as exc:
|
|
31
|
+
if exc.name != "mcp":
|
|
32
|
+
raise
|
|
33
|
+
FastMCP = None
|
|
34
|
+
|
|
35
|
+
mcp = FastMCP("botable", instructions="Botable CLI tools for Telegram bots") if FastMCP else None
|
|
36
|
+
tool = mcp.tool if mcp else lambda: lambda fn: fn
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Helpers
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
DEFAULT_TIMEOUT = 120
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _botable_command() -> list[str]:
|
|
46
|
+
"""Find the CLI, preferring an explicit executable and the user's PATH."""
|
|
47
|
+
override = os.environ.get("BOTABLE_CMD")
|
|
48
|
+
if override:
|
|
49
|
+
return [override]
|
|
50
|
+
on_path = shutil.which("botable")
|
|
51
|
+
if on_path:
|
|
52
|
+
return [on_path]
|
|
53
|
+
sibling = Path(sys.executable).with_name("botable")
|
|
54
|
+
if sibling.is_file() and os.access(sibling, os.X_OK):
|
|
55
|
+
return [str(sibling)]
|
|
56
|
+
if importlib.util.find_spec("botable") and importlib.util.find_spec("botable.__main__"):
|
|
57
|
+
return [sys.executable, "-m", "botable"]
|
|
58
|
+
raise FileNotFoundError("Botable CLI not found. Install botable in this Python environment or set BOTABLE_CMD to its executable path.")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
async def run_botable(args: list[str], cwd: str | None = None) -> str:
|
|
62
|
+
"""Run a botable CLI command and return its output."""
|
|
63
|
+
try:
|
|
64
|
+
result = subprocess.run(
|
|
65
|
+
_botable_command() + args,
|
|
66
|
+
capture_output=True,
|
|
67
|
+
text=True,
|
|
68
|
+
cwd=cwd,
|
|
69
|
+
timeout=DEFAULT_TIMEOUT,
|
|
70
|
+
)
|
|
71
|
+
except FileNotFoundError as exc:
|
|
72
|
+
return f"Error: {exc}"
|
|
73
|
+
except subprocess.TimeoutExpired:
|
|
74
|
+
return f"Error: command timed out after {DEFAULT_TIMEOUT}s"
|
|
75
|
+
|
|
76
|
+
if result.returncode != 0:
|
|
77
|
+
stderr = result.stderr.strip()
|
|
78
|
+
stdout = result.stdout.strip()
|
|
79
|
+
detail = stderr or stdout or "(no output)"
|
|
80
|
+
return f"Error (exit {result.returncode}): {detail}"
|
|
81
|
+
|
|
82
|
+
return result.stdout
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _list_created_files(directory: str) -> list[str]:
|
|
86
|
+
"""Return a list of files under *directory*, relative to it."""
|
|
87
|
+
base = Path(directory)
|
|
88
|
+
if not base.is_dir():
|
|
89
|
+
return []
|
|
90
|
+
return sorted(
|
|
91
|
+
str(p.relative_to(base))
|
|
92
|
+
for p in base.rglob("*")
|
|
93
|
+
if p.is_file()
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
# Tools
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@tool()
|
|
103
|
+
async def botable_init(name: str, directory: str) -> str:
|
|
104
|
+
"""Create a new Botable bot project.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
name: Project / bot name (e.g. "my-bot").
|
|
108
|
+
directory: Absolute path to the parent directory where the project
|
|
109
|
+
folder will be created.
|
|
110
|
+
"""
|
|
111
|
+
directory = os.path.expanduser(directory)
|
|
112
|
+
output = await run_botable(["init", name], cwd=directory)
|
|
113
|
+
|
|
114
|
+
if output.startswith("Error"):
|
|
115
|
+
return output
|
|
116
|
+
|
|
117
|
+
project_dir = os.path.join(directory, name)
|
|
118
|
+
files = _list_created_files(project_dir)
|
|
119
|
+
|
|
120
|
+
parts = [output.strip()] if output.strip() else [f"Project '{name}' created."]
|
|
121
|
+
if files:
|
|
122
|
+
parts.append("\nCreated files:")
|
|
123
|
+
for f in files:
|
|
124
|
+
parts.append(f" {f}")
|
|
125
|
+
|
|
126
|
+
return "\n".join(parts)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@tool()
|
|
130
|
+
async def botable_generate(description: str, project_dir: str) -> str:
|
|
131
|
+
"""Generate bot code from a natural-language description.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
description: What the bot should do, e.g.
|
|
135
|
+
"echo bot that repeats user messages".
|
|
136
|
+
project_dir: Absolute path to an existing Botable project directory.
|
|
137
|
+
"""
|
|
138
|
+
project_dir = os.path.expanduser(project_dir)
|
|
139
|
+
|
|
140
|
+
if not os.path.isdir(project_dir):
|
|
141
|
+
return f"Error: project directory does not exist: {project_dir}"
|
|
142
|
+
|
|
143
|
+
output = await run_botable(["generate", description], cwd=project_dir)
|
|
144
|
+
return output
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@tool()
|
|
148
|
+
async def botable_templates() -> str:
|
|
149
|
+
"""List available Botable bot templates."""
|
|
150
|
+
output = await run_botable(["templates"])
|
|
151
|
+
return output
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@tool()
|
|
155
|
+
async def botable_dev(project_dir: str) -> str:
|
|
156
|
+
"""Start a bot locally in the background.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
project_dir: Absolute path to the Botable project to run.
|
|
160
|
+
|
|
161
|
+
Returns a message with the background process PID so you can stop it later.
|
|
162
|
+
"""
|
|
163
|
+
project_dir = os.path.expanduser(project_dir)
|
|
164
|
+
|
|
165
|
+
if not os.path.isdir(project_dir):
|
|
166
|
+
return f"Error: project directory does not exist: {project_dir}"
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
proc = subprocess.Popen(
|
|
170
|
+
_botable_command() + ["dev"],
|
|
171
|
+
cwd=project_dir,
|
|
172
|
+
stdout=subprocess.PIPE,
|
|
173
|
+
stderr=subprocess.PIPE,
|
|
174
|
+
start_new_session=True, # detach from this process group
|
|
175
|
+
)
|
|
176
|
+
except FileNotFoundError as exc:
|
|
177
|
+
return f"Error: {exc}"
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
f"Bot dev server started (PID {proc.pid}).\n"
|
|
181
|
+
f"Working directory: {project_dir}\n"
|
|
182
|
+
f"To stop: kill {proc.pid}"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@tool()
|
|
187
|
+
async def botable_deploy(project_dir: str) -> str:
|
|
188
|
+
"""Deploy a Botable project; return JSON with project_id, state, and message.
|
|
189
|
+
|
|
190
|
+
The CLI uploads the project's .env file with its other project files.
|
|
191
|
+
"""
|
|
192
|
+
project_dir = os.path.expanduser(project_dir)
|
|
193
|
+
if not os.path.isdir(project_dir):
|
|
194
|
+
return f"Error: project directory does not exist: {project_dir}"
|
|
195
|
+
return await run_botable(["deploy", "--json"], cwd=project_dir)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@tool()
|
|
199
|
+
async def botable_bots_list() -> str:
|
|
200
|
+
"""List the authenticated user's deployed bots as JSON."""
|
|
201
|
+
return await run_botable(["bots", "list", "--json"])
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@tool()
|
|
205
|
+
async def botable_bot_status(project_id: str) -> str:
|
|
206
|
+
"""Get a bot's current state and running status as a JSON snapshot."""
|
|
207
|
+
return await run_botable(["logs", project_id, "--json"])
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@tool()
|
|
211
|
+
async def botable_bot_logs(project_id: str) -> str:
|
|
212
|
+
"""Get the API's status snapshot; runtime log lines are not available yet."""
|
|
213
|
+
return await run_botable(["logs", project_id, "--json"])
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# ---------------------------------------------------------------------------
|
|
217
|
+
# Entry point
|
|
218
|
+
# ---------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
def main():
|
|
221
|
+
if mcp is None:
|
|
222
|
+
raise SystemExit("MCP dependency missing. Install botable-dev[mcp] in this Python environment.")
|
|
223
|
+
mcp.run(transport="stdio")
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
if __name__ == "__main__":
|
|
227
|
+
main()
|