homeconsole-cli 0.0.2__tar.gz → 0.0.4__tar.gz

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.
Files changed (65) hide show
  1. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/PKG-INFO +32 -3
  2. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/README.md +31 -2
  3. homeconsole_cli-0.0.4/hc/__init__.py +10 -0
  4. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/client.py +6 -3
  5. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/_client_helpers.py +2 -1
  6. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/deploy.py +31 -3
  7. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/env.py +35 -3
  8. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/update.py +29 -2
  9. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/constants.py +1 -2
  10. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/core_ops.py +9 -2
  11. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/repl.py +19 -14
  12. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/homeconsole_cli.egg-info/PKG-INFO +32 -3
  13. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/pyproject.toml +1 -1
  14. homeconsole_cli-0.0.2/hc/__init__.py +0 -6
  15. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/api.py +0 -0
  16. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/capabilities.py +0 -0
  17. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/__init__.py +0 -0
  18. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/_compose_helpers.py +0 -0
  19. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/auth.py +0 -0
  20. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/connect.py +0 -0
  21. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/core.py +0 -0
  22. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/install.py +0 -0
  23. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/logs.py +0 -0
  24. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/marketplace.py +0 -0
  25. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/module.py +0 -0
  26. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/ping.py +0 -0
  27. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/plugin.py +0 -0
  28. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/recovery/__init__.py +0 -0
  29. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/recovery/compose.py +0 -0
  30. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/recovery/config.py +0 -0
  31. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/recovery/core.py +0 -0
  32. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/recovery/db.py +0 -0
  33. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/recovery/mode.py +0 -0
  34. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/recovery/redis.py +0 -0
  35. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/recovery/ui.py +0 -0
  36. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/remove.py +0 -0
  37. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/reset.py +0 -0
  38. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/search.py +0 -0
  39. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/secrets.py +0 -0
  40. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/setup.py +0 -0
  41. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/setup_wizard.py +0 -0
  42. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/status.py +0 -0
  43. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/config.py +0 -0
  44. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/core_source.py +0 -0
  45. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/env_bootstrap.py +0 -0
  46. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/errors.py +0 -0
  47. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/main.py +0 -0
  48. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/marketplace_operation.py +0 -0
  49. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/native_core.py +0 -0
  50. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/setup_runner.py +0 -0
  51. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/shell.py +0 -0
  52. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/homeconsole_cli.egg-info/SOURCES.txt +0 -0
  53. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/homeconsole_cli.egg-info/dependency_links.txt +0 -0
  54. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/homeconsole_cli.egg-info/entry_points.txt +0 -0
  55. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/homeconsole_cli.egg-info/requires.txt +0 -0
  56. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/homeconsole_cli.egg-info/top_level.txt +0 -0
  57. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/setup.cfg +0 -0
  58. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/tests/test_config_roundtrip.py +0 -0
  59. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/tests/test_core_ops.py +0 -0
  60. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/tests/test_env_bootstrap.py +0 -0
  61. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/tests/test_main_root_callback.py +0 -0
  62. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/tests/test_marketplace_operation_parse.py +0 -0
  63. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/tests/test_native_core_helpers.py +0 -0
  64. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/tests/test_nav_cli.py +0 -0
  65. {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/tests/test_setup_runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: homeconsole-cli
3
- Version: 0.0.2
3
+ Version: 0.0.4
4
4
  Summary: HomeConsole CLI (hc) — управление платформой через HTTP API
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -18,13 +18,42 @@ Requires-Dist: pytest>=8; extra == "dev"
18
18
 
19
19
  CLI-утилита для управления платформой HomeConsole **исключительно через HTTP API** CoreRuntime.
20
20
 
21
- ## Установка (локально)
21
+ ## Установка
22
+
23
+ ### Рекомендуется — `pipx` (Debian / Ubuntu / Raspbian / OrangePi OS)
24
+
25
+ `pipx` создаёт изолированный venv автоматически и регистрирует `hc` глобально:
26
+
27
+ ```bash
28
+ apt install pipx # или: pip install pipx --user
29
+ pipx ensurepath # добавляет ~/.local/bin в PATH (один раз)
30
+ pipx install homeconsole-cli
31
+ ```
32
+
33
+ После `pipx ensurepath` перезапусти шелл или выполни `source ~/.bashrc`.
34
+
35
+ Обновление:
36
+ ```bash
37
+ pipx upgrade homeconsole-cli
38
+ ```
39
+
40
+ ### Альтернатива — `pip --user`
41
+
42
+ ```bash
43
+ pip install --user homeconsole-cli
44
+ ```
45
+
46
+ Убедись, что `~/.local/bin` в `PATH`:
47
+ ```bash
48
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc
49
+ ```
50
+
51
+ ### Локальная разработка (из исходников)
22
52
 
23
53
  ```bash
24
54
  cd home-console-cli
25
55
  python -m venv .venv
26
56
  source .venv/bin/activate
27
- pip install -U pip
28
57
  pip install -e .
29
58
  ```
30
59
 
@@ -2,13 +2,42 @@
2
2
 
3
3
  CLI-утилита для управления платформой HomeConsole **исключительно через HTTP API** CoreRuntime.
4
4
 
5
- ## Установка (локально)
5
+ ## Установка
6
+
7
+ ### Рекомендуется — `pipx` (Debian / Ubuntu / Raspbian / OrangePi OS)
8
+
9
+ `pipx` создаёт изолированный venv автоматически и регистрирует `hc` глобально:
10
+
11
+ ```bash
12
+ apt install pipx # или: pip install pipx --user
13
+ pipx ensurepath # добавляет ~/.local/bin в PATH (один раз)
14
+ pipx install homeconsole-cli
15
+ ```
16
+
17
+ После `pipx ensurepath` перезапусти шелл или выполни `source ~/.bashrc`.
18
+
19
+ Обновление:
20
+ ```bash
21
+ pipx upgrade homeconsole-cli
22
+ ```
23
+
24
+ ### Альтернатива — `pip --user`
25
+
26
+ ```bash
27
+ pip install --user homeconsole-cli
28
+ ```
29
+
30
+ Убедись, что `~/.local/bin` в `PATH`:
31
+ ```bash
32
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc
33
+ ```
34
+
35
+ ### Локальная разработка (из исходников)
6
36
 
7
37
  ```bash
8
38
  cd home-console-cli
9
39
  python -m venv .venv
10
40
  source .venv/bin/activate
11
- pip install -U pip
12
41
  pip install -e .
13
42
  ```
14
43
 
@@ -0,0 +1,10 @@
1
+ from __future__ import annotations
2
+
3
+ __all__ = ["__version__"]
4
+
5
+ try:
6
+ from importlib.metadata import version as _pkg_version
7
+ __version__ = _pkg_version("homeconsole-cli")
8
+ except Exception:
9
+ __version__ = "0.0.2"
10
+
@@ -20,6 +20,7 @@ class HCClient:
20
20
  auth: str = "auto" # auto|bearer|api-key
21
21
  refresh_token: str = ""
22
22
  on_token_refreshed: Callable[[str], None] | None = field(default=None)
23
+ silent_connect: bool = False # suppress "Core недоступен" during background probes
23
24
 
24
25
  def _auth_hint(self, status_code: int) -> None:
25
26
  console = Console()
@@ -59,11 +60,13 @@ class HCClient:
59
60
  ) as client:
