calvyn-code 0.14.10 → 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
@@ -2679,7 +2679,7 @@ class HermesCLI:
2679
2679
  # env vars would stomp each other.
2680
2680
  _model_config = CLI_CONFIG.get("model", {})
2681
2681
  _config_model = (_model_config.get("default") or _model_config.get("model") or "") if isinstance(_model_config, dict) else (_model_config or "")
2682
- _DEFAULT_CONFIG_MODEL = ""
2682
+ _DEFAULT_CONFIG_MODEL = "gpt-5.3-codex"
2683
2683
  self.model = model or _config_model or _DEFAULT_CONFIG_MODEL
2684
2684
  # Auto-detect model from local server if still on default
2685
2685
  if self.model == _DEFAULT_CONFIG_MODEL:
@@ -4618,7 +4618,10 @@ class HermesCLI:
4618
4618
  self._console_print()
4619
4619
  self._console_print()
4620
4620
  term_width = shutil.get_terminal_size().columns
4621
- 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:
4622
4625
  self._console_print(_logo)
4623
4626
  self._console_print()
4624
4627
  self._console_print(_build_compact_banner())
@@ -6901,13 +6904,15 @@ class HermesCLI:
6901
6904
  _cprint(" Prompt caching: enabled")
6902
6905
  if result.warning_message:
6903
6906
  _cprint(f" ⚠ {result.warning_message}")
6904
- if persist_global:
6905
- save_config_value("model.default", result.new_model)
6906
- if result.provider_changed:
6907
- save_config_value("model.provider", result.target_provider)
6908
- _cprint(" Saved to config.yaml (--global)")
6909
- else:
6910
- _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")
6911
6916
 
6912
6917
  def _handle_model_picker_selection(self, persist_global: bool = False) -> None:
6913
6918
  state = self._model_picker_state
@@ -7144,13 +7149,15 @@ class HermesCLI:
7144
7149
  _cprint(f" ⚠ {result.warning_message}")
7145
7150
 
7146
7151
  # Persistence
7147
- if persist_global:
7148
- save_config_value("model.default", result.new_model)
7149
- if result.provider_changed:
7150
- save_config_value("model.provider", result.target_provider)
7151
- _cprint(" Saved to config.yaml (--global)")
7152
- else:
7153
- _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")
7154
7161
 
7155
7162
  def _handle_codex_runtime(self, cmd_original: str) -> None:
7156
7163
  """Handle /codex-runtime — toggle the codex app-server runtime opt-in.
@@ -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.10",
3
+ "version": "0.14.11",
4
4
  "description": "Calvyn Code — AI агент с инструментами, мессенджерами и локальным CLI",
5
5
  "bin": {
6
6
  "calvyn": "bin/calvyn.js",
@@ -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",),