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.
Files changed (140) hide show
  1. htcli-1.1.0.dist-info/METADATA +509 -0
  2. htcli-1.1.0.dist-info/RECORD +140 -0
  3. htcli-1.1.0.dist-info/WHEEL +4 -0
  4. htcli-1.1.0.dist-info/entry_points.txt +2 -0
  5. htcli-1.1.0.dist-info/licenses/LICENSE +21 -0
  6. src/__init__.py +0 -0
  7. src/htcli/__init__.py +5 -0
  8. src/htcli/client/__init__.py +338 -0
  9. src/htcli/client/extrinsics/__init__.py +26 -0
  10. src/htcli/client/extrinsics/base.py +487 -0
  11. src/htcli/client/extrinsics/consensus.py +79 -0
  12. src/htcli/client/extrinsics/governance.py +714 -0
  13. src/htcli/client/extrinsics/identity.py +490 -0
  14. src/htcli/client/extrinsics/node.py +1054 -0
  15. src/htcli/client/extrinsics/overwatch.py +401 -0
  16. src/htcli/client/extrinsics/staking.py +1504 -0
  17. src/htcli/client/extrinsics/subnet.py +2218 -0
  18. src/htcli/client/extrinsics/validator.py +203 -0
  19. src/htcli/client/extrinsics/wallet.py +323 -0
  20. src/htcli/client/offchain/__init__.py +10 -0
  21. src/htcli/client/offchain/backup.py +385 -0
  22. src/htcli/client/offchain/config.py +541 -0
  23. src/htcli/client/offchain/wallet.py +839 -0
  24. src/htcli/client/rpc/__init__.py +20 -0
  25. src/htcli/client/rpc/chain.py +568 -0
  26. src/htcli/client/rpc/node.py +783 -0
  27. src/htcli/client/rpc/overwatch.py +680 -0
  28. src/htcli/client/rpc/staking.py +216 -0
  29. src/htcli/client/rpc/subnet.py +2104 -0
  30. src/htcli/client/rpc/wallet.py +912 -0
  31. src/htcli/commands/__init__.py +31 -0
  32. src/htcli/commands/chain/__init__.py +66 -0
  33. src/htcli/commands/chain/display.py +204 -0
  34. src/htcli/commands/chain/handlers.py +260 -0
  35. src/htcli/commands/config/__init__.py +158 -0
  36. src/htcli/commands/config/display.py +353 -0
  37. src/htcli/commands/config/handlers.py +347 -0
  38. src/htcli/commands/config/prompts.py +357 -0
  39. src/htcli/commands/consensus/__init__.py +61 -0
  40. src/htcli/commands/consensus/handlers.py +100 -0
  41. src/htcli/commands/governance/__init__.py +49 -0
  42. src/htcli/commands/governance/handlers.py +81 -0
  43. src/htcli/commands/node/__init__.py +304 -0
  44. src/htcli/commands/node/display.py +749 -0
  45. src/htcli/commands/node/error_handling.py +470 -0
  46. src/htcli/commands/node/handlers.py +844 -0
  47. src/htcli/commands/node/prompts.py +346 -0
  48. src/htcli/commands/overwatch/__init__.py +219 -0
  49. src/htcli/commands/overwatch/display.py +396 -0
  50. src/htcli/commands/overwatch/error_handling.py +276 -0
  51. src/htcli/commands/overwatch/handlers.py +443 -0
  52. src/htcli/commands/overwatch/prompts.py +359 -0
  53. src/htcli/commands/stake/__init__.py +736 -0
  54. src/htcli/commands/stake/display.py +1103 -0
  55. src/htcli/commands/stake/error_handling.py +425 -0
  56. src/htcli/commands/stake/handlers.py +1902 -0
  57. src/htcli/commands/stake/prompts.py +1080 -0
  58. src/htcli/commands/subnet/__init__.py +639 -0
  59. src/htcli/commands/subnet/display.py +801 -0
  60. src/htcli/commands/subnet/error_handling.py +524 -0
  61. src/htcli/commands/subnet/handlers.py +2855 -0
  62. src/htcli/commands/subnet/prompts.py +1225 -0
  63. src/htcli/commands/validator/__init__.py +192 -0
  64. src/htcli/commands/validator/display.py +54 -0
  65. src/htcli/commands/validator/handlers.py +340 -0
  66. src/htcli/commands/wallet/__init__.py +546 -0
  67. src/htcli/commands/wallet/display.py +806 -0
  68. src/htcli/commands/wallet/error_handling.py +210 -0
  69. src/htcli/commands/wallet/handlers.py +3040 -0
  70. src/htcli/commands/wallet/prompts.py +1518 -0
  71. src/htcli/config.py +184 -0
  72. src/htcli/dependencies.py +186 -0
  73. src/htcli/errors/__init__.py +63 -0
  74. src/htcli/errors/base.py +141 -0
  75. src/htcli/errors/display.py +20 -0
  76. src/htcli/errors/handlers.py +710 -0
  77. src/htcli/main.py +343 -0
  78. src/htcli/models/__init__.py +21 -0
  79. src/htcli/models/enums/enum_types.py +35 -0
  80. src/htcli/models/errors.py +103 -0
  81. src/htcli/models/requests/__init__.py +197 -0
  82. src/htcli/models/requests/config.py +70 -0
  83. src/htcli/models/requests/consensus.py +19 -0
  84. src/htcli/models/requests/governance.py +38 -0
  85. src/htcli/models/requests/identity.py +51 -0
  86. src/htcli/models/requests/key.py +22 -0
  87. src/htcli/models/requests/node.py +91 -0
  88. src/htcli/models/requests/overwatch.py +64 -0
  89. src/htcli/models/requests/staking.py +580 -0
  90. src/htcli/models/requests/subnet.py +195 -0
  91. src/htcli/models/requests/validator.py +139 -0
  92. src/htcli/models/requests/wallet.py +118 -0
  93. src/htcli/models/responses/__init__.py +147 -0
  94. src/htcli/models/responses/base.py +18 -0
  95. src/htcli/models/responses/chain.py +39 -0
  96. src/htcli/models/responses/config.py +58 -0
  97. src/htcli/models/responses/identity.py +102 -0
  98. src/htcli/models/responses/overwatch.py +51 -0
  99. src/htcli/models/responses/staking.py +502 -0
  100. src/htcli/models/responses/subnet.py +856 -0
  101. src/htcli/models/responses/wallet.py +185 -0
  102. src/htcli/ui/__init__.py +87 -0
  103. src/htcli/ui/colors.py +309 -0
  104. src/htcli/ui/components/__init__.py +60 -0
  105. src/htcli/ui/components/panels.py +174 -0
  106. src/htcli/ui/components/progress.py +166 -0
  107. src/htcli/ui/components/spinners.py +92 -0
  108. src/htcli/ui/components/tables.py +809 -0
  109. src/htcli/ui/components/trees.py +721 -0
  110. src/htcli/ui/display.py +336 -0
  111. src/htcli/ui/prompts.py +870 -0
  112. src/htcli/utils/__init__.py +76 -0
  113. src/htcli/utils/blockchain/__init__.py +75 -0
  114. src/htcli/utils/blockchain/formatting.py +368 -0
  115. src/htcli/utils/blockchain/patches.py +286 -0
  116. src/htcli/utils/blockchain/peer_id.py +186 -0
  117. src/htcli/utils/blockchain/staking.py +448 -0
  118. src/htcli/utils/blockchain/type_registry.py +1373 -0
  119. src/htcli/utils/blockchain/validation.py +179 -0
  120. src/htcli/utils/cache.py +613 -0
  121. src/htcli/utils/constants.py +38 -0
  122. src/htcli/utils/legacy/__init__.py +12 -0
  123. src/htcli/utils/legacy/colors.py +311 -0
  124. src/htcli/utils/legacy/crypto.py +1176 -0
  125. src/htcli/utils/legacy/formatting.py +452 -0
  126. src/htcli/utils/legacy/interactive.py +306 -0
  127. src/htcli/utils/legacy/subnet_manifest.py +265 -0
  128. src/htcli/utils/legacy/validation.py +488 -0
  129. src/htcli/utils/logging.py +183 -0
  130. src/htcli/utils/network/__init__.py +20 -0
  131. src/htcli/utils/network/subnet.py +344 -0
  132. src/htcli/utils/prompts.py +27 -0
  133. src/htcli/utils/scale_codec.py +155 -0
  134. src/htcli/utils/validation/__init__.py +57 -0
  135. src/htcli/utils/validation/prompt_validators.py +267 -0
  136. src/htcli/utils/wallet/__init__.py +65 -0
  137. src/htcli/utils/wallet/auth.py +151 -0
  138. src/htcli/utils/wallet/core.py +1069 -0
  139. src/htcli/utils/wallet/crypto.py +1615 -0
  140. 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)