cave-cli 3.5.1__tar.gz → 3.5.2__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 (44) hide show
  1. {cave_cli-3.5.1/cave_cli.egg-info → cave_cli-3.5.2}/PKG-INFO +16 -8
  2. {cave_cli-3.5.1 → cave_cli-3.5.2}/README.md +15 -7
  3. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/cli.py +73 -20
  4. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/commands/create.py +20 -19
  5. cave_cli-3.5.2/cave_cli/commands/doctor.py +170 -0
  6. cave_cli-3.5.2/cave_cli/commands/kill.py +36 -0
  7. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/commands/list_cmd.py +11 -5
  8. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/commands/list_versions.py +16 -9
  9. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/commands/prettify.py +2 -2
  10. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/commands/purge.py +18 -5
  11. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/commands/reset.py +11 -4
  12. cave_cli-3.5.2/cave_cli/commands/run.py +343 -0
  13. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/commands/sync_cmd.py +12 -12
  14. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/commands/test.py +8 -7
  15. cave_cli-3.5.2/cave_cli/commands/theme.py +34 -0
  16. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/commands/uninstall.py +5 -3
  17. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/commands/update.py +6 -4
  18. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/commands/upgrade.py +10 -5
  19. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/commands/version.py +9 -7
  20. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/utils/cache.py +62 -0
  21. cave_cli-3.5.2/cave_cli/utils/display.py +969 -0
  22. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/utils/docker.py +175 -69
  23. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/utils/git.py +0 -2
  24. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/utils/logger.py +28 -6
  25. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/utils/validate.py +2 -2
  26. {cave_cli-3.5.1 → cave_cli-3.5.2/cave_cli.egg-info}/PKG-INFO +16 -8
  27. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli.egg-info/SOURCES.txt +3 -0
  28. {cave_cli-3.5.1 → cave_cli-3.5.2}/pyproject.toml +2 -2
  29. cave_cli-3.5.1/cave_cli/commands/kill.py +0 -23
  30. cave_cli-3.5.1/cave_cli/commands/run.py +0 -189
  31. {cave_cli-3.5.1 → cave_cli-3.5.2}/LICENSE +0 -0
  32. {cave_cli-3.5.1 → cave_cli-3.5.2}/NOTICE.md +0 -0
  33. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/__init__.py +0 -0
  34. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/commands/__init__.py +0 -0
  35. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/utils/__init__.py +0 -0
  36. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/utils/constants.py +0 -0
  37. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/utils/env.py +0 -0
  38. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/utils/net.py +0 -0
  39. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/utils/subprocess.py +0 -0
  40. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli/utils/sync.py +0 -0
  41. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli.egg-info/dependency_links.txt +0 -0
  42. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli.egg-info/entry_points.txt +0 -0
  43. {cave_cli-3.5.1 → cave_cli-3.5.2}/cave_cli.egg-info/top_level.txt +0 -0
  44. {cave_cli-3.5.1 → cave_cli-3.5.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cave_cli
3
- Version: 3.5.1
3
+ Version: 3.5.2
4
4
  Summary: CLI for creating and managing Docker-based CAVE web applications.
5
5
  Author-email: MIT-CAVE <cave-contact@mit.edu>
6
6
  License: Apache-2.0
@@ -50,9 +50,7 @@ docker run hello-world
50
50
  <details>
51
51
  <summary>Windows</summary>
52
52
 
53
- - Install Docker Desktop for WSL: https://docs.docker.com/desktop/wsl/
54
- - Install WSL2 with Ubuntu: https://learn.microsoft.com/en-us/windows/wsl/install
55
- - Open your WSL Ubuntu terminal for all `cave` commands
53
+ - Install Docker Desktop: https://docs.docker.com/docker-for-windows/install/
56
54
 
57
55
  </details>
58
56
 
@@ -81,7 +79,7 @@ pipx ensurepath
81
79
 
82
80
  </details>
83
81
  <details>
84
- <summary>Other Linux / Windows (WSL)</summary>
82
+ <summary>Other Linux / Windows</summary>
85
83
 
86
84
  ```sh
87
85
  python3 -m pip install --user pipx
@@ -91,10 +89,10 @@ pipx ensurepath
91
89
 
92
90
  For more options see the [pipx installation guide](https://pipx.pypa.io/stable/installation/).
93
91
 
94
- Verify the installation:
92
+ Verify the installation and check your environment health:
95
93
 
96
94
  ```sh
97
- cave --version
95
+ cave doctor
98
96
  ```
99
97
 
100
98
  ## Quick Start
@@ -117,7 +115,8 @@ cave --help
117
115
  | Command | Description |
118
116
  |---|---|
119
117
  | `cave create <name>` | Create a new CAVE app from the template repository |
120
- | `cave run` | Build Docker image and run the app |
118
+ | `cave run` | Build Docker image and run the app with a live TUI dashboard |
119
+ | `cave doctor` | Check the health of your Docker, Git, and Pipx environment |
121
120
 
122
121
  ### Peripheral Commands
123
122
 
@@ -137,6 +136,7 @@ cave --help
137
136
  | `cave list` | List running CAVE apps |
138
137
  | `cave kill` | Stop Docker containers for an app |
139
138
  | `cave list-versions` | List available CAVE app versions |
139
+ | `cave theme <name>` | Set the CLI color theme (dark, light, solarized, monokai) |
140
140
  | `cave update` | Update the CAVE CLI itself |
141
141
  | `cave uninstall` | Remove the CAVE CLI |
142
142
  | `cave version` | Print version information |
@@ -149,6 +149,14 @@ cave --help
149
149
  | `--loglevel LEVEL` | Set log level: DEBUG, INFO, WARN, ERROR, SILENT |
150
150
  | `-y`, `--yes` | Automatically answer confirmation prompts with yes |
151
151
 
152
+ ### `cave run` Options
153
+
154
+ | Flag | Description |
155
+ |---|---|
156
+ | `--all` | Show raw container output instead of the TUI dashboard |
157
+ | `-it`, `--interactive` | Run in interactive mode (drops into a bash shell) |
158
+ | `ip:port` | Optional argument for LAN hosting (e.g. `192.168.1.1:8000`) |
159
+
152
160
  ## Updating
153
161
 
154
162
  ```sh
@@ -33,9 +33,7 @@ docker run hello-world
33
33
  <details>
34
34
  <summary>Windows</summary>
35
35
 
36
- - Install Docker Desktop for WSL: https://docs.docker.com/desktop/wsl/
37
- - Install WSL2 with Ubuntu: https://learn.microsoft.com/en-us/windows/wsl/install
38
- - Open your WSL Ubuntu terminal for all `cave` commands
36
+ - Install Docker Desktop: https://docs.docker.com/docker-for-windows/install/
39
37
 
40
38
  </details>
41
39
 
@@ -64,7 +62,7 @@ pipx ensurepath
64
62
 
65
63
  </details>
66
64
  <details>
67
- <summary>Other Linux / Windows (WSL)</summary>
65
+ <summary>Other Linux / Windows</summary>
68
66
 
69
67
  ```sh
70
68
  python3 -m pip install --user pipx
@@ -74,10 +72,10 @@ pipx ensurepath
74
72
 
75
73
  For more options see the [pipx installation guide](https://pipx.pypa.io/stable/installation/).
76
74
 
77
- Verify the installation:
75
+ Verify the installation and check your environment health:
78
76
 
79
77
  ```sh
80
- cave --version
78
+ cave doctor
81
79
  ```
82
80
 
83
81
  ## Quick Start
@@ -100,7 +98,8 @@ cave --help
100
98
  | Command | Description |
101
99
  |---|---|
102
100
  | `cave create <name>` | Create a new CAVE app from the template repository |
103
- | `cave run` | Build Docker image and run the app |
101
+ | `cave run` | Build Docker image and run the app with a live TUI dashboard |
102
+ | `cave doctor` | Check the health of your Docker, Git, and Pipx environment |
104
103
 
105
104
  ### Peripheral Commands
106
105
 
@@ -120,6 +119,7 @@ cave --help
120
119
  | `cave list` | List running CAVE apps |
121
120
  | `cave kill` | Stop Docker containers for an app |
122
121
  | `cave list-versions` | List available CAVE app versions |
122
+ | `cave theme <name>` | Set the CLI color theme (dark, light, solarized, monokai) |
123
123
  | `cave update` | Update the CAVE CLI itself |
124
124
  | `cave uninstall` | Remove the CAVE CLI |
125
125
  | `cave version` | Print version information |
@@ -132,6 +132,14 @@ cave --help
132
132
  | `--loglevel LEVEL` | Set log level: DEBUG, INFO, WARN, ERROR, SILENT |
133
133
  | `-y`, `--yes` | Automatically answer confirmation prompts with yes |
134
134
 
135
+ ### `cave run` Options
136
+
137
+ | Flag | Description |
138
+ |---|---|
139
+ | `--all` | Show raw container output instead of the TUI dashboard |
140
+ | `-it`, `--interactive` | Run in interactive mode (drops into a bash shell) |
141
+ | `ip:port` | Optional argument for LAN hosting (e.g. `192.168.1.1:8000`) |
142
+
135
143
  ## Updating
136
144
 
137
145
  ```sh
@@ -142,6 +142,13 @@ def main():
142
142
  default=False,
143
143
  help="Run in interactive mode (entrypoint set to bash)",
144
144
  )
