telegram-opencode-bridge-bot 0.1.5__tar.gz → 0.1.6__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.
- {telegram_opencode_bridge_bot-0.1.5/telegram_opencode_bridge_bot.egg-info → telegram_opencode_bridge_bot-0.1.6}/PKG-INFO +7 -3
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/README.md +6 -2
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/bot.py +21 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/handlers/commands.py +440 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/handlers/messages.py +1 -1
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/opencode/client.py +12 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/pyproject.toml +1 -1
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6/telegram_opencode_bridge_bot.egg-info}/PKG-INFO +7 -3
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/.env.example +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/MANIFEST.in +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/config.py +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/handlers/__init__.py +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/list_session_models.py +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/opencode/__init__.py +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/opencode/server.py +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/requirements.txt +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/sessions/__init__.py +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/sessions/manager.py +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/setup.cfg +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/telegram_opencode_bridge_bot.egg-info/SOURCES.txt +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/telegram_opencode_bridge_bot.egg-info/dependency_links.txt +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/telegram_opencode_bridge_bot.egg-info/entry_points.txt +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/telegram_opencode_bridge_bot.egg-info/requires.txt +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/telegram_opencode_bridge_bot.egg-info/top_level.txt +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/utils/__init__.py +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/utils/formatting.py +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/utils/security.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: telegram-opencode-bridge-bot
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: A Telegram bot that bridges messages directly to OpenCode — an AI coding agent running on your machine
|
|
5
5
|
Author-email: MaheshNagabhairava <maheshnagabhirava12345@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -23,7 +23,7 @@ A lightweight Python bot that bridges your Telegram messages directly to [OpenCo
|
|
|
23
23
|
|
|
24
24
|
- **Direct OpenCode integration** — routes your messages to OpenCode's HTTP API
|
|
25
25
|
- **Persistent sessions** — conversations maintain context across messages
|
|
26
|
-
- **Session management** — create, switch, list, and share sessions
|
|
26
|
+
- **Session management** — create, delete, switch, list, and share sessions
|
|
27
27
|
- **Model switching** — change AI models on the fly (`/model`)
|
|
28
28
|
- **Plan/Build modes** — toggle between read-only analysis and full execution
|
|
29
29
|
- **Smart formatting** — code blocks with syntax highlighting in Telegram
|
|
@@ -31,6 +31,7 @@ A lightweight Python bot that bridges your Telegram messages directly to [OpenCo
|
|
|
31
31
|
- **Security** — whitelist-based access control + rate limiting
|
|
32
32
|
- **Workspace Switching** — Switching from one workspace to another
|
|
33
33
|
- **Uploading from Mobile to WorkSpace** - U can upload the documents/images from your mobile to the opencode agent workspace
|
|
34
|
+
- **Workspace Management** — Creation and deletion of the workspace/folder
|
|
34
35
|
|
|
35
36
|
## 📋 Prerequisites
|
|
36
37
|
|
|
@@ -46,7 +47,7 @@ A lightweight Python bot that bridges your Telegram messages directly to [OpenCo
|
|
|
46
47
|
|
|
47
48
|
### Quick Installation:
|
|
48
49
|
```bash
|
|
49
|
-
pip install telegram-opencode-bridge-bot==0.1.
|
|
50
|
+
pip install telegram-opencode-bridge-bot==0.1.6
|
|
50
51
|
telegram-opencode-bot
|
|
51
52
|
telegram-opencode-bot --env (use --env flag if u want to re-configure later anytime)
|
|
52
53
|
```
|
|
@@ -88,6 +89,9 @@ Open Telegram, find your bot, and start asking! 🎉
|
|
|
88
89
|
| `/project` | To view the current workspace, sub folder workspaces and to change the workspace |
|
|
89
90
|
| `/enable` | To enable the streaming |
|
|
90
91
|
| `/disable` | To disable the streaming |
|
|
92
|
+
| `/create_project` | To create new project/folder/workspace |
|
|
93
|
+
| `/delete_project` | To create new project/folder/workspace |
|
|
94
|
+
| `/delete` | To delete a conversation |
|
|
91
95
|
|
|
92
96
|
## 🏗️ Architecture
|
|
93
97
|
|
|
@@ -6,7 +6,7 @@ A lightweight Python bot that bridges your Telegram messages directly to [OpenCo
|
|
|
6
6
|
|
|
7
7
|
- **Direct OpenCode integration** — routes your messages to OpenCode's HTTP API
|
|
8
8
|
- **Persistent sessions** — conversations maintain context across messages
|
|
9
|
-
- **Session management** — create, switch, list, and share sessions
|
|
9
|
+
- **Session management** — create, delete, switch, list, and share sessions
|
|
10
10
|
- **Model switching** — change AI models on the fly (`/model`)
|
|
11
11
|
- **Plan/Build modes** — toggle between read-only analysis and full execution
|
|
12
12
|
- **Smart formatting** — code blocks with syntax highlighting in Telegram
|
|
@@ -14,6 +14,7 @@ A lightweight Python bot that bridges your Telegram messages directly to [OpenCo
|
|
|
14
14
|
- **Security** — whitelist-based access control + rate limiting
|
|
15
15
|
- **Workspace Switching** — Switching from one workspace to another
|
|
16
16
|
- **Uploading from Mobile to WorkSpace** - U can upload the documents/images from your mobile to the opencode agent workspace
|
|
17
|
+
- **Workspace Management** — Creation and deletion of the workspace/folder
|
|
17
18
|
|
|
18
19
|
## 📋 Prerequisites
|
|
19
20
|
|
|
@@ -29,7 +30,7 @@ A lightweight Python bot that bridges your Telegram messages directly to [OpenCo
|
|
|
29
30
|
|
|
30
31
|
### Quick Installation:
|
|
31
32
|
```bash
|
|
32
|
-
pip install telegram-opencode-bridge-bot==0.1.
|
|
33
|
+
pip install telegram-opencode-bridge-bot==0.1.6
|
|
33
34
|
telegram-opencode-bot
|
|
34
35
|
telegram-opencode-bot --env (use --env flag if u want to re-configure later anytime)
|
|
35
36
|
```
|
|
@@ -71,6 +72,9 @@ Open Telegram, find your bot, and start asking! 🎉
|
|
|
71
72
|
| `/project` | To view the current workspace, sub folder workspaces and to change the workspace |
|
|
72
73
|
| `/enable` | To enable the streaming |
|
|
73
74
|
| `/disable` | To disable the streaming |
|
|
75
|
+
| `/create_project` | To create new project/folder/workspace |
|
|
76
|
+
| `/delete_project` | To create new project/folder/workspace |
|
|
77
|
+
| `/delete` | To delete a conversation |
|
|
74
78
|
|
|
75
79
|
## 🏗️ Architecture
|
|
76
80
|
|
|
@@ -41,6 +41,7 @@ from handlers.commands import (
|
|
|
41
41
|
help_command,
|
|
42
42
|
new_command,
|
|
43
43
|
sessions_command,
|
|
44
|
+
delete_command,
|
|
44
45
|
plan_command,
|
|
45
46
|
build_command,
|
|
46
47
|
share_command,
|
|
@@ -49,6 +50,8 @@ from handlers.commands import (
|
|
|
49
50
|
models_command,
|
|
50
51
|
stop_command,
|
|
51
52
|
project_command,
|
|
53
|
+
create_project_command,
|
|
54
|
+
delete_project_command,
|
|
52
55
|
enable_command,
|
|
53
56
|
disable_command,
|
|
54
57
|
set_bot_commands,
|
|
@@ -105,6 +108,10 @@ def build_authorized_handlers(authorizer: UserAuthorizer, rate_limiter: RateLimi
|
|
|
105
108
|
async def _sessions(update, context):
|
|
106
109
|
await sessions_command(update, context)
|
|
107
110
|
|
|
111
|
+
@authorized(authorizer, rate_limiter)
|
|
112
|
+
async def _delete(update, context):
|
|
113
|
+
await delete_command(update, context)
|
|
114
|
+
|
|
108
115
|
@authorized(authorizer, rate_limiter)
|
|
109
116
|
async def _plan(update, context):
|
|
110
117
|
await plan_command(update, context)
|
|
@@ -137,6 +144,14 @@ def build_authorized_handlers(authorizer: UserAuthorizer, rate_limiter: RateLimi
|
|
|
137
144
|
async def _project(update, context):
|
|
138
145
|
await project_command(update, context)
|
|
139
146
|
|
|
147
|
+
@authorized(authorizer, rate_limiter)
|
|
148
|
+
async def _create_project(update, context):
|
|
149
|
+
await create_project_command(update, context)
|
|
150
|
+
|
|
151
|
+
@authorized(authorizer, rate_limiter)
|
|
152
|
+
async def _delete_project(update, context):
|
|
153
|
+
await delete_project_command(update, context)
|
|
154
|
+
|
|
140
155
|
@authorized(authorizer, rate_limiter)
|
|
141
156
|
async def _enable(update, context):
|
|
142
157
|
await enable_command(update, context)
|
|
@@ -162,9 +177,12 @@ def build_authorized_handlers(authorizer: UserAuthorizer, rate_limiter: RateLimi
|
|
|
162
177
|
"help": _help,
|
|
163
178
|
"new": _new,
|
|
164
179
|
"sessions": _sessions,
|
|
180
|
+
"delete": _delete,
|
|
165
181
|
"models": _models,
|
|
166
182
|
"stop": _stop,
|
|
167
183
|
"project": _project,
|
|
184
|
+
"create_project": _create_project,
|
|
185
|
+
"delete_project": _delete_project,
|
|
168
186
|
"enable": _enable,
|
|
169
187
|
"disable": _disable,
|
|
170
188
|
"plan": _plan,
|
|
@@ -451,9 +469,12 @@ def main():
|
|
|
451
469
|
application.add_handler(CommandHandler("help", handlers["help"], block=False))
|
|
452
470
|
application.add_handler(CommandHandler("new", handlers["new"], block=False))
|
|
453
471
|
application.add_handler(CommandHandler("sessions", handlers["sessions"], block=False))
|
|
472
|
+
application.add_handler(CommandHandler("delete", handlers["delete"], block=False))
|
|
454
473
|
application.add_handler(CommandHandler("models", handlers["models"], block=False))
|
|
455
474
|
application.add_handler(CommandHandler("stop", handlers["stop"], block=False))
|
|
456
475
|
application.add_handler(CommandHandler("project", handlers["project"], block=False))
|
|
476
|
+
application.add_handler(CommandHandler("create_project", handlers["create_project"], block=False))
|
|
477
|
+
application.add_handler(CommandHandler("delete_project", handlers["delete_project"], block=False))
|
|
457
478
|
application.add_handler(CommandHandler("enable", handlers["enable"], block=False))
|
|
458
479
|
application.add_handler(CommandHandler("disable", handlers["disable"], block=False))
|
|
459
480
|
application.add_handler(CommandHandler("plan", handlers["plan"], block=False))
|
{telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/handlers/commands.py
RENAMED
|
@@ -56,9 +56,12 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
|
|
|
56
56
|
"/new — Start a fresh conversation (clears current session)\n"
|
|
57
57
|
"/stop — Stop/abort the current active task\n"
|
|
58
58
|
"/project — View & switch active project directories\n"
|
|
59
|
+
"/create_project — Create a new workspace folder\n"
|
|
60
|
+
"/delete_project — Delete a workspace folder\n"
|
|
59
61
|
"/enable — Enable live tool call & progress streaming\n"
|
|
60
62
|
"/disable — Disable live progress streaming\n"
|
|
61
63
|
"/sessions — List your recent sessions (tap to switch)\n"
|
|
64
|
+
"/delete — Permanently delete a session\n"
|
|
62
65
|
"/models — List all available models (tap to change)\n"
|
|
63
66
|
"/plan — Switch to plan mode (read-only)\n"
|
|
64
67
|
"/build — Switch to build mode (read, write, execute)\n"
|
|
@@ -251,6 +254,88 @@ async def sessions_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -
|
|
|
251
254
|
await update.message.reply_text("\n".join(lines), reply_markup=reply_markup, parse_mode="HTML")
|
|
252
255
|
|
|
253
256
|
|
|
257
|
+
# ──────────────────────────────────────────────
|
|
258
|
+
# Command: /delete
|
|
259
|
+
# ──────────────────────────────────────────────
|
|
260
|
+
async def delete_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
261
|
+
"""List recent sessions for this user in their active workspace for deletion."""
|
|
262
|
+
user_id = update.effective_user.id
|
|
263
|
+
session_mgr = context.bot_data["session_manager"]
|
|
264
|
+
oc_client = context.bot_data["opencode_client"]
|
|
265
|
+
config = context.bot_data["config"]
|
|
266
|
+
|
|
267
|
+
# Ensure OpenCode server is running dynamically
|
|
268
|
+
from handlers.messages import ensure_server_running
|
|
269
|
+
if not await ensure_server_running(update, context, user_id):
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
# Resolve workspace
|
|
273
|
+
base_dir = os.path.abspath(config.opencode_work_dir)
|
|
274
|
+
current_dir = await session_mgr.get_user_work_dir(user_id, base_dir)
|
|
275
|
+
current_dir = os.path.abspath(current_dir)
|
|
276
|
+
|
|
277
|
+
def norm(p):
|
|
278
|
+
if not p:
|
|
279
|
+
return ""
|
|
280
|
+
return os.path.normcase(os.path.normpath(os.path.abspath(p)))
|
|
281
|
+
|
|
282
|
+
current_dir_norm = norm(current_dir)
|
|
283
|
+
|
|
284
|
+
# Fetch server sessions
|
|
285
|
+
server_sessions = []
|
|
286
|
+
try:
|
|
287
|
+
server_sessions = await oc_client.list_sessions()
|
|
288
|
+
except Exception as e:
|
|
289
|
+
logger.warning(f"Could not fetch sessions from server during delete call: {e}")
|
|
290
|
+
await update.message.reply_text(
|
|
291
|
+
"⚠️ Could not reach the OpenCode server to list sessions.\n"
|
|
292
|
+
"Make sure <code>opencode serve</code> is running.",
|
|
293
|
+
parse_mode="HTML",
|
|
294
|
+
)
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
# Filter to current workspace
|
|
298
|
+
workspace_sessions = []
|
|
299
|
+
for s in server_sessions:
|
|
300
|
+
s_dir = s.get("directory", "")
|
|
301
|
+
if norm(s_dir) == current_dir_norm:
|
|
302
|
+
workspace_sessions.append(s)
|
|
303
|
+
|
|
304
|
+
if not workspace_sessions:
|
|
305
|
+
folder_name = os.path.basename(current_dir) or "Root"
|
|
306
|
+
await update.message.reply_text(
|
|
307
|
+
f"📭 No sessions found in project <b>{html.escape(folder_name)}</b> to delete.",
|
|
308
|
+
parse_mode="HTML",
|
|
309
|
+
)
|
|
310
|
+
return
|
|
311
|
+
|
|
312
|
+
workspace_sessions.sort(
|
|
313
|
+
key=lambda s: s.get("time", {}).get("updated", 0),
|
|
314
|
+
reverse=True,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
active_sid = await session_mgr.get_active_session(user_id)
|
|
318
|
+
|
|
319
|
+
folder_name = os.path.basename(current_dir) or "Root"
|
|
320
|
+
text = (
|
|
321
|
+
f"🗑️ <b>Delete Session (Workspace: {html.escape(folder_name)})</b>\n\n"
|
|
322
|
+
f"Choose a session below to permanently delete it from your local machine and the server.\n\n"
|
|
323
|
+
f"⚠️ <b>WARNING:</b> This cannot be undone!"
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
keyboard = []
|
|
327
|
+
for s in workspace_sessions:
|
|
328
|
+
s_id = s.get("id", "")
|
|
329
|
+
s_title = s.get("title", "") or s_id[:8]
|
|
330
|
+
is_active = (s_id == active_sid)
|
|
331
|
+
marker = "🔹 (Current)" if is_active else "📄"
|
|
332
|
+
button_text = f"❌ Delete {s_title} {marker}"
|
|
333
|
+
keyboard.append([InlineKeyboardButton(button_text, callback_data=f"delsess:{s_id}")])
|
|
334
|
+
|
|
335
|
+
reply_markup = InlineKeyboardMarkup(keyboard)
|
|
336
|
+
await update.message.reply_text(text, reply_markup=reply_markup, parse_mode="HTML")
|
|
337
|
+
|
|
338
|
+
|
|
254
339
|
# ──────────────────────────────────────────────
|
|
255
340
|
# Command: /switch <session_id>
|
|
256
341
|
# ──────────────────────────────────────────────
|
|
@@ -867,9 +952,12 @@ async def set_bot_commands(app) -> None:
|
|
|
867
952
|
BotCommand("new", "Start a fresh conversation"),
|
|
868
953
|
BotCommand("stop", "Stop/abort the current active task"),
|
|
869
954
|
BotCommand("project", "View & switch project folders"),
|
|
955
|
+
BotCommand("create_project", "Create a new workspace folder"),
|
|
956
|
+
BotCommand("delete_project", "Delete an existing workspace folder"),
|
|
870
957
|
BotCommand("enable", "Enable live progress streaming"),
|
|
871
958
|
BotCommand("disable", "Disable live progress streaming"),
|
|
872
959
|
BotCommand("sessions", "List your sessions"),
|
|
960
|
+
BotCommand("delete", "Permanently delete a session"),
|
|
873
961
|
BotCommand("models", "List all available models"),
|
|
874
962
|
BotCommand("plan", "Switch to plan mode (read-only)"),
|
|
875
963
|
BotCommand("build", "Switch to build mode (read, write, execute)"),
|
|
@@ -1004,6 +1092,58 @@ async def callback_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -
|
|
|
1004
1092
|
parse_mode="HTML",
|
|
1005
1093
|
)
|
|
1006
1094
|
|
|
1095
|
+
# 1.3 Delete Session tap
|
|
1096
|
+
elif data.startswith("delsess:"):
|
|
1097
|
+
target_id = data[len("delsess:"):]
|
|
1098
|
+
|
|
1099
|
+
from handlers.messages import ensure_server_running
|
|
1100
|
+
if not await ensure_server_running(update, context, user_id):
|
|
1101
|
+
return
|
|
1102
|
+
|
|
1103
|
+
resolved_id = None
|
|
1104
|
+
server_sessions = []
|
|
1105
|
+
try:
|
|
1106
|
+
server_sessions = await oc_client.list_sessions()
|
|
1107
|
+
for s in server_sessions:
|
|
1108
|
+
s_id = s.get("id", "")
|
|
1109
|
+
if s_id.lower().startswith(target_id.lower()):
|
|
1110
|
+
resolved_id = s_id
|
|
1111
|
+
break
|
|
1112
|
+
except Exception as e:
|
|
1113
|
+
logger.warning(f"Could not verify session list on server during delete callback: {e}")
|
|
1114
|
+
|
|
1115
|
+
if not resolved_id:
|
|
1116
|
+
resolved_id = target_id
|
|
1117
|
+
|
|
1118
|
+
try:
|
|
1119
|
+
# 1. Delete on OpenCode Server
|
|
1120
|
+
await oc_client.delete_session(resolved_id)
|
|
1121
|
+
except Exception as e:
|
|
1122
|
+
logger.warning(f"Failed to delete session {resolved_id} on server, proceeding locally: {e}")
|
|
1123
|
+
|
|
1124
|
+
# 2. Delete locally in SQLite DB
|
|
1125
|
+
try:
|
|
1126
|
+
await session_mgr._db.execute(
|
|
1127
|
+
"DELETE FROM sessions WHERE user_id = ? AND opencode_session_id = ?",
|
|
1128
|
+
(user_id, resolved_id)
|
|
1129
|
+
)
|
|
1130
|
+
await session_mgr._db.commit()
|
|
1131
|
+
except Exception as e:
|
|
1132
|
+
logger.error(f"Failed to delete session {resolved_id} in local database: {e}")
|
|
1133
|
+
|
|
1134
|
+
# 3. If active session was deleted, clear active cache and reset
|
|
1135
|
+
active_sid = await session_mgr.get_active_session(user_id)
|
|
1136
|
+
if active_sid == resolved_id:
|
|
1137
|
+
if user_id in session_mgr._active_sessions:
|
|
1138
|
+
del session_mgr._active_sessions[user_id]
|
|
1139
|
+
# Try to auto-resolve first remaining session or let the bot create a new one lazily
|
|
1140
|
+
await session_mgr.clear_session(user_id)
|
|
1141
|
+
|
|
1142
|
+
await query.edit_message_text(
|
|
1143
|
+
f"🗑️ <b>Deleted:</b> Session <code>{html.escape(resolved_id[:8])}</code> has been permanently removed.",
|
|
1144
|
+
parse_mode="HTML",
|
|
1145
|
+
)
|
|
1146
|
+
|
|
1007
1147
|
# 1.5 Handle Sensitive Operations / Tool Permissions
|
|
1008
1148
|
elif data.startswith("perm:"):
|
|
1009
1149
|
parts = data.split(":")
|
|
@@ -1200,3 +1340,303 @@ async def callback_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -
|
|
|
1200
1340
|
logger.error(f"Error navigating subfolder: {e}", exc_info=True)
|
|
1201
1341
|
await query.edit_message_text(f"❌ Navigation error: {e}")
|
|
1202
1342
|
|
|
1343
|
+
# 4. Workspace Deletion callback query flows
|
|
1344
|
+
elif data.startswith("delproj_ask:"):
|
|
1345
|
+
target_folder = data[len("delproj_ask:"):]
|
|
1346
|
+
|
|
1347
|
+
# Verify it's not active
|
|
1348
|
+
base_dir = os.path.abspath(config.opencode_work_dir)
|
|
1349
|
+
current_dir = await session_mgr.get_user_work_dir(user_id, base_dir)
|
|
1350
|
+
current_dir = os.path.abspath(current_dir)
|
|
1351
|
+
|
|
1352
|
+
target_path = os.path.abspath(os.path.join(base_dir, target_folder))
|
|
1353
|
+
|
|
1354
|
+
if os.path.normcase(target_path) == os.path.normcase(current_dir):
|
|
1355
|
+
await query.edit_message_text(
|
|
1356
|
+
f"❌ <b>Cannot Delete Active Workspace:</b>\n"
|
|
1357
|
+
f"You cannot delete your currently active workspace directory <code>{html.escape(target_folder)}</code>.\n"
|
|
1358
|
+
f"Please switch to another workspace folder first using /project, then attempt deletion.",
|
|
1359
|
+
parse_mode="HTML"
|
|
1360
|
+
)
|
|
1361
|
+
return
|
|
1362
|
+
|
|
1363
|
+
warning_text = (
|
|
1364
|
+
f"⚠️ <b>WARNING: Permanent Deletion!</b>\n\n"
|
|
1365
|
+
f"Are you absolutely sure you want to permanently delete the folder <code>{html.escape(target_folder)}</code>?\n\n"
|
|
1366
|
+
f"This will physically **erase all source files and directories** inside this workspace on your computer!\n\n"
|
|
1367
|
+
f"🚨 <b>THIS ACTION CANNOT BE UNDONE!</b>"
|
|
1368
|
+
)
|
|
1369
|
+
|
|
1370
|
+
keyboard = [
|
|
1371
|
+
[
|
|
1372
|
+
InlineKeyboardButton("💥 Yes, Erase Folder", callback_data=f"delproj_confirm:{target_folder}"),
|
|
1373
|
+
InlineKeyboardButton("↩️ Cancel", callback_data="delproj_cancel")
|
|
1374
|
+
]
|
|
1375
|
+
]
|
|
1376
|
+
|
|
1377
|
+
await query.edit_message_text(warning_text, reply_markup=InlineKeyboardMarkup(keyboard), parse_mode="HTML")
|
|
1378
|
+
|
|
1379
|
+
elif data.startswith("delproj_confirm:"):
|
|
1380
|
+
target_folder = data[len("delproj_confirm:"):]
|
|
1381
|
+
base_dir = os.path.abspath(config.opencode_work_dir)
|
|
1382
|
+
target_path = os.path.abspath(os.path.join(base_dir, target_folder))
|
|
1383
|
+
|
|
1384
|
+
# 1. Security Check: Path traversal exploit guard
|
|
1385
|
+
if not os.path.normcase(target_path).startswith(os.path.normcase(base_dir)) or target_path == base_dir:
|
|
1386
|
+
await query.edit_message_text(
|
|
1387
|
+
"❌ <b>Security Violation:</b> Deletion path lies outside your parent workspace directory.",
|
|
1388
|
+
parse_mode="HTML"
|
|
1389
|
+
)
|
|
1390
|
+
return
|
|
1391
|
+
|
|
1392
|
+
# 2. Check if currently active (double check)
|
|
1393
|
+
current_dir = await session_mgr.get_user_work_dir(user_id, base_dir)
|
|
1394
|
+
current_dir = os.path.abspath(current_dir)
|
|
1395
|
+
if os.path.normcase(target_path) == os.path.normcase(current_dir):
|
|
1396
|
+
await query.edit_message_text(
|
|
1397
|
+
"❌ <b>Cannot Delete Active Workspace:</b> Please switch to another workspace first.",
|
|
1398
|
+
parse_mode="HTML"
|
|
1399
|
+
)
|
|
1400
|
+
return
|
|
1401
|
+
|
|
1402
|
+
# 3. Perform physical deletion & SQLite sync
|
|
1403
|
+
import shutil
|
|
1404
|
+
try:
|
|
1405
|
+
if os.path.exists(target_path):
|
|
1406
|
+
# Clean up local SQLite DB session history inside the workspace
|
|
1407
|
+
# Let's delete all sessions associated with this path
|
|
1408
|
+
# Since workspace paths are normalized, let's normalize this target path
|
|
1409
|
+
norm_target_path = os.path.normcase(target_path)
|
|
1410
|
+
|
|
1411
|
+
# Fetch all sessions in the DB to match work_dir
|
|
1412
|
+
cursor = await session_mgr._db.execute("SELECT opencode_session_id, work_dir FROM sessions WHERE user_id = ?", (user_id,))
|
|
1413
|
+
rows = await cursor.fetchall()
|
|
1414
|
+
stale_sids = []
|
|
1415
|
+
|
|
1416
|
+
def norm(p):
|
|
1417
|
+
if not p:
|
|
1418
|
+
return ""
|
|
1419
|
+
return os.path.normcase(os.path.normpath(os.path.abspath(p)))
|
|
1420
|
+
|
|
1421
|
+
for row in rows:
|
|
1422
|
+
sid, wd = row
|
|
1423
|
+
if norm(wd) == norm_target_path:
|
|
1424
|
+
stale_sids.append(sid)
|
|
1425
|
+
|
|
1426
|
+
if stale_sids:
|
|
1427
|
+
for sid in stale_sids:
|
|
1428
|
+
await session_mgr._db.execute(
|
|
1429
|
+
"DELETE FROM sessions WHERE user_id = ? AND opencode_session_id = ?",
|
|
1430
|
+
(user_id, sid)
|
|
1431
|
+
)
|
|
1432
|
+
await session_mgr._db.commit()
|
|
1433
|
+
|
|
1434
|
+
# Clear session mgr active cache if active
|
|
1435
|
+
active_sid = await session_mgr.get_active_session(user_id)
|
|
1436
|
+
if active_sid in stale_sids:
|
|
1437
|
+
if user_id in session_mgr._active_sessions:
|
|
1438
|
+
del session_mgr._active_sessions[user_id]
|
|
1439
|
+
|
|
1440
|
+
# Physical rmtree
|
|
1441
|
+
shutil.rmtree(target_path)
|
|
1442
|
+
|
|
1443
|
+
await query.edit_message_text(
|
|
1444
|
+
f"🗑️ <b>Folder Deleted Successfully!</b>\n\n"
|
|
1445
|
+
f"The workspace folder <code>{html.escape(target_folder)}</code> and all its files have been permanently erased from your hard drive, "
|
|
1446
|
+
f"and all session logs are cleared.",
|
|
1447
|
+
parse_mode="HTML"
|
|
1448
|
+
)
|
|
1449
|
+
else:
|
|
1450
|
+
await query.edit_message_text(
|
|
1451
|
+
f"❌ <b>Folder not found:</b> The folder <code>{html.escape(target_folder)}</code> no longer exists on disk.",
|
|
1452
|
+
parse_mode="HTML"
|
|
1453
|
+
)
|
|
1454
|
+
except Exception as e:
|
|
1455
|
+
logger.error(f"Failed to delete folder {target_path}: {e}", exc_info=True)
|
|
1456
|
+
await query.edit_message_text(
|
|
1457
|
+
f"❌ <b>Deletion Failed:</b> An error occurred while erasing the directory:\n"
|
|
1458
|
+
f"<code>{html.escape(str(e))}</code>",
|
|
1459
|
+
parse_mode="HTML"
|
|
1460
|
+
)
|
|
1461
|
+
|
|
1462
|
+
elif data == "delproj_cancel":
|
|
1463
|
+
await query.edit_message_text(
|
|
1464
|
+
"↩️ <b>Deletion Cancelled.</b>\n\n"
|
|
1465
|
+
"Your workspace folder and code remain completely untouched.",
|
|
1466
|
+
parse_mode="HTML"
|
|
1467
|
+
)
|
|
1468
|
+
|
|
1469
|
+
|
|
1470
|
+
# ──────────────────────────────────────────────
|
|
1471
|
+
# Commands: /create_project and /delete_project
|
|
1472
|
+
# ──────────────────────────────────────────────
|
|
1473
|
+
|
|
1474
|
+
async def create_project_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
1475
|
+
"""Create a new workspace folder inside the base directory."""
|
|
1476
|
+
import os
|
|
1477
|
+
import html
|
|
1478
|
+
|
|
1479
|
+
user_id = update.effective_user.id
|
|
1480
|
+
config = context.bot_data["config"]
|
|
1481
|
+
|
|
1482
|
+
if not context.args:
|
|
1483
|
+
await update.message.reply_text(
|
|
1484
|
+
"⚠️ <b>Usage:</b> /create_project <code><new_folder_name></code>\n\n"
|
|
1485
|
+
"Example: <code>/create_project backend-api</code>",
|
|
1486
|
+
parse_mode="HTML"
|
|
1487
|
+
)
|
|
1488
|
+
return
|
|
1489
|
+
|
|
1490
|
+
name = " ".join(context.args).strip()
|
|
1491
|
+
|
|
1492
|
+
# Strict filename sanitization (keep alphanumeric, space, dash, underscore)
|
|
1493
|
+
cleaned_name = "".join(c for c in name if c.isalnum() or c in ("-", "_", " ")).strip()
|
|
1494
|
+
|
|
1495
|
+
if not cleaned_name:
|
|
1496
|
+
await update.message.reply_text(
|
|
1497
|
+
"⚠️ <b>Invalid folder name.</b> Only alphanumeric characters, dashes, underscores, and spaces are allowed.",
|
|
1498
|
+
parse_mode="HTML"
|
|
1499
|
+
)
|
|
1500
|
+
return
|
|
1501
|
+
|
|
1502
|
+
base_dir = os.path.abspath(config.opencode_work_dir)
|
|
1503
|
+
target_path = os.path.abspath(os.path.join(base_dir, cleaned_name))
|
|
1504
|
+
|
|
1505
|
+
# Security traversal check
|
|
1506
|
+
if not os.path.normcase(target_path).startswith(os.path.normcase(base_dir)):
|
|
1507
|
+
await update.message.reply_text(
|
|
1508
|
+
"❌ <b>Security Violation:</b> Target path lies outside your parent workspace directory.",
|
|
1509
|
+
parse_mode="HTML"
|
|
1510
|
+
)
|
|
1511
|
+
return
|
|
1512
|
+
|
|
1513
|
+
# Check existence
|
|
1514
|
+
if os.path.exists(target_path):
|
|
1515
|
+
if os.path.isdir(target_path):
|
|
1516
|
+
await update.message.reply_text(
|
|
1517
|
+
f"ℹ️ <b>Folder already exists.</b> Switching you to <code>{html.escape(cleaned_name)}</code>...",
|
|
1518
|
+
parse_mode="HTML"
|
|
1519
|
+
)
|
|
1520
|
+
await execute_project_switch(update, context, user_id, target_path)
|
|
1521
|
+
else:
|
|
1522
|
+
await update.message.reply_text(
|
|
1523
|
+
f"❌ <b>File conflict:</b> A file named <code>{html.escape(cleaned_name)}</code> already exists at that path.",
|
|
1524
|
+
parse_mode="HTML"
|
|
1525
|
+
)
|
|
1526
|
+
return
|
|
1527
|
+
|
|
1528
|
+
# Physical directory creation
|
|
1529
|
+
try:
|
|
1530
|
+
os.makedirs(target_path, exist_ok=True)
|
|
1531
|
+
await execute_project_switch(update, context, user_id, target_path)
|
|
1532
|
+
except Exception as e:
|
|
1533
|
+
logger.error(f"Failed to create workspace folder {target_path}: {e}", exc_info=True)
|
|
1534
|
+
await update.message.reply_text(
|
|
1535
|
+
f"❌ <b>Folder Creation Failed:</b>\n<code>{html.escape(str(e))}</code>",
|
|
1536
|
+
parse_mode="HTML"
|
|
1537
|
+
)
|
|
1538
|
+
|
|
1539
|
+
|
|
1540
|
+
async def delete_project_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
1541
|
+
"""List workspaces in base directory for secure deletion."""
|
|
1542
|
+
import os
|
|
1543
|
+
import html
|
|
1544
|
+
|
|
1545
|
+
user_id = update.effective_user.id
|
|
1546
|
+
session_mgr = context.bot_data["session_manager"]
|
|
1547
|
+
config = context.bot_data["config"]
|
|
1548
|
+
|
|
1549
|
+
base_dir = os.path.abspath(config.opencode_work_dir)
|
|
1550
|
+
|
|
1551
|
+
# If the user passed a direct folder name in context.args
|
|
1552
|
+
if context.args:
|
|
1553
|
+
arg_folder = " ".join(context.args).strip()
|
|
1554
|
+
cleaned_arg = "".join(c for c in arg_folder if c.isalnum() or c in ("-", "_", " ")).strip()
|
|
1555
|
+
|
|
1556
|
+
target_path = os.path.abspath(os.path.join(base_dir, cleaned_arg))
|
|
1557
|
+
if not os.path.normcase(target_path).startswith(os.path.normcase(base_dir)) or target_path == base_dir:
|
|
1558
|
+
await update.message.reply_text(
|
|
1559
|
+
"❌ <b>Security Violation:</b> Target path lies outside your parent workspace directory.",
|
|
1560
|
+
parse_mode="HTML"
|
|
1561
|
+
)
|
|
1562
|
+
return
|
|
1563
|
+
|
|
1564
|
+
if not os.path.exists(target_path) or not os.path.isdir(target_path):
|
|
1565
|
+
await update.message.reply_text(
|
|
1566
|
+
f"❌ <b>Workspace not found:</b> The folder <code>{html.escape(cleaned_arg)}</code> does not exist.",
|
|
1567
|
+
parse_mode="HTML"
|
|
1568
|
+
)
|
|
1569
|
+
return
|
|
1570
|
+
|
|
1571
|
+
# Verify it's not active
|
|
1572
|
+
current_dir = await session_mgr.get_user_work_dir(user_id, base_dir)
|
|
1573
|
+
current_dir = os.path.abspath(current_dir)
|
|
1574
|
+
if os.path.normcase(target_path) == os.path.normcase(current_dir):
|
|
1575
|
+
await update.message.reply_text(
|
|
1576
|
+
f"❌ <b>Cannot Delete Active Workspace:</b>\n"
|
|
1577
|
+
f"You cannot delete your active workspace. Please switch to another workspace first using /project.",
|
|
1578
|
+
parse_mode="HTML"
|
|
1579
|
+
)
|
|
1580
|
+
return
|
|
1581
|
+
|
|
1582
|
+
warning_text = (
|
|
1583
|
+
f"⚠️ <b>WARNING: Permanent Deletion!</b>\n\n"
|
|
1584
|
+
f"Are you absolutely sure you want to permanently delete the folder <code>{html.escape(cleaned_arg)}</code>?\n\n"
|
|
1585
|
+
f"This will physically **erase all source files and directories** inside this workspace on your computer!\n\n"
|
|
1586
|
+
f"🚨 <b>THIS ACTION CANNOT BE UNDONE!</b>"
|
|
1587
|
+
)
|
|
1588
|
+
|
|
1589
|
+
keyboard = [
|
|
1590
|
+
[
|
|
1591
|
+
InlineKeyboardButton("💥 Yes, Erase Folder", callback_data=f"delproj_confirm:{cleaned_arg}"),
|
|
1592
|
+
InlineKeyboardButton("↩️ Cancel", callback_data="delproj_cancel")
|
|
1593
|
+
]
|
|
1594
|
+
]
|
|
1595
|
+
|
|
1596
|
+
await update.message.reply_text(warning_text, reply_markup=InlineKeyboardMarkup(keyboard), parse_mode="HTML")
|
|
1597
|
+
return
|
|
1598
|
+
|
|
1599
|
+
# Otherwise list all subfolders for interactive selection
|
|
1600
|
+
subdirs = get_subdirectories(base_dir)
|
|
1601
|
+
|
|
1602
|
+
if not subdirs:
|
|
1603
|
+
await update.message.reply_text(
|
|
1604
|
+
"📭 <b>No workspace subdirectories found to delete.</b>",
|
|
1605
|
+
parse_mode="HTML"
|
|
1606
|
+
)
|
|
1607
|
+
return
|
|
1608
|
+
|
|
1609
|
+
current_dir = await session_mgr.get_user_work_dir(user_id, base_dir)
|
|
1610
|
+
current_dir = os.path.abspath(current_dir)
|
|
1611
|
+
|
|
1612
|
+
text = (
|
|
1613
|
+
"🗑️ <b>Delete Workspace Folder</b>\n\n"
|
|
1614
|
+
"Select a folder below to permanently delete it from your local machine. "
|
|
1615
|
+
"Stale session histories will be synced automatically.\n\n"
|
|
1616
|
+
"⚠️ <b>WARNING:</b> This erases physical code files!"
|
|
1617
|
+
)
|
|
1618
|
+
|
|
1619
|
+
keyboard = []
|
|
1620
|
+
for name in subdirs:
|
|
1621
|
+
target_path = os.path.abspath(os.path.join(base_dir, name))
|
|
1622
|
+
is_active = (os.path.normcase(target_path) == os.path.normcase(current_dir))
|
|
1623
|
+
|
|
1624
|
+
# Don't show delete button for the currently active workspace
|
|
1625
|
+
if is_active:
|
|
1626
|
+
continue
|
|
1627
|
+
|
|
1628
|
+
button_text = f"❌ Delete {name}"
|
|
1629
|
+
keyboard.append([InlineKeyboardButton(button_text, callback_data=f"delproj_ask:{name}")])
|
|
1630
|
+
|
|
1631
|
+
if not keyboard:
|
|
1632
|
+
await update.message.reply_text(
|
|
1633
|
+
"📭 <b>No other workspace folders found to delete.</b>\n"
|
|
1634
|
+
"<i>(You cannot delete your currently active workspace)</i>",
|
|
1635
|
+
parse_mode="HTML"
|
|
1636
|
+
)
|
|
1637
|
+
return
|
|
1638
|
+
|
|
1639
|
+
keyboard.append([InlineKeyboardButton("↩️ Cancel", callback_data="delproj_cancel")])
|
|
1640
|
+
|
|
1641
|
+
await update.message.reply_text(text, reply_markup=InlineKeyboardMarkup(keyboard), parse_mode="HTML")
|
|
1642
|
+
|
{telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/handlers/messages.py
RENAMED
|
@@ -415,7 +415,7 @@ async def _listen_and_stream_events(
|
|
|
415
415
|
except Exception as e:
|
|
416
416
|
logger.debug(f"Failed to update status message: {e}")
|
|
417
417
|
|
|
418
|
-
async with aiohttp.ClientSession() as sse_session:
|
|
418
|
+
async with aiohttp.ClientSession(read_bufsize=100 * 1024 * 1024) as sse_session:
|
|
419
419
|
try:
|
|
420
420
|
async with sse_session.get(url, headers={"Accept": "text/event-stream"}) as resp:
|
|
421
421
|
async for line in resp.content:
|
{telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/opencode/client.py
RENAMED
|
@@ -133,6 +133,7 @@ class OpenCodeClient:
|
|
|
133
133
|
self._session = aiohttp.ClientSession(
|
|
134
134
|
timeout=self.timeout,
|
|
135
135
|
auth=self._auth,
|
|
136
|
+
read_bufsize=100 * 1024 * 1024,
|
|
136
137
|
)
|
|
137
138
|
return self._session
|
|
138
139
|
|
|
@@ -489,3 +490,14 @@ class OpenCodeClient:
|
|
|
489
490
|
)
|
|
490
491
|
raise
|
|
491
492
|
|
|
493
|
+
async def delete_session(self, session_id: str) -> bool:
|
|
494
|
+
"""Permanently delete a session and all its context on the OpenCode server."""
|
|
495
|
+
try:
|
|
496
|
+
result = await self._request("DELETE", f"/session/{session_id}")
|
|
497
|
+
if isinstance(result, dict):
|
|
498
|
+
return result.get("success", True)
|
|
499
|
+
return True
|
|
500
|
+
except Exception as e:
|
|
501
|
+
logger.error(f"Failed to delete session {session_id} on server: {e}")
|
|
502
|
+
raise
|
|
503
|
+
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "telegram-opencode-bridge-bot"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.6"
|
|
8
8
|
description = "A Telegram bot that bridges messages directly to OpenCode — an AI coding agent running on your machine"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: telegram-opencode-bridge-bot
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: A Telegram bot that bridges messages directly to OpenCode — an AI coding agent running on your machine
|
|
5
5
|
Author-email: MaheshNagabhairava <maheshnagabhirava12345@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -23,7 +23,7 @@ A lightweight Python bot that bridges your Telegram messages directly to [OpenCo
|
|
|
23
23
|
|
|
24
24
|
- **Direct OpenCode integration** — routes your messages to OpenCode's HTTP API
|
|
25
25
|
- **Persistent sessions** — conversations maintain context across messages
|
|
26
|
-
- **Session management** — create, switch, list, and share sessions
|
|
26
|
+
- **Session management** — create, delete, switch, list, and share sessions
|
|
27
27
|
- **Model switching** — change AI models on the fly (`/model`)
|
|
28
28
|
- **Plan/Build modes** — toggle between read-only analysis and full execution
|
|
29
29
|
- **Smart formatting** — code blocks with syntax highlighting in Telegram
|
|
@@ -31,6 +31,7 @@ A lightweight Python bot that bridges your Telegram messages directly to [OpenCo
|
|
|
31
31
|
- **Security** — whitelist-based access control + rate limiting
|
|
32
32
|
- **Workspace Switching** — Switching from one workspace to another
|
|
33
33
|
- **Uploading from Mobile to WorkSpace** - U can upload the documents/images from your mobile to the opencode agent workspace
|
|
34
|
+
- **Workspace Management** — Creation and deletion of the workspace/folder
|
|
34
35
|
|
|
35
36
|
## 📋 Prerequisites
|
|
36
37
|
|
|
@@ -46,7 +47,7 @@ A lightweight Python bot that bridges your Telegram messages directly to [OpenCo
|
|
|
46
47
|
|
|
47
48
|
### Quick Installation:
|
|
48
49
|
```bash
|
|
49
|
-
pip install telegram-opencode-bridge-bot==0.1.
|
|
50
|
+
pip install telegram-opencode-bridge-bot==0.1.6
|
|
50
51
|
telegram-opencode-bot
|
|
51
52
|
telegram-opencode-bot --env (use --env flag if u want to re-configure later anytime)
|
|
52
53
|
```
|
|
@@ -88,6 +89,9 @@ Open Telegram, find your bot, and start asking! 🎉
|
|
|
88
89
|
| `/project` | To view the current workspace, sub folder workspaces and to change the workspace |
|
|
89
90
|
| `/enable` | To enable the streaming |
|
|
90
91
|
| `/disable` | To disable the streaming |
|
|
92
|
+
| `/create_project` | To create new project/folder/workspace |
|
|
93
|
+
| `/delete_project` | To create new project/folder/workspace |
|
|
94
|
+
| `/delete` | To delete a conversation |
|
|
91
95
|
|
|
92
96
|
## 🏗️ Architecture
|
|
93
97
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/handlers/__init__.py
RENAMED
|
File without changes
|
{telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/list_session_models.py
RENAMED
|
File without changes
|
{telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/opencode/__init__.py
RENAMED
|
File without changes
|
{telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/opencode/server.py
RENAMED
|
File without changes
|
|
File without changes
|
{telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/sessions/__init__.py
RENAMED
|
File without changes
|
{telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/sessions/manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.6}/utils/formatting.py
RENAMED
|
File without changes
|
|
File without changes
|