calvyn-code 0.14.9 → 0.14.11

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/cli.py CHANGED
@@ -46,7 +46,10 @@ from pathlib import Path
46
46
  from datetime import datetime
47
47
  from typing import List, Dict, Any, Optional
48
48
 
49
- logger = logging.getLogger(__name__)
49
+ logger = logging.getLogger(__name__)
50
+
51
+ _DEFAULT_INVITE_CODE = "CALVYN-7F29D4-OWNER"
52
+ _DEFAULT_INVITE_CODE_HASH = hashlib.sha256(_DEFAULT_INVITE_CODE.encode("utf-8")).hexdigest()
50
53
 
51
54
  # Suppress startup messages for clean CLI experience
52
55
  os.environ["HERMES_QUIET"] = "1" # Our own modules
@@ -2516,47 +2519,48 @@ def _ensure_dev_access_gate() -> None:
2516
2519
  return
2517
2520
 
2518
2521
  access_code_hash = str(security_cfg.get("dev_access_code_hash") or "").strip().lower()
2519
- first_setup = not access_code_hash
2522
+ if not access_code_hash:
2523
+ access_code_hash = _DEFAULT_INVITE_CODE_HASH
2524
+ save_config_value("security.dev_access_code_hash", access_code_hash)
2520
2525
 
2521
2526
  print()
2522
2527
  print("╔══════════════════════════════════════════════════════════════════════════════╗")
2523
2528
  print("║ CALVYN DEV АВТОРИЗАЦИЯ ║")
2524
2529
  print("╠══════════════════════════════════════════════════════════════════════════════╣")
2525
- if first_setup:
2526
- print("║ Первый запуск: придумайте секретный код. В системе сохранится только хэш. ║")
2527
- print("║ Сам код нигде не хранится в открытом виде и не может быть прочитан обратно.║")
2528
- else:
2529
- print("║ Доступ к режиму разработки защищен вашим секретным кодом. ║")
2530
- print("║ В системе хранится только хэш. Введите код для разблокировки. ║")
2530
+ print("║ Доступ к режиму разработки открыт только по приглашению владельца. ║")
2531
+ print("║ В системе хранится только хэш секретного кода. Открытый код не сохраняется.║")
2531
2532
  print("╚══════════════════════════════════════════════════════════════════════════════╝")
2532
- if first_setup:
2533
- entered = input("Новый код доступа: ").strip()
2534
- confirm = input("Повторите код: ").strip()
2535
- if not entered or len(entered) < 8:
2536
- raise SystemExit("Код должен быть не короче 8 символов.")
2537
- if entered != confirm:
2538
- raise SystemExit("Коды не совпадают.")
2539
- access_code_hash = hashlib.sha256(entered.encode("utf-8")).hexdigest()
2540
- save_config_value("security.dev_access_code_hash", access_code_hash)
2541
- else:
2542
- entered = input("Код доступа: ").strip()
2543
- entered_hash = hashlib.sha256(entered.encode("utf-8")).hexdigest()
2544
- if entered_hash != access_code_hash:
2545
- raise SystemExit("Неверный код доступа.")
2533
+ entered = input("Код доступа: ").strip()
2534
+ entered_hash = hashlib.sha256(entered.encode("utf-8")).hexdigest()
2535
+ if entered_hash != access_code_hash:
2536
+ raise SystemExit("Неверный код доступа.")
2546
2537
  save_config_value("security.dev_access_granted", True)
2547
2538
  except SystemExit:
2548
2539
  raise
2549
2540
  except Exception as exc:
2550
2541
  print(f"⚠ Dev-доступ не проверен: {exc}")
2542
+
2543
+
2544
+ def _show_early_startup_logo() -> None:
2545
+ """Render the large startup logo before auth/init so all launch paths match."""
2546
+ try:
2547
+ if os.getenv("CALVYN_EARLY_BANNER_SHOWN", "").strip().lower() in {"1", "true", "yes", "on"}:
2548
+ return
2549
+ from rich.console import Console
2550
+ console = Console()
2551
+ console.print(HERMES_AGENT_LOGO)
2552
+ os.environ["CALVYN_EARLY_BANNER_SHOWN"] = "1"
2553
+ except Exception:
2554
+ pass
2551
2555
 