145
+ p_run.add_argument(
146
+ "--all",
147
+ dest="show_all",
148
+ action="store_true",
149
+ default=False,
150
+ help="Show raw container output instead of the TUI dashboard (also enabled by --verbose)",
151
+ )
145
152
 
146
153
  # ------------------------------------------------------------------ #
147
154
  # reset #
@@ -265,6 +272,13 @@ def main():
265
272
  help="Stop Docker containers for a CAVE app",
266
273
  )
267
274
  add_global_args(p_kill)
275
+ p_kill.add_argument(
276
+ "name",
277
+ nargs="?",
278
+ default=None,
279
+ metavar="app-name",
280
+ help="Name of the CAVE app to kill",
281
+ )
268
282
  p_kill.add_argument(
269
283
  "-a",
270
284
  "--all",
@@ -309,6 +323,15 @@ def main():
309
323
  help="Glob pattern to filter versions (e.g. v3.*, v3.4.*)",
310
324
  )
311
325
 
326
+ # ------------------------------------------------------------------ #
327
+ # doctor #
328
+ # ------------------------------------------------------------------ #
329
+ p_doctor = subparsers.add_parser(
330
+ "doctor",
331
+ help="Check the health of the CAVE environment",
332
+ )
333
+ add_global_args(p_doctor)
334
+
312
335
  # ------------------------------------------------------------------ #
