-
-
Notifications
You must be signed in to change notification settings - Fork 388
Add CLI #142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Add CLI #142
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,116 @@ | ||||||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| import click | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| from mnemonic import Mnemonic | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @click.group() | ||||||||||||||||||||||||||||||||
| def cli() -> None: | ||||||||||||||||||||||||||||||||
| """BIP-39 mnemonic phrase generator and validator.""" | ||||||||||||||||||||||||||||||||
| pass | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @cli.command() | ||||||||||||||||||||||||||||||||
| @click.option( | ||||||||||||||||||||||||||||||||
| "-l", | ||||||||||||||||||||||||||||||||
| "--language", | ||||||||||||||||||||||||||||||||
| default="english", | ||||||||||||||||||||||||||||||||
| type=str, | ||||||||||||||||||||||||||||||||
| help="Language for the mnemonic wordlist.", | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| @click.option( | ||||||||||||||||||||||||||||||||
| "-s", | ||||||||||||||||||||||||||||||||
| "--strength", | ||||||||||||||||||||||||||||||||
| default=128, | ||||||||||||||||||||||||||||||||
| type=int, | ||||||||||||||||||||||||||||||||
| help="Entropy strength in bits (128, 160, 192, 224, or 256).", | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| @click.option( | ||||||||||||||||||||||||||||||||
| "-p", | ||||||||||||||||||||||||||||||||
| "--passphrase", | ||||||||||||||||||||||||||||||||
| default="", | ||||||||||||||||||||||||||||||||
| type=str, | ||||||||||||||||||||||||||||||||
| help="Optional passphrase for seed derivation.", | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
|
Comment on lines
+29
to
+35
|
||||||||||||||||||||||||||||||||
| def create( | ||||||||||||||||||||||||||||||||
| language: str, | ||||||||||||||||||||||||||||||||
| passphrase: str, | ||||||||||||||||||||||||||||||||
| strength: int, | ||||||||||||||||||||||||||||||||
| ) -> None: | ||||||||||||||||||||||||||||||||
| """Generate a new mnemonic phrase and its derived seed.""" | ||||||||||||||||||||||||||||||||
| mnemo = Mnemonic(language) | ||||||||||||||||||||||||||||||||
| words = mnemo.generate(strength) | ||||||||||||||||||||||||||||||||
| seed = mnemo.to_seed(words, passphrase) | ||||||||||||||||||||||||||||||||
| click.echo(f"Mnemonic: {words}") | ||||||||||||||||||||||||||||||||
| click.echo(f"Seed: {seed.hex()}") | ||||||||||||||||||||||||||||||||
|
Comment on lines
+42
to
+46
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @cli.command() | ||||||||||||||||||||||||||||||||
| @click.option( | ||||||||||||||||||||||||||||||||
| "-l", | ||||||||||||||||||||||||||||||||
| "--language", | ||||||||||||||||||||||||||||||||
| default=None, | ||||||||||||||||||||||||||||||||
| type=str, | ||||||||||||||||||||||||||||||||
| help="Language for the mnemonic wordlist. Auto-detected if not specified.", | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| @click.argument("words", nargs=-1) | ||||||||||||||||||||||||||||||||
| def check(language: str | None, words: tuple[str, ...]) -> None: | ||||||||||||||||||||||||||||||||
| """Validate a mnemonic phrase's checksum. | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| WORDS can be provided as arguments or piped via stdin. | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
| if words: | ||||||||||||||||||||||||||||||||
| mnemonic = " ".join(words) | ||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||
| mnemonic = sys.stdin.read().strip() | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if not mnemonic: | ||||||||||||||||||||||||||||||||
| click.secho("Error: No mnemonic provided.", fg="red", err=True) | ||||||||||||||||||||||||||||||||
| sys.exit(1) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||
| if language is None: | ||||||||||||||||||||||||||||||||
| language = Mnemonic.detect_language(mnemonic) | ||||||||||||||||||||||||||||||||
| mnemo = Mnemonic(language) | ||||||||||||||||||||||||||||||||
| if mnemo.check(mnemonic): | ||||||||||||||||||||||||||||||||
| click.secho("Valid mnemonic.", fg="green") | ||||||||||||||||||||||||||||||||
| sys.exit(0) | ||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||
| click.secho("Invalid mnemonic checksum.", fg="red", err=True) | ||||||||||||||||||||||||||||||||
| sys.exit(1) | ||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||
| click.secho(f"Error: {e}", fg="red", err=True) | ||||||||||||||||||||||||||||||||
| sys.exit(1) | ||||||||||||||||||||||||||||||||
|
Comment on lines
+82
to
+84
|
||||||||||||||||||||||||||||||||
| except Exception as e: | |
| click.secho(f"Error: {e}", fg="red", err=True) | |
| sys.exit(1) | |
| except ValueError as e: | |
| # Handle expected user-related errors (e.g., invalid language or mnemonic format) | |
| click.secho(f"Error: {e}", fg="red", err=True) | |
| sys.exit(1) | |
| except Exception as e: | |
| # Handle unexpected internal errors separately with a distinct exit code | |
| click.secho(f"Unexpected error: {e}", fg="red", err=True) | |
| sys.exit(2) |
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new CLI functionality (check command) lacks test coverage. The repository has comprehensive automated testing for the Mnemonic class, so similar test coverage should be added for the CLI commands. Consider adding tests to verify the command correctly validates both valid and invalid mnemonics, handles stdin input, and properly detects language when not specified.
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The passphrase parameter is passed via command-line option, which means it will be visible in shell history and process listings. This poses a security risk as sensitive passphrases could be exposed. Consider adding support for reading the passphrase from stdin or an environment variable as a more secure alternative.
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new CLI functionality (to-seed command) lacks test coverage. The repository has comprehensive automated testing for the Mnemonic class, so similar test coverage should be added for the CLI commands. Consider adding tests to verify the command correctly derives seeds, handles stdin input, and applies the passphrase parameter correctly.
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The to_seed command lacks error handling for invalid mnemonics. If the mnemonic is invalid or the language cannot be detected, the Mnemonic.to_seed() call may fail or produce unexpected results. Consider adding validation using Mnemonic.check() or wrapping the to_seed call in a try-except block to provide clear error messages to users.
| seed = Mnemonic.to_seed(mnemonic, passphrase) | |
| click.echo(seed.hex()) | |
| try: | |
| # Auto-detect language and validate mnemonic before deriving seed | |
| language = Mnemonic.detect_language(mnemonic) | |
| mnemo = Mnemonic(language) | |
| if not mnemo.check(mnemonic): | |
| click.secho("Invalid mnemonic checksum.", fg="red", err=True) | |
| sys.exit(1) | |
| seed = mnemo.to_seed(mnemonic, passphrase) | |
| click.echo(seed.hex()) | |
| except Exception as e: | |
| click.secho(f"Error: {e}", fg="red", err=True) | |
| sys.exit(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The strength parameter should use click.Choice to restrict values to the valid options [128, 160, 192, 224, 256] instead of accepting any integer. This would provide better user experience by showing valid options in the help text and catching invalid values before calling the API.