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.
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/botable-dev?color=blue)](https://pypi.org/project/botable-dev/)
45
+ [![Python](https://img.shields.io/pypi/pyversions/botable-dev)](https://pypi.org/project/botable-dev/)
46
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ botable = botable.cli:app
3
+ botable-mcp = botable_mcp.server:main
@@ -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.
@@ -0,0 +1,3 @@
1
+ """Botable MCP Server — exposes Botable CLI tools via the Model Context Protocol."""
2
+
3
+ __version__ = "0.1.0"
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()