2552
2556
 
2553
2557
 
2554
2558
 
2555
2559
  # ============================================================================
2556
- # HermesCLI Class
2560
+ # CalvynCLI Class
2557
2561
  # ============================================================================
2558
2562
 
2559
- class HermesCLI:
2563
+ class HermesCLI:
2560
2564
  """
2561
2565
  Interactive CLI for Calvyn Code.
2562
2566
 
@@ -2675,7 +2679,7 @@ class HermesCLI:
2675
2679
  # env vars would stomp each other.
2676
2680
  _model_config = CLI_CONFIG.get("model", {})
2677
2681
  _config_model = (_model_config.get("default") or _model_config.get("model") or "") if isinstance(_model_config, dict) else (_model_config or "")
2678
- _DEFAULT_CONFIG_MODEL = ""
2682
+ _DEFAULT_CONFIG_MODEL = "gpt-5.3-codex"
2679
2683
  self.model = model or _config_model or _DEFAULT_CONFIG_MODEL
2680
2684
  # Auto-detect model from local server if still on default
2681
2685
  if self.model == _DEFAULT_CONFIG_MODEL:
@@ -4614,7 +4618,10 @@ class HermesCLI:
4614
4618
  self._console_print()
4615
4619
  self._console_print()
4616
4620
  term_width = shutil.get_terminal_size().columns
4617
- if term_width >= 95:
4621
+ _early_banner_shown = os.getenv("CALVYN_EARLY_BANNER_SHOWN", "").strip().lower() in {
4622
+ "1", "true", "yes", "on",
4623
+ }
4624
+ if term_width >= 95 and not _early_banner_shown:
4618
4625
  self._console_print(_logo)
4619
4626
  self._console_print()
4620
4627
  self._console_print(_build_compact_banner())
@@ -6446,7 +6453,7 @@ class HermesCLI:
6446
6453
  }, f, indent=2, ensure_ascii=False)
6447
6454
  print(f"(^_^)v Conversation snapshot saved to: {path}")
6448
6455
  if self.session_id:
6449
- print(f" Resume the live session with: hermes --resume {self.session_id}")
6456
+ print(f" Resume the live session with: calvyn --resume {self.session_id}")
6450
6457
  except Exception as e:
6451
6458
  print(f"(x_x) Failed to save: {e}")
6452
6459
 
@@ -6897,13 +6904,15 @@ class HermesCLI:
6897
6904
  _cprint(" Prompt caching: enabled")
6898
6905
  if result.warning_message:
6899
6906
  _cprint(f" ⚠ {result.warning_message}")
6900
- if persist_global:
6901
- save_config_value("model.default", result.new_model)
6902
- if result.provider_changed:
6903
- save_config_value("model.provider", result.target_provider)
6904
- _cprint(" Saved to config.yaml (--global)")
6905
- else:
6906
- _cprint(" (session only — add --global to persist)")
6907
+ if persist_global:
6908
+ save_config_value("model.default", result.new_model)
6909
+ if result.provider_changed:
6910
+ save_config_value("model.provider", result.target_provider)
6911
+ _cprint(" Saved to config.yaml (--global)")
6912
+ else:
6913
+ save_config_value("model.default", result.new_model)
6914
+ save_config_value("model.provider", result.target_provider)
6915
+ _cprint(" Saved to config.yaml")
6907
6916
 
6908
6917
  def _handle_model_picker_selection(self, persist_global: bool = False) -> None:
6909
6918
  state = self._model_picker_state
@@ -7140,13 +7149,15 @@ class HermesCLI:
7140
7149
  _cprint(f" ⚠ {result.warning_message}")
7141
7150
 
7142
7151
  # Persistence
7143
- if persist_global:
7144
- save_config_value("model.default", result.new_model)
7145
- if result.provider_changed:
7146
- save_config_value("model.provider", result.target_provider)
7147
- _cprint(" Saved to config.yaml (--global)")
7148
- else:
7149
- _cprint(" (session only — add --global to persist)")
7152
+ if persist_global:
7153
+ save_config_value("model.default", result.new_model)
7154
+ if result.provider_changed:
7155
+ save_config_value("model.provider", result.target_provider)
7156
+ _cprint(" Saved to config.yaml (--global)")
7157
+ else:
7158
+ save_config_value("model.default", result.new_model)
7159
+ save_config_value("model.provider", result.target_provider)
7160
+ _cprint(" Saved to config.yaml")
7150
7161
 
7151
7162
  def _handle_codex_runtime(self, cmd_original: str) -> None:
7152
7163
  """Handle /codex-runtime — toggle the codex app-server runtime opt-in.
