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.
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/PKG-INFO +32 -3
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/README.md +31 -2
- homeconsole_cli-0.0.4/hc/__init__.py +10 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/client.py +6 -3
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/_client_helpers.py +2 -1
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/deploy.py +31 -3
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/env.py +35 -3
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/update.py +29 -2
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/constants.py +1 -2
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/core_ops.py +9 -2
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/repl.py +19 -14
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/homeconsole_cli.egg-info/PKG-INFO +32 -3
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/pyproject.toml +1 -1
- homeconsole_cli-0.0.2/hc/__init__.py +0 -6
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/api.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/capabilities.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/__init__.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/_compose_helpers.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/auth.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/connect.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/core.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/install.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/logs.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/marketplace.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/module.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/ping.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/plugin.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/recovery/__init__.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/recovery/compose.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/recovery/config.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/recovery/core.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/recovery/db.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/recovery/mode.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/recovery/redis.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/recovery/ui.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/remove.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/reset.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/search.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/secrets.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/setup.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/setup_wizard.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/commands/status.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/config.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/core_source.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/env_bootstrap.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/errors.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/main.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/marketplace_operation.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/native_core.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/setup_runner.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/hc/shell.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/homeconsole_cli.egg-info/SOURCES.txt +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/homeconsole_cli.egg-info/dependency_links.txt +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/homeconsole_cli.egg-info/entry_points.txt +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/homeconsole_cli.egg-info/requires.txt +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/homeconsole_cli.egg-info/top_level.txt +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/setup.cfg +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/tests/test_config_roundtrip.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/tests/test_core_ops.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/tests/test_env_bootstrap.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/tests/test_main_root_callback.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/tests/test_marketplace_operation_parse.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/tests/test_native_core_helpers.py +0 -0
- {homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/tests/test_nav_cli.py +0 -0
- {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.
|
|
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
|
|
|
@@ -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
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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="
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
237
|
-
|
|
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
|
|
254
|
-
return []
|
|
255
|
-
return
|
|
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
|
-
|
|
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.
|
|
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
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{homeconsole_cli-0.0.2 → homeconsole_cli-0.0.4}/homeconsole_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|