cave-cli 3.5.0__py3-none-any.whl

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.
@@ -0,0 +1,129 @@
1
+ import subprocess
2
+ import sys
3
+
4
+ from cave_cli.utils.logger import logger
5
+
6
+
7
+ def run(
8
+ args: list[str],
9
+ capture: bool = True,
10
+ check: bool = False,
11
+ cwd: str | None = None,
12
+ inherit_io: bool = False,
13
+ ) -> subprocess.CompletedProcess:
14
+ """
15
+ Usage:
16
+
17
+ - Runs a subprocess command with standardized handling
18
+
19
+ Requires:
20
+
21
+ - ``args``:
22
+ - Type: list[str]
23
+ - What: The command and arguments to run
24
+
25
+ Optional:
26
+
27
+ - ``capture``:
28
+ - Type: bool
29
+ - What: Whether to capture stdout/stderr
30
+ - Default: True
31
+
32
+ - ``check``:
33
+ - Type: bool
34
+ - What: Whether to raise on non-zero exit
35
+ - Default: False
36
+
37
+ - ``cwd``:
38
+ - Type: str | None
39
+ - What: Working directory for the command
40
+ - Default: None
41
+
42
+ - ``inherit_io``:
43
+ - Type: bool
44
+ - What: Whether to pass through stdin/stdout/stderr for
45
+ interactive processes
46
+ - Default: False
47
+
48
+ Returns:
49
+
50
+ - ``result``:
51
+ - Type: subprocess.CompletedProcess
52
+ - What: The completed process result
53
+ """
54
+ kwargs: dict = {"cwd": cwd}
55
+ if inherit_io:
56
+ kwargs["stdin"] = sys.stdin
57
+ kwargs["stdout"] = sys.stdout
58
+ kwargs["stderr"] = sys.stderr
59
+ elif capture:
60
+ kwargs["stdout"] = subprocess.PIPE
61
+ kwargs["stderr"] = subprocess.PIPE
62
+ kwargs["text"] = True
63
+ return subprocess.run(args, check=check, **kwargs)
64
+
65
+
66
+ def run_and_log(
67
+ args: list[str],
68
+ level: str = "DEBUG",
69
+ cwd: str | None = None,
70
+ ) -> subprocess.CompletedProcess:
71
+ """
72
+ Usage:
73
+
74
+ - Runs a subprocess and pipes each output line through the logger
75
+
76
+ Requires:
77
+
78
+ - ``args``:
79
+ - Type: list[str]
80
+ - What: The command and arguments to run
81
+
82
+ Optional:
83
+
84
+ - ``level``:
85
+ - Type: str
86
+ - What: Log level for output lines
87
+ - Default: "DEBUG"
88
+
89
+ - ``cwd``:
90
+ - Type: str | None
91
+ - What: Working directory for the command
92
+ - Default: None
93
+
94
+ Returns:
95
+
96
+ - ``result``:
97
+ - Type: subprocess.CompletedProcess
98
+ - What: The completed process result
99
+ """
100
+ result = run(args, cwd=cwd, capture=True)
101
+ log_fn = getattr(logger, level.lower(), logger.debug)
102
+ if result.stdout:
103
+ for line in result.stdout.strip().splitlines():
104
+ log_fn(line)
105
+ if result.stderr:
106
+ for line in result.stderr.strip().splitlines():
107
+ log_fn(line)
108
+ return result
109
+
110
+
111
+ def version_tuple(v: str) -> tuple[int, ...]:
112
+ """
113
+ Usage:
114
+
115
+ - Parses a version string into a comparable tuple of integers
116
+
117
+ Requires:
118
+
119
+ - ``v``:
120
+ - Type: str
121
+ - What: A version string like "23.0.6"
122
+
123
+ Returns:
124
+
125
+ - ``parts``:
126
+ - Type: tuple[int, ...]
127
+ - What: A tuple of integers for comparison
128
+ """
129
+ return tuple(int(x) for x in v.split(".") if x.isdigit())
cave_cli/utils/sync.py ADDED
@@ -0,0 +1,89 @@
1
+ import fnmatch
2
+ import os
3
+ import shutil
4
+ from pathlib import Path
5
+
6
+ from cave_cli.utils.logger import logger
7
+
8
+
9
+ def sync_files(
10
+ source: str,
11
+ dest: str,
12
+ includes: list[str] | None = None,
13
+ excludes: list[str] | None = None,
14
+ ) -> None:
15
+ """
16
+ Usage:
17
+
18
+ - Syncs files from a source directory to a destination directory
19
+
20
+ Requires:
21
+
22
+ - ``source``:
23
+ - Type: str
24
+ - What: The source directory to copy from
25
+
26
+ - ``dest``:
27
+ - Type: str
28
+ - What: The destination directory to copy to
29
+
30
+ Optional:
31
+
32
+ - ``includes``:
33
+ - Type: list[str] | None
34
+ - What: Patterns for files that should always be included
35
+ (overrides excludes)
36
+ - Default: None
37
+
38
+ - ``excludes``:
39
+ - Type: list[str] | None
40
+ - What: Patterns for files that should be excluded
41
+ - Default: None
42
+
43
+ Notes:
44
+
45
+ - Always excludes ``.git``
46
+ - Include patterns override exclude patterns (matching rsync semantics)
47
+ - Uses ``shutil.copytree`` with ``dirs_exist_ok=True`` for merge behavior
48
+ """
49
+ clean_includes = [strip_quotes(p) for p in (includes or [])]
50
+ clean_excludes = [strip_quotes(p) for p in (excludes or [])]
51
+ clean_excludes.append(".git")
52
+
53
+ def ignore_fn(directory: str, contents: list[str]) -> set[str]:
54
+ rel_dir = os.path.relpath(directory, source)
55
+ ignored: set[str] = set()
56
+ for name in contents:
57
+ if rel_dir == ".":
58
+ rel_path = name
59
+ else:
60
+ rel_path = os.path.join(rel_dir, name)
61
+ if matches_any(rel_path, name, clean_includes):
62
+ continue
63
+ if matches_any(rel_path, name, clean_excludes):
64
+ ignored.add(name)
65
+ return ignored
66
+
67
+ shutil.copytree(
68
+ source, dest, ignore=ignore_fn, dirs_exist_ok=True
69
+ )
70
+
71
+
72
+ def strip_quotes(pattern: str) -> str:
73
+ pattern = pattern.strip()
74
+ if (pattern.startswith("'") and pattern.endswith("'")) or (
75
+ pattern.startswith('"') and pattern.endswith('"')
76
+ ):
77
+ return pattern[1:-1]
78
+ return pattern
79
+
80
+
81
+ def matches_any(
82
+ rel_path: str, name: str, patterns: list[str]
83
+ ) -> bool:
84
+ for pattern in patterns:
85
+ if fnmatch.fnmatch(name, pattern):
86
+ return True
87
+ if fnmatch.fnmatch(rel_path, pattern):
88
+ return True
89
+ return False
@@ -0,0 +1,218 @@
1
+ import os
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ from cave_cli.utils.constants import (
6
+ CHAR_LINE,
7
+ CURRENT_ENV_VARIABLES,
8
+ RETIRED_ENV_VARIABLES,
9
+ INVALID_NAME_END_RE,
10
+ INVALID_NAME_HYPHEN_UNDER_RE,
11
+ INVALID_NAME_START_RE,
12
+ INVALID_NAME_UNDER_HYPHEN_RE,
13
+ VALID_NAME_RE,
14
+ )
15
+ from cave_cli.utils.logger import logger
16
+
17
+
18
+ def validate_app_name(name: str) -> str | None:
19
+ """
20
+ Usage:
21
+
22
+ - Validates a CAVE app name against naming rules
23
+
24
+ Requires:
25
+
26
+ - ``name``:
27
+ - Type: str
28
+ - What: The app name to validate
29
+
30
+ Returns:
31
+
32
+ - ``error``:
33
+ - Type: str | None
34
+ - What: An error message if invalid, or None if valid
35
+ """
36
+ if len(name) < 2 or len(name) > 255:
37
+ return "The app name needs to be two to 255 characters"
38
+ if not VALID_NAME_RE.match(name):
39
+ return (
40
+ "The app name can only contain lowercase letters, "
41
+ "numbers, hyphens (-), and underscores (_)"
42
+ )
43
+ if INVALID_NAME_START_RE.match(name):
44
+ return (
45
+ "The app name cannot start with a hyphen (-) "
46
+ "or an underscore (_)"
47
+ )
48
+ if INVALID_NAME_END_RE.match(name):
49
+ return (
50
+ "The app name cannot end with a hyphen (-) "
51
+ "or an underscore (_)"
52
+ )
53
+ if INVALID_NAME_HYPHEN_UNDER_RE.search(name):
54
+ return (
55
+ "The app name cannot contain a hyphen (-) "
56
+ "followed by an underscore (_)"
57
+ )
58
+ if INVALID_NAME_UNDER_HYPHEN_RE.search(name):
59
+ return (
60
+ "The app name cannot contain an underscore (_) "
61
+ "followed by a hyphen (-)"
62
+ )
63
+ return None
64
+
65
+
66
+ def validate_app_dir(path: str) -> list[str]:
67
+ """
68
+ Usage:
69
+
70
+ - Checks if a directory is a valid CAVE app directory
71
+
72
+ Requires:
73
+
74
+ - ``path``:
75
+ - Type: str
76
+ - What: The path to validate
77
+
78
+ Returns:
79
+
80
+ - ``errors``:
81
+ - Type: list[str]
82
+ - What: A list of error messages. Empty if the directory is valid.
83
+ """
84
+ p = Path(path)
85
+ errors: list[str] = []
86
+
87
+ if not (p / "manage.py").is_file() or not (p / "cave_core").is_dir():
88
+ return ["Not a CAVE app directory"]
89
+
90
+ for folder in ("cave_api", "cave_app", "cave_core"):
91
+ if not (p / folder).is_dir():
92
+ errors.append(
93
+ f"The folder '{folder}' is missing "
94
+ "in the root project directory."
95
+ )
96
+
97
+ for file in (".env", "manage.py", "requirements.txt", "Dockerfile"):
98
+ if not (p / file).is_file():
99
+ errors.append(
100
+ f"The file '{file}' is missing "
101
+ "in the root project directory."
102
+ )
103
+
104
+ env_path = p / ".env"
105
+ if env_path.is_file():
106
+ from cave_cli.utils.env import parse_env
107
+
108
+ env_vars = parse_env(str(env_path))
109
+ for var in CURRENT_ENV_VARIABLES:
110
+ if var not in env_vars:
111
+ errors.append(
112
+ f"The env variable '{var}' is missing "
113
+ "from the '.env' file."
114
+ )
115
+ for var in RETIRED_ENV_VARIABLES:
116
+ if var in env_vars:
117
+ errors.append(
118
+ f"The env variable '{var}' is retired and "
119
+ "should be removed from the '.env' file."
120
+ )
121
+
122
+ if not (p / "Dockerfile").is_file():
123
+ errors.append("No Dockerfile found in current directory.")
124
+
125
+ return errors
126
+
127
+
128
+ def find_app_dir(start: str | None = None) -> str:
129
+ """
130
+ Usage:
131
+
132
+ - Walks up the directory tree to find a valid CAVE app directory
133
+
134
+ Optional:
135
+
136
+ - ``start``:
137
+ - Type: str | None
138
+ - What: The starting directory to search from
139
+ - Default: None (uses current working directory)
140
+
141
+ Returns:
142
+
143
+ - ``path``:
144
+ - Type: str
145
+ - What: The absolute path to the CAVE app directory
146
+ """
147
+ path = Path(start or os.getcwd()).resolve()
148
+ while True:
149
+ errors = validate_app_dir(str(path))
150
+ if not errors:
151
+ return str(path)
152
+ parent = path.parent
153
+ if parent == path:
154
+ for err in errors:
155
+ logger.error(err)
156
+ logger.error("Ensure you are in a valid CAVE app directory")
157
+ sys.exit(1)
158
+ path = parent
159
+
160
+
161
+ def get_app(start: str | None = None) -> tuple[str, str]:
162
+ """
163
+ Usage:
164
+
165
+ - Finds the CAVE app directory and returns its path and name
166
+
167
+ Optional:
168
+
169
+ - ``start``:
170
+ - Type: str | None
171
+ - What: The starting directory to search from
172
+ - Default: None (uses current working directory)
173
+
174
+ Returns:
175
+
176
+ - ``app_dir``:
177
+ - Type: str
178
+ - What: The absolute path to the CAVE app directory
179
+
180
+ - ``app_name``:
181
+ - Type: str
182
+ - What: The base name of the app directory
183
+ """
184
+ app_dir = find_app_dir(start)
185
+ app_name = Path(app_dir).resolve().name
186
+ return app_dir, app_name
187
+
188
+
189
+ def confirm_action(message: str, auto_yes: bool = False) -> None:
190
+ """
191
+ Usage:
192
+
193
+ - Prompts the user for confirmation before proceeding
194
+
195
+ Requires:
196
+
197
+ - ``message``:
198
+ - Type: str
199
+ - What: The action description to display
200
+
201
+ Optional:
202
+
203
+ - ``auto_yes``:
204
+ - Type: bool
205
+ - What: If True, bypasses the prompt and continues
206
+ - Default: False
207
+ """
208
+ if auto_yes:
209
+ return
210
+ try:
211
+ response = input(f"{message}. Would you like to continue? [y/N] ")
212
+ except (EOFError, KeyboardInterrupt):
213
+ print()
214
+ logger.error("Operation canceled.")
215
+ sys.exit(1)
216
+ if response.strip().lower() not in ("y", "yes"):
217
+ logger.error("Operation canceled.")
218
+ sys.exit(1)
@@ -0,0 +1,149 @@
1
+ Metadata-Version: 2.4
2
+ Name: cave_cli
3
+ Version: 3.5.0
4
+ Summary: CLI for creating and managing Docker-based CAVE web applications.
5
+ Author-email: MIT-CAVE <cave-contact@mit.edu>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/MIT-CAVE/cave_cli
8
+ Project-URL: Bug Tracker, https://github.com/MIT-CAVE/cave_cli/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.11
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ License-File: NOTICE.md
16
+ Dynamic: license-file
17
+
18
+ # CAVE CLI
19
+
20
+ A cross-platform CLI for creating and managing Docker-based CAVE web applications.
21
+
22
+ Developed by [MIT-CAVE](https://cave.mit.edu/) (Center for Transportation & Logistics). Licensed under Apache 2.0.
23
+
24
+ ## Prerequisites
25
+
26
+ - [Python](https://www.python.org/downloads/) 3.11+
27
+ - [Docker](https://docs.docker.com/get-docker/) 23.0.6+
28
+ - [Git](https://git-scm.com/)
29
+
30
+ <details>
31
+ <summary>Ubuntu</summary>
32
+
33
+ ```sh
34
+ # Install Docker
35
+ curl -fsSL https://get.docker.com -o get-docker.sh
36
+ sudo sh ./get-docker.sh
37
+ # Add the current user to the docker group
38
+ dockerd-rootless-setuptool.sh install
39
+ # Make sure it works outside of sudo
40
+ docker run hello-world
41
+ ```
42
+
43
+ </details>
44
+ <details>
45
+ <summary>macOS</summary>
46
+
47
+ - Install Docker Desktop: https://docs.docker.com/docker-for-mac/install/
48
+
49
+ </details>
50
+ <details>
51
+ <summary>Windows</summary>
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
56
+
57
+ </details>
58
+
59
+ ## Installation
60
+
61
+ ```sh
62
+ pip install git+https://github.com/MIT-CAVE/cave_cli.git
63
+ ```
64
+
65
+ Or with [pipx](https://pipx.pypa.io/) (recommended for CLI tools):
66
+
67
+ ```sh
68
+ pipx install git+https://github.com/MIT-CAVE/cave_cli.git
69
+ ```
70
+
71
+ Verify the installation:
72
+
73
+ ```sh
74
+ cave --version
75
+ ```
76
+
77
+ ## Quick Start
78
+
79
+ ```sh
80
+ cave create my_app
81
+ cd my_app
82
+ cave run
83
+ # Open http://localhost:8000/ in your browser
84
+ ```
85
+
86
+ ## CLI Commands
87
+
88
+ ```sh
89
+ cave --help
90
+ ```
91
+
92
+ ### Core Commands
93
+
94
+ | Command | Description |
95
+ |---|---|
96
+ | `cave create <name>` | Create a new CAVE app from the template repository |
97
+ | `cave run` | Build Docker image and run the app |
98
+
99
+ ### Peripheral Commands
100
+
101
+ | Command | Description |
102
+ |---|---|
103
+ | `cave reset` | Remove containers/volumes and rebuild from scratch |
104
+ | `cave upgrade` | Upgrade app files from the upstream template |
105
+ | `cave sync --url <url>` | Merge files from another repository into the app |
106
+ | `cave test` | Run tests in `cave_api/tests/` |
107
+ | `cave prettify` | Format code with autoflake and black |
108
+ | `cave purge <path>` | Remove an app and all its Docker resources |
109
+
110
+ ### Utility Commands
111
+
112
+ | Command | Description |
113
+ |---|---|
114
+ | `cave list` | List running CAVE apps |
115
+ | `cave kill` | Stop Docker containers for an app |
116
+ | `cave list-versions` | List available CAVE app versions |
117
+ | `cave update` | Update the CAVE CLI itself |
118
+ | `cave uninstall` | Remove the CAVE CLI |
119
+ | `cave version` | Print version information |
120
+
121
+ ### Global Flags
122
+
123
+ | Flag | Description |
124
+ |---|---|
125
+ | `-v`, `--verbose` | Enable verbose (DEBUG) logging output |
126
+ | `--loglevel LEVEL` | Set log level: DEBUG, INFO, WARN, ERROR, SILENT |
127
+ | `-y`, `--yes` | Automatically answer confirmation prompts with yes |
128
+
129
+ ## Updating
130
+
131
+ ```sh
132
+ pip install --upgrade git+https://github.com/MIT-CAVE/cave_cli.git
133
+ ```
134
+
135
+ Or:
136
+
137
+ ```sh
138
+ cave update
139
+ ```
140
+
141
+ ## License Notice
142
+
143
+ Copyright 2023 Massachusetts Institute of Technology (MIT), Center for Transportation & Logistics (CTL)
144
+
145
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
146
+
147
+ http://www.apache.org/licenses/LICENSE-2.0
148
+
149
+ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
@@ -0,0 +1,35 @@
1
+ cave_cli/__init__.py,sha256=87INpPcp1F7_PqWBWVyWIJMFP4m5wqP3zwTouzbVx68,74
2
+ cave_cli/cli.py,sha256=4rBrxNCWfLIZsu-DzxKL-2XWU8UCEXz3x59W1mUKW2w,14106
3
+ cave_cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ cave_cli/commands/create.py,sha256=5RrZd8GGQOJiajNlTYnUYl68B40Pu7XTm7p3u8Rvhqo,5192
5
+ cave_cli/commands/kill.py,sha256=WYHL5DojY0hhYiV2mSEpEXsW6z9k2tmcnqp41UuksKE,603
6
+ cave_cli/commands/list_cmd.py,sha256=fpeDAwhejC5ipwl7mxNQWbAr18YqF8ZjBXps9Ct8yjw,1236
7
+ cave_cli/commands/list_versions.py,sha256=NpoqTJ7LMKA9T77gtl5SP1SjtLSkwr4S3RbO1Dln3q0,2649
8
+ cave_cli/commands/prettify.py,sha256=eQb11E9jAfZT8E_anQ3bzGsoKAp-Gpn3llFTxJFFKOA,704
9
+ cave_cli/commands/purge.py,sha256=EkSg3sMJDawJGtQXzWIlOgArkcQk19UhVsHjY_7RsCA,1915
10
+ cave_cli/commands/reset.py,sha256=bVGBouEjLCsnc4wa2BN2HdCtTXDDd05sDNpekWKSwbM,1239
11
+ cave_cli/commands/run.py,sha256=gaXqPXw6VQrLY7sivtNAIBdCSmG4BgamnIVFcNEldBo,5669
12
+ cave_cli/commands/sync_cmd.py,sha256=dZAED2fOBFIgXxt6itw6gTZaf6F4ABHyQ5AjZ3aStt8,2163
13
+ cave_cli/commands/test.py,sha256=SLbpFiYpKl3_7wzL_NfLMSl7LxJW-nqjoAUOgvEWmrk,978
14
+ cave_cli/commands/uninstall.py,sha256=v34VoFo7tYphUYciloYGMgRLSNGJtkGKE1yYLE5RUno,971
15
+ cave_cli/commands/update.py,sha256=o_HB4d3YmUjRGahvKSokqmQvMBdGWehNksX6-IRCbhM,991
16
+ cave_cli/commands/upgrade.py,sha256=fLeEhWjuCeFFPXafb4vAG_9NN6IdVLGwRXJf25DELLQ,2199
17
+ cave_cli/commands/version.py,sha256=eIvBERe1_MaN7DwDyeOIhnUD0rOfq4Z8W2Z5onqHQVE,1924
18
+ cave_cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ cave_cli/utils/cache.py,sha256=lutofWOa9cLSdDkVnSRm_PSzarxN_GosZZN2yunSt_g,6068
20
+ cave_cli/utils/constants.py,sha256=IPgGvBj2BVyiXrFAWfIKPqI6Q_jhAhElqjAMmfiA4C8,1123
21
+ cave_cli/utils/docker.py,sha256=Fj3fRgHOVqkXZP2gmsShmXJ1f4bGX-ypl1POww6EIs4,12553
22
+ cave_cli/utils/env.py,sha256=nlKKLOKtetviMLfpqkdhd7I_CiMU601lh2mxW6DC5mM,7154
23
+ cave_cli/utils/git.py,sha256=Dx8IwnLQ5yA_n_En13nP8IBycV1LcguB1xjJQyUpOdo,4672
24
+ cave_cli/utils/logger.py,sha256=qELmwrAMCJ8xxd6dV4hfgGczg6XLnHN_1mAcBb09zZA,1849
25
+ cave_cli/utils/net.py,sha256=qpKKACgrgJLW_BdciFd7EVtBvcBRk8oCimLwk4JqGIg,1866
26
+ cave_cli/utils/subprocess.py,sha256=s6a1VvAmfNo3uQnuckW9CbVlCwYkf5070AXdzygZa28,2877
27
+ cave_cli/utils/sync.py,sha256=-1Fma2Y923zKjyI6g5FmuUHkEdRSc5Rk_K7mlbSHdtk,2348
28
+ cave_cli/utils/validate.py,sha256=5B8vPls699IJhDckSmEJ68Ew6F4AeJ8rayRP2gL97MQ,5656
29
+ cave_cli-3.5.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
30
+ cave_cli-3.5.0.dist-info/licenses/NOTICE.md,sha256=2qN436eW01DWcPaXriThoEXhGNqG8OHNrHWYsGd2t3Q,631
31
+ cave_cli-3.5.0.dist-info/METADATA,sha256=KHtpHl-BxKhNnECgIicrugyfxH7MT_UrQyKeZ124JXc,4092
32
+ cave_cli-3.5.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
33
+ cave_cli-3.5.0.dist-info/entry_points.txt,sha256=rxKeJ_S2t0gTfCyPYSAEYlWF1H7D-kjUer8DQP3fldY,43
34
+ cave_cli-3.5.0.dist-info/top_level.txt,sha256=CDnV_I6P0bNNl97Ao7S-2Dqe0K0TFs_1TcPZiQkEXzw,9
35
+ cave_cli-3.5.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ cave = cave_cli.cli:main