linkgravity 1.4.0 → 1.4.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linkgravity",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Discord bot bridge for the Antigravity (agy) CLI, with voice interaction support",
5
5
  "scripts": {
6
6
  "postinstall": "node npm-scripts/postinstall.js",
@@ -14,7 +14,7 @@ from config import TELEGRAM_TOKEN, allowed, bot_settings, logger, save_bot_setti
14
14
  from core.atomic_io import atomic_write_json, safe_load_json
15
15
  from handlers.message_router import handle_message
16
16
  from messengers.registry import register_adapter
17
- from messengers.telegram_adapter import TelegramAdapter, markdown_to_telegram_html
17
+ from messengers.telegram_adapter import TelegramAdapter, markdown_to_telegram_html, safe_query_edit
18
18
  from utils.utils import get_default_cwd
19
19
 
20
20
 
@@ -91,21 +91,45 @@ async def cmd_model(update: Update, context) -> None:
91
91
  await update.message.reply_text("⚠️ No active session here. Start one with /new first.")
92
92
  return
93
93
 
94
+ from cogs.general_cog import load_cached_models
95
+
96
+ cached_models = load_cached_models()
97
+ current_model = session.get("model") or bot_settings.get("default_model")
98
+
99
+ def _apply_model(final_model: str) -> str:
100
+ session_manager.update_session(str(chat_id), "model", final_model)
101
+ bot_settings["default_model"] = final_model
102
+ save_bot_settings(bot_settings)
103
+ return final_model
104
+
94
105
  if not context.args:
95
- await update.message.reply_text("Usage: /model <name>\nExample: /model gemini-3.6-flash-high")
106
+ prompt_id = uuid.uuid4().hex[:12]
107
+ keyboard = []
108
+ for m in cached_models:
109
+ key = f"{prompt_id}:{m}"
110
+ label = ("✅ " if m == current_model else "") + m
111
+
112
+ async def pick(query, m=m):
113
+ final_model = _apply_model(m)
114
+ await query.answer()
115
+ await safe_query_edit(
116
+ query,
117
+ text=markdown_to_telegram_html(f"🤖 Model changed: **{final_model}**"),
118
+ parse_mode="HTML",
119
+ )
120
+
121
+ adapter.register_callback(key, pick)
122
+ keyboard.append([InlineKeyboardButton(label, callback_data=key)])
123
+
124
+ await update.message.reply_text(
125
+ "Pick a model (or send /model <name> to type one):", reply_markup=InlineKeyboardMarkup(keyboard)
126
+ )
96
127
  return
97
128
 
98
129
  requested = " ".join(context.args)
99
- from cogs.general_cog import load_cached_models
100
-
101
- cached_models = load_cached_models()
102
130
  exact = next((m for m in cached_models if m.lower() == requested.lower()), None)
103
131
  partial = next((m for m in cached_models if requested.lower() in m.lower()), None)
104
- final_model = exact or partial or requested
105
-
106
- session_manager.update_session(str(chat_id), "model", final_model)
107
- bot_settings["default_model"] = final_model
108
- save_bot_settings(bot_settings)
132
+ final_model = _apply_model(exact or partial or requested)
109
133
 
110
134
  await adapter.send_message(
111
135
  chat_id, f"🤖 Model changed: **{final_model}**\n💾 Also set as the default for new sessions."
@@ -114,27 +138,47 @@ async def cmd_model(update: Update, context) -> None:
114
138
 
115
139
  async def cmd_credit(update: Update, context) -> None:
116
140
  user = update.effective_user
141
+ adapter: TelegramAdapter = context.bot_data["adapter"]
142
+
117
143
  if not allowed(user.id, "telegram"):
118
144
  await update.message.reply_text("❌ Denied")
119
145
  return
120
146
 
121
- if not context.args or context.args[0].lower() not in ("on", "off"):
122
- await update.message.reply_text("Usage: /credit <on|off>")
123
- return
124
-
125
- use_credits = context.args[0].lower() == "on"
126
147
  settings_path = Path(os.getenv("HOME", "/root")) / ".gemini/antigravity-cli/settings.json"
127
- try:
128
- data = safe_load_json(settings_path, {}, logger=logger)
129
- data["useG1Credits"] = use_credits
130
- atomic_write_json(settings_path, data)
148
+ current = bool(safe_load_json(settings_path, {}, logger=logger).get("useG1Credits", False))
149
+
150
+ async def set_credit(use_credits: bool, query) -> None:
151
+ try:
152
+ data = safe_load_json(settings_path, {}, logger=logger)
153
+ data["useG1Credits"] = use_credits
154
+ atomic_write_json(settings_path, data)
155
+ except Exception as e:
156
+ await query.answer()
157
+ await safe_query_edit(query, text=f"⚠️ Failed to update settings: {e}")
158
+ return
131
159
 
132
160
  status_text = "🟢 **ON** (Using AI Credits)" if use_credits else "🔴 **OFF** (Using default/free model)"
133
- await update.message.reply_text(
134
- markdown_to_telegram_html(f"✅ AI Credit setting updated: {status_text}"), parse_mode="HTML"
161
+ await query.answer()
162
+ await safe_query_edit(
163
+ query,
164
+ text=markdown_to_telegram_html(f"✅ AI Credit setting updated: {status_text}"),
165
+ parse_mode="HTML",
135
166
  )
136
- except Exception as e:
137
- await update.message.reply_text(f"⚠️ Failed to update settings: {e}")
167
+
168
+ prompt_id = uuid.uuid4().hex[:12]
169
+ on_key, off_key = f"{prompt_id}:on", f"{prompt_id}:off"
170
+ adapter.register_callback(on_key, lambda query: set_credit(True, query))
171
+ adapter.register_callback(off_key, lambda query: set_credit(False, query))
172
+
173
+ keyboard = InlineKeyboardMarkup(
174
+ [
175
+ [
176
+ InlineKeyboardButton(("✅ " if current else "") + "🟢 ON", callback_data=on_key),
177
+ InlineKeyboardButton(("✅ " if not current else "") + "🔴 OFF", callback_data=off_key),
178
+ ]
179
+ ]
180
+ )
181
+ await update.message.reply_text("AI Credits:", reply_markup=keyboard)
138
182
 
139
183
 
140
184
  async def on_message(update: Update, context) -> None:
@@ -192,6 +236,8 @@ async def run_telegram(stop_event: asyncio.Event) -> None:
192
236
  app = build_application()
193
237
  logger.info("✅ Telegram bot starting (polling mode)...")
194
238
  await app.initialize()
239
+ if app.post_init: # not auto-called outside run_polling()/run_webhook()
240
+ await app.post_init(app)
195
241
  await app.start()
196
242
  await app.updater.start_polling(allowed_updates=Update.ALL_TYPES)
197
243
  try:
@@ -200,3 +246,5 @@ async def run_telegram(stop_event: asyncio.Event) -> None:
200
246
  await app.updater.stop()
201
247
  await app.stop()
202
248
  await app.shutdown()
249
+ if app.post_shutdown: # same gotcha as post_init: not auto-called here
250
+ await app.post_shutdown(app)
@@ -42,7 +42,7 @@ def markdown_to_telegram_html(text: str) -> str:
42
42
  return "".join(out)
43
43
 
44
44
 
45
- async def _safe_query_edit(query, **kwargs) -> None:
45
+ async def safe_query_edit(query, **kwargs) -> None:
46
46
  try:
47
47
  await query.edit_message_text(**kwargs)
48
48
  except TelegramError as e:
@@ -258,7 +258,7 @@ class TelegramAdapter(MessengerAdapter):
258
258
  new_text = "❌ <b>Rejected</b>"
259
259
  handle.text = new_text
260
260
  await query.answer()
261
- await _safe_query_edit(query, text=new_text, parse_mode="HTML")
261
+ await safe_query_edit(query, text=new_text, parse_mode="HTML")
262
262
 
263
263
  allow_key = f"{prompt_id}:allow"
264
264
  self._callbacks[allow_key] = lambda query: resolve("allow", None, query)
@@ -303,11 +303,11 @@ class TelegramAdapter(MessengerAdapter):
303
303
  new_text = f"✅ <b>{note}: {html.escape(chosen_text)}</b>"
304
304
  handle.text = new_text
305
305
  await query.answer()
306
- await _safe_query_edit(query, text=new_text, parse_mode="HTML")
306
+ await safe_query_edit(query, text=new_text, parse_mode="HTML")
307
307
 
308
308
  async def write_in(query):
309
309
  await query.answer()
310
- await _safe_query_edit(
310
+ await safe_query_edit(
311
311
  query,
312
312
  text=f"❓ <b>{html.escape(question)}</b>\n\n💬 Reply with your answer as a message.",
313
313
  parse_mode="HTML",