cli-wizard 1.0.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.
- cli_wizard/__init__.py +4 -0
- cli_wizard/cli.py +37 -0
- cli_wizard/commands/__init__.py +4 -0
- cli_wizard/commands/common.py +36 -0
- cli_wizard/commands/config.py +82 -0
- cli_wizard/commands/generate.py +231 -0
- cli_wizard/config/__init__.py +0 -0
- cli_wizard/config/configuration.py +53 -0
- cli_wizard/config/default_config.yaml +7 -0
- cli_wizard/config/schema.py +182 -0
- cli_wizard/constants.py +11 -0
- cli_wizard/generator/__init__.py +22 -0
- cli_wizard/generator/generator.py +241 -0
- cli_wizard/generator/models.py +123 -0
- cli_wizard/generator/parser.py +171 -0
- cli_wizard/templates/README.md.j2 +31 -0
- cli_wizard/templates/cli.py.j2 +85 -0
- cli_wizard/templates/client.py.j2 +134 -0
- cli_wizard/templates/command_group.py.j2 +161 -0
- cli_wizard/templates/commands_config.py.j2 +206 -0
- cli_wizard/templates/commands_init.py.j2 +14 -0
- cli_wizard/templates/constants.py.j2 +67 -0
- cli_wizard/templates/logging.py.j2 +175 -0
- cli_wizard/templates/package_init.py.j2 +8 -0
- cli_wizard/templates/profile.py.j2 +87 -0
- cli_wizard/templates/pyproject.toml.j2 +28 -0
- cli_wizard-1.0.0.dist-info/METADATA +236 -0
- cli_wizard-1.0.0.dist-info/RECORD +32 -0
- cli_wizard-1.0.0.dist-info/WHEEL +5 -0
- cli_wizard-1.0.0.dist-info/entry_points.txt +2 -0
- cli_wizard-1.0.0.dist-info/licenses/LICENSE +21 -0
- cli_wizard-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# AUTO-GENERATED FILE - DO NOT EDIT
|
|
2
|
+
# Generated from OpenAPI specification by cli-wizard
|
|
3
|
+
|
|
4
|
+
"""HTTP client for API requests."""
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import requests
|
|
10
|
+
|
|
11
|
+
from .constants import DEFAULT_BASE_URL, DEFAULT_TIMEOUT, DEFAULT_CA_FILE
|
|
12
|
+
from .logging import log_debug
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ApiClient:
|
|
16
|
+
"""HTTP client for making API requests."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
base_url: str | None = None,
|
|
21
|
+
access_token: str | None = None,
|
|
22
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
23
|
+
ca_file: str | None = DEFAULT_CA_FILE,
|
|
24
|
+
verify_ssl: bool = True,
|
|
25
|
+
debug: bool = False,
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Initialize the API client."""
|
|
28
|
+
self.base_url = (base_url or DEFAULT_BASE_URL).rstrip("/")
|
|
29
|
+
self.access_token = access_token
|
|
30
|
+
self.timeout = timeout
|
|
31
|
+
self.ca_file = ca_file
|
|
32
|
+
self.verify_ssl = verify_ssl
|
|
33
|
+
self.debug = debug
|
|
34
|
+
self.session = requests.Session()
|
|
35
|
+
self._setup_headers()
|
|
36
|
+
self._setup_ssl()
|
|
37
|
+
|
|
38
|
+
def _setup_headers(self) -> None:
|
|
39
|
+
"""Set up default headers."""
|
|
40
|
+
self.session.headers["Content-Type"] = "application/json"
|
|
41
|
+
self.session.headers["Accept"] = "application/json"
|
|
42
|
+
if self.access_token:
|
|
43
|
+
self.session.headers["Authorization"] = f"Bearer {self.access_token}"
|
|
44
|
+
|
|
45
|
+
def _setup_ssl(self) -> None:
|
|
46
|
+
"""Set up SSL verification with custom CA file if provided."""
|
|
47
|
+
if not self.verify_ssl:
|
|
48
|
+
self.session.verify = False
|
|
49
|
+
elif self.ca_file:
|
|
50
|
+
ca_path = Path(self.ca_file) if isinstance(self.ca_file, str) else self.ca_file
|
|
51
|
+
if ca_path and ca_path.exists():
|
|
52
|
+
self.session.verify = str(ca_path)
|
|
53
|
+
|
|
54
|
+
def _url(self, path: str) -> str:
|
|
55
|
+
"""Build full URL from path."""
|
|
56
|
+
return f"{self.base_url}{path}"
|
|
57
|
+
|
|
58
|
+
def _log_request(self, method: str, url: str, params: dict | None = None, json_data: dict | None = None) -> None:
|
|
59
|
+
"""Log request details when debug is enabled."""
|
|
60
|
+
if not self.debug:
|
|
61
|
+
return
|
|
62
|
+
log_debug(f"Request: {method} {url}")
|
|
63
|
+
if params:
|
|
64
|
+
log_debug(f"Params: {params}")
|
|
65
|
+
if json_data:
|
|
66
|
+
log_debug(f"Body: {json_data}")
|
|
67
|
+
log_debug(f"Headers: {dict(self.session.headers)}")
|
|
68
|
+
|
|
69
|
+
def _log_response(self, response: requests.Response) -> None:
|
|
70
|
+
"""Log response details when debug is enabled."""
|
|
71
|
+
if not self.debug:
|
|
72
|
+
return
|
|
73
|
+
log_debug(f"Response: {response.status_code} {response.reason}")
|
|
74
|
+
log_debug(f"Response Headers: {dict(response.headers)}")
|
|
75
|
+
if response.text:
|
|
76
|
+
log_debug(f"Response Body: {response.text[:1000]}")
|
|
77
|
+
|
|
78
|
+
def get(
|
|
79
|
+
self, path: str, params: dict[str, Any] | None = None
|
|
80
|
+
) -> requests.Response:
|
|
81
|
+
"""Make a GET request."""
|
|
82
|
+
url = self._url(path)
|
|
83
|
+
self._log_request("GET", url, params=params)
|
|
84
|
+
response = self.session.get(url, params=params, timeout=self.timeout)
|
|
85
|
+
self._log_response(response)
|
|
86
|
+
return response
|
|
87
|
+
|
|
88
|
+
def post(
|
|
89
|
+
self,
|
|
90
|
+
path: str,
|
|
91
|
+
json_data: dict[str, Any] | None = None,
|
|
92
|
+
) -> requests.Response:
|
|
93
|
+
"""Make a POST request."""
|
|
94
|
+
url = self._url(path)
|
|
95
|
+
self._log_request("POST", url, json_data=json_data)
|
|
96
|
+
response = self.session.post(url, json=json_data, timeout=self.timeout)
|
|
97
|
+
self._log_response(response)
|
|
98
|
+
return response
|
|
99
|
+
|
|
100
|
+
def put(
|
|
101
|
+
self,
|
|
102
|
+
path: str,
|
|
103
|
+
json_data: dict[str, Any] | None = None,
|
|
104
|
+
) -> requests.Response:
|
|
105
|
+
"""Make a PUT request."""
|
|
106
|
+
url = self._url(path)
|
|
107
|
+
self._log_request("PUT", url, json_data=json_data)
|
|
108
|
+
response = self.session.put(url, json=json_data, timeout=self.timeout)
|
|
109
|
+
self._log_response(response)
|
|
110
|
+
return response
|
|
111
|
+
|
|
112
|
+
def patch(
|
|
113
|
+
self,
|
|
114
|
+
path: str,
|
|
115
|
+
json_data: dict[str, Any] | None = None,
|
|
116
|
+
) -> requests.Response:
|
|
117
|
+
"""Make a PATCH request."""
|
|
118
|
+
url = self._url(path)
|
|
119
|
+
self._log_request("PATCH", url, json_data=json_data)
|
|
120
|
+
response = self.session.patch(url, json=json_data, timeout=self.timeout)
|
|
121
|
+
self._log_response(response)
|
|
122
|
+
return response
|
|
123
|
+
|
|
124
|
+
def delete(
|
|
125
|
+
self,
|
|
126
|
+
path: str,
|
|
127
|
+
json_data: dict[str, Any] | None = None,
|
|
128
|
+
) -> requests.Response:
|
|
129
|
+
"""Make a DELETE request."""
|
|
130
|
+
url = self._url(path)
|
|
131
|
+
self._log_request("DELETE", url, json_data=json_data)
|
|
132
|
+
response = self.session.delete(url, json=json_data, timeout=self.timeout)
|
|
133
|
+
self._log_response(response)
|
|
134
|
+
return response
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# AUTO-GENERATED FILE - DO NOT EDIT
|
|
2
|
+
# Generated from OpenAPI specification by cli-wizard
|
|
3
|
+
|
|
4
|
+
"""{{ group.name }} commands."""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from {{ package_name }}.constants import DEFAULT_BASE_URL, DEFAULT_CA_FILE
|
|
14
|
+
from {{ package_name }}.client import ApiClient
|
|
15
|
+
from {{ package_name }}.logging import log_info, log_error, log_debug, set_debug
|
|
16
|
+
from {{ package_name }}.profile import load_profile, get_profile_value
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _get_client(
|
|
20
|
+
base_url: str | None,
|
|
21
|
+
no_verify_ssl: bool,
|
|
22
|
+
ca_file: Path | None,
|
|
23
|
+
debug: bool = False,
|
|
24
|
+
) -> ApiClient:
|
|
25
|
+
"""Create an API client with the given options."""
|
|
26
|
+
effective_base_url = base_url or os.environ.get("API_BASE_URL") or DEFAULT_BASE_URL
|
|
27
|
+
effective_ca_file = None if no_verify_ssl else (ca_file or DEFAULT_CA_FILE)
|
|
28
|
+
access_token = get_profile_value("accessToken")
|
|
29
|
+
return ApiClient(
|
|
30
|
+
base_url=effective_base_url,
|
|
31
|
+
access_token=access_token,
|
|
32
|
+
ca_file=effective_ca_file,
|
|
33
|
+
verify_ssl=not no_verify_ssl,
|
|
34
|
+
debug=debug,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@click.group(name="{{ group.cli_name }}")
|
|
39
|
+
def {{ group.module_name }}() -> None:
|
|
40
|
+
"""{{ group.description }}"""
|
|
41
|
+
{% for op in group.operations %}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@{{ group.module_name }}.command(name="{{ op.command_name }}")
|
|
45
|
+
{% for param in op.parameters if param.location == "path" %}
|
|
46
|
+
@click.argument("{{ param.python_name }}"{% if param.click_type != "str" %}, type={{ param.click_type }}{% endif %})
|
|
47
|
+
{% endfor %}
|
|
48
|
+
{% for param in op.parameters if param.location == "query" %}
|
|
49
|
+
{% if param.enum %}
|
|
50
|
+
@click.option("--{{ param.cli_name }}", type=click.Choice([{% for e in param.enum %}"{{ e }}"{% if not loop.last %}, {% endif %}{% endfor %}]){% if param.required %}, required=True{% endif %}{% if param.default is not none %}, default="{{ param.default }}"{% endif %}, help="{{ param.description }}")
|
|
51
|
+
{% else %}
|
|
52
|
+
@click.option("--{{ param.cli_name }}"{% if param.click_type != "str" %}, type={{ param.click_type }}{% endif %}{% if param.required %}, required=True{% endif %}{% if param.default is not none %}, default={{ param.default }}{% endif %}, help="{{ param.description }}")
|
|
53
|
+
{% endif %}
|
|
54
|
+
{% endfor %}
|
|
55
|
+
{% for prop in op.body_properties %}
|
|
56
|
+
@click.option("--{{ prop.cli_name }}"{% if prop.click_type == "bool" %}, is_flag=True{% elif prop.click_type != "str" %}, type={{ prop.click_type }}{% endif %}{% if prop.required %}, required=True{% endif %}, help="{{ prop.description }}")
|
|
57
|
+
{% endfor %}
|
|
58
|
+
@click.option("--profile", "-p", default="default", help="Profile name to use")
|
|
59
|
+
@click.option("--debug", "-d", is_flag=True, help="Enable debug output")
|
|
60
|
+
@click.option("--base-url", "-u", envvar="API_BASE_URL", help="API base URL")
|
|
61
|
+
@click.option("--no-verify-ssl", is_flag=True, default=False, help="Disable SSL certificate verification")
|
|
62
|
+
@click.option("--ca-file", type=click.Path(exists=True, dir_okay=False, path_type=Path), help="CA certificate file for SSL verification")
|
|
63
|
+
def {{ op.function_name }}(
|
|
64
|
+
{% for param in op.parameters if param.location == "path" %}
|
|
65
|
+
{{ param.python_name }}: {{ param.click_type }},
|
|
66
|
+
{% endfor %}
|
|
67
|
+
{% for param in op.parameters if param.location == "query" %}
|
|
68
|
+
{{ param.python_name }}: {{ param.click_type }}{% if not param.required %} | None{% endif %},
|
|
69
|
+
{% endfor %}
|
|
70
|
+
{% for prop in op.body_properties %}
|
|
71
|
+
{{ prop.python_name }}: {{ prop.click_type }}{% if not prop.required %} | None{% endif %},
|
|
72
|
+
{% endfor %}
|
|
73
|
+
profile: str,
|
|
74
|
+
debug: bool,
|
|
75
|
+
base_url: str | None,
|
|
76
|
+
no_verify_ssl: bool,
|
|
77
|
+
ca_file: Path | None,
|
|
78
|
+
) -> None:
|
|
79
|
+
"""{{ op.summary or op.operation_id }}"""
|
|
80
|
+
# Enable debug logging if --debug flag is set
|
|
81
|
+
set_debug(debug)
|
|
82
|
+
|
|
83
|
+
# Load profile
|
|
84
|
+
load_profile(profile)
|
|
85
|
+
# Log command execution start
|
|
86
|
+
cmd_params = {
|
|
87
|
+
{% for param in op.parameters if param.location == "path" %}
|
|
88
|
+
"{{ param.name }}": {{ param.python_name }},
|
|
89
|
+
{% endfor %}
|
|
90
|
+
{% for param in op.parameters if param.location == "query" %}
|
|
91
|
+
"{{ param.name }}": {{ param.python_name }},
|
|
92
|
+
{% endfor %}
|
|
93
|
+
{% for prop in op.body_properties %}
|
|
94
|
+
"{{ prop.name }}": {{ prop.python_name }},
|
|
95
|
+
{% endfor %}
|
|
96
|
+
}
|
|
97
|
+
log_debug(f"Executing command '{{ group.cli_name }} {{ op.command_name }}' with params: {cmd_params}")
|
|
98
|
+
|
|
99
|
+
client = _get_client(base_url, no_verify_ssl, ca_file, debug)
|
|
100
|
+
{% if op.parameters | selectattr("location", "equalto", "query") | list %}
|
|
101
|
+
|
|
102
|
+
params: dict[str, Any] = {}
|
|
103
|
+
{% for param in op.parameters if param.location == "query" %}
|
|
104
|
+
if {{ param.python_name }} is not None:
|
|
105
|
+
params["{{ param.name }}"] = {{ param.python_name }}
|
|
106
|
+
{% endfor %}
|
|
107
|
+
{% endif %}
|
|
108
|
+
{% if op.body_properties %}
|
|
109
|
+
|
|
110
|
+
body: dict[str, Any] = {}
|
|
111
|
+
{% for prop in op.body_properties %}
|
|
112
|
+
if {{ prop.python_name }} is not None:
|
|
113
|
+
body["{{ prop.name }}"] = {{ prop.python_name }}
|
|
114
|
+
{% endfor %}
|
|
115
|
+
{% endif %}
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
{% if op.method == "GET" %}
|
|
119
|
+
{% if op.parameters | selectattr("location", "equalto", "query") | list %}
|
|
120
|
+
response = client.get("{{ op | url_path }}", params=params)
|
|
121
|
+
{% else %}
|
|
122
|
+
response = client.get("{{ op | url_path }}")
|
|
123
|
+
{% endif %}
|
|
124
|
+
{% elif op.method == "POST" %}
|
|
125
|
+
{% if op.body_properties %}
|
|
126
|
+
response = client.post("{{ op | url_path }}", json_data=body)
|
|
127
|
+
{% else %}
|
|
128
|
+
response = client.post("{{ op | url_path }}")
|
|
129
|
+
{% endif %}
|
|
130
|
+
{% elif op.method == "PUT" %}
|
|
131
|
+
{% if op.body_properties %}
|
|
132
|
+
response = client.put("{{ op | url_path }}", json_data=body)
|
|
133
|
+
{% else %}
|
|
134
|
+
response = client.put("{{ op | url_path }}")
|
|
135
|
+
{% endif %}
|
|
136
|
+
{% elif op.method == "PATCH" %}
|
|
137
|
+
{% if op.body_properties %}
|
|
138
|
+
response = client.patch("{{ op | url_path }}", json_data=body)
|
|
139
|
+
{% else %}
|
|
140
|
+
response = client.patch("{{ op | url_path }}")
|
|
141
|
+
{% endif %}
|
|
142
|
+
{% elif op.method == "DELETE" %}
|
|
143
|
+
{% if op.body_properties %}
|
|
144
|
+
response = client.delete("{{ op | url_path }}", json_data=body)
|
|
145
|
+
{% else %}
|
|
146
|
+
response = client.delete("{{ op | url_path }}")
|
|
147
|
+
{% endif %}
|
|
148
|
+
{% endif %}
|
|
149
|
+
response.raise_for_status()
|
|
150
|
+
if response.text:
|
|
151
|
+
output = json.dumps(response.json(), indent=2)
|
|
152
|
+
log_debug(f"Command '{{ group.cli_name }} {{ op.command_name }}' completed with output: {output[:500]}")
|
|
153
|
+
click.echo(output)
|
|
154
|
+
else:
|
|
155
|
+
log_debug(f"Command '{{ group.cli_name }} {{ op.command_name }}' completed successfully")
|
|
156
|
+
click.echo("Success")
|
|
157
|
+
except Exception as e:
|
|
158
|
+
log_error(f"Command '{{ group.cli_name }} {{ op.command_name }}' failed: {e}")
|
|
159
|
+
click.secho(f"Error: {e}", fg="red", err=True)
|
|
160
|
+
raise SystemExit(1)
|
|
161
|
+
{% endfor %}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# AUTO-GENERATED FILE - DO NOT EDIT
|
|
2
|
+
# Generated from OpenAPI specification by cli-wizard
|
|
3
|
+
|
|
4
|
+
"""Config commands for managing CLI profiles."""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
import yaml
|
|
11
|
+
|
|
12
|
+
from {{ package_name }}.constants import PROFILE_FILE
|
|
13
|
+
from {{ package_name }}.logging import log_info, log_error, log_warning
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.group(name="config")
|
|
17
|
+
def config() -> None:
|
|
18
|
+
"""Configure the CLI"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@config.command(name="init")
|
|
22
|
+
def config_init() -> None:
|
|
23
|
+
"""Initialize the profile file with default profile."""
|
|
24
|
+
if PROFILE_FILE.exists():
|
|
25
|
+
result = {"status": "exists", "path": str(PROFILE_FILE)}
|
|
26
|
+
click.echo(json.dumps(result, indent=2))
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
PROFILE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
31
|
+
default_content = {"default": {}}
|
|
32
|
+
with open(PROFILE_FILE, "w") as f:
|
|
33
|
+
yaml.safe_dump(default_content, f, default_flow_style=False)
|
|
34
|
+
log_info(f"Created profile file: {PROFILE_FILE}")
|
|
35
|
+
result = {"status": "created", "path": str(PROFILE_FILE)}
|
|
36
|
+
click.echo(json.dumps(result, indent=2))
|
|
37
|
+
except (IOError, OSError) as e:
|
|
38
|
+
log_error(f"Failed to create profile file: {e}")
|
|
39
|
+
result = {"status": "error", "message": str(e)}
|
|
40
|
+
click.echo(json.dumps(result, indent=2), err=True)
|
|
41
|
+
raise SystemExit(1)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@config.command(name="list-profiles")
|
|
45
|
+
def config_list_profiles() -> None:
|
|
46
|
+
"""List all available profiles."""
|
|
47
|
+
if not PROFILE_FILE.exists():
|
|
48
|
+
result = {"profiles": []}
|
|
49
|
+
click.echo(json.dumps(result, indent=2))
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
with open(PROFILE_FILE) as f:
|
|
54
|
+
profiles = yaml.safe_load(f) or {}
|
|
55
|
+
except (yaml.YAMLError, IOError) as e:
|
|
56
|
+
log_error(f"Failed to load profile file: {e}")
|
|
57
|
+
result = {"status": "error", "message": str(e)}
|
|
58
|
+
click.echo(json.dumps(result, indent=2), err=True)
|
|
59
|
+
raise SystemExit(1)
|
|
60
|
+
|
|
61
|
+
result = {"profiles": list(profiles.keys())}
|
|
62
|
+
click.echo(json.dumps(result, indent=2))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@config.command(name="show")
|
|
66
|
+
@click.option("--profile", "-p", default="default", help="Profile name")
|
|
67
|
+
def config_show(profile: str) -> None:
|
|
68
|
+
"""Show all parameters and values for a profile."""
|
|
69
|
+
if not PROFILE_FILE.exists():
|
|
70
|
+
click.echo(json.dumps({}, indent=2))
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
with open(PROFILE_FILE) as f:
|
|
75
|
+
profiles = yaml.safe_load(f) or {}
|
|
76
|
+
except (yaml.YAMLError, IOError) as e:
|
|
77
|
+
log_error(f"Failed to load profile file: {e}")
|
|
78
|
+
result = {"status": "error", "message": str(e)}
|
|
79
|
+
click.echo(json.dumps(result, indent=2), err=True)
|
|
80
|
+
raise SystemExit(1)
|
|
81
|
+
|
|
82
|
+
if profile not in profiles:
|
|
83
|
+
click.echo(json.dumps({}, indent=2))
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
profile_data = profiles[profile] or {}
|
|
87
|
+
click.echo(json.dumps(profile_data, indent=2))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@config.command(name="get")
|
|
91
|
+
@click.argument("param")
|
|
92
|
+
@click.option("--profile", "-p", default="default", help="Profile name")
|
|
93
|
+
def config_get(param: str, profile: str) -> None:
|
|
94
|
+
"""Get a configuration value from a profile."""
|
|
95
|
+
if not PROFILE_FILE.exists():
|
|
96
|
+
result = {"key": param, "value": None}
|
|
97
|
+
click.echo(json.dumps(result, indent=2))
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
with open(PROFILE_FILE) as f:
|
|
102
|
+
profiles = yaml.safe_load(f) or {}
|
|
103
|
+
except (yaml.YAMLError, IOError) as e:
|
|
104
|
+
log_error(f"Failed to load profile file: {e}")
|
|
105
|
+
result = {"status": "error", "message": str(e)}
|
|
106
|
+
click.echo(json.dumps(result, indent=2), err=True)
|
|
107
|
+
raise SystemExit(1)
|
|
108
|
+
|
|
109
|
+
if profile not in profiles:
|
|
110
|
+
result = {"key": param, "value": None}
|
|
111
|
+
click.echo(json.dumps(result, indent=2))
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
profile_data = profiles[profile] or {}
|
|
115
|
+
value = profile_data.get(param)
|
|
116
|
+
result = {"key": param, "value": value}
|
|
117
|
+
click.echo(json.dumps(result, indent=2))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@config.command(name="set")
|
|
121
|
+
@click.argument("param")
|
|
122
|
+
@click.argument("value")
|
|
123
|
+
@click.option("--profile", "-p", default="default", help="Profile name")
|
|
124
|
+
def config_set(param: str, value: str, profile: str) -> None:
|
|
125
|
+
"""Set a configuration value in a profile."""
|
|
126
|
+
if not PROFILE_FILE.exists():
|
|
127
|
+
PROFILE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
128
|
+
profiles = {}
|
|
129
|
+
else:
|
|
130
|
+
try:
|
|
131
|
+
with open(PROFILE_FILE) as f:
|
|
132
|
+
profiles = yaml.safe_load(f) or {}
|
|
133
|
+
except (yaml.YAMLError, IOError) as e:
|
|
134
|
+
log_error(f"Failed to load profile file: {e}")
|
|
135
|
+
result = {"status": "error", "message": str(e)}
|
|
136
|
+
click.echo(json.dumps(result, indent=2), err=True)
|
|
137
|
+
raise SystemExit(1)
|
|
138
|
+
|
|
139
|
+
if profile not in profiles:
|
|
140
|
+
profiles[profile] = {}
|
|
141
|
+
|
|
142
|
+
# Get old value
|
|
143
|
+
old_value = profiles[profile].get(param)
|
|
144
|
+
|
|
145
|
+
# Try to parse value as JSON for complex types
|
|
146
|
+
try:
|
|
147
|
+
parsed_value = json.loads(value)
|
|
148
|
+
except json.JSONDecodeError:
|
|
149
|
+
parsed_value = value
|
|
150
|
+
|
|
151
|
+
profiles[profile][param] = parsed_value
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
with open(PROFILE_FILE, "w") as f:
|
|
155
|
+
yaml.safe_dump(profiles, f, default_flow_style=False)
|
|
156
|
+
log_info(f"Set '{param}' = '{parsed_value}' in profile '{profile}'")
|
|
157
|
+
result = {"key": param, "value": parsed_value, "oldValue": old_value}
|
|
158
|
+
click.echo(json.dumps(result, indent=2))
|
|
159
|
+
except (IOError, OSError) as e:
|
|
160
|
+
log_error(f"Failed to save profile file: {e}")
|
|
161
|
+
result = {"status": "error", "message": str(e)}
|
|
162
|
+
click.echo(json.dumps(result, indent=2), err=True)
|
|
163
|
+
raise SystemExit(1)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@config.command(name="unset")
|
|
167
|
+
@click.argument("param")
|
|
168
|
+
@click.option("--profile", "-p", default="default", help="Profile name")
|
|
169
|
+
def config_unset(param: str, profile: str) -> None:
|
|
170
|
+
"""Remove a configuration value from a profile."""
|
|
171
|
+
if not PROFILE_FILE.exists():
|
|
172
|
+
result = {"key": param, "oldValue": None}
|
|
173
|
+
click.echo(json.dumps(result, indent=2))
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
with open(PROFILE_FILE) as f:
|
|
178
|
+
profiles = yaml.safe_load(f) or {}
|
|
179
|
+
except (yaml.YAMLError, IOError) as e:
|
|
180
|
+
log_error(f"Failed to load profile file: {e}")
|
|
181
|
+
result = {"status": "error", "message": str(e)}
|
|
182
|
+
click.echo(json.dumps(result, indent=2), err=True)
|
|
183
|
+
raise SystemExit(1)
|
|
184
|
+
|
|
185
|
+
if profile not in profiles:
|
|
186
|
+
result = {"key": param, "oldValue": None}
|
|
187
|
+
click.echo(json.dumps(result, indent=2))
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
profile_data = profiles[profile] or {}
|
|
191
|
+
old_value = profile_data.get(param)
|
|
192
|
+
|
|
193
|
+
if param in profile_data:
|
|
194
|
+
del profiles[profile][param]
|
|
195
|
+
try:
|
|
196
|
+
with open(PROFILE_FILE, "w") as f:
|
|
197
|
+
yaml.safe_dump(profiles, f, default_flow_style=False)
|
|
198
|
+
log_info(f"Removed '{param}' from profile '{profile}'")
|
|
199
|
+
except (IOError, OSError) as e:
|
|
200
|
+
log_error(f"Failed to save profile file: {e}")
|
|
201
|
+
result = {"status": "error", "message": str(e)}
|
|
202
|
+
click.echo(json.dumps(result, indent=2), err=True)
|
|
203
|
+
raise SystemExit(1)
|
|
204
|
+
|
|
205
|
+
result = {"key": param, "oldValue": old_value}
|
|
206
|
+
click.echo(json.dumps(result, indent=2))
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# AUTO-GENERATED FILE - DO NOT EDIT
|
|
2
|
+
# Generated from OpenAPI specification by cli-wizard
|
|
3
|
+
|
|
4
|
+
"""Generated CLI commands."""
|
|
5
|
+
|
|
6
|
+
{% for tag, group in groups.items() %}
|
|
7
|
+
from .{{ group.module_name }} import {{ group.module_name }}
|
|
8
|
+
{% endfor %}
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
{% for tag, group in groups.items() %}
|
|
12
|
+
"{{ group.module_name }}",
|
|
13
|
+
{% endfor %}
|
|
14
|
+
]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# AUTO-GENERATED FILE - DO NOT EDIT
|
|
2
|
+
# Generated from OpenAPI specification by cli-wizard
|
|
3
|
+
|
|
4
|
+
"""Constants for the generated CLI."""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _read_version() -> str:
|
|
11
|
+
"""Read version from VERSION file."""
|
|
12
|
+
version_file = Path(__file__).parent.parent.parent / "VERSION"
|
|
13
|
+
if version_file.exists():
|
|
14
|
+
return version_file.read_text().strip()
|
|
15
|
+
return "0.0.0"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__version__ = _read_version()
|
|
19
|
+
|
|
20
|
+
# Main directory for CLI data (config, cache, etc.)
|
|
21
|
+
MAIN_DIR = Path(os.path.expandvars("{{ main_dir }}"))
|
|
22
|
+
|
|
23
|
+
# Profile configuration
|
|
24
|
+
PROFILE_FILE = Path(os.path.expandvars("{{ config.ProfileFile | default('#[MainDir]/profiles.yaml') }}"))
|
|
25
|
+
|
|
26
|
+
DEFAULT_BASE_URL = "{{ config.DefaultBaseUrl | default('http://localhost:8080') }}"
|
|
27
|
+
DEFAULT_TIMEOUT = {{ config.Timeout | default(30) }}
|
|
28
|
+
{% if ca_file_name %}
|
|
29
|
+
DEFAULT_CA_FILE = Path(__file__).parent / "resources" / "{{ ca_file_name }}"
|
|
30
|
+
{% else %}
|
|
31
|
+
DEFAULT_CA_FILE = None
|
|
32
|
+
{% endif %}
|
|
33
|
+
|
|
34
|
+
# Splash screen configuration
|
|
35
|
+
{% if splash_file_name %}
|
|
36
|
+
SPLASH_ENABLED = True
|
|
37
|
+
SPLASH_FILE = Path(__file__).parent / "resources" / "{{ splash_file_name }}"
|
|
38
|
+
SPLASH_COLOR = "{{ config.SplashColor | default('#FFFFFF') }}"
|
|
39
|
+
{% else %}
|
|
40
|
+
SPLASH_ENABLED = False
|
|
41
|
+
SPLASH_FILE = None
|
|
42
|
+
SPLASH_COLOR = None
|
|
43
|
+
{% endif %}
|
|
44
|
+
|
|
45
|
+
# Log configuration
|
|
46
|
+
LOG_LEVEL = "{{ config.LogLevel | default('INFO') }}"
|
|
47
|
+
LOG_FORMAT = "{{ config.LogFormat | default('%(asctime)s [%(levelname)s] %(message)s') }}"
|
|
48
|
+
LOG_TIMESTAMP_FORMAT = "{{ config.LogTimestampFormat | default('%Y-%m-%dT%H:%M:%S') }}"
|
|
49
|
+
LOG_TIMEZONE = "{{ config.LogTimezone | default('UTC') }}"
|
|
50
|
+
{% if config.LogFile %}
|
|
51
|
+
LOG_FILE = Path(os.path.expandvars("{{ config.LogFile }}"))
|
|
52
|
+
{% else %}
|
|
53
|
+
LOG_FILE = None
|
|
54
|
+
{% endif %}
|
|
55
|
+
LOG_ROTATION_TYPE = "{{ config.LogRotationType | default('days') }}"
|
|
56
|
+
LOG_ROTATION_SIZE_MB = {{ config.LogRotationSize | default(10) }}
|
|
57
|
+
LOG_ROTATION_DAYS = {{ config.LogRotationDays | default(30) }}
|
|
58
|
+
LOG_ROTATION_BACKUP_COUNT = {{ config.LogRotationBackupCount | default(5) }}
|
|
59
|
+
|
|
60
|
+
# Log color configuration
|
|
61
|
+
LOG_COLOR_STYLE = "{{ config.LogColorStyle | default('level') }}"
|
|
62
|
+
LOG_COLORS = {
|
|
63
|
+
"DEBUG": "{{ config.LogColorDebug | default('#808080') }}",
|
|
64
|
+
"INFO": "{{ config.LogColorInfo | default('#00FF00') }}",
|
|
65
|
+
"WARNING": "{{ config.LogColorWarning | default('#FFFF00') }}",
|
|
66
|
+
"ERROR": "{{ config.LogColorError | default('#FF0000') }}",
|
|
67
|
+
}
|