313
336
  # update #
314
337
  # ------------------------------------------------------------------ #
@@ -342,6 +365,22 @@ def main():
342
365
  )
343
366
  add_global_args(p_version)
344
367
 
368
+ # ------------------------------------------------------------------ #
369
+ # theme #
370
+ # ------------------------------------------------------------------ #
371
+ p_theme = subparsers.add_parser(
372
+ "theme",
373
+ help="Set the CLI color theme",
374
+ )
375
+ add_global_args(p_theme)
376
+ p_theme.add_argument(
377
+ "name",
378
+ nargs="?",
379
+ default=None,
380
+ metavar="THEME",
381
+ help="Name of the theme to apply (dark, light, solarized, monokai)",
382
+ )
383
+
345
384
  # ------------------------------------------------------------------ #
346
385
  # Dispatch #
347
386
  # ------------------------------------------------------------------ #
@@ -371,85 +410,99 @@ def main():
371
410
  # Command dispatch #
372
411
  # ------------------------------------------------------------------ #
373
412
  if args.command == "create":
374
- from cave_cli.utils.docker import check_docker
413
+ from cave_cli.commands.doctor import check_all
375
414
  from cave_cli.commands.create import create
376
415
 
377
- check_docker()
416
+ check_all()
378
417
  create(args)
379
418
 
380
419
  elif args.command == "run":
