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,870 @@
1
+ """
2
+ HTCLI interactive prompts and user input handling.
3
+ Provides consistent prompt styles and validation.
4
+ """
5
+
6
+ import getpass
7
+ from typing import Any, Callable, Optional, Union
8
+
9
+ import typer
10
+ from rich.console import Console
11
+ from rich.panel import Panel
12
+ from rich.prompt import Confirm, FloatPrompt, IntPrompt, Prompt
13
+ from rich.text import Text
14
+
15
+ from .display import get_console, print_error
16
+
17
+
18
+ class HTCLIPrompt:
19
+ """Enhanced prompt system for HTCLI."""
20
+
21
+ def __init__(self, console: Console = None):
22
+ self.console = console or get_console()
23
+
24
+ def text(
25
+ self,
26
+ message: str,
27
+ default: str = None,
28
+ placeholder: str = None,
29
+ validator: Callable[[str], bool] = None,
30
+ error_message: str = "Invalid input. Please try again.",
31
+ style: str = "htcli.prompt",
32
+ required: bool = True,
33
+ ) -> str:
34
+ """Prompt for text input with validation."""
35
+ if placeholder and not default:
36
+ message = f"{message} [{placeholder}]"
37
+
38
+ while True:
39
+ try:
40
+ result = Prompt.ask(
41
+ Text(message, style=style),
42
+ default=default,
43
+ console=self.console.console,
44
+ )
45
+ # Normalize None -> empty string for easier handling
46
+ value = "" if result is None else str(result)
47
+
48
+ # If required, enforce non-empty input
49
+ if required and not value.strip():
50
+ print_error("Input is required.")
51
+ continue
52
+
53
+ # If optional and empty, return empty string to signal 'keep current'
54
+ if not required and not value.strip():
55
+ return ""
56
+
57
+ # Only run validator when user entered a non-empty value
58
+ if validator and value.strip() and not validator(value):
59
+ print_error(error_message)
60
+ continue
61
+
62
+ return value.strip()
63
+
64
+ except (KeyboardInterrupt, EOFError):
65
+ self.console.print("\n[htcli.warning]Operation cancelled.[/]")
66
+ raise typer.Abort()
67
+ except Exception as e:
68
+ # Don't continue on EOF errors - they should be handled above
69
+ error_msg = str(e).lower()
70
+ if "eof" in error_msg or "end of file" in error_msg:
71
+ self.console.print("\n[htcli.warning]Operation cancelled.[/]")
72
+ raise typer.Abort()
73
+ # Don't loop indefinitely on errors - abort
74
+ print_error(f"Input error: {str(e)}" if str(e) else "Unknown error")
75
+ raise typer.Abort()
76
+
77
+ def password(
78
+ self,
79
+ message: str = "Password",
80
+ confirm: bool = False,
81
+ min_length: int = 0,
82
+ style: str = "htcli.prompt",
83
+ ) -> str:
84
+ """Prompt for password input."""
85
+ while True:
86
+ try:
87
+ password = getpass.getpass(prompt=f"{message}: ")
88
+
89
+ if min_length > 0 and len(password) < min_length:
90
+ print_error(f"Password must be at least {min_length} characters.")
91
+ continue
92
+
93
+ if confirm:
94
+ confirm_password = getpass.getpass(prompt="Confirm password: ")
95
+ if password != confirm_password:
96
+ print_error("Passwords do not match. Please try again.")
97
+ continue
98
+
99
+ return password
100
+
101
+ except (KeyboardInterrupt, EOFError):
102
+ self.console.print("\n[htcli.warning]Operation cancelled.[/]")
103
+ raise typer.Abort()
104
+
105
+ def confirm(
106
+ self,
107
+ message: str,
108
+ default: bool = False,
109
+ style: str = "htcli.prompt",
110
+ ) -> bool:
111
+ """Prompt for yes/no confirmation."""
112
+ try:
113
+ return Confirm.ask(
114
+ Text(message, style=style),
115
+ default=default,
116
+ console=self.console.console,
117
+ )
118
+ except (KeyboardInterrupt, EOFError):
119
+ self.console.print("\n[htcli.warning]Operation cancelled.[/]")
120
+ raise
121
+
122
+ def select(
123
+ self,
124
+ message: str,
125
+ choices: list[Union[str, tuple]],
126
+ default: Any = None,
127
+ style: str = "htcli.prompt",
128
+ show_choices: bool = True,
129
+ ) -> str:
130
+ """Prompt for selection from a list of choices."""
131
+ # Convert choices to a consistent format
132
+ choice_map = {}
133
+ display_choices = []
134
+
135
+ for i, choice in enumerate(choices):
136
+ if isinstance(choice, tuple):
137
+ key, display, value = (
138
+ choice if len(choice) == 3 else (*choice, choice[0])
139
+ )
140
+ else:
141
+ key = display = value = choice
142
+
143
+ choice_map[str(i + 1)] = value
144
+ choice_map[key.lower()] = value
145
+ display_choices.append(
146
+ f"[htcli.value]{i + 1}[/]. [htcli.highlight]{display}[/]"
147
+ )
148
+
149
+ if show_choices:
150
+ self.console.print("\n[htcli.subtitle]Available options:[/]")
151
+ for choice in display_choices:
152
+ self.console.print(f" {choice}")
153
+ self.console.print()
154
+
155
+ while True:
156
+ try:
157
+ result = self.text(
158
+ message,
159
+ default=str(default) if default else None,
160
+ required=True,
161
+ style=style,
162
+ )
163
+
164
+ # Try to match the input
165
+ if result in choice_map:
166
+ return choice_map[result]
167
+ elif result.lower() in choice_map:
168
+ return choice_map[result.lower()]
169
+ else:
170
+ print_error(
171
+ "Invalid selection. Please choose from the available options."
172
+ )
173
+ continue
174
+
175
+ except typer.Abort:
176
+ # Re-raise typer.Abort to let it propagate
177
+ raise
178
+ except (KeyboardInterrupt, EOFError):
179
+ self.console.print("\n[htcli.warning]Operation cancelled.[/]")
180
+ raise typer.Abort()
181
+ except Exception as e:
182
+ # Don't continue on EOF errors - they should be handled above
183
+ error_msg = str(e).lower()
184
+ if "eof" in error_msg or "end of file" in error_msg:
185
+ self.console.print("\n[htcli.warning]Operation cancelled.[/]")
186
+ raise typer.Abort()
187
+ # Check if it's a cancellation error from typer
188
+ if isinstance(e, typer.Abort):
189
+ raise
190
+ print_error(f"Selection error: {str(e)}")
191
+ continue
192
+
193
+ def integer(
194
+ self,
195
+ message: str,
196
+ default: int = None,
197
+ min_value: int = None,
198
+ max_value: int = None,
199
+ style: str = "htcli.prompt",
200
+ allow_none: bool = False,
201
+ ) -> Optional[int]:
202
+ """Prompt for integer input with validation.
203
+
204
+ Args:
205
+ message: Prompt message
206
+ default: Default value
207
+ min_value: Minimum allowed value
208
+ max_value: Maximum allowed value
209
+ style: Prompt style
210
+ allow_none: If True, allow empty input and return None
211
+
212
+ Returns:
213
+ int if input provided, Optional[int] (None) if allow_none=True and input is empty
214
+ """
215
+ while True:
216
+ try:
217
+ result = IntPrompt.ask(
218
+ Text(message, style=style),
219
+ default=default,
220
+ console=self.console.console,
221
+ )
222
+
223
+ # Handle None result (empty input)
224
+ if result is None:
225
+ if allow_none:
226
+ return None
227
+ print_error("Input is required. Please enter a valid integer.")
228
+ continue
229
+
230
+ if min_value is not None and result < min_value:
231
+ print_error(f"Value must be at least {min_value}.")
232
+ continue
233
+
234
+ if max_value is not None and result > max_value:
235
+ print_error(f"Value must be at most {max_value}.")
236
+ continue
237
+
238
+ return result
239
+
240
+ except (KeyboardInterrupt, EOFError):
241
+ self.console.print("\n[htcli.warning]Operation cancelled.[/]")
242
+ raise typer.Abort()
243
+ except Exception as e:
244
+ print_error(f"Input error: {str(e)}" if str(e) else "Unknown error")
245
+ raise typer.Abort()
246
+
247
+ # Backwards-compatibility helpers (legacy code still calls these)
248
+ def integer_prompt(
249
+ self,
250
+ message: str,
251
+ default: int = None,
252
+ min_value: int = None,
253
+ max_value: int = None,
254
+ style: str = "htcli.prompt",
255
+ allow_none: bool = False,
256
+ ) -> Optional[int]:
257
+ """Alias for `integer` to support existing prompt usage."""
258
+ return self.integer(
259
+ message,
260
+ default=default,
261
+ min_value=min_value,
262
+ max_value=max_value,
263
+ style=style,
264
+ allow_none=allow_none,
265
+ )
266
+
267
+ def text_prompt(
268
+ self,
269
+ message: str,
270
+ default: str = None,
271
+ placeholder: str = None,
272
+ validator: Callable[[str], bool] = None,
273
+ error_message: str = "Invalid input. Please try again.",
274
+ style: str = "htcli.prompt",
275
+ required: bool = True,
276
+ ) -> str:
277
+ """Alias for `text` to support existing prompt usage."""
278
+ return self.text(
279
+ message,
280
+ default=default,
281
+ placeholder=placeholder,
282
+ validator=validator,
283
+ error_message=error_message,
284
+ style=style,
285
+ required=required,
286
+ )
287
+
288
+ def float(
289
+ self,
290
+ message: str,
291
+ default: float = None,
292
+ min_value: float = None,
293
+ max_value: float = None,
294
+ style: str = "htcli.prompt",
295
+ ) -> float:
296
+ """Prompt for float input with validation."""
297
+ while True:
298
+ try:
299
+ result = FloatPrompt.ask(
300
+ Text(message, style=style),
301
+ default=default,
302
+ console=self.console.console,
303
+ )
304
+
305
+ if result is None:
306
+ if default is not None:
307
+ result = default
308
+ else:
309
+ print_error("Value is required.")
310
+ continue
311
+
312
+ if min_value is not None and result < min_value:
313
+ print_error(f"Value must be at least {min_value}.")
314
+ continue
315
+
316
+ if max_value is not None and result > max_value:
317
+ print_error(f"Value must be at most {max_value}.")
318
+ continue
319
+
320
+ return result
321
+
322
+ except (KeyboardInterrupt, EOFError):
323
+ self.console.print("\n[htcli.warning]Operation cancelled.[/]")
324
+ raise typer.Abort()
325
+ except Exception as e:
326
+ print_error(f"Input error: {str(e)}" if str(e) else "Unknown error")
327
+ raise typer.Abort()
328
+
329
+ def address(
330
+ self,
331
+ message: str = "Enter blockchain address",
332
+ default: str = None,
333
+ allow_empty: bool = False,
334
+ style: str = "htcli.prompt",
335
+ ) -> str:
336
+ """Prompt for blockchain address with validation."""
337
+
338
+ def validate_address(addr: str) -> bool:
339
+ if not addr and allow_empty:
340
+ return True
341
+ # Basic validation - can be enhanced
342
+ if len(addr) < 20:
343
+ return False
344
+ if addr.startswith("0x") and len(addr) == 42:
345
+ return True # EVM address
346
+ if len(addr) in [47, 48] and addr.startswith("5"):
347
+ return True # Substrate address
348
+ return False
349
+
350
+ return self.text(
351
+ message,
352
+ default=default,
353
+ validator=validate_address,
354
+ error_message="Invalid blockchain address format.",
355
+ style=style,
356
+ required=not allow_empty,
357
+ )
358
+
359
+ def amount(
360
+ self,
361
+ message: str = "Enter amount",
362
+ currency: str = "TENSOR",
363
+ min_amount: float = 0.0,
364
+ max_amount: float = None,
365
+ default: float = None,
366
+ decimals: int = 6,
367
+ style: str = "htcli.prompt",
368
+ ) -> float:
369
+ """Prompt for token amount with validation."""
370
+ full_message = f"{message} ({currency})"
371
+
372
+ return self.float(
373
+ full_message,
374
+ default=default,
375
+ min_value=min_amount,
376
+ max_value=max_amount,
377
+ style=style,
378
+ )
379
+
380
+ def wallet_selection(
381
+ self,
382
+ wallets: list[dict[str, Any]],
383
+ message: str = "Select wallet",
384
+ style: str = "htcli.prompt",
385
+ ) -> dict[str, Any]:
386
+ """Prompt for wallet selection from available wallets."""
387
+ if not wallets:
388
+ raise ValueError("No wallets available for selection.")
389
+
390
+ choices = []
391
+ for i, wallet in enumerate(wallets):
392
+ name = wallet.get("name", f"Wallet {i + 1}")
393
+ address = wallet.get("address", "Unknown")
394
+ display_addr = (
395
+ f"{address[:8]}...{address[-8:]}" if len(address) > 16 else address
396
+ )
397
+ choices.append((str(i + 1), f"{name} ({display_addr})", wallet))
398
+
399
+ self.console.print("\n[htcli.subtitle]Available wallets:[/]")
400
+ for i, wallet in enumerate(wallets):
401
+ name = wallet.get("name", f"Wallet {i + 1}")
402
+ address = wallet.get("address", "Unknown")
403
+ balance = wallet.get("balance", "Unknown")
404
+ self.console.print(
405
+ f" [htcli.value]{i + 1}[/]. [htcli.highlight]{name}[/] "
406
+ f"([htcli.address]{address[:8]}...{address[-8:]}[/]) "
407
+ f"- [htcli.amount]{balance} TENSOR[/]"
408
+ )
409
+ self.console.print()
410
+
411
+ while True:
412
+ try:
413
+ selection = self.integer(
414
+ message,
415
+ min_value=1,
416
+ max_value=len(wallets),
417
+ style=style,
418
+ )
419
+ return wallets[selection - 1]
420
+
421
+ except (KeyboardInterrupt, EOFError):
422
+ self.console.print("\n[htcli.warning]Operation cancelled.[/]")
423
+ raise typer.Abort()
424
+
425
+ def confirmation_panel(
426
+ self,
427
+ title: str,
428
+ details: dict[str, Any],
429
+ warning: str = None,
430
+ require_confirmation: bool = True,
431
+ ) -> bool:
432
+ """Show a confirmation panel with transaction details."""
433
+ # Create content for the panel
434
+ content = []
435
+ for key, value in details.items():
436
+ if key.lower() in ["amount", "balance", "stake"]:
437
+ formatted_value = f"[htcli.amount]{value}[/]"
438
+ elif key.lower() in ["address", "to", "from", "account"]:
439
+ formatted_value = f"[htcli.address]{value}[/]"
440
+ elif key.lower() in ["fee", "gas"]:
441
+ formatted_value = f"[htcli.fee]{value}[/]"
442
+ else:
443
+ formatted_value = f"[htcli.value]{value}[/]"
444
+
445
+ content.append(f"[htcli.subtitle]{key}:[/] {formatted_value}")
446
+
447
+ panel_content = "\n".join(content)
448
+
449
+ if warning:
450
+ panel_content += f"\n\n[htcli.warning]⚠️ {warning}[/]"
451
+
452
+ # Display the confirmation panel
453
+ panel = Panel(
454
+ panel_content,
455
+ title=f"[htcli.title]{title}[/]",
456
+ border_style="htcli.primary",
457
+ padding=(1, 2),
458
+ )
459
+ self.console.console.print(panel)
460
+
461
+ if not require_confirmation:
462
+ return True
463
+
464
+ return self.confirm(
465
+ "\nProceed with this transaction?",
466
+ default=False,
467
+ )
468
+
469
+
470
+ # Global prompt instance
471
+ _prompt = HTCLIPrompt()
472
+
473
+
474
+ # Convenience functions
475
+ def text_prompt(
476
+ message: str, default: str = None, validator: Callable[[str], bool] = None, **kwargs
477
+ ) -> str:
478
+ """Quick text prompt."""
479
+ return _prompt.text(message, default=default, validator=validator, **kwargs)
480
+
481
+
482
+ def password_prompt(
483
+ message: str = "Password",
484
+ confirm: bool = False,
485
+ min_length: int = 8,
486
+ ) -> str:
487
+ """Quick password prompt."""
488
+ return _prompt.password(message, confirm=confirm, min_length=min_length)
489
+
490
+
491
+ def confirm_prompt(message: str, default: bool = False) -> bool:
492
+ """Quick confirmation prompt."""
493
+ return _prompt.confirm(message, default=default)
494
+
495
+
496
+ def integer_prompt(
497
+ message: str,
498
+ default: int = None,
499
+ min_value: int = None,
500
+ max_value: int = None,
501
+ allow_none: bool = False,
502
+ ) -> Optional[int]:
503
+ """Quick integer prompt.
504
+
505
+ Args:
506
+ message: Prompt message
507
+ default: Default value
508
+ min_value: Minimum allowed value
509
+ max_value: Maximum allowed value
510
+ allow_none: If True, allow empty input and return None
511
+ """
512
+ return _prompt.integer(
513
+ message,
514
+ default=default,
515
+ min_value=min_value,
516
+ max_value=max_value,
517
+ allow_none=allow_none,
518
+ )
519
+
520
+
521
+ def select_prompt(
522
+ message: str,
523
+ choices: list[Union[str, tuple]],
524
+ default: Any = None,
525
+ ) -> str:
526
+ """Quick selection prompt."""
527
+ return _prompt.select(message, choices, default=default)
528
+
529
+
530
+ def amount_prompt(
531
+ message: str = "Enter amount",
532
+ currency: str = "TENSOR",
533
+ min_amount: float = 0.0,
534
+ max_amount: float = None,
535
+ default: float = None,
536
+ ) -> float:
537
+ """Quick amount prompt."""
538
+ return _prompt.amount(message, currency, min_amount, max_amount, default=default)
539
+
540
+
541
+ def address_prompt(
542
+ message: str = "Enter blockchain address",
543
+ default: str = None,
544
+ allow_empty: bool = False,
545
+ ) -> str:
546
+ """Quick address prompt."""
547
+ return _prompt.address(message, default=default, allow_empty=allow_empty)
548
+
549
+
550
+ def wallet_selection_prompt(
551
+ wallets: list[dict[str, Any]],
552
+ message: str = "Select wallet",
553
+ ) -> dict[str, Any]:
554
+ """Quick wallet selection prompt."""
555
+ return _prompt.wallet_selection(wallets, message)
556
+
557
+
558
+ def confirmation_panel_prompt(
559
+ title: str,
560
+ details: dict[str, Any],
561
+ warning: str = None,
562
+ require_confirmation: bool = True,
563
+ ) -> bool:
564
+ """Quick confirmation panel prompt."""
565
+ return _prompt.confirmation_panel(title, details, warning, require_confirmation)
566
+
567
+
568
+ # Additional utility functions for complex prompting scenarios
569
+ def prompt_with_validation(
570
+ param_name: str,
571
+ provided_value: Optional[Any],
572
+ param_type: type,
573
+ validator: Optional[Callable[[Any], tuple[bool, Optional[str]]]] = None,
574
+ prompt_func: Optional[Callable] = None,
575
+ **prompt_kwargs
576
+ ) -> Any:
577
+ """
578
+ Helper function that validates provided value and prompts if invalid or None.
579
+
580
+ Args:
581
+ param_name: Name of the parameter
582
+ provided_value: Value provided via CLI (may be None)
583
+ param_type: Expected type (str, int, float, bool)
584
+ validator: Optional validation function that returns (bool, Optional[str])
585
+ prompt_func: Function to call for prompting (defaults to prompt_for_required)
586
+ **prompt_kwargs: Additional arguments to pass to prompt function
587
+
588
+ Returns:
589
+ Validated value from provided_value or from interactive prompt
590
+ """
591
+ # Validate provided value if it exists
592
+ if provided_value is not None and validator:
593
+ is_valid, error_msg = validator(provided_value)
594
+ if not is_valid:
595
+ print_error(f"Invalid {param_name}: {error_msg}")
596
+ provided_value = None # Force interactive prompt
597
+
598
+ # Prompt if None (either originally or after failed validation)
599
+ if provided_value is None:
600
+ if prompt_func:
601
+ return prompt_func(param_name, param_type=param_type, **prompt_kwargs)
602
+ else:
603
+ # Default to prompt_for_required
604
+ return prompt_for_required(
605
+ param_name, param_type=param_type, **prompt_kwargs
606
+ )
607
+
608
+ return provided_value
609
+
610
+
611
+ def prompt_for_required(
612
+ param_name: str,
613
+ param_type: type,
614
+ help_text: str = "",
615
+ validator: Optional[Callable] = None,
616
+ default: Optional[Any] = None,
617
+ allow_empty: bool = False,
618
+ provided_value: Optional[Any] = None,
619
+ ) -> Any:
620
+ """
621
+ Prompt user for a required parameter value.
622
+
623
+ If provided_value is given and validator is provided, validates it first.
624
+ If validation fails, prompts interactively.
625
+
626
+ Args:
627
+ param_name: Name of the parameter
628
+ param_type: Expected type (str, int, float, bool)
629
+ help_text: Help text to display
630
+ validator: Optional validation function (returns bool or (bool, Optional[str]))
631
+ default: Default value to suggest
632
+ allow_empty: Whether empty values are allowed
633
+ provided_value: Value provided via CLI (validated if validator is provided)
634
+
635
+ Returns:
636
+ The user input converted to the specified type
637
+ """
638
+ # Validate provided value if it exists
639
+ if provided_value is not None and validator:
640
+ try:
641
+ result = validator(provided_value)
642
+ # Handle both bool and tuple return types
643
+ if isinstance(result, tuple):
644
+ is_valid, error_msg = result
645
+ if not is_valid:
646
+ error_display = error_msg if error_msg else f"Invalid {param_name}: {provided_value}"
647
+ print_error(f"Invalid {param_name}: {error_display}")
648
+ provided_value = None # Force interactive prompt
649
+ else:
650
+ # Boolean return type
651
+ if not result:
652
+ print_error(f"Invalid {param_name}: {provided_value}")
653
+ provided_value = None # Force interactive prompt
654
+ except Exception as e:
655
+ print_error(f"Validation error for {param_name}: {str(e)}")
656
+ provided_value = None # Force interactive prompt
657
+
658
+ # If we have a valid provided value, return it
659
+ if provided_value is not None:
660
+ return provided_value
661
+
662
+ while True:
663
+ # Build the prompt text
664
+ prompt_text = param_name
665
+ if help_text:
666
+ prompt_text += f" ({help_text})"
667
+
668
+ try:
669
+ if param_type == str:
670
+ result = _prompt.text(
671
+ prompt_text,
672
+ default=str(default) if default is not None else None,
673
+ validator=validator,
674
+ required=not allow_empty,
675
+ )
676
+ return result if result else (None if allow_empty else "")
677
+ elif param_type == int:
678
+ return _prompt.integer(
679
+ prompt_text,
680
+ default=default,
681
+ )
682
+ elif param_type == float:
683
+ return _prompt.float(
684
+ prompt_text,
685
+ default=default,
686
+ )
687
+ elif param_type == bool:
688
+ return _prompt.confirm(
689
+ prompt_text,
690
+ default=default if default is not None else False,
691
+ )
692
+ else:
693
+ result = _prompt.text(
694
+ prompt_text,
695
+ default=str(default) if default is not None else None,
696
+ validator=validator,
697
+ required=not allow_empty,
698
+ )
699
+ return result
700
+ except (KeyboardInterrupt, EOFError, typer.Abort):
701
+ # Re-raise cancellation exceptions to properly exit
702
+ raise
703
+ except Exception as e:
704
+ print_error(f"Input error: {str(e)}")
705
+ continue
706
+
707
+
708
+ def prompt_for_optional(
709
+ param_name: str,
710
+ param_type: type,
711
+ help_text: str = "",
712
+ validator: Optional[Callable] = None,
713
+ default: Optional[Any] = None,
714
+ provided_value: Optional[Any] = None,
715
+ ) -> Optional[Any]:
716
+ """
717
+ Prompt user for an optional parameter value.
718
+
719
+ If provided_value is given and validator is provided, validates it first.
720
+ If validation fails, prompts interactively.
721
+
722
+ Args:
723
+ param_name: Name of the parameter
724
+ param_type: Expected type (str, int, float, bool)
725
+ help_text: Help text to display
726
+ validator: Optional validation function (returns bool or (bool, Optional[str]))
727
+ default: Default value to suggest
728
+ provided_value: Value provided via CLI (validated if validator is provided)
729
+
730
+ Returns:
731
+ The user input converted to the specified type, or None if skipped
732
+ """
733
+ # Validate provided value if it exists
734
+ if provided_value is not None and validator:
735
+ try:
736
+ result = validator(provided_value)
737
+ # Handle both bool and tuple return types
738
+ if isinstance(result, tuple):
739
+ is_valid, error_msg = result
740
+ if not is_valid:
741
+ error_display = error_msg if error_msg else f"Invalid {param_name}: {provided_value}"
742
+ print_error(f"Invalid {param_name}: {error_display}")
743
+ provided_value = None # Force interactive prompt
744
+ else:
745
+ # Boolean return type
746
+ if not result:
747
+ print_error(f"Invalid {param_name}: {provided_value}")
748
+ provided_value = None # Force interactive prompt
749
+ except Exception as e:
750
+ print_error(f"Validation error for {param_name}: {str(e)}")
751
+ provided_value = None # Force interactive prompt
752
+
753
+ # If we have a valid provided value, return it
754
+ if provided_value is not None:
755
+ return provided_value
756
+
757
+ prompt_text = f"{param_name} (optional)"
758
+ if help_text:
759
+ prompt_text += f" ({help_text})"
760
+
761
+ try:
762
+ if param_type == str:
763
+ result = _prompt.text(
764
+ prompt_text,
765
+ default=str(default) if default is not None else None,
766
+ required=False,
767
+ )
768
+ return result if result and result.strip() else None
769
+ elif param_type == int:
770
+ return _prompt.integer(
771
+ prompt_text,
772
+ default=default,
773
+ )
774
+ elif param_type == float:
775
+ return _prompt.float(
776
+ prompt_text,
777
+ default=default,
778
+ )
779
+ elif param_type == bool:
780
+ return _prompt.confirm(
781
+ prompt_text,
782
+ default=default if default is not None else False,
783
+ )
784
+ else:
785
+ result = _prompt.text(
786
+ prompt_text,
787
+ default=str(default) if default is not None else None,
788
+ required=False,
789
+ )
790
+ return result if result.strip() else None
791
+ except (KeyboardInterrupt, EOFError, typer.Abort):
792
+ # Re-raise cancellation exceptions to properly exit
793
+ raise
794
+ except Exception:
795
+ return None
796
+
797
+
798
+ def prompt_for_list(
799
+ param_name: str,
800
+ help_text: str,
801
+ separator: str = ",",
802
+ validator: Optional[Callable[[Any], bool]] = None,
803
+ default: Optional[str] = None,
804
+ ) -> list[str]:
805
+ """
806
+ Prompt user for a list of values.
807
+
808
+ Args:
809
+ param_name: Name of the parameter
810
+ help_text: Help text to display
811
+ separator: Character to separate list items
812
+ validator: Optional validation function for individual items
813
+ default: Default value to suggest
814
+
815
+ Returns:
816
+ List of user input values
817
+ """
818
+ prompt_text = param_name
819
+ if help_text:
820
+ prompt_text += f" ({help_text})"
821
+ prompt_text += f" (separate with '{separator}')"
822
+
823
+ while True:
824
+ try:
825
+ user_input = _prompt.text(
826
+ prompt_text,
827
+ default=default,
828
+ required=True,
829
+ )
830
+
831
+ # Split the input
832
+ items = [
833
+ item.strip() for item in user_input.split(separator) if item.strip()
834
+ ]
835
+
836
+ if not items:
837
+ print_error("No valid items found. Please provide at least one value.")
838
+ continue
839
+
840
+ # Validate individual items if validator provided
841
+ if validator:
842
+ invalid_items = [item for item in items if not validator(item)]
843
+ if invalid_items:
844
+ print_error(f"Invalid items: {', '.join(invalid_items)}")
845
+ continue
846
+
847
+ return items
848
+ except (KeyboardInterrupt, EOFError, typer.Abort):
849
+ # Re-raise cancellation exceptions to properly exit
850
+ raise
851
+ except Exception as e:
852
+ print_error(f"Input error: {str(e)}")
853
+ continue
854
+
855
+
856
+ def prompt_for_confirmation(
857
+ message: str,
858
+ default: bool = False,
859
+ ) -> bool:
860
+ """
861
+ Prompt user for confirmation.
862
+
863
+ Args:
864
+ message: Message to display
865
+ default: Default value
866
+
867
+ Returns:
868
+ True if user confirms, False otherwise
869
+ """
870
+ return _prompt.confirm(message, default=default)