diff --git a/src/pyhdwallet.py b/src/pyhdwallet.py
index 1111111..2222222 100755
--- a/src/pyhdwallet.py
+++ b/src/pyhdwallet.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 """
-pyhdwallet v1.0.4 (Python 3.11+)
+pyhdwallet v1.0.4 (Python 3.11+)
 
 Commands:
   - fetchkey (online): download ASCII-armored PGP public key and print SHA256 + fingerprint
@@ -33,6 +33,7 @@ import tempfile
 import urllib.request
 import warnings
 from pathlib import Path
+from typing import Iterable
 from typing import Any, Dict, List, Optional, Tuple
 
 # -----------------------------------------------------------------------------
@@ -112,6 +113,123 @@ def gen_base58_password(length: int) -> str:
     return "".join(secrets.choice(alphabet) for _ in range(length))
 
 
+# -----------------------------------------------------------------------------
+# Recover input helpers (interactive + stdin)
+# -----------------------------------------------------------------------------
+
+
+def _prompt_hidden_or_visible(prompt: str) -> str:
+    """
+    Prompt for sensitive input.
+
+    Prefer getpass (no echo). If getpass can't reliably hide input in the current
+    environment, warn and fall back to visible input (user-selected behavior B).
+    """
+    try:
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter("always")
+            s = getpass.getpass(prompt)
+            if w:
+                print("⚠️  WARNING: getpass could not reliably hide input in this environment.", file=sys.stderr)
+                print("⚠️  Falling back to visible input. Use a real terminal for hidden input.", file=sys.stderr)
+                return input(prompt.replace("(hidden)", "(VISIBLE)"))
+            return s
+    except (Exception, KeyboardInterrupt):
+        print("⚠️  WARNING: Hidden prompt failed; falling back to visible input.", file=sys.stderr)
+        return input(prompt.replace("(hidden)", "(VISIBLE)"))
+
+
+def _read_stdin_all() -> str:
+    # Read all of stdin (for piping from secure sources).
+    return sys.stdin.read().strip()
+
+
+def _bip39_english_wordset() -> set[str]:
+    """
+    Load BIP39 English wordlist using bip_utils built-in word list getter.
+    """
+    from bip_utils import Bip39Languages, Bip39WordsListGetter
+
+    wl = Bip39WordsListGetter.GetByLanguage(Bip39Languages.ENGLISH)
+    # wl provides the official 2048-word list via bip_utils. [web:342]
+    return set(wl.GetWordList())
+
+
+def _prompt_word_count() -> int:
+    valid_counts = {12, 15, 18, 21, 24}
+    while True:
+        raw = input("Word count (12/15/18/21/24): ").strip()
+        if raw.isdigit() and int(raw) in valid_counts:
+            return int(raw)
+        print("❌ Invalid word count. Choose 12/15/18/21/24.")
+
+
+def interactive_mnemonic_word_by_word() -> str:
+    """
+    Guided mnemonic entry (English-only):
+      - choose word count
+      - enter each word with membership validation (✅/❌)
+      - final checksum validation via Bip39MnemonicValidator
+    """
+    from bip_utils import Bip39MnemonicValidator
+
+    wordset = _bip39_english_wordset()
+    n = _prompt_word_count()
+
+    words: List[str] = []
+    i = 1
+    while i <= n:
+        w = _prompt_hidden_or_visible(f"{i}) Enter word (hidden): ").strip().lower()
+        if w in wordset:
+            print(f"{i}) ✅")
+            words.append(w)
+            i += 1
+        else:
+            print(f"{i}) ❌ Invalid BIP39 English word. Try again.")
+
+    mnemonic = " ".join(words)
+    if not Bip39MnemonicValidator().IsValid(mnemonic):
+        raise ValueError("Mnemonic checksum/format invalid (words valid but checksum failed).")
+    return mnemonic
+
+
 # -----------------------------------------------------------------------------
 # Fingerprint
 # -----------------------------------------------------------------------------
@@ -474,39 +592,52 @@ def cmd_recover(args) -> None:
         if args.off_screen:
             print("⚠️  Off-screen mode enabled: Sensitive data will not be printed to stdout.")
 
         if args.export_private and not args.pgp_pubkey_file:
             raise ValueError("--export-private requires --pgp-pubkey-file")
 
         from bip_utils import Bip39MnemonicValidator, Bip39SeedGenerator
 
-        # Validate input methods
-        input_methods = [bool(args.mnemonic), bool(args.seed), args.interactive]
-        if sum(input_methods) > 1:
-            raise ValueError("Provide only one of --mnemonic, --seed, or --interactive")
-        if sum(input_methods) == 0:
-            raise ValueError("Missing input (use --mnemonic/--seed/--interactive)")
-
         mnemonic: Optional[str] = None
         seed_hex: Optional[str] = None
 
-        if args.interactive:
-            mode = input("Enter 'm' for mnemonic or 's' for seed: ").strip().lower()
-            if mode == "m":
-                mnemonic = getpass.getpass("BIP39 mnemonic (hidden): ").strip()
-            elif mode == "s":
-                seed_hex = getpass.getpass("Seed hex (hidden, 128 hex chars): ").strip()
-            else:
-                raise ValueError("Invalid choice")
-        elif args.mnemonic:
-            mnemonic = args.mnemonic.strip()
-        else:
-            seed_hex = args.seed.strip()
+        # New input model: exactly one of:
+        #   --interactive (guided mnemonic only, English-only)
+        #   --mnemonic-stdin (read full phrase from stdin)
+        #   --seed-stdin (read seed hex from stdin)
+        if args.interactive:
+            mnemonic = interactive_mnemonic_word_by_word()
+        elif args.mnemonic_stdin:
+            mnemonic = _read_stdin_all()
+            if not mnemonic:
+                raise ValueError("Empty stdin for --mnemonic-stdin")
+        elif args.seed_stdin:
+            seed_hex = _read_stdin_all()
+            if not seed_hex:
+                raise ValueError("Empty stdin for --seed-stdin")
+        else:
+            raise ValueError("Missing input mode (use --interactive / --mnemonic-stdin / --seed-stdin)")
 
         passphrase = getpass.getpass("Enter BIP39 passphrase (hidden): ") if args.passphrase else ""
 
         if mnemonic:
             if not Bip39MnemonicValidator().IsValid(mnemonic):
                 raise ValueError("Invalid BIP39 mnemonic")
             seed_bytes = Bip39SeedGenerator(mnemonic).Generate(passphrase)
         else:
             b = bytes.fromhex(seed_hex or "")
             if len(b) != 64:
                 raise ValueError("Invalid seed hex (expected 64 bytes / 128 hex chars)")
             seed_bytes = b
 
         fp = get_master_fingerprint(seed_bytes)
@@ -676,18 +807,30 @@ def build_parser() -> argparse.ArgumentParser:
 
     # recover
     p_rec = sub.add_parser("recover", help="Recover addresses from mnemonic/seed (offline)")
     add_common(p_rec)
-    p_rec.add_argument("--mnemonic", default="")
-    p_rec.add_argument("--seed", default="")
-    p_rec.add_argument("--interactive", action="store_true")
+
+    # New safer input model for recover:
+    # - No plaintext secrets in CLI args.
+    # - Use guided interactive entry or stdin.
+    g = p_rec.add_mutually_exclusive_group(required=True)
+    g.add_argument(
+        "--interactive",
+        action="store_true",
+        help="Guided mnemonic entry (English-only, per-word validation)",
+    )
+    g.add_argument(
+        "--mnemonic-stdin",
+        dest="mnemonic_stdin",
+        action="store_true",
+        help="Read BIP39 mnemonic from stdin (non-interactive)",
+    )
+    g.add_argument(
+        "--seed-stdin",
+        dest="seed_stdin",
+        action="store_true",
+        help="Read seed hex (128 hex chars) from stdin (non-interactive)",
+    )
 
     # test
     p_test = sub.add_parser("test", help="Run minimal offline tests")
     p_test.add_argument("--off-screen", action="store_true", help="Enable off-screen mode: no printing of sensitive data to stdout.")
 
     return parser