60
61
  return await client.request(method, path, headers=self._headers(), **kwargs)
61
62
  except httpx.ConnectError:
62
- hostport = self.base_url.replace("http://", "").replace("https://", "")
63
- console.print(f"[red]Ошибка: Core недоступен на {hostport}[/red]")
63
+ if not self.silent_connect:
64
+ hostport = self.base_url.replace("http://", "").replace("https://", "")
65
+ console.print(f"[red]Ошибка: Core недоступен на {hostport}[/red]")
64
66
  return None
65
67
  except httpx.RequestError as e:
66
- console.print(f"[red]Ошибка: {e}[/red]")
68
+ if not self.silent_connect:
69
+ console.print(f"[red]Ошибка: {e}[/red]")
67
70
  return None
68
71
 
69
72
  async def _try_refresh(self) -> bool:
@@ -11,9 +11,10 @@ from hc.config import Config
11
11
 
12
12
 
13
13
  def _mute_auth_hints(client: HCClient) -> HCClient:
14
- # Capabilities probe / фоновые операции не должны спамить подсказками при 403.
14
+ # Фоновые проbes не должны спамить ни auth-подсказками, ни "Core недоступен".
15
15
  client._auth_hint = lambda *a, **kw: None # type: ignore[attr-defined]
16
16
  client._expired_session_hint = lambda: None # type: ignore[attr-defined]