381
- from cave_cli.utils.docker import check_docker
420
+ from cave_cli.commands.doctor import check_all
382
421
  from cave_cli.commands.run import run
383
422
 
384
- check_docker()
423
+ check_all()
385
424
  run(args)
386
425
 
387
426
  elif args.command == "reset":
388
- from cave_cli.utils.docker import check_docker
427
+ from cave_cli.commands.doctor import check_all
389
428
  from cave_cli.commands.reset import reset
390
429
 
391
- check_docker()
430
+ check_all()
392
431
  reset(args)
393
432
 
394
433
  elif args.command == "upgrade":
395
- from cave_cli.utils.docker import check_docker
434
+ from cave_cli.commands.doctor import check_all
396
435
  from cave_cli.commands.upgrade import upgrade
397
436
 
398
- check_docker()
437
+ check_all()
399
438
  upgrade(args)
400
439
 
401
440
  elif args.command == "sync":
402
- from cave_cli.utils.docker import check_docker
441
+ from cave_cli.commands.doctor import check_all
403
442
  from cave_cli.commands.sync_cmd import sync_cmd
404
443
 
405
- check_docker()
444
+ check_all()
406
445
  sync_cmd(args)
407
446
 
408
447
  elif args.command == "test":
409
- from cave_cli.utils.docker import check_docker
448
+ from cave_cli.commands.doctor import check_all
410
449
  from cave_cli.commands.test import test
411
450
 
412
- check_docker()
451
+ check_all()
413
452
  test(args)
414
453
 
415
454
  elif args.command == "prettify":
416
- from cave_cli.utils.docker import check_docker
455
+ from cave_cli.commands.doctor import check_all
417
456
  from cave_cli.commands.prettify import prettify
418
457
 
419
- check_docker()
458
+ check_all()
420
459
  prettify(args)
421
460
 
422
461
  elif args.command == "list":
423
- from cave_cli.utils.docker import check_docker
462
+ from cave_cli.commands.doctor import check_all
424
463
  from cave_cli.commands.list_cmd import list_cmd
425
464
 
426
- check_docker()
465
+ check_all()
427
466
  list_cmd(args)
428
467
 
429
468
  elif args.command == "kill":
430
- from cave_cli.utils.docker import check_docker
469
+ from cave_cli.commands.doctor import check_all
431
470
  from cave_cli.commands.kill import kill
432
471
 
433
- check_docker()
472
+ check_all()
434
473
  kill(args)
435
474
 
436
475
  elif args.command == "purge":
437
- from cave_cli.utils.docker import check_docker
476
+ from cave_cli.commands.doctor import check_all
438
477
  from cave_cli.commands.purge import purge
439
478
 
440
- check_docker()
479
+ check_all()
441
480
  purge(args)
442
481
 
443
482
  elif args.command == "list-versions":
483
+ from cave_cli.commands.doctor import check_all
444
484
  from cave_cli.commands.list_versions import list_versions
445
485
 
486
+ check_all()
446
487
  list_versions(args)
447
488
 
448
489
  elif args.command == "update":
490
+ from cave_cli.commands.doctor import check_all
449
491
  from cave_cli.commands.update import update
450
492
 
493
+ check_all()
451
494
  update(args)
452
495
 
496
+ elif args.command == "doctor":
497
+ from cave_cli.commands.doctor import doctor
498
+
499
+ doctor(args)
500
+
501
+ elif args.command == "theme":
502
+ from cave_cli.commands.theme import theme_cmd
503
+
504
+ theme_cmd(args)
505
+
453
506
  elif args.command == "uninstall":
454
507
  from cave_cli.commands.uninstall import uninstall
455
508
 
@@ -8,6 +8,7 @@ from pathlib import Path
8
8
  from cave_cli.commands.reset import reset
9
9
  from cave_cli.commands.run import run_cave
10
10
  from cave_cli.utils.constants import HTTPS_URL
11
+ from cave_cli.utils.display import print_section, step_done, step_fail, step_start
11
12
  from cave_cli.utils.docker import build_image, check_docker, generate_secret_key
