calvyn-code 0.14.0 → 0.14.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/bin/calvyn.js CHANGED
@@ -49,14 +49,37 @@ function runPostinstallIfNeeded() {
49
49
  return calvynBinary
50
50
  }
51
51
 
52
+ function shouldShowBanner(args) {
53
+ if (!args.length) return true
54
+ const first = String(args[0] || "").trim().toLowerCase()
55
+ return !["-h", "--help", "-v", "--version", "version"].includes(first)
56
+ }
57
+
58
+ function printNpmLaunchHeader() {
59
+ console.log("")
60
+ console.log("")
61
+ console.log("")
62
+ console.log(" ╔════════════════════════════════════════════════════════════════════════════╗")
63
+ console.log(" ║ ✦ CALVYN CODE ║")
64
+ console.log(" ║ Запуск через npm ║")
65
+ console.log(" ╚════════════════════════════════════════════════════════════════════════════╝")
66
+ console.log("")
67
+ }
68
+
69
+ const args = process.argv.slice(2)
70
+ if (shouldShowBanner(args)) {
71
+ printNpmLaunchHeader()
72
+ }
73
+
52
74
  const calvynBinary = runPostinstallIfNeeded()
53
- const result = spawnSync(calvynBinary, process.argv.slice(2), {
75
+ const result = spawnSync(calvynBinary, args, {
54
76
  stdio: "inherit",
55
77
  shell: false,
56
78
  env: {
57
79
  ...process.env,
58
80
  CALVYN_LAUNCHED_FROM_NPM: "1",
59
81
  CALVYN_REPO_ROOT: packageRoot,
82
+ CALVYN_HOME: process.env.CALVYN_HOME || path.join(require("os").homedir(), ".calvyn"),
60
83
  },
61
84
  })
62
85
 
@@ -1,4 +1,4 @@
1
- """Shared constants for Hermes Agent.
1
+ """Shared constants for Calvyn Code.
2
2
 
3
3
  Import-safe module with no dependencies — can be imported from anywhere
4
4
  without risk of circular imports.
@@ -8,14 +8,24 @@ import os
8
8
  from pathlib import Path
9
9
 
10
10
 
11
- _profile_fallback_warned: bool = False
11
+ _profile_fallback_warned: bool = False
12
+ _CALVYN_HOME_ENV = "CALVYN_HOME"
13
+ _LEGACY_HOME_ENV = "HERMES_HOME"
14
+ _DEFAULT_HOME_DIRNAME = ".calvyn"
15
+ _LEGACY_HOME_DIRNAME = ".hermes"
12
16
 
13
17
 
14
- def get_hermes_home() -> Path:
15
- """Return the Hermes home directory (default: ~/.hermes).
16
-
17
- Reads HERMES_HOME env var, falls back to ~/.hermes.
18
- This is the single source of truth — all other copies should import this.
18
+ def get_hermes_home() -> Path:
19
+ """Return the Calvyn home directory.
20
+
21
+ Preference order:
22
+ 1. ``CALVYN_HOME``
23
+ 2. ``HERMES_HOME`` for legacy compatibility
24
+ 3. ``~/.calvyn``
25
+ 4. ``~/.hermes`` only when it already exists and ``~/.calvyn`` does not
26
+
27
+ This keeps Calvyn isolated from Hermes by default while still allowing
28
+ explicit legacy overrides.
19
29
 
20
30
  When ``HERMES_HOME`` is unset but an ``active_profile`` file indicates
21
31
  a non-default profile is active, logs a loud one-shot warning to
@@ -27,22 +37,26 @@ def get_hermes_home() -> Path:
27
37
  template in ``hermes_cli/gateway.py`` and the kanban dispatcher in
28
38
  ``hermes_cli/kanban_db.py``). See https://github.com/NousResearch/hermes-agent/issues/18594.
29
39
  """
30
- val = os.environ.get("HERMES_HOME", "").strip()
31
- if val:
32
- return Path(val)
40
+ val = os.environ.get(_CALVYN_HOME_ENV, "").strip()
41
+ if val:
42
+ return Path(val)
43
+
44
+ val = os.environ.get(_LEGACY_HOME_ENV, "").strip()
45
+ if val:
46
+ return Path(val)
33
47
 
34
48
  # Guard: if a non-default profile is sticky-active, warn once that
35
49
  # the fallback to the default profile is almost certainly wrong.
36
50
  global _profile_fallback_warned
37
51
  if not _profile_fallback_warned:
38
52
  try:
39
- # Inline the default-root resolution from get_default_hermes_root()
40
- # to stay import-safe (this function is called from module scope
41
- # in 30+ files; we cannot afford to trigger logging setup here).
42
- active_path = (Path.home() / ".hermes" / "active_profile")
43
- active = active_path.read_text().strip() if active_path.exists() else ""
44
- except (UnicodeDecodeError, OSError):
45
- active = ""
53
+ # Inline the default-root resolution from get_default_hermes_root()
54
+ # to stay import-safe (this function is called from module scope
55
+ # in 30+ files; we cannot afford to trigger logging setup here).
56
+ active_path = (Path.home() / _DEFAULT_HOME_DIRNAME / "active_profile")
57
+ active = active_path.read_text().strip() if active_path.exists() else ""
58
+ except (UnicodeDecodeError, OSError):
59
+ active = ""
46
60
  if active and active != "default":
47
61
  _profile_fallback_warned = True
48
62
  # Write directly to stderr. We intentionally do NOT route this
@@ -52,24 +66,28 @@ def get_hermes_home() -> Path:
52
66
  # on consoles where a StreamHandler is already attached.
53
67
  import sys
54
68
  msg = (
55
- f"[HERMES_HOME fallback] HERMES_HOME is unset but active "
56
- f"profile is {active!r}. Falling back to ~/.hermes, which "
57
- f"is the DEFAULT profile — not {active!r}. Any data this "
58
- f"process writes will land in the wrong profile. The "
59
- f"subprocess spawner should pass HERMES_HOME explicitly "
60
- f"(see issue #18594)."
61
- )
69
+ f"[CALVYN_HOME fallback] CALVYN_HOME is unset but active "
70
+ f"profile is {active!r}. Falling back to ~/{_DEFAULT_HOME_DIRNAME}, which "
71
+ f"is the DEFAULT profile — not {active!r}. Any data this "
72
+ f"process writes will land in the wrong profile. The "
73
+ f"subprocess spawner should pass CALVYN_HOME explicitly "
74
+ f"(see issue #18594)."
75
+ )
62
76
  try:
63
77
  sys.stderr.write(msg + "\n")
64
78
  sys.stderr.flush()
65
79
  except Exception:
66
80
  pass
67
81
 
68
- return Path.home() / ".hermes"
82
+ calvyn_home = Path.home() / _DEFAULT_HOME_DIRNAME
83
+ legacy_home = Path.home() / _LEGACY_HOME_DIRNAME
84
+ if calvyn_home.exists() or not legacy_home.exists():
85
+ return calvyn_home
86
+ return legacy_home
69
87
 
70
88
 
71
- def get_default_hermes_root() -> Path:
72
- """Return the root Hermes directory for profile-level operations.
89
+ def get_default_hermes_root() -> Path:
90
+ """Return the root Calvyn directory for profile-level operations.
73
91
 
74
92
  In standard deployments this is ``~/.hermes``.
75
93
 
@@ -84,8 +102,8 @@ def get_default_hermes_root() -> Path:
84
102
 
85
103
  Import-safe — no dependencies beyond stdlib.
86
104
  """
87
- native_home = Path.home() / ".hermes"
88
- env_home = os.environ.get("HERMES_HOME", "")
105
+ native_home = Path.home() / _DEFAULT_HOME_DIRNAME
106
+ env_home = os.environ.get(_CALVYN_HOME_ENV, "") or os.environ.get(_LEGACY_HOME_ENV, "")
89
107
  if not env_home:
90
108
  return native_home
91
109
  env_path = Path(env_home)
@@ -110,10 +128,11 @@ def get_default_hermes_root() -> Path:
110
128
  def get_optional_skills_dir(default: Path | None = None) -> Path:
111
129
  """Return the optional-skills directory, honoring package-manager wrappers.
112
130
 
113
- Packaged installs may ship ``optional-skills`` outside the Python package
114
- tree and expose it via ``HERMES_OPTIONAL_SKILLS``.
115
- """
116
- override = os.getenv("HERMES_OPTIONAL_SKILLS", "").strip()
131
+ Packaged installs may ship ``optional-skills`` outside the Python package
132
+ tree and expose it via ``CALVYN_OPTIONAL_SKILLS`` or the legacy
133
+ ``HERMES_OPTIONAL_SKILLS``.
134
+ """
135
+ override = os.getenv("CALVYN_OPTIONAL_SKILLS", "").strip() or os.getenv("HERMES_OPTIONAL_SKILLS", "").strip()
117
136
  if override:
118
137
  return Path(override)
119
138
  if default is not None:
@@ -122,7 +141,7 @@ def get_optional_skills_dir(default: Path | None = None) -> Path:
122
141
 
123
142
 
124
143
  def get_hermes_dir(new_subpath: str, old_name: str) -> Path:
125
- """Resolve a Hermes subdirectory with backward compatibility.
144
+ """Resolve a Calvyn subdirectory with backward compatibility.
126
145
 
127
146
  New installs get the consolidated layout (e.g. ``cache/images``).
128
147
  Existing installs that already have the old path (e.g. ``image_cache``)
@@ -143,17 +162,17 @@ def get_hermes_dir(new_subpath: str, old_name: str) -> Path:
143
162
 
144
163
 
145
164
  def display_hermes_home() -> str:
146
- """Return a user-friendly display string for the current HERMES_HOME.
165
+ """Return a user-friendly display string for the current Calvyn home.
147
166
 
148
167
  Uses ``~/`` shorthand for readability::
149
168
 
150
- default: ``~/.hermes``
151
- profile: ``~/.hermes/profiles/coder``
152
- custom: ``/opt/hermes-custom``
169
+ default: ``~/.calvyn``
170
+ profile: ``~/.calvyn/profiles/coder``
171
+ custom: ``/opt/hermes-custom``
153
172
 
154
- Use this in **user-facing** print/log messages instead of hardcoding
155
- ``~/.hermes``. For code that needs a real ``Path``, use
156
- :func:`get_hermes_home` instead.
173
+ Use this in **user-facing** print/log messages instead of hardcoding
174
+ ``~/.calvyn``. For code that needs a real ``Path``, use
175
+ :func:`get_hermes_home` instead.
157
176
  """
158
177
  home = get_hermes_home()
159
178
  try:
@@ -165,7 +184,7 @@ def display_hermes_home() -> str:
165
184
  def get_subprocess_home() -> str | None:
166
185
  """Return a per-profile HOME directory for subprocesses, or None.
167
186
 
168
- When ``{HERMES_HOME}/home/`` exists on disk, subprocesses should use it
187
+ When ``{CALVYN_HOME}/home/`` exists on disk, subprocesses should use it
169
188
  as ``HOME`` so system tools (git, ssh, gh, npm …) write their configs
170
189
  inside the Hermes data directory instead of the OS-level ``/root`` or
171
190
  ``~/``. This provides:
@@ -179,7 +198,7 @@ def get_subprocess_home() -> str | None:
179
198
  Activation is directory-based: if the ``home/`` subdirectory doesn't
180
199
  exist, returns ``None`` and behavior is unchanged.
181
200
  """
182
- hermes_home = os.getenv("HERMES_HOME")
201
+ hermes_home = os.getenv(_CALVYN_HOME_ENV) or os.getenv(_LEGACY_HOME_ENV)
183
202
  if not hermes_home:
184
203
  return None
185
204
  profile_home = os.path.join(hermes_home, "home")
@@ -0,0 +1,21 @@
1
+ model:
2
+ provider: "freemodel"
3
+ default: "gpt-5.5"
4
+ base_url: "https://api.freemodel.dev"
5
+ api_mode: "codex_responses"
6
+
7
+ agent:
8
+ reasoning_effort: "xhigh"
9
+
10
+ providers:
11
+ freemodel:
12
+ name: "freemodel"
13
+ base_url: "https://api.freemodel.dev"
14
+ key_env: "FREEMODEL_API_KEY"
15
+ api_mode: "codex_responses"
16
+ model: "gpt-5.5"
17
+
18
+ model_provider: "freemodel"
19
+ model_reasoning_effort: "xhigh"
20
+ disable_response_storage: true
21
+ preferred_auth_method: "apikey"
package/cli.py CHANGED
@@ -11542,12 +11542,18 @@ class HermesCLI:
11542
11542
  # responses, and prompt all appear pinned to the bottom — empty
11543
11543
  # space stays above, not below. This prints enough blank lines to
11544
11544
  # scroll the cursor to the last row before any content is rendered.
11545
- try:
11546
- _term_lines = shutil.get_terminal_size().lines
11547
- if _term_lines > 2:
11548
- print("\n" * (_term_lines - 1), end="", flush=True)
11549
- except Exception:
11550
- pass
11545
+ try:
11546
+ if os.getenv("CALVYN_LAUNCHED_FROM_NPM", "").strip().lower() not in {
11547
+ "1",
11548
+ "true",
11549
+ "yes",
11550
+ "on",
11551
+ }:
11552
+ _term_lines = shutil.get_terminal_size().lines
11553
+ if _term_lines > 2:
11554
+ print("\n" * (_term_lines - 1), end="", flush=True)
11555
+ except Exception:
11556
+ pass
11551
11557
 
11552
11558
  self.show_banner()
11553
11559
  # Surface any active supply-chain security advisories right after the
@@ -0,0 +1 @@
1
+ from calvyn_bootstrap import * # noqa: F401,F403
@@ -14,7 +14,7 @@ Provides subcommands for:
14
14
  import os
15
15
  import sys
16
16
 
17
- __version__ = "0.14.0"
17
+ __version__ = "0.14.1"
18
18
  __release_date__ = "2026.5.16"
19
19
 
20
20
 
@@ -1387,8 +1387,9 @@ def resolve_provider(
1387
1387
  "kimi": "kimi-coding", "kimi-for-coding": "kimi-coding", "moonshot": "kimi-coding",
1388
1388
  "kimi-cn": "kimi-coding-cn", "moonshot-cn": "kimi-coding-cn",
1389
1389
  "step": "stepfun", "stepfun-coding-plan": "stepfun",
1390
- "arcee-ai": "arcee", "arceeai": "arcee",
1391
- "gmi-cloud": "gmi", "gmicloud": "gmi",
1390
+ "arcee-ai": "arcee", "arceeai": "arcee",
1391
+ "gmi-cloud": "gmi", "gmicloud": "gmi",
1392
+ "freemodel": "freemodel", "free-model": "freemodel", "free_model": "freemodel",
1392
1393
  "minimax-china": "minimax-cn", "minimax_cn": "minimax-cn",
1393
1394
  "minimax-portal": "minimax-oauth", "minimax-global": "minimax-oauth", "minimax_oauth": "minimax-oauth",
1394
1395
  "alibaba_coding": "alibaba-coding-plan", "alibaba-coding": "alibaba-coding-plan",
@@ -949,7 +949,8 @@ CANONICAL_PROVIDERS: list[ProviderEntry] = [
949
949
  ProviderEntry("minimax-cn", "MiniMax (China)", "MiniMax China (domestic direct API)"),
950
950
  ProviderEntry("ollama-cloud", "Ollama Cloud", "Ollama Cloud (cloud-hosted open models — ollama.com)"),
951
951
  ProviderEntry("arcee", "Arcee AI", "Arcee AI (Trinity models — direct API)"),
952
- ProviderEntry("gmi", "GMI Cloud", "GMI Cloud (multi-model direct API)"),
952
+ ProviderEntry("gmi", "GMI Cloud", "GMI Cloud (multi-model direct API)"),
953
+ ProviderEntry("freemodel", "FreeModel", "FreeModel (OpenAI-compatible direct API)"),
953
954
  ProviderEntry("kilocode", "Kilo Code", "Kilo Code (Kilo Gateway API)"),
954
955
  ProviderEntry("opencode-zen", "OpenCode Zen", "OpenCode Zen (35+ curated models, pay-as-you-go)"),
955
956
  ProviderEntry("opencode-go", "OpenCode Go", "OpenCode Go (open models, $10/month subscription)"),
@@ -1005,8 +1006,11 @@ _PROVIDER_ALIASES = {
1005
1006
  "stepfun-coding-plan": "stepfun",
1006
1007
  "arcee-ai": "arcee",
1007
1008
  "arceeai": "arcee",
1008
- "gmi-cloud": "gmi",
1009
- "gmicloud": "gmi",
1009
+ "gmi-cloud": "gmi",
1010
+ "gmicloud": "gmi",
1011
+ "freemodel": "freemodel",
1012
+ "free-model": "freemodel",
1013
+ "free_model": "freemodel",
1010
1014
  "minimax-china": "minimax-cn",
1011
1015
  "minimax_cn": "minimax-cn",
1012
1016
  "minimax-portal": "minimax-oauth",
@@ -190,12 +190,18 @@ HERMES_OVERLAYS: Dict[str, HermesOverlay] = {
190
190
  base_url_override="https://api.arcee.ai/api/v1",
191
191
  base_url_env_var="ARCEE_BASE_URL",
192
192
  ),
193
- "gmi": HermesOverlay(
194
- transport="openai_chat",
195
- extra_env_vars=("GMI_API_KEY",),
196
- base_url_override="https://api.gmi-serving.com/v1",
197
- base_url_env_var="GMI_BASE_URL",
198
- ),
193
+ "gmi": HermesOverlay(
194
+ transport="openai_chat",
195
+ extra_env_vars=("GMI_API_KEY",),
196
+ base_url_override="https://api.gmi-serving.com/v1",
197
+ base_url_env_var="GMI_BASE_URL",
198
+ ),
199
+ "freemodel": HermesOverlay(
200
+ transport="openai_chat",
201
+ extra_env_vars=("FREEMODEL_API_KEY",),
202
+ base_url_override="https://api.freemodel.xyz/v1",
203
+ base_url_env_var="FREEMODEL_BASE_URL",
204
+ ),
199
205
  "ollama-cloud": HermesOverlay(
200
206
  transport="openai_chat",
201
207
  base_url_env_var="OLLAMA_BASE_URL",
@@ -349,8 +355,11 @@ ALIASES: Dict[str, str] = {
349
355
  "arceeai": "arcee",
350
356
 
351
357
  # gmi
352
- "gmi-cloud": "gmi",
353
- "gmicloud": "gmi",
358
+ "gmi-cloud": "gmi",
359
+ "gmicloud": "gmi",
360
+ "freemodel": "freemodel",
361
+ "free-model": "freemodel",
362
+ "free_model": "freemodel",
354
363
 
355
364
  # Local server aliases → virtual "local" concept (resolved via user config)
356
365
  "lmstudio": "lmstudio",
@@ -374,7 +383,8 @@ _LABEL_OVERRIDES: Dict[str, str] = {
374
383
  "copilot-acp": "GitHub Copilot ACP",
375
384
  "stepfun": "StepFun Step Plan",
376
385
  "xiaomi": "Xiaomi MiMo",
377
- "gmi": "GMI Cloud",
386
+ "gmi": "GMI Cloud",
387
+ "freemodel": "FreeModel",
378
388
  "tencent-tokenhub": "Tencent TokenHub",
379
389
  "lmstudio": "LM Studio",
380
390
  "local": "Local endpoint",
@@ -0,0 +1 @@
1
+ from calvyn_constants import * # noqa: F401,F403
@@ -0,0 +1 @@
1
+ from calvyn_logging import * # noqa: F401,F403
@@ -0,0 +1 @@
1
+ from calvyn_state import * # noqa: F401,F403
package/hermes_time.py ADDED
@@ -0,0 +1 @@
1
+ from calvyn_time import * # noqa: F401,F403
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "calvyn-code",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "description": "Calvyn Code — AI агент с инструментами, мессенджерами и локальным CLI",
5
5
  "bin": {
6
- "calvyn": "./bin/calvyn.js"
6
+ "calvyn": "./bin/calvyn.js",
7
+ "calvyn-code": "./bin/calvyn.js"
7
8
  },
8
9
  "main": "./bin/calvyn.js",
9
10
  "keywords": [
@@ -40,6 +41,9 @@
40
41
  "engines": {
41
42
  "node": ">=20.0.0"
42
43
  },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
43
47
  "files": [
44
48
  "bin",
45
49
  "scripts",
@@ -68,9 +72,15 @@
68
72
  "calvyn_state.py",
69
73
  "calvyn_time.py",
70
74
  "calvyn_logging.py",
75
+ "hermes_bootstrap.py",
76
+ "hermes_constants.py",
77
+ "hermes_state.py",
78
+ "hermes_time.py",
79
+ "hermes_logging.py",
71
80
  "mcp_serve.py",
72
81
  "utils.py",
73
82
  "pyproject.toml",
83
+ "cli-config.yaml",
74
84
  "README.md",
75
85
  "LICENSE"
76
86
  ]
@@ -0,0 +1,16 @@
1
+ """FreeModel provider profile."""
2
+
3
+ from providers import register_provider
4
+ from providers.base import ProviderProfile
5
+
6
+ freemodel = ProviderProfile(
7
+ name="freemodel",
8
+ aliases=("free-model", "free_model"),
9
+ display_name="FreeModel",
10
+ description="FreeModel — OpenAI-compatible direct API",
11
+ signup_url="https://freemodel.xyz/",
12
+ env_vars=("FREEMODEL_API_KEY", "FREEMODEL_BASE_URL"),
13
+ base_url="https://api.freemodel.xyz/v1",
14
+ )
15
+
16
+ register_provider(freemodel)
@@ -0,0 +1,5 @@
1
+ name: freemodel-profile
2
+ kind: model-provider
3
+ version: 1.0.0
4
+ description: FreeModel direct API provider profile
5
+ author: Calvyn Code Contributors
package/pyproject.toml CHANGED
@@ -3,8 +3,8 @@ requires = ["setuptools>=61.0"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
- name = "hermes-agent"
7
- version = "0.14.0"
6
+ name = "calvyn-code"
7
+ version = "0.14.1"
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"
@@ -128,23 +128,23 @@ bedrock = ["boto3==1.42.89"]
128
128
  termux = [
129
129
  # Baseline Android / Termux path for reliable fresh installs.
130
130
  "python-telegram-bot[webhooks]==22.6",
131
- "hermes-agent[cron]",
132
- "hermes-agent[cli]",
133
- "hermes-agent[pty]",
134
- "hermes-agent[mcp]",
135
- "hermes-agent[honcho]",
136
- "hermes-agent[acp]",
131
+ "calvyn-code[cron]",
132
+ "calvyn-code[cli]",
133
+ "calvyn-code[pty]",
134
+ "calvyn-code[mcp]",
135
+ "calvyn-code[honcho]",
136
+ "calvyn-code[acp]",
137
137
  ]
138
138
  termux-all = [
139
139
  # Best-effort "install all" profile for Termux. Same policy as [all]:
140
140
  # only includes extras that aren't covered by `tools/lazy_deps.py`.
141
141
  # Backends like telegram/slack/dingtalk/feishu/honcho lazy-install at
142
142
  # first use, so they're no longer eager-installed here.
143
- "hermes-agent[termux]",
144
- "hermes-agent[google]",
145
- "hermes-agent[homeassistant]",
146
- "hermes-agent[sms]",
147
- "hermes-agent[web]",
143
+ "calvyn-code[termux]",
144
+ "calvyn-code[google]",
145
+ "calvyn-code[homeassistant]",
146
+ "calvyn-code[sms]",
147
+ "calvyn-code[web]",
148
148
  ]
149
149
  dingtalk = ["dingtalk-stream==0.24.3", "alibabacloud-dingtalk==2.2.42", "qrcode==7.4.2"]
150
150
  feishu = ["lark-oapi==1.5.3", "qrcode==7.4.2"]
@@ -188,25 +188,23 @@ all = [
188
188
  # [all], `uv sync --locked` on Windows tried to build it from sdist
189
189
  # and failed on `make`. Lazy-install routes that build to first use,
190
190
  # where the user is expected to have a toolchain available.
191
- "hermes-agent[cron]",
192
- "hermes-agent[cli]",
193
- "hermes-agent[dev]",
194
- "hermes-agent[pty]",
195
- "hermes-agent[mcp]",
196
- "hermes-agent[homeassistant]",
197
- "hermes-agent[sms]",
198
- "hermes-agent[acp]",
199
- "hermes-agent[google]",
200
- "hermes-agent[web]",
201
- "hermes-agent[youtube]",
191
+ "calvyn-code[cron]",
192
+ "calvyn-code[cli]",
193
+ "calvyn-code[dev]",
194
+ "calvyn-code[pty]",
195
+ "calvyn-code[mcp]",
196
+ "calvyn-code[homeassistant]",
197
+ "calvyn-code[sms]",
198
+ "calvyn-code[acp]",
199
+ "calvyn-code[google]",
200
+ "calvyn-code[web]",
201
+ "calvyn-code[youtube]",
202
202
  ]
203
203
 
204
204
  [project.scripts]
205
205
  calvyn = "hermes_cli.main:main"
206
206
  calvyn-agent = "run_agent:main"
207
207
  calvyn-acp = "acp_adapter.entry:main"
208
- hermes-agent = "run_agent:main"
209
- hermes-acp = "acp_adapter.entry:main"
210
208
 
211
209
  [tool.setuptools]
212
210
  py-modules = ["run_agent", "model_tools", "toolsets", "batch_runner", "trajectory_compressor", "toolset_distributions", "cli", "calvyn_bootstrap", "calvyn_constants", "calvyn_state", "calvyn_time", "calvyn_logging", "utils"]
@@ -95,6 +95,25 @@ function getVenvPaths() {
95
95
  }
96
96
  }
97
97
 
98
+ function ensureDefaultConfig() {
99
+ const homeDir = process.env.CALVYN_HOME
100
+ ? path.resolve(process.env.CALVYN_HOME)
101
+ : path.join(require("os").homedir(), ".calvyn")
102
+ const configDir = homeDir
103
+ const configPath = path.join(configDir, "config.yaml")
104
+ const bundledConfig = path.join(packageRoot, "cli-config.yaml")
105
+
106
+ if (!fs.existsSync(bundledConfig)) {
107
+ return
108
+ }
109
+
110
+ fs.mkdirSync(configDir, { recursive: true })
111
+ if (!fs.existsSync(configPath)) {
112
+ fs.copyFileSync(bundledConfig, configPath)
113
+ log(`Положил стартовый конфиг в ${configPath}`)
114
+ }
115
+ }
116
+
98
117
  function ensureVenv(python) {
99
118
  const venv = getVenvPaths()
100
119
  if (!fs.existsSync(venv.dir)) {
@@ -137,6 +156,7 @@ function main() {
137
156
  }
138
157
 
139
158
  ensureInstalled(venv.python)
159
+ ensureDefaultConfig()
140
160
 
141
161
  if (!fs.existsSync(venv.calvyn)) {
142
162
  fail("Установка прошла не до конца: команда calvyn внутри .venv не найдена")