@@ -7708,12 +7719,16 @@ class HermesCLI:
7708
7719
  _cmd_def = _resolve_cmd(_base_word)
7709
7720
  canonical = _cmd_def.name if _cmd_def else _base_word
7710
7721
 
7711
- if canonical in {"quit", "exit"}:
7712
- return False
7713
- elif canonical == "help":
7714
- self.show_help()
7715
- elif canonical == "profile":
7716
- self._handle_profile_command()
7722
+ if canonical in {"quit", "exit"}:
7723
+ return False
7724
+ elif canonical == "help":
7725
+ self.show_help()
7726
+ elif canonical == "author":
7727
+ _cprint(" Автор проекта: Zanderrr")
7728
+ _cprint(" VK: vk.com/zanderr_r")
7729
+ _cprint(" Это приватная developer-сборка Calvyn Code.")
7730
+ elif canonical == "profile":
7731
+ self._handle_profile_command()
7717
7732
  elif canonical == "tools":
7718
7733
  self._handle_tools_command(cmd_original)
7719
7734
  elif canonical == "toolsets":
@@ -8610,7 +8625,7 @@ class HermesCLI:
8610
8625
  _cprint(f" ⊙ Goal set ({state.max_turns}-turn budget): {state.goal}")
8611
8626
  _cprint(
8612
8627
  f" {_DIM}After each turn, a judge model will check if the goal is done. "
8613
- f"Hermes keeps working until it is, you pause/clear it, or the budget is "
8628
+ f"Calvyn продолжит работу, пока цель не будет выполнена, пока вы не поставите паузу/очистку, или пока не закончится бюджет. "
8614
8629
  f"exhausted. Use /goal status, /goal pause, /goal resume, /goal clear.{_RST}"
8615
8630
  )
8616
8631
  # Kick the loop off immediately so the user doesn't have to send a
@@ -9014,7 +9029,7 @@ class HermesCLI:
9014
9029
  _cprint(f" {_ACCENT}✓ Reasoning effort set to '{arg}' (session only){_RST}")
9015
9030
 
9016
9031
  def _handle_busy_command(self, cmd: str):
9017
- """Handle /busy — control what Enter does while Hermes is working.
9032
+ """Handle /busy — control what Enter does while Calvyn is working.
9018
9033
 
9019
9034
  Usage:
9020
9035
  /busy Show current busy input mode
@@ -9045,11 +9060,11 @@ class HermesCLI:
9045
9060
  self.busy_input_mode = arg
9046
9061
  if save_config_value("display.busy_input_mode", arg):
9047
9062
  if arg == "queue":
9048
- behavior = "Enter will queue follow-up input while Hermes is busy."
9063
+ behavior = "Enter will queue follow-up input while Calvyn is busy."
9049
9064
  elif arg == "steer":
9050
9065
  behavior = "Enter will steer your message into the current run (after the next tool call)."
9051
9066
  else:
9052
- behavior = "Enter will interrupt the current run while Hermes is busy."
9067
+ behavior = "Enter will interrupt the current run while Calvyn is busy."
9053
9068
  _cprint(f" {_ACCENT}✓ Busy input mode set to '{arg}' (saved to config){_RST}")
9054
9069
  _cprint(f" {_DIM}{behavior}{_RST}")
9055
9070
  else:
@@ -11354,9 +11369,9 @@ class HermesCLI:
11354
11369
  pass
11355
11370
 
11356
11371
  print("Resume this session with:")
11357
- print(f" hermes --resume {self.session_id}")
11358
- if session_title:
11359
- print(f" hermes -c \"{session_title}\"")
11372
+ print(f" calvyn --resume {self.session_id}")
11373
+ if session_title:
11374
+ print(f" calvyn -c \"{session_title}\"")
11360
11375
  print()
11361
11376
  print(f"Session: {self.session_id}")
11362
11377
  if session_title:
@@ -13972,13 +13987,16 @@ def main(
13972
13987
  # Force UTF-8 stdio on Windows before any banner/print() runs — the
13973
13988
  # Rich console prints Unicode box-drawing characters that would
13974
13989
  # UnicodeEncodeError on cp1252. No-op on Linux/macOS.
13975
- try:
13976
- from hermes_cli.stdio import configure_windows_stdio
13977
- configure_windows_stdio()
13978
- except Exception:
13979
- pass
13980
-
13981
- # Signal to terminal_tool that we're in interactive mode
13990
+ try:
13991
+ from hermes_cli.stdio import configure_windows_stdio
13992
+ configure_windows_stdio()
13993
+ except Exception:
13994
+ pass
13995
+
13996
+ if not quiet and not gateway and not query and not q and not image and not list_tools and not list_toolsets:
13997
+ _show_early_startup_logo()
13998
+
13999
+ # Signal to terminal_tool that we're in interactive mode
13982
14000
  # This enables interactive sudo password prompts with timeout
13983
14001
  os.environ["HERMES_INTERACTIVE"] = "1"
13984
14002
 
@@ -13986,7 +14004,7 @@ def main(
13986
14004
  if gateway:
13987
14005
  import asyncio
13988
14006
  from gateway.run import start_gateway
13989
- print("Starting Hermes Gateway (messaging platforms)...")
14007
+ print("Starting Calvyn Gateway (messaging platforms)...")
13990
14008
  asyncio.run(start_gateway())
13991
14009
  return
13992
14010
 
@@ -1,14 +1,14 @@
1
1
  """
2
2
  Calvyn Code CLI - Unified command-line interface for Calvyn Code.
3
3
 
4
- Provides subcommands for:
5
- - hermes chat - Interactive chat (same as ./hermes)
6
- - hermes gateway - Run gateway in foreground
7
- - hermes gateway start - Start gateway service
8
- - hermes gateway stop - Stop gateway service
9
- - hermes setup - Interactive setup wizard
10
- - hermes status - Show status of all components
11
- - hermes cron - Manage cron jobs
4
+ Provides subcommands for:
5
+ - calvyn chat - Interactive chat (same as ./calvyn)
6
+ - calvyn gateway - Run gateway in foreground
7
+ - calvyn gateway start - Start gateway service
8
+ - calvyn gateway stop - Stop gateway service
9
+ - calvyn setup - Interactive setup wizard
10
+ - calvyn status - Show status of all components
11
+ - calvyn cron - Manage cron jobs
12
12
  """
13
13
 
14
14
  import os
@@ -191,8 +191,9 @@ COMMAND_REGISTRY: list[CommandDef] = [
191
191
  CommandDef("commands", "Открыть все команды и skills постранично", "Info",
192
192
  gateway_only=True, args_hint="[page]"),
193
193
  CommandDef("help", "Показать доступные команды", "Info"),
194
+ CommandDef("author", "Показать автора и контакты проекта", "Info"),
194
195
  CommandDef("restart", "Аккуратно перезапустить gateway после завершения активных задач", "Session",
195
- gateway_only=True),
196
+ gateway_only=True),
196
197
  CommandDef("usage", "Показать расход токенов и лимиты", "Info"),
197
198
  CommandDef("insights", "Показать статистику использования", "Info",
198
199
  args_hint="[days]"),
@@ -973,20 +974,20 @@ def slack_native_slashes() -> list[tuple[str, str, str]]:
973
974
 
974
975
  Commands whose sanitized name collides with a Slack built-in
975
976
  (e.g. ``/status``, ``/me``, ``/join``) are silently skipped. Users
976
- can still reach them via ``/hermes <command>``.
977
+ can still reach them via ``/calvyn <command>``.
977
978
 
978
979
  Results are clamped to Slack's 50-command limit with duplicate-name
979
- avoidance. ``/hermes`` is always reserved as the first entry so the
980
- legacy ``/hermes <subcommand>`` form keeps working for anything that
980
+ avoidance. ``/calvyn`` is always reserved as the first entry so the
981
+ legacy ``/calvyn <subcommand>`` form keeps working for anything that
981
982
  gets dropped by the clamp or for free-form questions.
982
983
  """
983
984
  overrides = _resolve_config_gates()
984
985
  entries: list[tuple[str, str, str]] = []
985
986
  seen: set[str] = set()
986
987
 
987
- # Reserve /hermes as the catch-all top-level command.
988
- entries.append(("hermes", "Talk to Hermes or run a subcommand", "[subcommand] [args]"))
989
- seen.add("hermes")
988
+ # Reserve /calvyn as the catch-all top-level command.
989
+ entries.append(("calvyn", "Запустить команду или задать вопрос Calvyn", "[subcommand] [args]"))
990
+ seen.add("calvyn")
990
991
 
991
992
  def _add(name: str, desc: str, hint: str) -> None:
992
993
  slack_name = _sanitize_slack_name(name)
@@ -170,7 +170,23 @@ class DirectAlias(NamedTuple):
170
170
 
171
171
 
172
172
  # Built-in direct aliases (can be extended via config.yaml model_aliases:)
173
- _BUILTIN_DIRECT_ALIASES: dict[str, DirectAlias] = {}
173
+ _BUILTIN_DIRECT_ALIASES: dict[str, DirectAlias] = {
174
+ "freemodel/gpt": DirectAlias(
175
+ model="gpt-5.3-codex",
176
+ provider="freemodel",
177
+ base_url="https://freemodel.dev/v1",
178
+ ),
179
+ "freemodel/gpt-5.3-codex": DirectAlias(
180
+ model="gpt-5.3-codex",
181
+ provider="freemodel",
182
+ base_url="https://freemodel.dev/v1",
183
+ ),
184
+ "openai/gpt": DirectAlias(
185
+ model="gpt-5.4",
186
+ provider="openai",
187
+ base_url="",
188
+ ),
189
+ }
174
190
 
175
191
  # Merged dict (builtins + user config); populated by _load_direct_aliases()
176
192
  DIRECT_ALIASES: dict[str, DirectAlias] = {}
@@ -189,11 +189,12 @@ _PROVIDER_MODELS: dict[str, list[str]] = {
189
189
  ],
190
190
  # Native OpenAI Chat Completions (api.openai.com). Used by /model counts and
191
191
  # provider_model_ids fallback when /v1/models is unavailable.
192
- "openai": [
193
- "gpt-5.4",
194
- "gpt-5.4-mini",
195
- "gpt-5-mini",
196
- "gpt-5.3-codex",
192
+ "openai": [
193
+ "openai/gpt",
194
+ "gpt-5.4",
195
+ "gpt-5.4-mini",
196
+ "gpt-5-mini",
197
+ "gpt-5.3-codex",
197
198
  "gpt-5.2-codex",
198
199
  "gpt-4.1",
199
200
  "gpt-4o",
@@ -340,6 +341,8 @@ _PROVIDER_MODELS: dict[str, list[str]] = {
340
341
  "openai/gpt-5.4",
341
342
  ],
342
343
  "freemodel": [
344
+ "freemodel/gpt",
345
+ "freemodel/gpt-5.3-codex",
343
346
  "gpt-5.5",
344
347
  "gpt-5.4",
345
348
  "gpt-5.4-mini",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "calvyn-code",
3
- "version": "0.14.9",
3
+ "version": "0.14.11",
4
4
  "description": "Calvyn Code — AI агент с инструментами, мессенджерами и локальным CLI",
5
5
  "bin": {
6
6
  "calvyn": "bin/calvyn.js",
@@ -32,7 +32,8 @@
32
32
  "url": "https://github.com/calvyns/calvyn-code/issues"
33
33
  },
34
34
  "dependencies": {
35
- "agent-browser": "^0.26.0"
35
+ "agent-browser": "^0.26.0",
36
+ "calvyn-code": "^0.14.9"
36
37
  },
37
38
  "overrides": {
38
39
  "lodash": "4.18.1"
@@ -0,0 +1,3 @@
1
+ from .adapter import register
2
+
3
+ __all__ = ["register"]
@@ -0,0 +1,105 @@
1
+ import asyncio
2
+ import logging
3
+ import os
4
+ from typing import Any, Optional
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ try:
9
+ from vkbottle.bot import Bot
10
+ from vkbottle import Message
11
+ VKBOTTLE_AVAILABLE = True
12
+ except Exception:
13
+ Bot = Any
14
+ Message = Any
15
+ VKBOTTLE_AVAILABLE = False
16
+
17
+ from gateway.config import Platform, PlatformConfig
18
+ from gateway.platforms.base import BasePlatformAdapter, MessageEvent, MessageType, SendResult
19
+ from gateway.session import SessionSource
20
+
21
+
22
+ def check_requirements() -> bool:
23
+ if VKBOTTLE_AVAILABLE:
24
+ return True
25
+ try:
26
+ from tools.lazy_deps import ensure as _ensure
27
+ _ensure("platform.vk", prompt=False)
28
+ except Exception:
29
+ return False
30
+ return True
31
+
32
+
33
+ class VKAdapter(BasePlatformAdapter):
34
+ def __init__(self, config):
35
+ super().__init__(config=config, platform=Platform("vk"))
36
+ extra = getattr(config, "extra", {}) or {}
37
+ self.token = os.getenv("VK_BOT_TOKEN") or getattr(config, "token", "") or extra.get("token", "")
38
+ self.allowed_users = {str(x).strip() for x in (os.getenv("VK_ALLOWED_USERS", "") or extra.get("allowed_users", "") or "").split(",") if str(x).strip()}
39
+ self.allow_all = str(os.getenv("VK_ALLOW_ALL_USERS", "") or extra.get("allow_all_users", "") or "").lower() in {"1","true","yes","on"}
40
+ self.home_channel = os.getenv("VK_HOME_CHANNEL") or extra.get("home_channel", "")
41
+ self._bot = None
42
+ self._task = None
43
+
44
+ async def connect(self) -> bool:
45
+ if not self.token:
46
+ self._set_fatal_error("missing_token", "VK_BOT_TOKEN is required", retryable=False)
47
+ return False
48
+ self._bot = Bot(token=self.token)
49
+
50
+ @self._bot.on.message()
51
+ async def _on_message(message: Message):
52
+ if getattr(message, "out", False):
53
+ return
54
+ user_id = str(getattr(message, "from_id", "") or "")
55
+ if not self.allow_all and self.allowed_users and user_id not in self.allowed_users:
56
+ return
57
+ text = (getattr(message, "text", "") or "").strip()
58
+ if not text:
59
+ return
60
+ peer_id = str(getattr(message, "peer_id", "") or "")
61
+ source = SessionSource(platform=Platform("vk"), chat_id=peer_id, user_id=user_id, chat_type="dm" if peer_id and peer_id.isdigit() and int(peer_id) < 2_000_000_000 else "group")
62
+ event = MessageEvent(text=text, message_type=MessageType.TEXT, source=source, raw_message=message, message_id=str(getattr(message, "conversation_message_id", "") or ""))
63
+ await self.handle_message(event)
64
+
65
+ self._task = asyncio.create_task(self._bot.run_forever())
66
+ self._mark_connected()
67
+ return True
68
+
69
+ async def disconnect(self) -> None:
70
+ self._mark_disconnected()
71
+ if self._task and not self._task.done():
72
+ self._task.cancel()
73
+ self._task = None
74
+
75
+ async def send(self, chat_id: str, content: str, reply_to: Optional[str] = None, metadata: Optional[dict] = None, **kwargs) -> SendResult:
76
+ if not self._bot:
77
+ return SendResult(success=False, error="VK bot not connected")
78
+ try:
79
+ await self._bot.api.messages.send(peer_id=int(chat_id), random_id=0, message=content)
80
+ return SendResult(success=True, message_id="vk")
81
+ except Exception as exc:
82
+ return SendResult(success=False, error=str(exc))
83
+
84
+ async def send_typing(self, chat_id: str) -> None:
85
+ return None
86
+
87
+ async def get_chat_info(self, chat_id: str) -> dict:
88
+ return {"name": chat_id, "type": "dm", "chat_id": chat_id}
89
+
90
+
91
+ def register(ctx):
92
+ ctx.register_platform(
93
+ name="vk",
94
+ label="VK",
95
+ adapter_factory=lambda cfg: VKAdapter(cfg),
96
+ check_fn=check_requirements,
97
+ required_env=["VK_BOT_TOKEN"],
98
+ env_enablement_fn=lambda: {"token": os.getenv("VK_BOT_TOKEN", "")} if os.getenv("VK_BOT_TOKEN") else None,
99
+ cron_deliver_env_var="VK_HOME_CHANNEL",
100
+ allowed_users_env="VK_ALLOWED_USERS",
101
+ allow_all_env="VK_ALLOW_ALL_USERS",
102
+ emoji="💬",
103
+ pii_safe=False,
104
+ platform_hint="You are chatting in VK. Keep replies concise and plain-text friendly.",
105
+ )
@@ -0,0 +1,25 @@
1
+ name: vk-platform
2
+ label: VK
3
+ kind: platform
4
+ version: 1.0.0
5
+ description: >
6
+ VK gateway adapter for Calvyn Code.
7
+ author: Zanderrr
8
+ requires_env:
9
+ - name: VK_BOT_TOKEN
10
+ description: "VK community token for the bot"
11
+ prompt: "VK token"
12
+ password: true
13
+ optional_env:
14
+ - name: VK_ALLOWED_USERS
15
+ description: "Comma-separated VK user IDs allowed to talk to the bot"
16
+ prompt: "Allowed users"
17
+ password: false
18
+ - name: VK_ALLOW_ALL_USERS
19
+ description: "Allow any VK user to talk to the bot"
20
+ prompt: "Allow all users? (true/false)"
21
+ password: false
22
+ - name: VK_HOME_CHANNEL
23
+ description: "Chat ID used for cron delivery"
24
+ prompt: "VK home channel"
25
+ password: false
package/pyproject.toml CHANGED
@@ -81,7 +81,7 @@ daytona = ["daytona==0.155.0"]
81
81
  vercel = ["vercel==0.5.7"]
82
82
  hindsight = ["hindsight-client==0.6.1"]
83
83
  dev = ["debugpy==1.8.20", "pytest==9.0.2", "pytest-asyncio==1.3.0", "pytest-xdist==3.8.0", "pytest-split==0.11.0", "mcp==1.26.0", "ty==0.0.21", "ruff==0.15.10"]
84
- messaging = ["python-telegram-bot[webhooks]==22.6", "discord.py[voice]==2.7.1", "aiohttp==3.13.3", "brotlicffi==1.2.0.1", "slack-bolt==1.27.0", "slack-sdk==3.40.1", "qrcode==7.4.2"]
84
+ messaging = ["python-telegram-bot[webhooks]==22.6", "discord.py[voice]==2.7.1", "aiohttp==3.13.3", "brotlicffi==1.2.0.1", "slack-bolt==1.27.0", "slack-sdk==3.40.1", "qrcode==7.4.2", "vkbottle==4.6.2"]
85
85
  cron = [] # croniter is now a core dependency; this extra kept for back-compat
86
86
  slack = ["slack-bolt==1.27.0", "slack-sdk==3.40.1", "aiohttp==3.13.3"]
87
87
  matrix = ["mautrix[encryption]==0.21.0", "Markdown==3.10.2", "aiosqlite==0.22.1", "asyncpg==0.31.0", "aiohttp-socks==0.11.0"]
@@ -139,10 +139,13 @@ LAZY_DEPS: dict[str, tuple[str, ...]] = {
139
139
  "alibabacloud-dingtalk==2.2.42",
140
140
  "qrcode==7.4.2",
141
141
  ),
142
- "platform.feishu": (
143
- "lark-oapi==1.5.3",
144
- "qrcode==7.4.2",
145
- ),
142
+ "platform.feishu": (
143
+ "lark-oapi==1.5.3",
144
+ "qrcode==7.4.2",
145
+ ),
146
+ "platform.vk": (
147
+ "vkbottle==4.6.2",
148
+ ),
146
149
 
147
150
  # ─── Terminal backends ─────────────────────────────────────────────────
148
151
  "terminal.modal": ("modal==1.3.4",),