htcli 1.1.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.
- htcli-1.1.0.dist-info/METADATA +509 -0
- htcli-1.1.0.dist-info/RECORD +140 -0
- htcli-1.1.0.dist-info/WHEEL +4 -0
- htcli-1.1.0.dist-info/entry_points.txt +2 -0
- htcli-1.1.0.dist-info/licenses/LICENSE +21 -0
- src/__init__.py +0 -0
- src/htcli/__init__.py +5 -0
- src/htcli/client/__init__.py +338 -0
- src/htcli/client/extrinsics/__init__.py +26 -0
- src/htcli/client/extrinsics/base.py +487 -0
- src/htcli/client/extrinsics/consensus.py +79 -0
- src/htcli/client/extrinsics/governance.py +714 -0
- src/htcli/client/extrinsics/identity.py +490 -0
- src/htcli/client/extrinsics/node.py +1054 -0
- src/htcli/client/extrinsics/overwatch.py +401 -0
- src/htcli/client/extrinsics/staking.py +1504 -0
- src/htcli/client/extrinsics/subnet.py +2218 -0
- src/htcli/client/extrinsics/validator.py +203 -0
- src/htcli/client/extrinsics/wallet.py +323 -0
- src/htcli/client/offchain/__init__.py +10 -0
- src/htcli/client/offchain/backup.py +385 -0
- src/htcli/client/offchain/config.py +541 -0
- src/htcli/client/offchain/wallet.py +839 -0
- src/htcli/client/rpc/__init__.py +20 -0
- src/htcli/client/rpc/chain.py +568 -0
- src/htcli/client/rpc/node.py +783 -0
- src/htcli/client/rpc/overwatch.py +680 -0
- src/htcli/client/rpc/staking.py +216 -0
- src/htcli/client/rpc/subnet.py +2104 -0
- src/htcli/client/rpc/wallet.py +912 -0
- src/htcli/commands/__init__.py +31 -0
- src/htcli/commands/chain/__init__.py +66 -0
- src/htcli/commands/chain/display.py +204 -0
- src/htcli/commands/chain/handlers.py +260 -0
- src/htcli/commands/config/__init__.py +158 -0
- src/htcli/commands/config/display.py +353 -0
- src/htcli/commands/config/handlers.py +347 -0
- src/htcli/commands/config/prompts.py +357 -0
- src/htcli/commands/consensus/__init__.py +61 -0
- src/htcli/commands/consensus/handlers.py +100 -0
- src/htcli/commands/governance/__init__.py +49 -0
- src/htcli/commands/governance/handlers.py +81 -0
- src/htcli/commands/node/__init__.py +304 -0
- src/htcli/commands/node/display.py +749 -0
- src/htcli/commands/node/error_handling.py +470 -0
- src/htcli/commands/node/handlers.py +844 -0
- src/htcli/commands/node/prompts.py +346 -0
- src/htcli/commands/overwatch/__init__.py +219 -0
- src/htcli/commands/overwatch/display.py +396 -0
- src/htcli/commands/overwatch/error_handling.py +276 -0
- src/htcli/commands/overwatch/handlers.py +443 -0
- src/htcli/commands/overwatch/prompts.py +359 -0
- src/htcli/commands/stake/__init__.py +736 -0
- src/htcli/commands/stake/display.py +1103 -0
- src/htcli/commands/stake/error_handling.py +425 -0
- src/htcli/commands/stake/handlers.py +1902 -0
- src/htcli/commands/stake/prompts.py +1080 -0
- src/htcli/commands/subnet/__init__.py +639 -0
- src/htcli/commands/subnet/display.py +801 -0
- src/htcli/commands/subnet/error_handling.py +524 -0
- src/htcli/commands/subnet/handlers.py +2855 -0
- src/htcli/commands/subnet/prompts.py +1225 -0
- src/htcli/commands/validator/__init__.py +192 -0
- src/htcli/commands/validator/display.py +54 -0
- src/htcli/commands/validator/handlers.py +340 -0
- src/htcli/commands/wallet/__init__.py +546 -0
- src/htcli/commands/wallet/display.py +806 -0
- src/htcli/commands/wallet/error_handling.py +210 -0
- src/htcli/commands/wallet/handlers.py +3040 -0
- src/htcli/commands/wallet/prompts.py +1518 -0
- src/htcli/config.py +184 -0
- src/htcli/dependencies.py +186 -0
- src/htcli/errors/__init__.py +63 -0
- src/htcli/errors/base.py +141 -0
- src/htcli/errors/display.py +20 -0
- src/htcli/errors/handlers.py +710 -0
- src/htcli/main.py +343 -0
- src/htcli/models/__init__.py +21 -0
- src/htcli/models/enums/enum_types.py +35 -0
- src/htcli/models/errors.py +103 -0
- src/htcli/models/requests/__init__.py +197 -0
- src/htcli/models/requests/config.py +70 -0
- src/htcli/models/requests/consensus.py +19 -0
- src/htcli/models/requests/governance.py +38 -0
- src/htcli/models/requests/identity.py +51 -0
- src/htcli/models/requests/key.py +22 -0
- src/htcli/models/requests/node.py +91 -0
- src/htcli/models/requests/overwatch.py +64 -0
- src/htcli/models/requests/staking.py +580 -0
- src/htcli/models/requests/subnet.py +195 -0
- src/htcli/models/requests/validator.py +139 -0
- src/htcli/models/requests/wallet.py +118 -0
- src/htcli/models/responses/__init__.py +147 -0
- src/htcli/models/responses/base.py +18 -0
- src/htcli/models/responses/chain.py +39 -0
- src/htcli/models/responses/config.py +58 -0
- src/htcli/models/responses/identity.py +102 -0
- src/htcli/models/responses/overwatch.py +51 -0
- src/htcli/models/responses/staking.py +502 -0
- src/htcli/models/responses/subnet.py +856 -0
- src/htcli/models/responses/wallet.py +185 -0
- src/htcli/ui/__init__.py +87 -0
- src/htcli/ui/colors.py +309 -0
- src/htcli/ui/components/__init__.py +60 -0
- src/htcli/ui/components/panels.py +174 -0
- src/htcli/ui/components/progress.py +166 -0
- src/htcli/ui/components/spinners.py +92 -0
- src/htcli/ui/components/tables.py +809 -0
- src/htcli/ui/components/trees.py +721 -0
- src/htcli/ui/display.py +336 -0
- src/htcli/ui/prompts.py +870 -0
- src/htcli/utils/__init__.py +76 -0
- src/htcli/utils/blockchain/__init__.py +75 -0
- src/htcli/utils/blockchain/formatting.py +368 -0
- src/htcli/utils/blockchain/patches.py +286 -0
- src/htcli/utils/blockchain/peer_id.py +186 -0
- src/htcli/utils/blockchain/staking.py +448 -0
- src/htcli/utils/blockchain/type_registry.py +1373 -0
- src/htcli/utils/blockchain/validation.py +179 -0
- src/htcli/utils/cache.py +613 -0
- src/htcli/utils/constants.py +38 -0
- src/htcli/utils/legacy/__init__.py +12 -0
- src/htcli/utils/legacy/colors.py +311 -0
- src/htcli/utils/legacy/crypto.py +1176 -0
- src/htcli/utils/legacy/formatting.py +452 -0
- src/htcli/utils/legacy/interactive.py +306 -0
- src/htcli/utils/legacy/subnet_manifest.py +265 -0
- src/htcli/utils/legacy/validation.py +488 -0
- src/htcli/utils/logging.py +183 -0
- src/htcli/utils/network/__init__.py +20 -0
- src/htcli/utils/network/subnet.py +344 -0
- src/htcli/utils/prompts.py +27 -0
- src/htcli/utils/scale_codec.py +155 -0
- src/htcli/utils/validation/__init__.py +57 -0
- src/htcli/utils/validation/prompt_validators.py +267 -0
- src/htcli/utils/wallet/__init__.py +65 -0
- src/htcli/utils/wallet/auth.py +151 -0
- src/htcli/utils/wallet/core.py +1069 -0
- src/htcli/utils/wallet/crypto.py +1615 -0
- src/htcli/utils/wallet/migration.py +159 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Config command handlers.
|
|
3
|
+
|
|
4
|
+
Business logic for configuration operations following the 3-step pattern.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from ...client.offchain.config import (
|
|
12
|
+
get_config_path,
|
|
13
|
+
get_config_value,
|
|
14
|
+
initialize_config,
|
|
15
|
+
load_config,
|
|
16
|
+
set_config_value,
|
|
17
|
+
validate_config,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Temporary imports from utils layer until client layer is complete
|
|
21
|
+
# from ...utils.config import (
|
|
22
|
+
# initialize_config,
|
|
23
|
+
# load_config,
|
|
24
|
+
# save_config,
|
|
25
|
+
# validate_config,
|
|
26
|
+
# set_config_value,
|
|
27
|
+
# get_config_value,
|
|
28
|
+
# get_config_path,
|
|
29
|
+
# )
|
|
30
|
+
from ...errors.base import HTCLIError
|
|
31
|
+
from ...errors.handlers import handle_and_display_error
|
|
32
|
+
from ...models.requests import (
|
|
33
|
+
ConfigPathRequest,
|
|
34
|
+
ConfigShowRequest,
|
|
35
|
+
ConfigValidateRequest,
|
|
36
|
+
)
|
|
37
|
+
from ...models.responses import (
|
|
38
|
+
ConfigGetResponse,
|
|
39
|
+
ConfigInitResponse,
|
|
40
|
+
ConfigPathResponse,
|
|
41
|
+
ConfigSetResponse,
|
|
42
|
+
ConfigShowResponse,
|
|
43
|
+
ConfigValidateResponse,
|
|
44
|
+
)
|
|
45
|
+
from .display import (
|
|
46
|
+
display_config_get_result,
|
|
47
|
+
display_config_init_result,
|
|
48
|
+
display_config_path_result,
|
|
49
|
+
display_config_set_result,
|
|
50
|
+
display_config_show_result,
|
|
51
|
+
display_config_validate_result,
|
|
52
|
+
)
|
|
53
|
+
from .prompts import (
|
|
54
|
+
_is_interactive,
|
|
55
|
+
confirm_config_overwrite,
|
|
56
|
+
prompt_config_get,
|
|
57
|
+
prompt_config_init,
|
|
58
|
+
prompt_config_set,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def init_handler(
|
|
63
|
+
config_file: Optional[str] = None,
|
|
64
|
+
force: bool = False,
|
|
65
|
+
endpoint: Optional[str] = None,
|
|
66
|
+
ws_endpoint: Optional[str] = None,
|
|
67
|
+
timeout: Optional[int] = None,
|
|
68
|
+
retry_attempts: Optional[int] = None,
|
|
69
|
+
output_format: Optional[str] = None,
|
|
70
|
+
verbose: Optional[bool] = None,
|
|
71
|
+
color: Optional[bool] = None,
|
|
72
|
+
wallet_path: Optional[str] = None,
|
|
73
|
+
default_wallet: Optional[str] = None,
|
|
74
|
+
encryption_enabled: Optional[bool] = None,
|
|
75
|
+
) -> None:
|
|
76
|
+
"""Handle configuration initialization."""
|
|
77
|
+
try:
|
|
78
|
+
# Get configuration path
|
|
79
|
+
config_path = get_config_path(config_file)
|
|
80
|
+
|
|
81
|
+
# Check if config exists and force flag
|
|
82
|
+
if config_path.exists() and not force:
|
|
83
|
+
if not confirm_config_overwrite(config_path):
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
# Prompt for configuration parameters (only missing ones)
|
|
87
|
+
request = prompt_config_init(
|
|
88
|
+
config_file=config_file,
|
|
89
|
+
force=force,
|
|
90
|
+
endpoint=endpoint,
|
|
91
|
+
ws_endpoint=ws_endpoint,
|
|
92
|
+
timeout=timeout,
|
|
93
|
+
retry_attempts=retry_attempts,
|
|
94
|
+
output_format=output_format,
|
|
95
|
+
verbose=verbose,
|
|
96
|
+
color=color,
|
|
97
|
+
wallet_path=wallet_path,
|
|
98
|
+
default_wallet=default_wallet,
|
|
99
|
+
encryption_enabled=encryption_enabled,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Initialize configuration
|
|
103
|
+
result = initialize_config(request)
|
|
104
|
+
|
|
105
|
+
# Create response
|
|
106
|
+
response = ConfigInitResponse(
|
|
107
|
+
config_path=str(result["config_path"]),
|
|
108
|
+
created=result["created"],
|
|
109
|
+
message=result["message"],
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Display result
|
|
113
|
+
display_config_init_result(response)
|
|
114
|
+
|
|
115
|
+
except Exception as e:
|
|
116
|
+
handle_and_display_error(e, "Configuration initialization failed")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def show_handler(
|
|
120
|
+
config_file: Optional[str] = None,
|
|
121
|
+
format_type: str = "table",
|
|
122
|
+
) -> None:
|
|
123
|
+
"""Handle configuration display."""
|
|
124
|
+
try:
|
|
125
|
+
# Create request
|
|
126
|
+
request = ConfigShowRequest(
|
|
127
|
+
config_file=config_file,
|
|
128
|
+
format_type=format_type,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Load configuration
|
|
132
|
+
result = load_config(request)
|
|
133
|
+
|
|
134
|
+
# Create response
|
|
135
|
+
response = ConfigShowResponse(
|
|
136
|
+
config_path=str(result["config_path"]),
|
|
137
|
+
config_data=result["config_data"],
|
|
138
|
+
exists=result["exists"],
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Display result
|
|
142
|
+
display_config_show_result(response, format_type)
|
|
143
|
+
|
|
144
|
+
except Exception as e:
|
|
145
|
+
handle_and_display_error(e, "Failed to show configuration")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def set_handler(
|
|
149
|
+
key: Optional[str] = None,
|
|
150
|
+
value: Optional[str] = None,
|
|
151
|
+
config_file: Optional[str] = None,
|
|
152
|
+
) -> None:
|
|
153
|
+
"""Handle configuration value setting."""
|
|
154
|
+
try:
|
|
155
|
+
if not _is_interactive():
|
|
156
|
+
if not key:
|
|
157
|
+
raise HTCLIError(
|
|
158
|
+
"Missing option '--key'. Provide --key in non-interactive mode."
|
|
159
|
+
)
|
|
160
|
+
if not value:
|
|
161
|
+
raise HTCLIError(
|
|
162
|
+
"Missing option '--value'. Provide --value in non-interactive mode."
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Prompt for parameters
|
|
166
|
+
request = prompt_config_set(key=key, value=value, config_file=config_file)
|
|
167
|
+
|
|
168
|
+
# Set configuration value
|
|
169
|
+
result = set_config_value(request)
|
|
170
|
+
|
|
171
|
+
# Check if value is unchanged (early exit)
|
|
172
|
+
if result.get("unchanged", False):
|
|
173
|
+
from ...ui.display import print_info
|
|
174
|
+
print_info(
|
|
175
|
+
f"Configuration key '{result['key']}' is already set to '{result['new_value']}'. No change needed.",
|
|
176
|
+
emoji=True,
|
|
177
|
+
)
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
# Create response
|
|
181
|
+
response = ConfigSetResponse(
|
|
182
|
+
config_path=str(result["config_path"]),
|
|
183
|
+
key=result["key"],
|
|
184
|
+
old_value=result["old_value"],
|
|
185
|
+
new_value=result["new_value"],
|
|
186
|
+
message=result["message"],
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Display result
|
|
190
|
+
display_config_set_result(response)
|
|
191
|
+
|
|
192
|
+
except (KeyboardInterrupt, typer.Abort):
|
|
193
|
+
# Re-raise cancellation exceptions to properly exit
|
|
194
|
+
raise
|
|
195
|
+
except Exception as e:
|
|
196
|
+
handle_and_display_error(e, "Failed to set configuration value")
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def get_handler(
|
|
200
|
+
key: Optional[str] = None,
|
|
201
|
+
config_file: Optional[str] = None,
|
|
202
|
+
) -> None:
|
|
203
|
+
"""Handle configuration value retrieval."""
|
|
204
|
+
try:
|
|
205
|
+
if not _is_interactive() and not key:
|
|
206
|
+
raise HTCLIError(
|
|
207
|
+
"Missing option '--key'. Provide --key in non-interactive mode."
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Prompt for parameters
|
|
211
|
+
request = prompt_config_get(key=key, config_file=config_file)
|
|
212
|
+
|
|
213
|
+
# Get configuration value
|
|
214
|
+
result = get_config_value(request)
|
|
215
|
+
|
|
216
|
+
# Create response
|
|
217
|
+
response = ConfigGetResponse(
|
|
218
|
+
key=result["key"],
|
|
219
|
+
value=result["value"],
|
|
220
|
+
value_type=result["value_type"],
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Display result
|
|
224
|
+
display_config_get_result(response)
|
|
225
|
+
|
|
226
|
+
except Exception as e:
|
|
227
|
+
handle_and_display_error(e, "Failed to get configuration value")
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def validate_handler(
|
|
231
|
+
config_file: Optional[str] = None,
|
|
232
|
+
) -> None:
|
|
233
|
+
"""Handle configuration validation."""
|
|
234
|
+
try:
|
|
235
|
+
# Create request
|
|
236
|
+
request = ConfigValidateRequest(
|
|
237
|
+
config_file=config_file,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# Validate configuration
|
|
241
|
+
result = validate_config(request)
|
|
242
|
+
|
|
243
|
+
# Create response
|
|
244
|
+
response = ConfigValidateResponse(
|
|
245
|
+
config_path=str(result["config_path"]),
|
|
246
|
+
valid=result["valid"],
|
|
247
|
+
errors=result.get("errors", []),
|
|
248
|
+
message=result["message"],
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# Display result
|
|
252
|
+
display_config_validate_result(response)
|
|
253
|
+
|
|
254
|
+
except Exception as e:
|
|
255
|
+
handle_and_display_error(e, "Configuration validation failed")
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def path_handler(
|
|
259
|
+
config_file: Optional[str] = None,
|
|
260
|
+
) -> None:
|
|
261
|
+
"""Handle configuration path display."""
|
|
262
|
+
try:
|
|
263
|
+
# Create request
|
|
264
|
+
request = ConfigPathRequest(
|
|
265
|
+
config_file=config_file,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Get configuration path
|
|
269
|
+
config_path = get_config_path(request.config_file)
|
|
270
|
+
|
|
271
|
+
# Create response
|
|
272
|
+
response = ConfigPathResponse(
|
|
273
|
+
config_path=str(config_path),
|
|
274
|
+
exists=config_path.exists(),
|
|
275
|
+
absolute_path=str(config_path.absolute()),
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
# Display result
|
|
279
|
+
display_config_path_result(response)
|
|
280
|
+
|
|
281
|
+
except Exception as e:
|
|
282
|
+
handle_and_display_error(e, "Failed to get configuration path")
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def edit_handler(
|
|
286
|
+
config_file: Optional[str] = None,
|
|
287
|
+
) -> None:
|
|
288
|
+
"""Handle configuration editing."""
|
|
289
|
+
try:
|
|
290
|
+
# Get configuration path
|
|
291
|
+
config_path = get_config_path(config_file)
|
|
292
|
+
|
|
293
|
+
# Check if config exists
|
|
294
|
+
if not config_path.exists():
|
|
295
|
+
raise FileNotFoundError(f"Configuration file not found: {config_path}")
|
|
296
|
+
|
|
297
|
+
# Load existing configuration for editing
|
|
298
|
+
load_request = ConfigShowRequest(
|
|
299
|
+
config_file=config_file,
|
|
300
|
+
format_type="yaml",
|
|
301
|
+
)
|
|
302
|
+
load_config(load_request)
|
|
303
|
+
|
|
304
|
+
# Prompt for new configuration based on existing
|
|
305
|
+
request = prompt_config_init(
|
|
306
|
+
config_file=config_file,
|
|
307
|
+
force=True, # Editing always overwrites
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# Save updated configuration
|
|
311
|
+
result = initialize_config(request)
|
|
312
|
+
|
|
313
|
+
# Create response
|
|
314
|
+
response = ConfigInitResponse(
|
|
315
|
+
config_path=str(result["config_path"]),
|
|
316
|
+
created=False, # Editing, not creating
|
|
317
|
+
message="Configuration updated successfully",
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
# Display result
|
|
321
|
+
display_config_init_result(response)
|
|
322
|
+
|
|
323
|
+
except Exception as e:
|
|
324
|
+
handle_and_display_error(e, "Configuration editing failed")
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
# Legacy aliases retained for older tests and callers.
|
|
328
|
+
def set_config_handler(
|
|
329
|
+
key: Optional[str] = None,
|
|
330
|
+
value: Optional[str] = None,
|
|
331
|
+
config_file: Optional[str] = None,
|
|
332
|
+
) -> None:
|
|
333
|
+
set_handler(key=key, value=value, config_file=config_file)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def get_config_handler(
|
|
337
|
+
key: Optional[str] = None,
|
|
338
|
+
config_file: Optional[str] = None,
|
|
339
|
+
) -> None:
|
|
340
|
+
get_handler(key=key, config_file=config_file)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def unset_config_handler(
|
|
344
|
+
key: Optional[str] = None,
|
|
345
|
+
config_file: Optional[str] = None,
|
|
346
|
+
) -> None:
|
|
347
|
+
raise NotImplementedError("Config unset is not implemented in this CLI.")
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Config command prompting logic.
|
|
3
|
+
|
|
4
|
+
Handles collecting user input for configuration operations using HTCLI UI components.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from ...errors.base import HTCLIError
|
|
12
|
+
from rich.prompt import Prompt
|
|
13
|
+
|
|
14
|
+
from ...models.requests import (
|
|
15
|
+
ConfigEditRequest,
|
|
16
|
+
ConfigGetRequest,
|
|
17
|
+
ConfigInitRequest,
|
|
18
|
+
ConfigPathRequest,
|
|
19
|
+
ConfigSetRequest,
|
|
20
|
+
ConfigShowRequest,
|
|
21
|
+
ConfigValidateRequest,
|
|
22
|
+
)
|
|
23
|
+
from ...ui.colors import info, warning
|
|
24
|
+
from ...ui.display import HTCLIConsole
|
|
25
|
+
from ...client.offchain.config import ConfigManager
|
|
26
|
+
from ...ui.prompts import (
|
|
27
|
+
prompt_for_confirmation,
|
|
28
|
+
prompt_for_optional,
|
|
29
|
+
prompt_for_required,
|
|
30
|
+
select_prompt,
|
|
31
|
+
)
|
|
32
|
+
from ...utils.validation import (
|
|
33
|
+
validate_retry_attempts,
|
|
34
|
+
validate_rpc_url_prompt,
|
|
35
|
+
validate_timeout,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
console = HTCLIConsole()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _is_interactive() -> bool:
|
|
42
|
+
"""Return whether stdin is interactive at call time."""
|
|
43
|
+
stdin = getattr(sys, "stdin", None)
|
|
44
|
+
return bool(stdin and stdin.isatty())
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def prompt_config_init(
|
|
48
|
+
config_file: Optional[str] = None,
|
|
49
|
+
force: bool = False,
|
|
50
|
+
endpoint: Optional[str] = None,
|
|
51
|
+
ws_endpoint: Optional[str] = None,
|
|
52
|
+
timeout: Optional[int] = None,
|
|
53
|
+
retry_attempts: Optional[int] = None,
|
|
54
|
+
output_format: Optional[str] = None,
|
|
55
|
+
verbose: Optional[bool] = None,
|
|
56
|
+
color: Optional[bool] = None,
|
|
57
|
+
wallet_path: Optional[str] = None,
|
|
58
|
+
default_wallet: Optional[str] = None,
|
|
59
|
+
encryption_enabled: Optional[bool] = None,
|
|
60
|
+
) -> ConfigInitRequest:
|
|
61
|
+
"""Prompt for configuration initialization parameters (only missing ones)."""
|
|
62
|
+
console.print(info("🔧 Configuration Initialization"))
|
|
63
|
+
console.print("Setting up your HTCLI configuration...\n")
|
|
64
|
+
|
|
65
|
+
# Network configuration
|
|
66
|
+
console.print(info("Network Configuration:"))
|
|
67
|
+
endpoint = prompt_for_optional(
|
|
68
|
+
"RPC Endpoint",
|
|
69
|
+
param_type=str,
|
|
70
|
+
default="wss://rpc.htcli.io/",
|
|
71
|
+
help_text="WebSocket URL for blockchain communication",
|
|
72
|
+
validator=validate_rpc_url_prompt,
|
|
73
|
+
provided_value=endpoint,
|
|
74
|
+
)
|
|
75
|
+
if endpoint:
|
|
76
|
+
# If endpoint was provided interactively, use it as default for ws_endpoint
|
|
77
|
+
if ws_endpoint is None:
|
|
78
|
+
ws_endpoint = endpoint
|
|
79
|
+
elif ws_endpoint is None:
|
|
80
|
+
# If endpoint was provided via switch but ws_endpoint wasn't, use endpoint
|
|
81
|
+
ws_endpoint = endpoint
|
|
82
|
+
|
|
83
|
+
ws_endpoint = prompt_for_optional(
|
|
84
|
+
"WebSocket Endpoint",
|
|
85
|
+
param_type=str,
|
|
86
|
+
default=endpoint or "wss://rpc.htcli.io/",
|
|
87
|
+
help_text="WebSocket endpoint for real-time communication",
|
|
88
|
+
validator=validate_rpc_url_prompt,
|
|
89
|
+
provided_value=ws_endpoint,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Handle timeout - convert to int if string, validate, then prompt if needed
|
|
93
|
+
timeout_value = timeout
|
|
94
|
+
if timeout is not None:
|
|
95
|
+
if isinstance(timeout, str):
|
|
96
|
+
try:
|
|
97
|
+
timeout_value = int(timeout)
|
|
98
|
+
except ValueError:
|
|
99
|
+
timeout_value = None
|
|
100
|
+
is_valid, error_msg = validate_timeout(timeout_value)
|
|
101
|
+
if not is_valid:
|
|
102
|
+
console.print(warning(f"Invalid timeout: {error_msg}"))
|
|
103
|
+
timeout_value = 30
|
|
104
|
+
|
|
105
|
+
if timeout_value is None and _is_interactive():
|
|
106
|
+
timeout_str = prompt_for_optional(
|
|
107
|
+
"Connection timeout (seconds)",
|
|
108
|
+
param_type=str,
|
|
109
|
+
default="30",
|
|
110
|
+
help_text="Timeout for network connections",
|
|
111
|
+
)
|
|
112
|
+
try:
|
|
113
|
+
timeout = int(timeout_str) if timeout_str else 30
|
|
114
|
+
except ValueError:
|
|
115
|
+
console.print(warning("Invalid timeout, using default: 30"))
|
|
116
|
+
timeout = 30
|
|
117
|
+
elif timeout_value is not None:
|
|
118
|
+
timeout = timeout_value
|
|
119
|
+
else:
|
|
120
|
+
timeout = 30
|
|
121
|
+
|
|
122
|
+
# Handle retry_attempts - convert to int if string, validate, then prompt if needed
|
|
123
|
+
retry_value = retry_attempts
|
|
124
|
+
if retry_attempts is not None:
|
|
125
|
+
if isinstance(retry_attempts, str):
|
|
126
|
+
try:
|
|
127
|
+
retry_value = int(retry_attempts)
|
|
128
|
+
except ValueError:
|
|
129
|
+
retry_value = None
|
|
130
|
+
is_valid, error_msg = validate_retry_attempts(retry_value)
|
|
131
|
+
if not is_valid:
|
|
132
|
+
console.print(warning(f"Invalid retry attempts: {error_msg}"))
|
|
133
|
+
retry_value = 3
|
|
134
|
+
|
|
135
|
+
if retry_value is None and _is_interactive():
|
|
136
|
+
retry_str = prompt_for_optional(
|
|
137
|
+
"Retry attempts",
|
|
138
|
+
param_type=str,
|
|
139
|
+
default="3",
|
|
140
|
+
help_text="Number of retry attempts for failed connections",
|
|
141
|
+
)
|
|
142
|
+
try:
|
|
143
|
+
retry_attempts = int(retry_str) if retry_str else 3
|
|
144
|
+
except ValueError:
|
|
145
|
+
console.print(warning("Invalid retry attempts, using default: 3"))
|
|
146
|
+
retry_attempts = 3
|
|
147
|
+
elif retry_value is not None:
|
|
148
|
+
retry_attempts = retry_value
|
|
149
|
+
else:
|
|
150
|
+
retry_attempts = 3
|
|
151
|
+
|
|
152
|
+
# Output configuration
|
|
153
|
+
if _is_interactive() and (output_format is None or verbose is None or color is None):
|
|
154
|
+
console.print(info("\nOutput Configuration:"))
|
|
155
|
+
|
|
156
|
+
if output_format is None:
|
|
157
|
+
output_format = Prompt.ask(
|
|
158
|
+
"Default output format",
|
|
159
|
+
choices=["table", "json", "csv"],
|
|
160
|
+
default="table",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
if verbose is None:
|
|
164
|
+
verbose = prompt_for_confirmation(
|
|
165
|
+
"Enable verbose output by default?", default=False
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
if color is None:
|
|
169
|
+
color = prompt_for_confirmation("Enable colored output?", default=True)
|
|
170
|
+
|
|
171
|
+
# Wallet configuration
|
|
172
|
+
if _is_interactive() and (
|
|
173
|
+
wallet_path is None or default_wallet is None or encryption_enabled is None
|
|
174
|
+
):
|
|
175
|
+
console.print(info("\nWallet Configuration:"))
|
|
176
|
+
|
|
177
|
+
if wallet_path is None:
|
|
178
|
+
wallet_path = prompt_for_optional(
|
|
179
|
+
"Wallet storage path",
|
|
180
|
+
param_type=str,
|
|
181
|
+
default="~/.htcli/wallets",
|
|
182
|
+
help_text="Directory where wallets will be stored",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if default_wallet is None:
|
|
186
|
+
default_wallet = prompt_for_optional(
|
|
187
|
+
"Default wallet name",
|
|
188
|
+
param_type=str,
|
|
189
|
+
default="default",
|
|
190
|
+
help_text="Default wallet to use",
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
if encryption_enabled is None:
|
|
194
|
+
encryption_enabled = prompt_for_confirmation(
|
|
195
|
+
"Enable wallet encryption?", default=True
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Ensure defaults are set
|
|
199
|
+
endpoint = endpoint or "wss://rpc.htcli.io/"
|
|
200
|
+
ws_endpoint = ws_endpoint or endpoint
|
|
201
|
+
timeout = timeout if timeout is not None else 30
|
|
202
|
+
retry_attempts = retry_attempts if retry_attempts is not None else 3
|
|
203
|
+
output_format = output_format or "table"
|
|
204
|
+
verbose = verbose if verbose is not None else False
|
|
205
|
+
color = color if color is not None else True
|
|
206
|
+
wallet_path = wallet_path or "~/.htcli/wallets"
|
|
207
|
+
default_wallet = default_wallet or "default"
|
|
208
|
+
encryption_enabled = encryption_enabled if encryption_enabled is not None else True
|
|
209
|
+
|
|
210
|
+
return ConfigInitRequest(
|
|
211
|
+
config_file=config_file,
|
|
212
|
+
force=force,
|
|
213
|
+
endpoint=endpoint,
|
|
214
|
+
ws_endpoint=ws_endpoint,
|
|
215
|
+
timeout=timeout,
|
|
216
|
+
retry_attempts=retry_attempts,
|
|
217
|
+
output_format=output_format,
|
|
218
|
+
verbose=verbose,
|
|
219
|
+
color=color,
|
|
220
|
+
wallet_path=wallet_path,
|
|
221
|
+
default_wallet=default_wallet,
|
|
222
|
+
encryption_enabled=encryption_enabled,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def prompt_config_show(
|
|
227
|
+
config_file: Optional[str] = None,
|
|
228
|
+
format_type: str = "table",
|
|
229
|
+
) -> ConfigShowRequest:
|
|
230
|
+
"""Prompt for configuration display parameters."""
|
|
231
|
+
return ConfigShowRequest(
|
|
232
|
+
config_file=config_file,
|
|
233
|
+
format_type=format_type,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _get_config_keys_for_selection(config_file: Optional[str] = None) -> list[str]:
|
|
238
|
+
"""Helper function to get flattened list of config keys for selection."""
|
|
239
|
+
# Build a list of available configuration keys for selection
|
|
240
|
+
manager = (
|
|
241
|
+
ConfigManager(Path(config_file).parent) if config_file else ConfigManager()
|
|
242
|
+
)
|
|
243
|
+
if config_file:
|
|
244
|
+
manager.config_file = Path(config_file).expanduser()
|
|
245
|
+
|
|
246
|
+
result = manager.load_config()
|
|
247
|
+
config_data = result["data"]
|
|
248
|
+
|
|
249
|
+
def flatten(prefix: str, data: dict, out: list[str]):
|
|
250
|
+
for k, v in data.items():
|
|
251
|
+
path = f"{prefix}.{k}" if prefix else k
|
|
252
|
+
if isinstance(v, dict):
|
|
253
|
+
flatten(path, v, out)
|
|
254
|
+
else:
|
|
255
|
+
out.append(path)
|
|
256
|
+
|
|
257
|
+
keys: list[str] = []
|
|
258
|
+
flatten("", config_data, keys)
|
|
259
|
+
keys.sort()
|
|
260
|
+
return keys
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def prompt_config_set(
|
|
264
|
+
key: Optional[str] = None,
|
|
265
|
+
value: Optional[str] = None,
|
|
266
|
+
config_file: Optional[str] = None,
|
|
267
|
+
) -> ConfigSetRequest:
|
|
268
|
+
"""Prompt for configuration value setting."""
|
|
269
|
+
# Validate key if provided (must be non-empty)
|
|
270
|
+
if key is not None:
|
|
271
|
+
if not key or not key.strip():
|
|
272
|
+
console.print(warning("Configuration key cannot be empty"))
|
|
273
|
+
key = None
|
|
274
|
+
|
|
275
|
+
if not key:
|
|
276
|
+
keys = _get_config_keys_for_selection(config_file)
|
|
277
|
+
choices = [(k, k, k) for k in keys]
|
|
278
|
+
console.print(info("Select configuration key to set"))
|
|
279
|
+
key = select_prompt("Enter number or key", choices)
|
|
280
|
+
|
|
281
|
+
# Validate value if provided (must be non-empty)
|
|
282
|
+
if value is not None:
|
|
283
|
+
if not value or not value.strip():
|
|
284
|
+
console.print(warning("Configuration value cannot be empty"))
|
|
285
|
+
value = None
|
|
286
|
+
|
|
287
|
+
if not value:
|
|
288
|
+
value = prompt_for_required(
|
|
289
|
+
f"Value for '{key}'", param_type=str, help_text="New value to set", provided_value=value
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
return ConfigSetRequest(
|
|
293
|
+
config_file=config_file,
|
|
294
|
+
key=key,
|
|
295
|
+
value=value,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def prompt_config_get(
|
|
300
|
+
key: Optional[str] = None,
|
|
301
|
+
config_file: Optional[str] = None,
|
|
302
|
+
) -> ConfigGetRequest:
|
|
303
|
+
"""Prompt for configuration value retrieval."""
|
|
304
|
+
# Validate key if provided (must be non-empty)
|
|
305
|
+
if key is not None:
|
|
306
|
+
if not key or not key.strip():
|
|
307
|
+
console.print(warning("Configuration key cannot be empty"))
|
|
308
|
+
key = None
|
|
309
|
+
|
|
310
|
+
if not key:
|
|
311
|
+
keys = _get_config_keys_for_selection(config_file)
|
|
312
|
+
choices = [(k, k, k) for k in keys]
|
|
313
|
+
console.print(info("Select configuration key to get"))
|
|
314
|
+
key = select_prompt("Enter number or key", choices)
|
|
315
|
+
|
|
316
|
+
return ConfigGetRequest(
|
|
317
|
+
config_file=config_file,
|
|
318
|
+
key=key,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def prompt_config_validate(
|
|
323
|
+
config_file: Optional[str] = None,
|
|
324
|
+
) -> ConfigValidateRequest:
|
|
325
|
+
"""Prompt for configuration validation."""
|
|
326
|
+
return ConfigValidateRequest(
|
|
327
|
+
config_file=config_file,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def prompt_config_path(
|
|
332
|
+
config_file: Optional[str] = None,
|
|
333
|
+
) -> ConfigPathRequest:
|
|
334
|
+
"""Prompt for configuration path display."""
|
|
335
|
+
return ConfigPathRequest(
|
|
336
|
+
config_file=config_file,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def prompt_config_edit(
|
|
341
|
+
config_file: Optional[str] = None,
|
|
342
|
+
) -> ConfigEditRequest:
|
|
343
|
+
"""Prompt for configuration editing."""
|
|
344
|
+
return ConfigEditRequest(
|
|
345
|
+
config_file=config_file,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def confirm_config_overwrite(config_path: Path) -> bool:
|
|
350
|
+
"""Confirm overwriting existing configuration."""
|
|
351
|
+
console.print(warning(f"⚠️ Configuration file already exists: {config_path}"))
|
|
352
|
+
return prompt_for_confirmation("Overwrite existing configuration?", default=False)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def confirm_config_changes() -> bool:
|
|
356
|
+
"""Confirm saving configuration changes."""
|
|
357
|
+
return prompt_for_confirmation("Save configuration changes?", default=True)
|