12
13
  from cave_cli.utils.env import create_env_interactive
13
14
  from cave_cli.utils.git import add, branch_rename, clone, commit, init
@@ -37,29 +38,31 @@ def create(args: argparse.Namespace) -> None:
37
38
  clone_url = getattr(args, "url", None) or HTTPS_URL
38
39
  version = getattr(args, "version", None)
39
40
 
40
- logger.header("App Creation:")
41
- logger.info("Downloading the app template...")
41
+ print_section("App Creation")
42
42
 
43
+ step_start("Downloading app template")
43
44
  success = clone(clone_url, app_name, branch=version)
44
45
  if not success or not os.path.isdir(app_name):
46
+ step_fail("Downloading app template")
45
47
  logger.error("Clone failed. Ensure you used a valid version.")
46
48
  logger.error(
47
49
  f"The version must be a tag (or branch) listed at {clone_url}."
48
50
  )
49
51
  sys.exit(1)
50
-
51
- logger.info("Done")
52
+ step_done("Downloading app template")
52
53
 
53
54
  app_dir = os.path.abspath(app_name)
54
55
  remove_licence_info(app_dir)
55
-
56
56
  Path(os.path.join(app_dir, ".env")).touch()
57
57
 
58
58
  example_env = os.path.join(app_dir, "example.env")
59
59
  env_path = os.path.join(app_dir, ".env")
60
60
 
61
61
  build_image(app_name, app_dir)
62
+
63
+ step_start("Generating secret key")
62
64
  secret_key = generate_secret_key(app_name)
65
+ step_done("Generating secret key")
63
66
 
64
67
  create_env_interactive(
65
68
  app_name=app_name,
@@ -73,10 +76,11 @@ def create(args: argparse.Namespace) -> None:
73
76
  verbose=getattr(args, "verbose", False),
74
77
  loglevel=getattr(args, "loglevel", "INFO"),
75
78
  )
76
- reset(reset_args, app_dir=app_dir, app_name=app_name)
79
+ reset(reset_args, app_dir=app_dir, app_name=app_name, skip_build=True)
77
80
 
78
- logger.header("Version Control:")
79
- logger.info("Configuring git repository...")
81
+ print_section("Version Control")
82
+
83
+ step_start("Configuring git repository")
80
84
  git_dir = os.path.join(app_dir, ".git")
81
85
  if os.path.isdir(git_dir):
82
86
  rmtree_force(git_dir)
@@ -97,9 +101,9 @@ def create(args: argparse.Namespace) -> None:
97
101
  add(app_dir)
98
102
  commit(app_dir, "Initialize CAVE App")
99
103
  branch_rename(app_dir, "main")
100
- logger.info("Done.")
104
+ step_done("Configuring git repository")
101
105
 
102
- logger.info("Generating LLM Docs...")
106
+ step_start("Generating LLM docs")
103
107
  docs_args = argparse.Namespace(
104
108
  entrypoint="./utils/generate_docs.sh",
105
109
  interactive=False,
@@ -110,15 +114,12 @@ def create(args: argparse.Namespace) -> None:
110
114
  verbose=getattr(args, "verbose", False),
111
115
  loglevel=getattr(args, "loglevel", "INFO"),
112
116
  )
113
- run_cave(app_dir, app_name, docs_args)
114
- logger.info("Done.")
115
-
116
- logger.header("App Creation Status:")
117
- logger.info(f"App '{app_name}' created successfully!")
118
- logger.info(
119
- f"Created variables and additional configuration options "
120
- f"are available in {app_name}/.env"
121
- )
117
+ run_cave(app_dir, app_name, docs_args, skip_header=True, skip_build=True)
118
+ step_done("Generating LLM docs")
119
+
120
+ print_section("Done")
121
+ step_done(f"App '{app_name}' created successfully!")
122
+ step_done(f"Configuration options available in {app_name}/.env")
122
123
 
123
124
 
124
125
  def force_remove(func, path, exc):
@@ -0,0 +1,170 @@
1
+ import argparse
2
+ import sys
3
+
4
+ from cave_cli.utils.display import (
5
+ print_section,
6
+ step_done,
7
+ step_fail,
8
+ step_start,
9
+ )
10
+ from cave_cli.utils.logger import logger
11
+ from cave_cli.utils.subprocess import run, version_tuple
12
+
13
+
14
+ def check_git() -> tuple[bool, str]:
15
+ """
16
+ Usage:
17
+
18
+ - Validates that git is installed
19
+
20
+ Returns:
21
+
22
+ - ``success``: bool
23
+ - ``message``: str remediation or version
24
+ """
25
+ try:
26
+ result = run(["git", "--version"])
27
+ if result.returncode == 0:
28
+ return True, result.stdout.strip()
29
+ except FileNotFoundError:
30
+ pass
31
+ return (
32
+ False,
33
+ "git is not installed. Please install git: https://git-scm.com/",
34
+ )
35
+
36
+
37
+ def check_pipx() -> tuple[bool, str]:
38
+ """
39
+ Usage:
40
+
41
+ - Validates that pipx is installed
42
+
43
+ Returns:
44
+
45
+ - ``success``: bool
46
+ - ``message``: str remediation or version
47
+ """
48
+ import shutil
49
+
50
+ pipx = shutil.which("pipx")
51
+ if pipx:
52
+ try:
53
+ result = run([pipx, "--version"])
54
+ if result.returncode == 0:
55
+ return True, f"pipx version {result.stdout.strip()}"
56
+ except Exception:
57
+ pass
58
+ return (
59
+ False,
60
+ "pipx is not installed. Please install pipx: https://pipx.pypa.io/",
61
+ )
62
+
63
+
64
+ def check_docker() -> tuple[bool, str]:
65
+ """
66
+ Usage:
67
+
68
+ - Validates that Docker is installed, running, and meets the minimum version
69
+
70
+ Returns:
71
+
72
+ - ``success``: bool
73
+ - ``message``: str remediation or version
74
+ """
75
+ from cave_cli.utils.constants import MIN_DOCKER_VERSION
76
+
77
+ try:
78
+ result = run(["docker", "--version"])
79
+ except FileNotFoundError:
80
+ return (
81
+ False,
82
+ f"Docker is not installed. "
83
+ f"Please install Docker version {MIN_DOCKER_VERSION} or greater: "
84
+ f"https://docs.docker.com/get-docker/",
85
+ )
86
+
87
+ if result.returncode != 0 or not result.stdout:
88
+ return (
89
+ False,
90
+ f"Could determine Docker version. "
91
+ f"Please install Docker version {MIN_DOCKER_VERSION} or greater.",
92
+ )
93
+
94
+ version_str = result.stdout.strip()
95
+ import re
96
+
97
+ match = re.search(r"(\d+\.\d+\.\d+)", version_str)
98
+ if not match:
99
+ return False, f"Could not parse Docker version from: {version_str}"
100
+
101
+ current = match.group(1)
102
+ if version_tuple(current) < version_tuple(MIN_DOCKER_VERSION):
103
+ return (
104
+ False,
105
+ f"Your current Docker version ({current}) is too old. "
106
+ f"Please install Docker version {MIN_DOCKER_VERSION} or greater.",
107
+ )
108
+
109
+ info_result = run(["docker", "info"])
110
+ if info_result.returncode != 0:
111
+ return False, "Docker is not running... Please start Docker."
112
+
113
+ return True, version_str
114
+
115
+
116
+ def check_all(exit_on_fail: bool = True) -> dict[str, tuple[bool, str]]:
117
+ """
118
+ Usage:
119
+
120
+ - Runs all environment checks
121
+
122
+ Optional:
123
+
124
+ - ``exit_on_fail``: bool - Exits the process if any check fails
125
+ """
126
+ results = {
127
+ "Docker": check_docker(),
128
+ "Git": check_git(),
129
+ "Pipx": check_pipx(),
130
+ }
131
+
132
+ if exit_on_fail:
133
+ failed = False
134
+ for name, (success, message) in results.items():
135
+ if not success:
136
+ logger.error(message)
137
+ failed = True
138
+ if failed:
139
+ sys.exit(1)
140
+
141
+ return results
142
+
143
+
144
+ def doctor(args: argparse.Namespace) -> None:
145
+ """
146
+ Usage:
147
+
148
+ - Checks the health of the CAVE environment (Docker, Git, Pipx)
149
+ """
150
+ print_section("CAVE Doctor")
151
+
152
+ results = check_all(exit_on_fail=False)
153
+
154
+ all_pass = True
155
+ for name, (success, message) in results.items():
156
+ if success:
157
+ step_done(f"{name}: {message}")
158
+ else:
159
+ step_fail(f"{name}: {message}")
160
+ all_pass = False
161
+
162
+ print_section("Status")
163
+ if all_pass:
164
+ logger.success("Your environment is healthy and ready to CAVE!")
165
+ else:
166
+ logger.error(
167
+ "Some issues were found in your environment. "
168
+ "Please follow the remediation steps above."
169
+ )
170
+ sys.exit(1)
@@ -0,0 +1,36 @@
1
+ import argparse
2
+
3
+ from cave_cli.utils.display import step_done, step_start
4
+ from cave_cli.utils.docker import get_running_apps, remove_containers
5
+ from cave_cli.utils.logger import logger
6
+ from cave_cli.utils.validate import get_app
7
+
8
+
9
+ def kill(args: argparse.Namespace) -> None:
10
+ """
11
+ Usage:
12
+
13
+ - Stops Docker containers for CAVE apps
14
+ """
15
+ kill_all = getattr(args, "all", False)
16
+ app_name = getattr(args, "name", None)
17
+
18
+ running_apps = get_running_apps()
19
+
20
+ if kill_all:
21
+ if not running_apps:
22
+ logger.info("No CAVE apps are currently running.")
23
+ return
24
+ for name in running_apps:
25
+ remove_containers(name)
26
+ elif app_name:
27
+ if app_name not in running_apps:
28
+ logger.error(f"App '{app_name}' is not running.")
29
+ return
30
+ remove_containers(app_name)
31
+ else:
32
+ _, app_name = get_app()
33
+ if app_name not in running_apps:
34
+ logger.info(f"App '{app_name}' is already stopped.")
35
+ return
36
+ remove_containers(app_name)
@@ -1,5 +1,6 @@
1
1
  import argparse
2
2
 
3
+ from cave_cli.utils.display import print_key_value, print_section
3
4
  from cave_cli.utils.docker import (
4
5
  get_container_env,
5
6
  get_container_host_port,
@@ -18,20 +19,25 @@ def list_cmd(args: argparse.Namespace) -> None:
18
19
  show_all = getattr(args, "all", False)
19
20
 
20
21
  if show_all:
21
- logger.header("CAVE App Containers (All):")
22
+ print_section("CAVE App Containers (All)")
22
23
  for suffix in ("_django", "_db_host", "_redis_host", "_nginx_host"):
23
24
  for name in get_all_containers(suffix):
24
- logger.info(name)
25
+ print(f" ● {name}")
25
26
  else:
26
- logger.header("CAVE Apps (Running):")
27
27
  apps = get_running_apps()
28
+ if not apps:
29
+ logger.info("No CAVE apps are currently running.")
30
+ return
31
+
32
+ print_section("CAVE Apps (Running)")
28
33
  for app_name in apps:
29
34
  nginx_containers = get_all_containers("_nginx_host")
30
35
  nginx_name = f"{app_name}_nginx_host"
31
36
  if nginx_name in nginx_containers:
32
37
  ip = get_container_env(nginx_name, "CAVE_IP")
33
38
  port = get_container_env(nginx_name, "CAVE_PORT")
34
- logger.info(f"{app_name} (https://{ip}:{port})")
39
+ print_key_value(app_name, f"https://{ip}:{port}")
35
40
  else:
36
41
  port = get_container_host_port(f"{app_name}_django")
37
- logger.info(f"{app_name} (http://localhost:{port})")
42
+ print_key_value(app_name, f"http://localhost:{port}")
43
+ print("")