salt-api-cli 1.0.0__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.
@@ -0,0 +1 @@
1
+ include README.md
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.4
2
+ Name: salt-api-cli
3
+ Version: 1.0.0
4
+ Summary: CLI to access salt-api
5
+ Author-email: Pradish Bijukchhe <pradish@sandbox.com.np>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/sandbox-pokhara/saltapi-cli
8
+ Project-URL: Issues, https://github.com/sandbox-pokhara/saltapi-cli/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Requires-Python: >=3.11
11
+ Description-Content-Type: text/markdown
12
+ Provides-Extra: pre-commit
13
+ Requires-Dist: pre-commit; extra == "pre-commit"
14
+
15
+ # salt-api-cli
16
+
17
+ Thin, stdlib-only Python CLI for [salt-api](https://docs.saltproject.io/en/latest/ref/netapi/all/salt.netapi.rest_cherrypy.html).
18
+
19
+ Logs in once with PAM credentials, caches the token in
20
+ `~/.cache/salt-api-cli/token.json`, then invokes salt-api's `local`,
21
+ `runner`, and `wheel` clients over HTTPS. The token auto-refreshes
22
+ when it expires.
23
+
24
+ ## Installation
25
+
26
+ ```
27
+ pip install salt-api-cli
28
+ ```
29
+
30
+ ## Configuration
31
+
32
+ Configuration is resolved in this order (later sources override earlier):
33
+
34
+ 1. `~/.saltapiclirc` — INI file, `[salt-api-cli]` section
35
+ 2. Environment variables — `SALT_API_URL`, `SALT_API_USER`, `SALT_API_PASS`, `SALT_API_INSECURE`
36
+ 3. Command-line flags — `--url`, `--user`, `--password`, `--insecure`
37
+
38
+ Example `~/.saltapiclirc`:
39
+
40
+ ```ini
41
+ [salt-api-cli]
42
+ url = https://salt.example.com
43
+ user = salt_api
44
+ password = secret
45
+ insecure = false
46
+ ```
47
+
48
+ `SALT_API_INSECURE=1` (or `insecure = true` in the config) skips TLS
49
+ certificate verification.
50
+
51
+ ## Usage
52
+
53
+ ```
54
+ # Local client — fan out to minions
55
+ salt-api-cli local '*' test.ping
56
+ salt-api-cli local 'bml*' cmd.run 'whoami'
57
+ salt-api-cli local 'bml1' cmd.run 'Get-Date' shell=powershell
58
+
59
+ # Runner client (master-side: manage.status, jobs.list_jobs, ...)
60
+ salt-api-cli runner manage.status
61
+ salt-api-cli runner jobs.list_jobs
62
+
63
+ # Wheel client (master-side, low-level)
64
+ salt-api-cli wheel key.list_all
65
+
66
+ # Key management (high-level wrapper around the wheel client)
67
+ salt-api-cli keys list
68
+ salt-api-cli keys accept <id-or-glob>
69
+ salt-api-cli keys accept-all
70
+ salt-api-cli keys reject <id-or-glob>
71
+ salt-api-cli keys delete <id-or-glob>
72
+ ```
73
+
74
+ Any `key=value` argument is parsed as a kwarg to the salt function;
75
+ anything else is positional.
76
+
77
+ You can also invoke the CLI as a module: `python -m salt_api_cli ...`.
78
+
79
+ ## License
80
+
81
+ This project is licensed under the terms of the MIT license.
@@ -0,0 +1,67 @@
1
+ # salt-api-cli
2
+
3
+ Thin, stdlib-only Python CLI for [salt-api](https://docs.saltproject.io/en/latest/ref/netapi/all/salt.netapi.rest_cherrypy.html).
4
+
5
+ Logs in once with PAM credentials, caches the token in
6
+ `~/.cache/salt-api-cli/token.json`, then invokes salt-api's `local`,
7
+ `runner`, and `wheel` clients over HTTPS. The token auto-refreshes
8
+ when it expires.
9
+
10
+ ## Installation
11
+
12
+ ```
13
+ pip install salt-api-cli
14
+ ```
15
+
16
+ ## Configuration
17
+
18
+ Configuration is resolved in this order (later sources override earlier):
19
+
20
+ 1. `~/.saltapiclirc` — INI file, `[salt-api-cli]` section
21
+ 2. Environment variables — `SALT_API_URL`, `SALT_API_USER`, `SALT_API_PASS`, `SALT_API_INSECURE`
22
+ 3. Command-line flags — `--url`, `--user`, `--password`, `--insecure`
23
+
24
+ Example `~/.saltapiclirc`:
25
+
26
+ ```ini
27
+ [salt-api-cli]
28
+ url = https://salt.example.com
29
+ user = salt_api
30
+ password = secret
31
+ insecure = false
32
+ ```
33
+
34
+ `SALT_API_INSECURE=1` (or `insecure = true` in the config) skips TLS
35
+ certificate verification.
36
+
37
+ ## Usage
38
+
39
+ ```
40
+ # Local client — fan out to minions
41
+ salt-api-cli local '*' test.ping
42
+ salt-api-cli local 'bml*' cmd.run 'whoami'
43
+ salt-api-cli local 'bml1' cmd.run 'Get-Date' shell=powershell
44
+
45
+ # Runner client (master-side: manage.status, jobs.list_jobs, ...)
46
+ salt-api-cli runner manage.status
47
+ salt-api-cli runner jobs.list_jobs
48
+
49
+ # Wheel client (master-side, low-level)
50
+ salt-api-cli wheel key.list_all
51
+
52
+ # Key management (high-level wrapper around the wheel client)
53
+ salt-api-cli keys list
54
+ salt-api-cli keys accept <id-or-glob>
55
+ salt-api-cli keys accept-all
56
+ salt-api-cli keys reject <id-or-glob>
57
+ salt-api-cli keys delete <id-or-glob>
58
+ ```
59
+
60
+ Any `key=value` argument is parsed as a kwarg to the salt function;
61
+ anything else is positional.
62
+
63
+ You can also invoke the CLI as a module: `python -m salt_api_cli ...`.
64
+
65
+ ## License
66
+
67
+ This project is licensed under the terms of the MIT license.
@@ -0,0 +1,43 @@
1
+ [build-system]
2
+ requires = ["setuptools>=70.0.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "salt-api-cli"
7
+ requires-python = ">=3.11"
8
+ authors = [{ name = "Pradish Bijukchhe", email = "pradish@sandbox.com.np" }]
9
+ description = "CLI to access salt-api"
10
+ readme = "README.md"
11
+ license = "MIT"
12
+ keywords = []
13
+ classifiers = ["Programming Language :: Python :: 3"]
14
+ dependencies = []
15
+ dynamic = ["version"]
16
+
17
+ [project.optional-dependencies]
18
+ pre-commit = ["pre-commit"]
19
+
20
+ [project.scripts]
21
+ salt-api-cli = "salt_api_cli.cli:main"
22
+
23
+ [project.urls]
24
+ Homepage = "https://github.com/sandbox-pokhara/saltapi-cli"
25
+ Issues = "https://github.com/sandbox-pokhara/saltapi-cli/issues"
26
+
27
+ [tool.setuptools]
28
+ include-package-data = true
29
+
30
+ [tool.setuptools.package-dir]
31
+ "salt_api_cli" = "salt_api_cli"
32
+
33
+ [tool.setuptools.dynamic]
34
+ version = { attr = "salt_api_cli.version.__version__" }
35
+
36
+ [tool.ruff.lint]
37
+ select = ["I"]
38
+
39
+ [tool.pyright]
40
+ venvPath = "."
41
+ venv = ".venv"
42
+ include = ["salt_api_cli"]
43
+ typeCheckingMode = "strict"
File without changes
@@ -0,0 +1,4 @@
1
+ from salt_api_cli.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,328 @@
1
+ """salt-api-cli — thin Python CLI for salt-api.
2
+
3
+ Stdlib-only. Logs in once with PAM creds, caches the token in
4
+ ~/.cache/salt-api-cli/token.json, then invokes the salt-api local/
5
+ runner/wheel clients over HTTPS. Token auto-refreshes when expired.
6
+
7
+ Configuration (later sources override earlier):
8
+ 1. ~/.saltapiclirc INI file, [salt-api-cli] section
9
+ 2. environment variables SALT_API_URL, SALT_API_USER,
10
+ SALT_API_PASS, SALT_API_INSECURE
11
+ 3. command-line flags --url, --user, --password,
12
+ --insecure
13
+
14
+ Any `key=value` argument to local/runner/wheel is parsed as a kwarg to
15
+ the salt function. Anything else is positional.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import argparse
21
+ import configparser
22
+ import json
23
+ import os
24
+ import ssl
25
+ import sys
26
+ import time
27
+ from dataclasses import dataclass
28
+ from pathlib import Path
29
+ from typing import Any
30
+ from urllib.error import HTTPError, URLError
31
+ from urllib.parse import urlencode
32
+ from urllib.request import Request, urlopen
33
+
34
+ CONFIG_FILE = Path.home() / ".saltapiclirc"
35
+ CONFIG_SECTION = "salt-api-cli"
36
+ TOKEN_FILE = Path.home() / ".cache" / "salt-api-cli" / "token.json"
37
+ USER_AGENT = "salt-api-cli/1.0 (Mozilla/5.0 compatible)"
38
+
39
+ # Wheel key.list_all groups minion IDs by acceptance state under these keys.
40
+ KEY_STATUS_LABELS = {
41
+ "minions": "Accepted",
42
+ "minions_pre": "Pending",
43
+ "minions_denied": "Denied",
44
+ "minions_rejected": "Rejected",
45
+ }
46
+
47
+
48
+ @dataclass
49
+ class Config:
50
+ url: str
51
+ user: str
52
+ password: str
53
+ insecure: bool
54
+
55
+
56
+ def _truthy(value: str) -> bool:
57
+ return value.strip().lower() in ("1", "true", "yes", "on")
58
+
59
+
60
+ def _load_config(args: argparse.Namespace) -> Config:
61
+ file_section: dict[str, str] = {}
62
+ if CONFIG_FILE.exists():
63
+ parser = configparser.ConfigParser()
64
+ parser.read(CONFIG_FILE)
65
+ if parser.has_section(CONFIG_SECTION):
66
+ file_section = dict(parser.items(CONFIG_SECTION))
67
+
68
+ url: str = args.url or os.environ.get("SALT_API_URL") or file_section.get("url", "")
69
+ user: str = (
70
+ args.user
71
+ or os.environ.get("SALT_API_USER")
72
+ or file_section.get("user", "salt_api")
73
+ )
74
+ password: str = (
75
+ args.password
76
+ or os.environ.get("SALT_API_PASS")
77
+ or file_section.get("password", "")
78
+ )
79
+ insecure: bool = (
80
+ args.insecure
81
+ or os.environ.get("SALT_API_INSECURE") == "1"
82
+ or _truthy(file_section.get("insecure", ""))
83
+ )
84
+
85
+ if not url:
86
+ sys.exit(
87
+ "salt-api URL not set (use --url, SALT_API_URL, or url= in ~/.saltapiclirc)"
88
+ )
89
+ if not password:
90
+ sys.exit(
91
+ "salt-api password not set "
92
+ "(use --password, SALT_API_PASS, or password= in ~/.saltapiclirc)"
93
+ )
94
+ return Config(
95
+ url=url.rstrip("/"),
96
+ user=user,
97
+ password=password,
98
+ insecure=insecure,
99
+ )
100
+
101
+
102
+ def _ssl_ctx(cfg: Config) -> ssl.SSLContext | None:
103
+ if not cfg.insecure:
104
+ return None
105
+ ctx = ssl.create_default_context()
106
+ ctx.check_hostname = False
107
+ ctx.verify_mode = ssl.CERT_NONE
108
+ return ctx
109
+
110
+
111
+ def _http(req: Request, cfg: Config) -> dict[str, Any]:
112
+ try:
113
+ with urlopen(req, context=_ssl_ctx(cfg), timeout=30) as resp:
114
+ data: Any = json.loads(resp.read())
115
+ return data
116
+ except HTTPError as e:
117
+ body = e.read().decode(errors="replace")
118
+ sys.exit(f"salt-api {e.code} {e.reason}: {body}")
119
+ except URLError as e:
120
+ sys.exit(f"salt-api unreachable: {e.reason}")
121
+
122
+
123
+ def _login(cfg: Config) -> dict[str, Any]:
124
+ body = urlencode(
125
+ {"username": cfg.user, "password": cfg.password, "eauth": "pam"}
126
+ ).encode()
127
+ req = Request(
128
+ f"{cfg.url}/login",
129
+ data=body,
130
+ headers={"Accept": "application/json", "User-Agent": USER_AGENT},
131
+ )
132
+ data = _http(req, cfg)
133
+ info: dict[str, Any] = data["return"][0]
134
+ if "token" not in info:
135
+ sys.exit(f"login failed: {info}")
136
+ return info
137
+
138
+
139
+ def _get_token(cfg: Config) -> str:
140
+ if TOKEN_FILE.exists():
141
+ try:
142
+ cached: dict[str, Any] = json.loads(TOKEN_FILE.read_text())
143
+ if cached.get("expire", 0) > time.time() + 60:
144
+ return str(cached["token"])
145
+ except (json.JSONDecodeError, OSError, AttributeError, TypeError):
146
+ pass
147
+ info = _login(cfg)
148
+ TOKEN_FILE.parent.mkdir(parents=True, exist_ok=True)
149
+ TOKEN_FILE.write_text(json.dumps(info))
150
+ try:
151
+ os.chmod(TOKEN_FILE, 0o600)
152
+ except OSError:
153
+ pass
154
+ return str(info["token"])
155
+
156
+
157
+ def _call(cfg: Config, client: str, **kwargs: Any) -> dict[str, Any]:
158
+ payload = [{"client": client, **kwargs}]
159
+ req = Request(
160
+ cfg.url,
161
+ data=json.dumps(payload).encode(),
162
+ headers={
163
+ "Accept": "application/json",
164
+ "Content-Type": "application/json",
165
+ "X-Auth-Token": _get_token(cfg),
166
+ "User-Agent": USER_AGENT,
167
+ },
168
+ )
169
+ return _http(req, cfg)
170
+
171
+
172
+ def _split_args(args: list[str]) -> tuple[list[str], dict[str, str]]:
173
+ """Split positional args from key=value kwargs."""
174
+ pos: list[str] = []
175
+ kw: dict[str, str] = {}
176
+ for a in args:
177
+ if "=" in a and not a.startswith("="):
178
+ k, v = a.split("=", 1)
179
+ if k.isidentifier():
180
+ kw[k] = v
181
+ continue
182
+ pos.append(a)
183
+ return pos, kw
184
+
185
+
186
+ def _print_local_result(result: dict[str, Any]) -> None:
187
+ """One row per minion. JSON-encode anything that isn't a scalar so
188
+ multi-value returns (e.g. cmd.run dicts) stay on one line."""
189
+ ret_list: Any = result.get("return")
190
+ if not ret_list:
191
+ print(json.dumps(result, indent=2))
192
+ return
193
+ ret: dict[str, Any] = ret_list[0]
194
+ if not ret:
195
+ print("(no minions responded)")
196
+ return
197
+ width = max(len(m) for m in ret)
198
+ for minion in sorted(ret):
199
+ val = ret[minion]
200
+ if isinstance(val, (str, int, float, bool)) or val is None:
201
+ print(f"{minion:<{width}} {val}")
202
+ else:
203
+ print(f"{minion:<{width}} {json.dumps(val)}")
204
+
205
+
206
+ def _run_local(cfg: Config, args: argparse.Namespace) -> None:
207
+ pos, kw = _split_args(list(args.args))
208
+ payload: dict[str, Any] = {"tgt": args.target, "fun": args.function, "arg": pos}
209
+ if kw:
210
+ payload["kwarg"] = kw
211
+ _print_local_result(_call(cfg, "local", **payload))
212
+
213
+
214
+ def _run_client(cfg: Config, client: str, args: argparse.Namespace) -> None:
215
+ pos, kw = _split_args(list(args.args))
216
+ payload: dict[str, Any] = {"fun": args.function, "arg": pos}
217
+ if kw:
218
+ payload["kwarg"] = kw
219
+ print(json.dumps(_call(cfg, client, **payload), indent=2))
220
+
221
+
222
+ def _run_keys(cfg: Config, args: argparse.Namespace) -> None:
223
+ action: str = args.action
224
+ if action == "list":
225
+ result = _call(cfg, "wheel", fun="key.list_all")
226
+ data: dict[str, Any] = result["return"][0]["data"]["return"]
227
+ for status_key, label in KEY_STATUS_LABELS.items():
228
+ keys: list[str] = data.get(status_key, [])
229
+ print(f"{label} ({len(keys)}):")
230
+ for k in keys:
231
+ print(f" {k}")
232
+ print()
233
+ return
234
+
235
+ fun_map = {
236
+ "accept": "key.accept",
237
+ "accept-all": "key.accept",
238
+ "reject": "key.reject",
239
+ "delete": "key.delete",
240
+ }
241
+ match: str = "*" if action == "accept-all" else args.match
242
+ result = _call(cfg, "wheel", fun=fun_map[action], match=match)
243
+ data = result["return"][0]["data"]
244
+ if not data.get("success"):
245
+ sys.exit(f"failed: {data}")
246
+ changed: dict[str, list[str]] = data.get("return", {})
247
+ if not changed:
248
+ print("(no keys changed)")
249
+ return
250
+ for status_key, ids in changed.items():
251
+ label = KEY_STATUS_LABELS.get(status_key, status_key)
252
+ print(f"{label}: {', '.join(ids) if ids else '(none)'}")
253
+
254
+
255
+ def _build_parser() -> argparse.ArgumentParser:
256
+ parser = argparse.ArgumentParser(
257
+ prog="salt-api-cli",
258
+ description="Thin Python CLI for salt-api.",
259
+ formatter_class=argparse.RawDescriptionHelpFormatter,
260
+ epilog=(
261
+ "examples:\n"
262
+ " salt-api-cli local '*' test.ping\n"
263
+ " salt-api-cli local 'bml*' cmd.run 'whoami'\n"
264
+ " salt-api-cli local 'bml1' cmd.run 'Get-Date' shell=powershell\n"
265
+ " salt-api-cli runner manage.status\n"
266
+ " salt-api-cli wheel key.list_all\n"
267
+ " salt-api-cli keys list\n"
268
+ " salt-api-cli keys accept '<id-or-glob>'\n"
269
+ " salt-api-cli keys accept-all\n"
270
+ ),
271
+ )
272
+ parser.add_argument("--url", help="salt-api base URL")
273
+ parser.add_argument("--user", help="PAM username")
274
+ parser.add_argument("--password", help="PAM password")
275
+ parser.add_argument(
276
+ "--insecure",
277
+ action="store_true",
278
+ help="skip TLS certificate verification",
279
+ )
280
+
281
+ sub = parser.add_subparsers(dest="command", required=True)
282
+
283
+ p_local = sub.add_parser("local", help="run a function on minions")
284
+ p_local.add_argument("target", help="minion target (id or glob)")
285
+ p_local.add_argument("function", help="salt function (e.g. test.ping)")
286
+ p_local.add_argument(
287
+ "args", nargs=argparse.REMAINDER, help="positional and key=value args"
288
+ )
289
+
290
+ p_runner = sub.add_parser("runner", help="invoke a master-side runner")
291
+ p_runner.add_argument("function")
292
+ p_runner.add_argument("args", nargs=argparse.REMAINDER)
293
+
294
+ p_wheel = sub.add_parser("wheel", help="invoke a master-side wheel function")
295
+ p_wheel.add_argument("function")
296
+ p_wheel.add_argument("args", nargs=argparse.REMAINDER)
297
+
298
+ p_keys = sub.add_parser("keys", help="manage minion keys")
299
+ keys_sub = p_keys.add_subparsers(dest="action", required=True)
300
+ keys_sub.add_parser("list", help="show keys grouped by status")
301
+ p_accept = keys_sub.add_parser("accept", help="accept a key by id or glob")
302
+ p_accept.add_argument("match")
303
+ keys_sub.add_parser("accept-all", help="accept every pending key")
304
+ p_reject = keys_sub.add_parser("reject", help="reject a key by id or glob")
305
+ p_reject.add_argument("match")
306
+ p_delete = keys_sub.add_parser("delete", help="delete a key by id or glob")
307
+ p_delete.add_argument("match")
308
+
309
+ return parser
310
+
311
+
312
+ def main() -> None:
313
+ parser = _build_parser()
314
+ args = parser.parse_args()
315
+ cfg = _load_config(args)
316
+
317
+ if args.command == "local":
318
+ _run_local(cfg, args)
319
+ elif args.command == "runner":
320
+ _run_client(cfg, "runner", args)
321
+ elif args.command == "wheel":
322
+ _run_client(cfg, "wheel", args)
323
+ elif args.command == "keys":
324
+ _run_keys(cfg, args)
325
+
326
+
327
+ if __name__ == "__main__":
328
+ main()
File without changes
@@ -0,0 +1 @@
1
+ __version__ = "1.0.0"
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.4
2
+ Name: salt-api-cli
3
+ Version: 1.0.0
4
+ Summary: CLI to access salt-api
5
+ Author-email: Pradish Bijukchhe <pradish@sandbox.com.np>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/sandbox-pokhara/saltapi-cli
8
+ Project-URL: Issues, https://github.com/sandbox-pokhara/saltapi-cli/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Requires-Python: >=3.11
11
+ Description-Content-Type: text/markdown
12
+ Provides-Extra: pre-commit
13
+ Requires-Dist: pre-commit; extra == "pre-commit"
14
+
15
+ # salt-api-cli
16
+
17
+ Thin, stdlib-only Python CLI for [salt-api](https://docs.saltproject.io/en/latest/ref/netapi/all/salt.netapi.rest_cherrypy.html).
18
+
19
+ Logs in once with PAM credentials, caches the token in
20
+ `~/.cache/salt-api-cli/token.json`, then invokes salt-api's `local`,
21
+ `runner`, and `wheel` clients over HTTPS. The token auto-refreshes
22
+ when it expires.
23
+
24
+ ## Installation
25
+
26
+ ```
27
+ pip install salt-api-cli
28
+ ```
29
+
30
+ ## Configuration
31
+
32
+ Configuration is resolved in this order (later sources override earlier):
33
+
34
+ 1. `~/.saltapiclirc` — INI file, `[salt-api-cli]` section
35
+ 2. Environment variables — `SALT_API_URL`, `SALT_API_USER`, `SALT_API_PASS`, `SALT_API_INSECURE`
36
+ 3. Command-line flags — `--url`, `--user`, `--password`, `--insecure`
37
+
38
+ Example `~/.saltapiclirc`:
39
+
40
+ ```ini
41
+ [salt-api-cli]
42
+ url = https://salt.example.com
43
+ user = salt_api
44
+ password = secret
45
+ insecure = false
46
+ ```
47
+
48
+ `SALT_API_INSECURE=1` (or `insecure = true` in the config) skips TLS
49
+ certificate verification.
50
+
51
+ ## Usage
52
+
53
+ ```
54
+ # Local client — fan out to minions
55
+ salt-api-cli local '*' test.ping
56
+ salt-api-cli local 'bml*' cmd.run 'whoami'
57
+ salt-api-cli local 'bml1' cmd.run 'Get-Date' shell=powershell
58
+
59
+ # Runner client (master-side: manage.status, jobs.list_jobs, ...)
60
+ salt-api-cli runner manage.status
61
+ salt-api-cli runner jobs.list_jobs
62
+
63
+ # Wheel client (master-side, low-level)
64
+ salt-api-cli wheel key.list_all
65
+
66
+ # Key management (high-level wrapper around the wheel client)
67
+ salt-api-cli keys list
68
+ salt-api-cli keys accept <id-or-glob>
69
+ salt-api-cli keys accept-all
70
+ salt-api-cli keys reject <id-or-glob>
71
+ salt-api-cli keys delete <id-or-glob>
72
+ ```
73
+
74
+ Any `key=value` argument is parsed as a kwarg to the salt function;
75
+ anything else is positional.
76
+
77
+ You can also invoke the CLI as a module: `python -m salt_api_cli ...`.
78
+
79
+ ## License
80
+
81
+ This project is licensed under the terms of the MIT license.
@@ -0,0 +1,14 @@
1
+ MANIFEST.in
2
+ README.md
3
+ pyproject.toml
4
+ salt_api_cli/__init__.py
5
+ salt_api_cli/__main__.py
6
+ salt_api_cli/cli.py
7
+ salt_api_cli/py.typed
8
+ salt_api_cli/version.py
9
+ salt_api_cli.egg-info/PKG-INFO
10
+ salt_api_cli.egg-info/SOURCES.txt
11
+ salt_api_cli.egg-info/dependency_links.txt
12
+ salt_api_cli.egg-info/entry_points.txt
13
+ salt_api_cli.egg-info/requires.txt
14
+ salt_api_cli.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ salt-api-cli = salt_api_cli.cli:main
@@ -0,0 +1,3 @@
1
+
2
+ [pre-commit]
3
+ pre-commit
@@ -0,0 +1 @@
1
+ salt_api_cli
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+