17
+ client.silent_connect = True
17
18
  return client
18
19
 
19
20
 
@@ -52,11 +52,15 @@ _STACK_ENV_COMPOSE: dict[str, str] = {
52
52
  }
53
53
 
54
54
 
55
+ _MONOREPO_SIBLINGS = frozenset({"home-console-cli", "packages", "platform-home-console"})
56
+
57
+
55
58
  def _find_repo_root() -> Path | None:
56
59
  here = Path(__file__).resolve()
57
60
  for p in [here, *here.parents]:
58
61
  if (p / "core-runtime-service").exists():
59
- return p
62
+ if any((p / s).exists() for s in _MONOREPO_SIBLINGS):
63
+ return p
60
64
  return None
61
65
 
62
66
 
@@ -106,6 +110,28 @@ def _run_env(cmd: list[str], *, cwd: Path | None = None, env: dict[str, str] | N
106
110
  raise typer.Exit(code=p.returncode)
107
111
 
108
112
 
113
+ def _run_pull(
114
+ cmd: list[str],
115
+ *,
116
+ cwd: Path | None = None,
117
+ env: dict[str, str] | None = None,
118
+ image: str,
119
+ ) -> None:
120
+ """docker compose pull с понятной подсказкой при ошибке авторизации."""
121
+ p = subprocess.run(cmd, cwd=str(cwd) if cwd else None, env=env, check=False) # noqa: S603
122
+ if p.returncode != 0:
123
+ registry = image.split("/")[0] if "/" in image else "ghcr.io"
124
+ raise HcCliError(
125
+ message=f"Не удалось загрузить образ {image}",
126
+ hint=(
127
+ f"Образ недоступен (denied / unauthorized).\n"
128
+ f" Авторизуйся в реестре:\n"
129
+ f" docker login {registry} -u <github_username> -p <PAT_read:packages>"
130
+ ),
131
+ exit_code=p.returncode,
132
+ )
133
+
134
+
109
135
  def _copy_dir_contents(src: Path, dst: Path) -> None:
110
136
  if dst.exists():
111
137
  shutil.rmtree(dst)
@@ -707,10 +733,11 @@ def register(app: typer.Typer) -> None:
707
733
  env = {**os.environ, "PLATFORM_IMAGE": full_image}
708
734
  console.print(f"[cyan]→[/cyan] Deploy platform image [bold]{full_image}[/bold]")
709
735
  if start:
710
- _run_env(
736
+ _run_pull(
711
737
  ["docker", "compose", "-f", str(compose_file), "pull", "platform-web"],
712
738
  cwd=platform_root,
713
739
  env=env,
740
+ image=full_image,
714
741
  )
715
742
  _run_env(
716
743
  ["docker", "compose", "-f", str(compose_file), "up", "-d"],
@@ -1477,10 +1504,11 @@ def register(app: typer.Typer) -> None:
1477
1504
  console.print(f"Local rollout: [bold]{full}[/bold]")
1478
1505
  env = {**os.environ, "CORE_RUNTIME_IMAGE": full, **_compose_env_overrides(db=db, cache=cache)}
1479
1506
  if do_pull:
1480
- _run_env(
1507
+ _run_pull(
1481
1508
  ["docker", "compose", "-f", str(project.compose_file), "pull", "core-runtime"],
1482
1509
  cwd=project.cwd,
1483
1510
  env=env,
1511
+ image=full,
1484
1512
  )
1485
1513
  _run_env(
1486
1514
  ["docker", "compose", "-f", str(project.compose_file), "up", "-d"],
@@ -11,7 +11,7 @@ from rich.console import Console
11
11
  from rich.table import Table
12
12
 
13
13
  from hc.core_ops import compose_project_from_source, require_docker
14
- from hc.core_source import CoreSource, get_core_source_from_repo, get_core_source_local
14
+ from hc.core_source import CoreSource, get_core_source_from_repo, get_core_source_local, init_core_source
15
15
  from hc.errors import CoreSourcesNotFoundError, HcCliError
16
16
 
17
17
 
@@ -105,11 +105,17 @@ _DB_HELP = "sqlite | postgres (без --db: интерактивный выбо
105
105
 
106
106
  # ─── Helpers ──────────────────────────────────────────────────────────────────
107
107
 
108
+ # Known siblings of core-runtime-service that confirm we're in the monorepo root.
109
+ # Without them, a standalone core-runtime-service clone in any parent dir would be mistaken for monorepo.
110
+ _MONOREPO_SIBLINGS = frozenset({"home-console-cli", "packages", "platform-home-console"})
111
+
112
+
108
113
  def _find_repo_root() -> Path | None:
109
114
  here = Path(__file__).resolve()
110
115
  for p in [here, *here.parents]:
111
116
  if (p / "core-runtime-service").exists():
112
- return p
117
+ if any((p / s).exists() for s in _MONOREPO_SIBLINGS):
118
+ return p
113
119
  return None
114
120
 
115
121
 
@@ -122,10 +128,36 @@ def _resolve_source(console: Console) -> CoreSource:
122
128
  src = get_core_source_local()
123
129
  if src:
124
130
  return src
131
+
132
+ # On a fresh machine: offer to auto-clone core-runtime-service.
133
+ from hc.constants import CORE_SRC_DIR, DEFAULT_CORE_REPO
134
+ console.print(f"[yellow]Исходники Core не найдены.[/yellow] ({CORE_SRC_DIR})")
135
+
136
+ if sys.stdin.isatty():
137
+ try:
138
+ import questionary
139
+ answer = questionary.confirm(
140
+ f"Скачать core-runtime-service в {CORE_SRC_DIR}?",
141
+ default=True,
142
+ ).ask()
143
+ except ImportError:
144
+ answer = None
145
+
146
+ if answer is None:
147
+ raise typer.Abort()
148
+ if not answer:
149
+ raise CoreSourcesNotFoundError(
150
+ message="Исходники Core не найдены.",
151
+ exit_code=1,
152
+ hint=f"Запусти: hc core init (клонирует {DEFAULT_CORE_REPO})",
153
+ )
154
+
155
+ return init_core_source(console, None, None)
156
+
125
157
  raise CoreSourcesNotFoundError(
126
158
  message="Исходники Core не найдены.",
127
159
  exit_code=1,
128
- hint="Сделай `hc core init` или запусти из монорепы HomeConsole.",
160
+ hint=f"Запусти: hc core init (клонирует {DEFAULT_CORE_REPO})",
129
161
  )
130
162
 
131
163
 
@@ -23,11 +23,15 @@ from hc.errors import (
23
23
  )
24
24
 
25
25
 
26
+ _MONOREPO_SIBLINGS = frozenset({"home-console-cli", "packages", "platform-home-console"})
27
+
28
+
26
29
  def _find_repo_root() -> Path | None:
27
30
  here = Path(__file__).resolve()
28
31
  for p in [here, *here.parents]:
29
32
  if (p / "core-runtime-service").exists():
30
- return p
33
+ if any((p / s).exists() for s in _MONOREPO_SIBLINGS):
34
+ return p
31
35
  return None
32
36
 
33
37
 
@@ -81,6 +85,28 @@ def _run(cmd: list[str], *, cwd: Path | None = None, env: dict[str, str] | None
81
85
  raise typer.Exit(code=p.returncode)
82
86
 
83
87
 
88
+ def _run_pull(
89
+ cmd: list[str],
90
+ *,
91
+ cwd: Path | None = None,
92
+ env: dict[str, str] | None = None,
93
+ image: str,
94
+ ) -> None:
95
+ """docker compose pull с понятной подсказкой при ошибке авторизации."""
96
+ p = subprocess.run(cmd, cwd=str(cwd) if cwd else None, env=env, check=False) # noqa: S603
97
+ if p.returncode != 0:
98
+ registry = image.split("/")[0] if "/" in image else "ghcr.io"
99
+ raise HcCliError(
100
+ message=f"Не удалось загрузить образ {image}",
101
+ hint=(
102
+ f"Образ недоступен (denied / unauthorized).\n"
103
+ f" Авторизуйся в реестре:\n"
104
+ f" docker login {registry} -u <github_username> -p <PAT_read:packages>"
105
+ ),
106
+ exit_code=p.returncode,
107
+ )
108
+
109
+
84
110
  def _fmt_s(seconds: float) -> str:
85
111
  if seconds < 60:
86
112
  return f"{seconds:.1f}s"
@@ -307,10 +333,11 @@ def register(app: typer.Typer) -> None:
307
333
  project = compose_project_from_source(console, src, mode=deploy_mode)
308
334
  env = {**os.environ, "CORE_RUNTIME_IMAGE": full}
309
335
  t0 = time.monotonic()
310
- _run(
336
+ _run_pull(
311
337
  ["docker", "compose", "-f", str(project.compose_file), "pull", "core-runtime"],
312
338
  cwd=project.cwd,
313
339
  env=env,
340
+ image=full,
314
341
  )
315
342
  _run(["docker", "compose", "-f", str(project.compose_file), "up", "-d"], cwd=project.cwd, env=env)
316
343
  dt = time.monotonic() - t0
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  from pathlib import Path
4
4
 
5
5
  APP_NAME = "HomeConsole CLI"
6
- APP_VERSION = "1.0"
7
6
  ENV_TOKEN = "HC_TOKEN"
8
7
 
9
8
  CONFIG_DIR = Path.home() / ".config" / "hc"
@@ -24,7 +23,7 @@ DEFAULT_CORE_REPO = "https://github.com/home-console/core-runtime-service"
24
23
  DEFAULT_CORE_REF = "master"
25
24
 
26
25
  # Образ для prod / dev-image rollout (Ghcr). Локально собранный тег задаёт через --image.
27
- DEFAULT_CORE_IMAGE = "ghcr.io/home-console/core-runtime"
26
+ DEFAULT_CORE_IMAGE = "ghcr.io/home-console/core-runtime-service"
28
27
 
29
28
  DEFAULT_HOST = "localhost"
30
29
  DEFAULT_PORT = 8080
@@ -9,7 +9,7 @@ import typer
9
9
  from rich.console import Console
10
10
 
11
11
  from hc.config import Config
12
- from hc.core_source import VALID_MODES, CoreSource
12
+ from hc.core_source import COMPOSE_MODES, VALID_MODES, CoreSource
13
13
  from hc.env_bootstrap import ensure_core_env
14
14
  from hc.errors import DockerNotFoundError, HcCliError
15
15
 
@@ -46,10 +46,17 @@ def compose_project_from_source(console: Console, src: CoreSource, mode: str | N
46
46
  hint=f"Допустимые режимы: {valid}",
47
47
  ) from exc
48
48
  if not compose.exists():
49
+ available = [m for m, rel in COMPOSE_MODES.items() if (src.path / rel).exists()]
50
+ hint = f"Режим {mode!r} → {src.compose_rel(mode)}. Файл не найден в {src.path}."
51
+ if available:
52
+ hint += f"\n Доступные режимы: {' | '.join(available)}"
53
+ hint += f"\n Попробуй: hc env up --mode {available[0]}"
54
+ else:
55
+ hint += "\n Сделай `hc core init` для загрузки исходников Core."
49
56
  raise HcCliError(
50
57
  message=f"Не найден compose-файл: {compose}",
51
58
  exit_code=1,
52
- hint=f"Режим {mode!r} → {src.compose_rel(mode)}. Проверь наличие файла в core-runtime-service.",
59
+ hint=hint,
53
60
  )
54
61
  return ComposeProject(compose_file=compose)
55
62
 
@@ -14,6 +14,7 @@ from rich.console import Console
14
14
 
15
15
  from typer.main import get_command as _typer_get_command
16
16
 
17
+ from hc import __version__
17
18
  from hc.config import Config
18
19
  from hc.constants import APP_NAME, HISTORY_PATH
19
20
  from hc.commands._client_helpers import require_client
@@ -229,32 +230,30 @@ def run_repl(app: typer.Typer) -> None:
229
230
  console = Console()
230
231
  cfg = Config.load()
231
232
  token = os.getenv("HC_TOKEN") or cfg.core.token
232
- connected = bool(cfg.core.host.strip()) and bool(token.strip())
233
+ cfg_ok = bool(cfg.core.host.strip()) and bool(token.strip())
233
234
  hostport = f"{cfg.core.host}:{cfg.core.port}"
234
235
 
235
236
  plugins: list[str] = []
236
- if connected:
237
- client = require_client(console)
237
+ connected = False
238
+ if cfg_ok:
239
+ client = require_client(console, silent=True)
238
240
 
239
- async def _get_names() -> list[str]:
241
+ async def _get_names() -> tuple[bool, list[str]]:
240
242
  # Пытаемся получить имена плагинов из inspector (админский источник истины).
241
243
  insp = await client.inspector_plugins()
242
244
  if isinstance(insp, dict):
243
245
  arr = insp.get("plugins") or []
244
246
  if isinstance(arr, list):
245
- names = []
246
- for p in arr:
247
- if isinstance(p, dict) and p.get("name"):
248
- names.append(str(p["name"]))
247
+ names = [str(p["name"]) for p in arr if isinstance(p, dict) and p.get("name")]
249
248
  if names:
250
- return names
249
+ return True, names
251
250
  # Fallback на старый эндпоинт (если он есть).
252
251
  items = await client.get_plugins()
253
- if not items:
254
- return []
255
- return [str(p.get("name", "")) for p in items if p.get("name")]
252
+ if items is not None:
253
+ return True, [str(p.get("name", "")) for p in items if p.get("name")]
254
+ return False, []
256
255
 
257
- plugins = anyio.run(_get_names)
256
+ connected, plugins = anyio.run(_get_names)
258
257
 
259
258
  commands = [
260
259
  "connect",
@@ -290,7 +289,13 @@ def run_repl(app: typer.Typer) -> None:
290
289
 
291
290
  completer = _HCCompleter(app=app, commands=commands, plugins=plugins, get_group_ctx=_get_ctx)
292
291
 
293
- console.print(f"{APP_NAME} 0.0.1 | " + (f"connected to {hostport}" if connected else "not connected"))
292
+ if connected:
293
+ status = f"connected to {hostport}"
294
+ elif cfg_ok:
295
+ status = f"offline • configured for {hostport}"
296
+ else:
297
+ status = "not connected"
298
+ console.print(f"{APP_NAME} {__version__} | {status}")
294
299
  console.print("Type 'help' or '?' for commands, 'exit' to quit")
295
300
 
296
301
  HISTORY_PATH.parent.mkdir(parents=True, exist_ok=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: homeconsole-cli
3
- Version: 0.0.2
3
+ Version: 0.0.4
4
4
  Summary: HomeConsole CLI (hc) — управление платформой через HTTP API
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -18,13 +18,42 @@ Requires-Dist: pytest>=8; extra == "dev"
18
18
 
19
19
  CLI-утилита для управления платформой HomeConsole **исключительно через HTTP API** CoreRuntime.
20
20
 
21
- ## Установка (локально)
21
+ ## Установка
22
+
23
+ ### Рекомендуется — `pipx` (Debian / Ubuntu / Raspbian / OrangePi OS)
24
+
25
+ `pipx` создаёт изолированный venv автоматически и регистрирует `hc` глобально:
26
+
27
+ ```bash
28
+ apt install pipx # или: pip install pipx --user
29
+ pipx ensurepath # добавляет ~/.local/bin в PATH (один раз)
30
+ pipx install homeconsole-cli
31
+ ```
32
+
33
+ После `pipx ensurepath` перезапусти шелл или выполни `source ~/.bashrc`.
34
+
35
+ Обновление:
36
+ ```bash
37
+ pipx upgrade homeconsole-cli
38
+ ```
39
+
40
+ ### Альтернатива — `pip --user`
41
+
42
+ ```bash
43
+ pip install --user homeconsole-cli
44
+ ```
45
+
46
+ Убедись, что `~/.local/bin` в `PATH`:
47
+ ```bash
48
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc
49
+ ```
50
+
51
+ ### Локальная разработка (из исходников)
22
52
 
23
53
  ```bash
24
54
  cd home-console-cli
25
55
  python -m venv .venv
26
56
  source .venv/bin/activate
27
- pip install -U pip
28
57
  pip install -e .
29
58
  ```
30
59
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "homeconsole-cli"
3
- version = "0.0.2"
3
+ version = "0.0.4"
4
4
  description = "HomeConsole CLI (hc) — управление платформой через HTTP API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -1,6 +0,0 @@
1
- from __future__ import annotations
2
-
3
- __all__ = ["__version__"]
4
-
5
- __version__ = "0.0.1"
6
-