calvyn-code 0.14.4 → 0.14.6

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/bin/calvyn.js CHANGED
@@ -18,6 +18,12 @@ function resolveCalvynBinary() {
18
18
  return path.join(packageRoot, ".venv", venvName, filename)
19
19
  }
20
20
 
21
+ function resolveVenvPython() {
22
+ return isWindows
23
+ ? path.join(packageRoot, ".venv", "Scripts", "python.exe")
24
+ : path.join(packageRoot, ".venv", "bin", "python")
25
+ }
26
+
21
27
  function runPostinstallIfNeeded() {
22
28
  const calvynBinary = resolveCalvynBinary()
23
29
  if (fs.existsSync(calvynBinary)) {
@@ -50,8 +56,10 @@ function runPostinstallIfNeeded() {
50
56
  }
51
57
 
52
58
  const args = process.argv.slice(2)
53
- const calvynBinary = runPostinstallIfNeeded()
54
- const result = spawnSync(calvynBinary, args, {
59
+ runPostinstallIfNeeded()
60
+ const venvPython = resolveVenvPython()
61
+ const cliEntry = path.join(packageRoot, "cli.py")
62
+ const result = spawnSync(venvPython, [cliEntry, ...args], {
55
63
  stdio: "inherit",
56
64
  shell: false,
57
65
  env: {
package/cli.py CHANGED
@@ -23,15 +23,17 @@ except ModuleNotFoundError:
23
23
  # means UTF-8 stdio setup is skipped on Windows; POSIX is unaffected.
24
24
  pass
25
25
 
26
- import logging
27
- import os
28
- import shutil
29
- import sys
30
- import json
31
- import re
32
- import concurrent.futures
33
- import base64
34
- import atexit
26
+ import logging
27
+ import os
28
+ import shutil
29
+ import sys
30
+ import json
31
+ import re
32
+ import concurrent.futures
33
+ import base64
34
+ import hashlib
35
+ import secrets
36
+ import atexit
35
37
  import errno
36
38
  import tempfile
37
39
  import time
@@ -2452,7 +2454,7 @@ def _parse_skills_argument(skills: str | list[str] | tuple[str, ...] | None) ->
2452
2454
  return parsed
2453
2455
 
2454
2456
 
2455
- def save_config_value(key_path: str, value: any) -> bool:
2457
+ def save_config_value(key_path: str, value: any) -> bool:
2456
2458
  """
2457
2459
  Save a value to the active config file at the specified key path.
2458
2460
 
@@ -2488,9 +2490,50 @@ def save_config_value(key_path: str, value: any) -> bool:
2488
2490
  pass
2489
2491
 
2490
2492
  return True
2491
- except Exception as e:
2492
- logger.error("Failed to save config: %s", e)
2493
- return False
2493
+ except Exception as e:
2494
+ logger.error("Failed to save config: %s", e)
2495
+ return False
2496
+
2497
+
2498
+ def _ensure_dev_access_gate() -> None:
2499
+ """Require a one-time dev access code and persist the approval."""
2500
+ try:
2501
+ cfg = load_cli_config() or {}
2502
+ security_cfg = cfg.get("security", {}) if isinstance(cfg, dict) else {}
2503
+ if not isinstance(security_cfg, dict):
2504
+ security_cfg = {}
2505
+ if not security_cfg.get("dev_access_required", True):
2506
+ return
2507
+ if security_cfg.get("dev_access_granted", False):
2508
+ return
2509
+
2510
+ access_code_hash = str(security_cfg.get("dev_access_code_hash") or "").strip().lower()
2511
+ generated_code = ""
2512
+ if not access_code_hash:
2513
+ generated_code = f"CALVYN-{secrets.token_hex(3).upper()}-{secrets.token_hex(3).upper()}"
2514
+ access_code_hash = hashlib.sha256(generated_code.encode("utf-8")).hexdigest()
2515
+ save_config_value("security.dev_access_code_hash", access_code_hash)
2516
+
2517
+ print()
2518
+ print("╔══════════════════════════════════════════════════════════════════════════════╗")
2519
+ print("║ CALVYN DEV АВТОРИЗАЦИЯ ║")
2520
+ print("╠══════════════════════════════════════════════════════════════════════════════╣")
2521
+ print("║ Доступ к режиму разработки защищен одноразовым кодом. ║")
2522
+ if generated_code:
2523
+ print("║ Новый секретный код сгенерирован и сохранен в виде хэша. ║")
2524
+ print(f"║ Код для первого входа: {generated_code:<42}║")
2525
+ else:
2526
+ print("║ Код уже сохранен в системе. Введите секретный ключ для разблокировки. ║")
2527
+ print("╚══════════════════════════════════════════════════════════════════════════════╝")
2528
+ entered = input("Код доступа: ").strip()
2529
+ entered_hash = hashlib.sha256(entered.encode("utf-8")).hexdigest()
2530
+ if entered_hash != access_code_hash:
2531
+ raise SystemExit("Неверный код доступа.")
2532
+ save_config_value("security.dev_access_granted", True)
2533
+ except SystemExit:
2534
+ raise
2535
+ except Exception as exc:
2536
+ print(f"⚠ Dev-доступ не проверен: {exc}")
2494
2537
 
2495
2538
 
2496
2539
 
@@ -13925,13 +13968,15 @@ def main(
13925
13968
  # This enables interactive sudo password prompts with timeout
13926
13969
  os.environ["HERMES_INTERACTIVE"] = "1"
13927
13970
 
13928
- # Handle gateway mode (messaging + cron)
13929
- if gateway:
13930
- import asyncio
13931
- from gateway.run import start_gateway
13932
- print("Starting Hermes Gateway (messaging platforms)...")
13933
- asyncio.run(start_gateway())
13934
- return
13971
+ # Handle gateway mode (messaging + cron)
13972
+ if gateway:
13973
+ import asyncio
13974
+ from gateway.run import start_gateway
13975
+ print("Starting Hermes Gateway (messaging platforms)...")
13976
+ asyncio.run(start_gateway())
13977
+ return
13978
+
13979
+ _ensure_dev_access_gate()
13935
13980
 
13936
13981
  # Skip worktree for list commands (they exit immediately)
13937
13982
  if not list_tools and not list_toolsets:
@@ -14,7 +14,7 @@ Provides subcommands for:
14
14
  import os
15
15
  import sys
16
16
 
17
- __version__ = "0.14.1"
17
+ __version__ = "0.1.0"
18
18
  __release_date__ = "2026.5.16"
19
19
 
20
20
 
@@ -473,7 +473,7 @@ DEFAULT_CONFIG = {
473
473
  "providers": {},
474
474
  "fallback_providers": [],
475
475
  "credential_pool_strategies": {},
476
- "toolsets": ["hermes-cli"],
476
+ "toolsets": ["hermes-cli", "web", "browser"],
477
477
  "agent": {
478
478
  "max_turns": 90,
479
479
  # Inactivity timeout for gateway agent execution (seconds).
@@ -650,10 +650,11 @@ DEFAULT_CONFIG = {
650
650
  "allow_private_urls": False, # Allow navigating to private/internal IPs (localhost, 192.168.x.x, etc.)
651
651
  # Browser engine for local mode. Passed as ``--engine <value>`` to
652
652
  # agent-browser v0.25.3+.
653
- # "auto" use Chrome (default, don't pass --engine at all)
654
- # "lightpanda" use Lightpanda (1.3-5.8x faster navigation, no screenshots)
655
- # "chrome" explicitly request Chrome
656
- # Also settable via AGENT_BROWSER_ENGINE env var.
653
+ # "auto" - use Chrome (default, don't pass --engine at all)
654
+ # "lightpanda" - use Lightpanda (1.3-5.8x faster navigation, no screenshots)
655
+ # "chrome" - explicitly request Chrome
656
+ # "puppeteer" - route local browser execution through Puppeteer Chrome
657
+ # Also settable via AGENT_BROWSER_ENGINE env var.
657
658
  "engine": "auto",
658
659
  "auto_local_for_private_urls": True, # When a cloud provider is set, auto-spawn local Chromium for LAN/localhost URLs instead of sending them to the cloud
659
660
  "cdp_url": "", # Optional persistent CDP endpoint for attaching to an existing Chromium/Chrome
@@ -1395,18 +1396,21 @@ DEFAULT_CONFIG = {
1395
1396
  "personalities": {},
1396
1397
 
1397
1398
  # Pre-exec security scanning via tirith
1398
- "security": {
1399
- "allow_private_urls": False, # Allow requests to private/internal IPs (for OpenWrt, proxies, VPNs)
1400
- "redact_secrets": True,
1401
- "tirith_enabled": True,
1402
- "tirith_path": "tirith",
1403
- "tirith_timeout": 5,
1404
- "tirith_fail_open": True,
1405
- "website_blocklist": {
1406
- "enabled": False,
1407
- "domains": [],
1408
- "shared_files": [],
1409
- },
1399
+ "security": {
1400
+ "allow_private_urls": False, # Allow requests to private/internal IPs (for OpenWrt, proxies, VPNs)
1401
+ "redact_secrets": True,
1402
+ "tirith_enabled": True,
1403
+ "tirith_path": "tirith",
1404
+ "tirith_timeout": 5,
1405
+ "tirith_fail_open": True,
1406
+ "dev_access_required": True,
1407
+ "dev_access_code_hash": "",
1408
+ "dev_access_granted": False,
1409
+ "website_blocklist": {
1410
+ "enabled": False,
1411
+ "domains": [],
1412
+ "shared_files": [],
1413
+ },
1410
1414
  # Acknowledged supply-chain security advisories. Each entry is the
1411
1415
  # ID of an advisory the user has read and acted on (uninstalled the
1412
1416
  # compromised package, rotated credentials). Acked advisories no
@@ -1214,12 +1214,12 @@ def run_doctor(args):
1214
1214
  if sys.platform == "win32":
1215
1215
  check_info(
1216
1216
  f"Install with: cd {PROJECT_ROOT} && "
1217
- "npx playwright install chromium"
1217
+ "npx playwright install chromium && npm install puppeteer"
1218
1218
  )
1219
1219
  else:
1220
1220
  check_info(
1221
1221
  f"Install with: cd {PROJECT_ROOT} && "
1222
- "npx playwright install --with-deps chromium"
1222
+ "npx playwright install --with-deps chromium && npm install puppeteer"
1223
1223
  )
1224
1224
  elif _is_termux():
1225
1225
  check_info("Node.js not found (browser tools are optional in the tested Termux path)")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "calvyn-code",
3
- "version": "0.14.4",
3
+ "version": "0.14.6",
4
4
  "description": "Calvyn Code — AI агент с инструментами, мессенджерами и локальным CLI",
5
5
  "bin": {
6
6
  "calvyn": "./bin/calvyn.js",
@@ -33,7 +33,8 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@askjo/camofox-browser": "^1.5.2",
36
- "agent-browser": "^0.26.0"
36
+ "agent-browser": "^0.26.0",
37
+ "puppeteer": "^25.0.2"
37
38
  },
38
39
  "overrides": {
39
40
  "lodash": "4.18.1"
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "calvyn-code"
7
- version = "0.14.2"
7
+ version = "0.1.0"
8
8
  description = "The self-improving AI agent — creates skills from experience, improves them during use, and runs anywhere"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
package/run_agent.py CHANGED
@@ -3210,12 +3210,32 @@ class AIAgent:
3210
3210
  except Exception:
3211
3211
  pass
3212
3212
 
3213
- def _emit_auxiliary_failure(self, task: str, exc: BaseException) -> None:
3214
- """Surface a compact warning for failed auxiliary work."""
3215
- try:
3216
- detail = self._summarize_api_error(exc)
3217
- except Exception:
3218
- detail = str(exc)
3213
+ def _emit_auxiliary_failure(self, task: str, exc: BaseException) -> None:
3214
+ """Surface a compact warning for failed auxiliary work."""
3215
+ try:
3216
+ from agent.auxiliary_client import (
3217
+ _is_not_found_error,
3218
+ _is_payment_error,
3219
+ _is_rate_limit_error,
3220
+ )
3221
+ except Exception:
3222
+ _is_payment_error = _is_rate_limit_error = _is_not_found_error = None
3223
+
3224
+ if task == "title generation":
3225
+ try:
3226
+ if (
3227
+ (_is_payment_error and _is_payment_error(exc))
3228
+ or (_is_rate_limit_error and _is_rate_limit_error(exc))
3229
+ or (_is_not_found_error and _is_not_found_error(exc))
3230
+ ):
3231
+ logger.debug("Suppressing auxiliary %s warning: %s", task, exc)
3232
+ return
3233
+ except Exception:
3234
+ pass
3235
+ try:
3236
+ detail = self._summarize_api_error(exc)
3237
+ except Exception:
3238
+ detail = str(exc)
3219
3239
  detail = (detail or exc.__class__.__name__).strip()
3220
3240
  if len(detail) > 220:
3221
3241
  detail = detail[:217].rstrip() + "..."
@@ -520,15 +520,15 @@ _auto_local_for_private_urls_resolved = False
520
520
  _cached_auto_local_for_private_urls: bool = True
521
521
 
522
522
 
523
- def _get_browser_engine() -> str:
524
- """Return the configured browser engine (``auto``, ``lightpanda``, or ``chrome``).
523
+ def _get_browser_engine() -> str:
524
+ """Return the configured browser engine (``auto``, ``lightpanda``, ``chrome``, or ``puppeteer``).
525
525
 
526
526
  Reads ``config["browser"]["engine"]`` once and caches the result.
527
527
  Falls back to the ``AGENT_BROWSER_ENGINE`` env var, then ``auto``.
528
528
 
529
529
  ``auto`` means: don't pass ``--engine`` at all (agent-browser defaults to
530
- Chrome). ``lightpanda`` or ``chrome`` are forwarded as
531
- ``--engine <value>`` to agent-browser v0.25.3+.
530
+ Chrome). ``lightpanda``, ``chrome``, or ``puppeteer`` are forwarded as
531
+ ``--engine <value>`` to agent-browser v0.25.3+.
532
532
 
533
533
  Lightpanda is 1.3-5.8x faster on navigation but has no graphical
534
534
  renderer (no screenshots).
@@ -556,8 +556,10 @@ def _get_browser_engine() -> str:
556
556
  if env_val:
557
557
  _cached_browser_engine = env_val
558
558
 
559
- # Validate: agent-browser only accepts "chrome" and "lightpanda".
560
- _VALID_ENGINES = {"auto", "lightpanda", "chrome"}
559
+ # Validate against the engines we intentionally expose in Calvyn.
560
+ # ``puppeteer`` currently maps to the Chrome local path while the Node
561
+ # dependency is managed explicitly in package.json for npm installs.
562
+ _VALID_ENGINES = {"auto", "lightpanda", "chrome", "puppeteer"}
561
563
  if _cached_browser_engine not in _VALID_ENGINES:
562
564
  logger.warning(
563
565
  "Unknown browser engine %r (valid: %s), falling back to 'auto'",
@@ -565,7 +567,10 @@ def _get_browser_engine() -> str:
565
567
  )
566
568
  _cached_browser_engine = "auto"
567
569
 
568
- return _cached_browser_engine
570
+ if _cached_browser_engine == "puppeteer":
571
+ return "chrome"
572
+
573
+ return _cached_browser_engine
569
574
 
570
575
 
571
576
  def _should_inject_engine(engine: str) -> bool: