cave-cli 3.5.0__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 (45) hide show
  1. {cave_cli-3.5.0/cave_cli.egg-info → cave_cli-3.5.2}/PKG-INFO +44 -13
  2. {cave_cli-3.5.0 → cave_cli-3.5.2}/README.md +43 -12
  3. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli/cli.py +73 -20
  4. {cave_cli-3.5.0 → 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.0 → cave_cli-3.5.2}/cave_cli/commands/list_cmd.py +11 -5
  8. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli/commands/list_versions.py +16 -9
  9. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli/commands/prettify.py +2 -2
  10. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli/commands/purge.py +18 -5
  11. {cave_cli-3.5.0 → 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.0 → cave_cli-3.5.2}/cave_cli/commands/sync_cmd.py +12 -12
  14. {cave_cli-3.5.0 → 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.0 → cave_cli-3.5.2}/cave_cli/commands/uninstall.py +18 -5
  17. cave_cli-3.5.2/cave_cli/commands/update.py +57 -0
  18. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli/commands/upgrade.py +10 -5
  19. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli/commands/version.py +9 -7
  20. {cave_cli-3.5.0 → 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.0 → cave_cli-3.5.2}/cave_cli/utils/docker.py +175 -69
  23. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli/utils/git.py +0 -2
  24. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli/utils/logger.py +28 -6
  25. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli/utils/validate.py +2 -2
  26. {cave_cli-3.5.0 → cave_cli-3.5.2/cave_cli.egg-info}/PKG-INFO +44 -13
  27. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli.egg-info/SOURCES.txt +3 -0
  28. {cave_cli-3.5.0 → cave_cli-3.5.2}/pyproject.toml +2 -2
  29. cave_cli-3.5.0/cave_cli/commands/kill.py +0 -23
  30. cave_cli-3.5.0/cave_cli/commands/run.py +0 -189
  31. cave_cli-3.5.0/cave_cli/commands/update.py +0 -38
  32. {cave_cli-3.5.0 → cave_cli-3.5.2}/LICENSE +0 -0
  33. {cave_cli-3.5.0 → cave_cli-3.5.2}/NOTICE.md +0 -0
  34. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli/__init__.py +0 -0
  35. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli/commands/__init__.py +0 -0
  36. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli/utils/__init__.py +0 -0
  37. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli/utils/constants.py +0 -0
  38. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli/utils/env.py +0 -0
  39. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli/utils/net.py +0 -0
  40. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli/utils/subprocess.py +0 -0
  41. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli/utils/sync.py +0 -0
  42. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli.egg-info/dependency_links.txt +0 -0
  43. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli.egg-info/entry_points.txt +0 -0
  44. {cave_cli-3.5.0 → cave_cli-3.5.2}/cave_cli.egg-info/top_level.txt +0 -0
  45. {cave_cli-3.5.0 → 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.0
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,28 +50,49 @@ 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
 
59
57
  ## Installation
60
58
 
59
+ Install with [pipx](https://pipx.pypa.io/) (recommended — keeps the CLI isolated from your system Python):
60
+
61
61
  ```sh
62
- pip install git+https://github.com/MIT-CAVE/cave_cli.git
62
+ pipx install cave_cli
63
63
  ```
64
64
 
65
- Or with [pipx](https://pipx.pypa.io/) (recommended for CLI tools):
65
+ If you don't have pipx, install it first:
66
+
67
+ <details>
68
+ <summary>macOS</summary>
66
69
 
67
70
  ```sh
68
- pipx install git+https://github.com/MIT-CAVE/cave_cli.git
71
+ # With Homebrew (recommended)
72
+ brew install pipx
73
+ pipx ensurepath
74
+
75
+ # Or with pip
76
+ pip3 install --user pipx
77
+ pipx ensurepath
69
78
  ```
70
79
 
71
- Verify the installation:
80
+ </details>
81
+ <details>
82
+ <summary>Other Linux / Windows</summary>
72
83
 
73
84
  ```sh
74
- cave --version
85
+ python3 -m pip install --user pipx
86
+ pipx ensurepath
87
+ ```
88
+ </details>
89
+
90
+ For more options see the [pipx installation guide](https://pipx.pypa.io/stable/installation/).
91
+
92
+ Verify the installation and check your environment health:
93
+
94
+ ```sh
95
+ cave doctor
75
96
  ```
76
97
 
77
98
  ## Quick Start
@@ -94,7 +115,8 @@ cave --help
94
115
  | Command | Description |
95
116
  |---|---|
96
117
  | `cave create <name>` | Create a new CAVE app from the template repository |
97
- | `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 |
98
120
 
99
121
  ### Peripheral Commands
100
122
 
@@ -114,6 +136,7 @@ cave --help
114
136
  | `cave list` | List running CAVE apps |
115
137
  | `cave kill` | Stop Docker containers for an app |
116
138
  | `cave list-versions` | List available CAVE app versions |
139
+ | `cave theme <name>` | Set the CLI color theme (dark, light, solarized, monokai) |
117
140
  | `cave update` | Update the CAVE CLI itself |
118
141
  | `cave uninstall` | Remove the CAVE CLI |
119
142
  | `cave version` | Print version information |
@@ -126,16 +149,24 @@ cave --help
126
149
  | `--loglevel LEVEL` | Set log level: DEBUG, INFO, WARN, ERROR, SILENT |
127
150
  | `-y`, `--yes` | Automatically answer confirmation prompts with yes |
128
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
+
129
160
  ## Updating
130
161
 
131
162
  ```sh
132
- pip install --upgrade git+https://github.com/MIT-CAVE/cave_cli.git
163
+ cave update
133
164
  ```
134
165
 
135
- Or:
166
+ Or directly via pipx:
136
167
 
137
168
  ```sh
138
- cave update
169
+ pipx upgrade cave_cli
139
170
  ```
140
171
 
141
172
  ## License Notice
@@ -33,28 +33,49 @@ 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
 
42
40
  ## Installation
43
41
 
42
+ Install with [pipx](https://pipx.pypa.io/) (recommended — keeps the CLI isolated from your system Python):
43
+
44
44
  ```sh
45
- pip install git+https://github.com/MIT-CAVE/cave_cli.git
45
+ pipx install cave_cli
46
46
  ```
47
47
 
48
- Or with [pipx](https://pipx.pypa.io/) (recommended for CLI tools):
48
+ If you don't have pipx, install it first:
49
+
50
+ <details>
51
+ <summary>macOS</summary>
49
52
 
50
53
  ```sh
51
- pipx install git+https://github.com/MIT-CAVE/cave_cli.git
54
+ # With Homebrew (recommended)
55
+ brew install pipx
56
+ pipx ensurepath
57
+
58
+ # Or with pip
59
+ pip3 install --user pipx
60
+ pipx ensurepath
52
61
  ```
53
62
 
54
- Verify the installation:
63
+ </details>
64
+ <details>
65
+ <summary>Other Linux / Windows</summary>
55
66
 
56
67
  ```sh
57
- cave --version
68
+ python3 -m pip install --user pipx
69
+ pipx ensurepath
70
+ ```
71
+ </details>
72
+
73
+ For more options see the [pipx installation guide](https://pipx.pypa.io/stable/installation/).
74
+
75
+ Verify the installation and check your environment health:
76
+
77
+ ```sh
78
+ cave doctor
58
79
  ```
59
80
 
60
81
  ## Quick Start
@@ -77,7 +98,8 @@ cave --help
77
98
  | Command | Description |
78
99
  |---|---|
79
100
  | `cave create <name>` | Create a new CAVE app from the template repository |
80
- | `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 |
81
103
 
82
104
  ### Peripheral Commands
83
105
 
@@ -97,6 +119,7 @@ cave --help
97
119
  | `cave list` | List running CAVE apps |
98
120
  | `cave kill` | Stop Docker containers for an app |
99
121
  | `cave list-versions` | List available CAVE app versions |
122
+ | `cave theme <name>` | Set the CLI color theme (dark, light, solarized, monokai) |
100
123
  | `cave update` | Update the CAVE CLI itself |
101
124
  | `cave uninstall` | Remove the CAVE CLI |
102
125
  | `cave version` | Print version information |
@@ -109,16 +132,24 @@ cave --help
109
132
  | `--loglevel LEVEL` | Set log level: DEBUG, INFO, WARN, ERROR, SILENT |
110
133
  | `-y`, `--yes` | Automatically answer confirmation prompts with yes |
111
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
+
112
143
  ## Updating
113
144
 
114
145
  ```sh
115
- pip install --upgrade git+https://github.com/MIT-CAVE/cave_cli.git
146
+ cave update
116
147
  ```
117
148
 
118
- Or:
149
+ Or directly via pipx:
119
150
 
120
151
  ```sh
121
- cave update
152
+ pipx upgrade cave_cli
122
153
  ```
123
154
 
124
155
  ## License Notice
@@ -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)