diff --git a/include/tadah/mlip/key_storage/environment_key_storage.h b/include/tadah/mlip/key_storage/environment_key_storage.h
new file mode 100644
index 0000000000000000000000000000000000000000..258e13beb4358a2cf92d765d47cb2bc99e615dee
--- /dev/null
+++ b/include/tadah/mlip/key_storage/environment_key_storage.h
@@ -0,0 +1,43 @@
+/**
+ * @file EnvironmentKeyStorage.h
+ * @brief Implementation of IKeyStorage that reads/writes keys from/to
+ * environment variables.
+ *
+ * By default, environment variables are read-only for user’s global
+ * environment. Storing can be done via setenv for the current process if
+ * desired.
+ */
+
+#ifndef ENVIRONMENTKEYSTORAGE_H
+#define ENVIRONMENTKEYSTORAGE_H
+
+#include <tadah/mlip/key_storage/ikey_storage.h>
+#include <string>
+
+/**
+ * @class EnvironmentKeyStorage
+ * @brief Reads a key from environment variables.
+ *
+ * Storing is optional: if storing is supported, it uses setenv to update
+ * the environment variable only for the current process.
+ */
+class EnvironmentKeyStorage : public IKeyStorage {
+public:
+  /**
+   * @brief Retrieves the environment variable named @p keyName.
+   * @param keyName Name of the env variable.
+   * @return The environment variable's value, or empty if not set.
+   */
+  std::string retrieveKey(const std::string &keyName) override;
+
+  /**
+   * @brief Stores a value in the process's environment using setenv.
+   * @param keyName Name of the env variable.
+   * @param keyValue Value to set.
+   * @return true if setenv succeeds, false otherwise.
+   */
+  bool storeKey(const std::string &keyName,
+                const std::string &keyValue) override;
+};
+
+#endif // ENVIRONMENTKEYSTORAGE_H
diff --git a/include/tadah/mlip/key_storage/fallback_key_storage.h b/include/tadah/mlip/key_storage/fallback_key_storage.h
new file mode 100644
index 0000000000000000000000000000000000000000..45e94fcc239a724590130fb81597ea5e91ac9167
--- /dev/null
+++ b/include/tadah/mlip/key_storage/fallback_key_storage.h
@@ -0,0 +1,51 @@
+/**
+ * @file FallbackKeyStorage.h
+ * @brief An aggregator that tries multiple IKeyStorage instances in order (for reading), 
+ *        and writes to a chosen target storage.
+ */
+
+#ifndef FALLBACKKEYSTORAGE_H
+#define FALLBACKKEYSTORAGE_H
+
+#include <tadah/mlip/key_storage/ikey_storage.h>
+#include <vector>
+#include <memory>
+
+/**
+ * @class FallbackKeyStorage
+ * @brief Aggregates multiple IKeyStorage implementations in a fallback chain.
+ *
+ * Reading: The first storage that returns a non-empty string is used.  
+ * Writing: By default, writes to the last storage in the list. 
+ * (This can be adjusted if a different policy is needed.)
+ */
+class FallbackKeyStorage : public IKeyStorage
+{
+public:
+  /**
+   * @brief Constructs with an ordered list of storages to try for retrieval.
+   * @param storages A vector of storages, from highest to lowest precedence for reading.
+   */
+  FallbackKeyStorage(std::vector<std::unique_ptr<IKeyStorage>> storages);
+
+  /**
+   * @brief Retrieves the key by checking storages in order, returning the first non-empty result.
+   * @param keyName Name of the key to retrieve.
+   * @return Key value, or empty string if none of the storages have it.
+   */
+  std::string retrieveKey(const std::string& keyName) override;
+
+  /**
+   * @brief Stores the key in the last storage by default. 
+   * @param keyName Name of the key.
+   * @param keyValue Value to store.
+   * @return true if stored successfully, false otherwise.
+   */
+  bool storeKey(const std::string& keyName, const std::string& keyValue) override;
+
+private:
+  std::vector<std::unique_ptr<IKeyStorage>> m_storages; 
+};
+
+#endif // FALLBACKKEYSTORAGE_H
+
diff --git a/include/tadah/mlip/key_storage/ikey_storage.h b/include/tadah/mlip/key_storage/ikey_storage.h
new file mode 100644
index 0000000000000000000000000000000000000000..0ce8276c2a4400573883a604a77f86ed884846db
--- /dev/null
+++ b/include/tadah/mlip/key_storage/ikey_storage.h
@@ -0,0 +1,38 @@
+/**
+ * @file IKeyStorage.h
+ * @brief Abstract interface for retrieving and storing keys (e.g., API tokens).
+ */
+
+#ifndef IKEYSTORAGE_H
+#define IKEYSTORAGE_H
+
+#include <string>
+
+/**
+ * @class IKeyStorage
+ * @brief Contract for any key storage mechanism.
+ *
+ * Classes implementing this interface can store and retrieve keys by name.
+ */
+class IKeyStorage
+{
+public:
+  virtual ~IKeyStorage() = default;
+
+  /**
+   * @brief Retrieves the value associated with @p keyName.
+   * @param keyName The name of the key to be retrieved.
+   * @return The value if found, otherwise an empty string.
+   */
+  virtual std::string retrieveKey(const std::string& keyName) = 0;
+
+  /**
+   * @brief Stores or updates the value for @p keyName.
+   * @param keyName The key name (e.g. "MY_REST_API_KEY").
+   * @param keyValue The value to store (e.g. "abc123").
+   * @return true if successfully stored, false if not supported or an error occurred.
+   */
+  virtual bool storeKey(const std::string& keyName, const std::string& keyValue) = 0;
+};
+
+#endif // IKEYSTORAGE_H
diff --git a/include/tadah/mlip/key_storage/key_manager.h b/include/tadah/mlip/key_storage/key_manager.h
new file mode 100644
index 0000000000000000000000000000000000000000..be98eccf6493d382bd94fcc4b0c4fce92d37cee6
--- /dev/null
+++ b/include/tadah/mlip/key_storage/key_manager.h
@@ -0,0 +1,85 @@
+/**
+ * @file KeyManager.h
+ * @brief Provides methods to retrieve and manage API keys for services like (MP, COD, AFLOW), 
+ *        offering a 4-option prompt when no key is found.
+ */
+
+#ifndef KEYMANAGER_H
+#define KEYMANAGER_H
+
+#include <string>
+
+/**
+ * @enum ServiceType
+ * @brief Enumerates supported services for which the KeyManager can retrieve API keys.
+ */
+enum class ServiceType {
+  MP,    ///< Materials Project
+  COD,   ///< Crystallography Open Database
+  AFLOW, ///< AFLOW Project
+  // Add more if needed
+};
+
+/**
+ * @class KeyManager
+ * @brief Central utility for retrieving (and if missing, prompting for) service API keys.
+ *
+ * Uses "EnvThenFile" fallback:
+ *   1) Environment variable (e.g. MP_REST_API_KEY)
+ *   2) Plain-text config in ~/.config/tadah/keys
+ *
+ * If still missing, shows four options:
+ *   [1] Store key in config file (persistent)
+ *   [2] Temporarily set environment variable for this run only
+ *   [3] Show how to export it in the shell (so it can be used across multiple runs)
+ *   [4] Abort (no key)
+ */
+class KeyManager {
+public:
+  /**
+   * @brief Retrieves the API key for the specified service.
+   *
+   * If the key is not found in environment or config, and @p allowPrompt is true,
+   * an interactive menu of four options is displayed:
+   *   1) Store key in config file (persist for future runs),
+   *   2) Temporarily set the environment variable for *this* run only,
+   *   3) Show instructions on how to export for the shell (useful across runs),
+   *   4) Abort (do nothing, return empty).
+   *
+   * @param service      The target service (MP, COD, AFLOW).
+   * @param allowPrompt  If false, no prompts occur; the method returns empty if key is missing.
+   * @return The acquired key, or an empty string if user aborts or no key provided.
+   */
+  static std::string getServiceKey(ServiceType service, bool allowPrompt = true);
+
+private:
+  /// Maps the enum to the environment variable name (e.g. "MP_REST_API_KEY").
+  static std::string resolveKeyName(ServiceType service);
+
+  /// A friendly string for prompts/logs (e.g. "Materials Project").
+  static std::string getServiceFriendlyName(ServiceType service);
+
+  /// Shows the four-option menu and returns the user's choice (1..4).
+  static int promptUserChoice();
+
+  /**
+   * @brief Sets the environment variable in the current process only,
+   *        so it is visible to subsequent code in this process but not in the user's shell.
+   *
+   * @param keyName  The environment variable name (e.g. "MP_REST_API_KEY").
+   * @param keyValue The value to set.
+   */
+  static void setEnvCurrentProcess(const std::string& keyName, const std::string& keyValue);
+
+  /**
+   * @brief Shows instructions for exporting in the user’s shell so the 
+   *        variable persists across multiple runs *in that same terminal session.*
+   *
+   * Also hints how to place it in shell configuration (e.g. ~/.bashrc) if desired.
+   *
+   * @param keyName  The env variable name.
+   * @param keyValue The key value to be exported.
+   */
+  static void showShellExportInstructions(const std::string& keyName, const std::string& keyValue);
+};
+#endif // KEYMANAGER_H
diff --git a/include/tadah/mlip/key_storage/key_storage_factory.h b/include/tadah/mlip/key_storage/key_storage_factory.h
new file mode 100644
index 0000000000000000000000000000000000000000..f1572bcdbbea4eff6af84ed7d6d5d60b6d7d95c0
--- /dev/null
+++ b/include/tadah/mlip/key_storage/key_storage_factory.h
@@ -0,0 +1,40 @@
+/**
+ * @file KeyStorageFactory.h
+ * @brief Factory for constructing complex IKeyStorage objects or single backends.
+ */
+
+#ifndef KEYSTORAGEFACTORY_H
+#define KEYSTORAGEFACTORY_H
+
+#include <memory>
+#include <string>
+#include <tadah/mlip/key_storage/ikey_storage.h>
+
+/**
+ * @class KeyStorageFactory
+ * @brief Produces aggregated or single IKeyStorage instances.
+ */
+class KeyStorageFactory
+{
+public:
+  /**
+   * @brief Creates a fallback storage that checks environment first, then file, etc.
+   * @return A unique_ptr to IKeyStorage implementing the fallback strategy.
+   */
+  static std::unique_ptr<IKeyStorage> createEnvThenFile();
+
+  /**
+   * @brief Creates a plain-text-only storage instance (no environment checks).
+   * @return A unique_ptr to IKeyStorage that uses plain-text only.
+   */
+  static std::unique_ptr<IKeyStorage> createPlainTextOnly();
+
+  /**
+   * @brief Creates an environment-only storage instance (no file fallback).
+   * @return A unique_ptr to IKeyStorage that uses environment only.
+   */
+  static std::unique_ptr<IKeyStorage> createEnvironmentOnly();
+};
+
+#endif // KEYSTORAGEFACTORY_H
+
diff --git a/include/tadah/mlip/key_storage/plain_text_key_storage.h b/include/tadah/mlip/key_storage/plain_text_key_storage.h
new file mode 100644
index 0000000000000000000000000000000000000000..c71a43eb88fa1c7aae5722fc4feafdfb7e9fe30c
--- /dev/null
+++ b/include/tadah/mlip/key_storage/plain_text_key_storage.h
@@ -0,0 +1,60 @@
+/**
+ * @file PlainTextKeyStorage.h
+ * @brief Implementation of IKeyStorage that reads/writes plain-text keys in ~/.config/tadah/keys.
+ */
+
+#ifndef PLAINTEXTKEYSTORAGE_H
+#define PLAINTEXTKEYSTORAGE_H
+
+#include <tadah/mlip/key_storage/ikey_storage.h>
+#include <vector>
+
+/**
+ * @class PlainTextKeyStorage
+ * @brief Handles storage of keys in a plain-text file.
+ *
+ * The file is located in "~/.config/tadah/keys" if possible, otherwise "." 
+ * is used as a fallback.
+ *
+ * Format of each line: KEY_NAME value
+ * Example: MY_REST_API_KEY abc123
+ *
+ * No encryption is performed in this implementation.
+ */
+class PlainTextKeyStorage : public IKeyStorage
+{
+public:
+  /**
+   * @brief Constructor ensures the directory is set up.
+   */
+  PlainTextKeyStorage();
+
+  /**
+   * @brief Retrieves @p keyName from the keys file (does not check environment).
+   * @param keyName The name of the key to look up.
+   * @return The value if found, otherwise empty.
+   */
+  std::string retrieveKey(const std::string& keyName) override;
+
+  /**
+   * @brief Writes or updates a line in the keys file, ignoring environment variables.
+   * @param keyName The name of the key.
+   * @param keyValue The value to associate with the key.
+   * @return true on successful file write, false on error.
+   */
+  bool storeKey(const std::string& keyName, const std::string& keyValue) override;
+
+private:
+  std::string m_configDir; ///< Directory path used for storing the "keys" file.
+
+  std::string prepareConfigDirectory();
+  std::string getKeysFilePath() const;
+
+  // Helpers for file read/write
+  std::string readKeyFromFile(const std::string& keyName);
+  std::vector<std::pair<std::string, std::string>> loadAllKeys();
+  bool writeAllKeys(const std::vector<std::pair<std::string, std::string>>& kvPairs);
+};
+
+#endif // PLAINTEXTKEYSTORAGE_H
+
diff --git a/include/tadah/mlip/structure_readers/castep_cell_reader.h b/include/tadah/mlip/structure_readers/castep_cell_reader.h
index d494f3034601773048cf35f500373ed38fc985e3..f75b38c39c5d188e6e0f225e20a10928c6565dc0 100644
--- a/include/tadah/mlip/structure_readers/castep_cell_reader.h
+++ b/include/tadah/mlip/structure_readers/castep_cell_reader.h
@@ -62,7 +62,7 @@ private:
    * @brief Converts atomic fractional coordinates (fx, fy, fz) to absolute in Ã…
    *        using the current m_structure.cell matrix.
    */
-  void applyFractional(double fx, double fy, double fz, double &cx, double &cy,
+  void fracToAbs(double fx, double fy, double fz, double &cx, double &cy,
                        double &cz);
 
   /**
diff --git a/src/castep_cell_reader.cpp b/src/castep_cell_reader.cpp
index fb09541490e45fd2ae749c0a22fe5579ba2be4fc..4451c5fb13b893f4ee7eb365bf8586c7bd33d3b1 100644
--- a/src/castep_cell_reader.cpp
+++ b/src/castep_cell_reader.cpp
@@ -122,8 +122,12 @@ void CastepCellReader::parseCellContents(const std::string &contents) {
     // Now parse lines if in any recognized block
     if (inLatticeCart) {
       // read up to 3 lines for a,b,c
-      if (lcCount < 3) {
+      if (lcCount < 4) {
         auto toks = split_line(line);
+        if (toks.size() == 1) {
+          // skip optional units line
+          continue;
+        }
         if (toks.size() < 3) {
           throw std::runtime_error("CastepCellReader: LATTICE_CART line missing coords.");
         }
@@ -147,7 +151,7 @@ void CastepCellReader::parseCellContents(const std::string &contents) {
       // e.g. "Si 0.0 0.0 0.0"
       auto toks = split_line(line);
       if (toks.size() < 4) {
-        // possibly a comment or empty line, skip
+        // skip empty lines which should not be there...
         continue;
       }
 
@@ -160,7 +164,7 @@ void CastepCellReader::parseCellContents(const std::string &contents) {
       double fz = std::stod(toks[3]);
 
       double cx, cy, cz;
-      applyFractional(fx, fy, fz, cx, cy, cz);
+      fracToAbs(fx, fy, fz, cx, cy, cz);
 
       Atom a;  
       // Copy base element data into 'a'
@@ -172,9 +176,9 @@ void CastepCellReader::parseCellContents(const std::string &contents) {
       m_structure.atoms.push_back(a);
     }
     else if (inPosAbsBlock) {
-      // e.g. "O 1.2 3.4 5.6"
       auto toks = split_line(line);
       if (toks.size() < 4) {
+        // skip units line
         continue;
       }
 
@@ -274,9 +278,9 @@ void CastepCellReader::parseLatticeABC(const std::vector<std::string> &lines) {
 }
 
 //--------------------------------------------------------------------
-// applyFractional
+// fracToAbs
 //--------------------------------------------------------------------
-void CastepCellReader::applyFractional(double fx, double fy, double fz, 
+void CastepCellReader::fracToAbs(double fx, double fy, double fz, 
                                        double &cx, double &cy, double &cz) {
   // r_cart = fx*a + fy*b + fz*c
   //    a, b, c are rows of the 3×3 matrix in m_structure.cell
diff --git a/src/environment_key_storage.cpp b/src/environment_key_storage.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f28444d169e8e6331edde8207a54deea0656780d
--- /dev/null
+++ b/src/environment_key_storage.cpp
@@ -0,0 +1,30 @@
+/**
+ * @file EnvironmentKeyStorage.cpp
+ * @brief Implementation of EnvironmentKeyStorage methods.
+ */
+
+#include <tadah/mlip/key_storage/environment_key_storage.h>
+#include <cstdlib>  // getenv, setenv
+#include <iostream>
+
+std::string EnvironmentKeyStorage::retrieveKey(const std::string& keyName)
+{
+  if (const char* val = std::getenv(keyName.c_str())) {
+    return std::string(val);
+  }
+  return "";
+}
+
+bool EnvironmentKeyStorage::storeKey(const std::string& keyName, const std::string& keyValue)
+{
+  // setenv updates this process environment only.
+  // If persistent environment changes are desired,
+  // the user would need to do that manually in shell profiles, etc.
+  if (setenv(keyName.c_str(), keyValue.c_str(), 1) == 0) {
+    return true;
+  }
+
+  std::cerr << "Warning: setenv failed for key: " << keyName << "\n";
+  return false;
+}
+
diff --git a/src/fallback_key_storage.cpp b/src/fallback_key_storage.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0a5a5c3ffc83e6a2972be2fcd580da63cb4b8996
--- /dev/null
+++ b/src/fallback_key_storage.cpp
@@ -0,0 +1,33 @@
+/**
+ * @file FallbackKeyStorage.cpp
+ * @brief Implementation of the aggregator for multiple storages in a fallback chain.
+ */
+
+#include <tadah/mlip/key_storage/fallback_key_storage.h>
+
+FallbackKeyStorage::FallbackKeyStorage(std::vector<std::unique_ptr<IKeyStorage>> storages)
+  : m_storages(std::move(storages))
+{
+}
+
+std::string FallbackKeyStorage::retrieveKey(const std::string& keyName)
+{
+  // Check each storage in order, return the first non-empty result
+  for (auto &storage : m_storages) {
+    std::string val = storage->retrieveKey(keyName);
+    if (!val.empty()) {
+      return val;
+    }
+  }
+  return ""; // Not found in any storage
+}
+
+bool FallbackKeyStorage::storeKey(const std::string& keyName, const std::string& keyValue)
+{
+  if (m_storages.empty()) {
+    return false;
+  }
+  // For simplicity: store in the last storage
+  return m_storages.back()->storeKey(keyName, keyValue);
+}
+
diff --git a/src/key_manager.cpp b/src/key_manager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4fb5a0600c5fe6da928c02f83fa28edb246b6e5e
--- /dev/null
+++ b/src/key_manager.cpp
@@ -0,0 +1,162 @@
+/**
+ * @file KeyManager.cpp
+ * @brief Implements a KeyManager that provides a four-option prompt 
+ *        if a requested key is missing.
+ */
+#include <tadah/mlip/key_storage/key_manager.h>
+#include <tadah/mlip/key_storage/key_storage_factory.h>
+
+#include <iostream>
+#include <string>
+#include <cstdlib>   // setenv on Unix-likes
+
+std::string KeyManager::getServiceKey(ServiceType service, bool allowPrompt)
+{
+  // 1) Resolve which env/config key to look for
+  const std::string keyName = resolveKeyName(service);
+
+  // 2) Acquire fallback storage that checks environment first, then the file
+  auto storage = KeyStorageFactory::createEnvThenFile();
+
+  // 3) Attempt to retrieve the key
+  std::string val = storage->retrieveKey(keyName);
+  if (!val.empty() || !allowPrompt) {
+    // Key found, or prompting disallowed => just return
+    return val;
+  }
+
+  // Key is missing and we can prompt -> show the 4-option menu
+  std::cout << "\nNo key found for " << getServiceFriendlyName(service)
+            << " (" << keyName << ").\n"
+            << "To obtain a key, visit the official website for instructions.\n\n";
+
+  int choice = promptUserChoice();
+  switch (choice) {
+    case 1:  // Store in config file
+    {
+      std::cout << "Please enter the key now (Ctrl+C to cancel):\n> ";
+      std::getline(std::cin, val);
+      if (val.empty()) {
+        std::cerr << "No key entered, returning empty.\n";
+        return "";
+      }
+      bool storeOk = storage->storeKey(keyName, val);
+      if (!storeOk) {
+        std::cerr << "Failed to store key in config.\n";
+      } else {
+        std::cout << "[Info] Key stored in config file (~/.config/tadah/keys).\n";
+      }
+      return val;
+    }
+
+    case 2:  // Temporarily set environment var for this run
+    {
+      std::cout << "Please enter the key now (Ctrl+C to cancel):\n> ";
+      std::getline(std::cin, val);
+      if (val.empty()) {
+        std::cerr << "No key entered, returning empty.\n";
+        return "";
+      }
+      setEnvCurrentProcess(keyName, val);
+      std::cout << "\n[Info] Key applied to this process only.\n"
+                << "It will not be seen by other programs or future runs.\n";
+      return val;
+    }
+
+    case 3:  // Show instructions for exporting in this shell
+    {
+      std::cout << "This option does not store or set the key immediately.\n"
+                << "It only shows how to export the key in your terminal.\n"
+                << "Please copy the example commands below.\n\n";
+      // We can guess a random placeholder or let user type it
+      // For a real flow, you might also prompt for the key
+      std::cout << "Please enter the key (Ctrl+C to cancel):\n> ";
+      std::getline(std::cin, val);
+      if (val.empty()) {
+        std::cerr << "No key entered, returning empty.\n";
+        return "";
+      }
+      showShellExportInstructions(keyName, val);
+      return val;
+    }
+
+    default: // 4: Abort
+      std::cerr << "Aborting. Key remains missing.\n";
+      return "";
+  }
+}
+
+std::string KeyManager::resolveKeyName(ServiceType service)
+{
+  switch (service) {
+    case ServiceType::MP:    return "MP_REST_API_KEY";
+    case ServiceType::COD:   return "COD_REST_API_KEY";
+    case ServiceType::AFLOW: return "AFLOW_REST_API_KEY";
+  }
+  return "UNKNOWN_SERVICE_KEY";
+}
+
+std::string KeyManager::getServiceFriendlyName(ServiceType service)
+{
+  switch (service) {
+    case ServiceType::MP:    return "Materials Project";
+    case ServiceType::COD:   return "Crystallography Open Database (COD)";
+    case ServiceType::AFLOW: return "AFLOW Project";
+  }
+  return "Unknown Service";
+}
+
+int KeyManager::promptUserChoice()
+{
+  // Four options now
+  std::cout << "Please select an option:\n"
+            << "[1] Store the key in a config file (persistent)\n"
+            << "[2] Temporarily set environment variable for THIS run only\n"
+            << "[3] View instructions for exporting in your shell (useful across multiple runs)\n"
+            << "[4] Abort (do nothing)\n"
+            << "\nEnter choice [1..4]: ";
+
+  std::string line;
+  std::getline(std::cin, line);
+  if (line.empty()) {
+    return 4; // default to abort if user just hits Enter
+  }
+  char c = line[0];
+  if (c == '1') return 1;
+  if (c == '2') return 2;
+  if (c == '3') return 3;
+  return 4; // invalid or '4' => abort
+}
+
+void KeyManager::setEnvCurrentProcess(const std::string &keyName, const std::string &keyValue)
+{
+#if defined(_WIN32)
+  // Windows example: _putenv_s
+  // For demonstration; might need <cstdlib> or <corecrt.h>
+  // _putenv_s(keyName.c_str(), keyValue.c_str());
+  // or use SetEnvironmentVariable (WinAPI call)
+  std::cout << "[Warning] Setting environment variables at runtime on Windows "
+               "might require different API calls.\n";
+#else
+  // Unix-like approach
+  if (setenv(keyName.c_str(), keyValue.c_str(), 1) != 0) {
+    std::cerr << "Could not set environment variable in this process.\n";
+  }
+#endif
+}
+
+void KeyManager::showShellExportInstructions(const std::string &keyName, const std::string &keyValue)
+{
+  std::cout << "------------------------------------------------------------\n"
+            << "To export this key for your current TERMINAL session:\n\n"
+            << "  export " << keyName << "=\"" << keyValue << "\"\n\n"
+            << "After this, any program run in the same terminal will see the key.\n"
+            << "------------------------------------------------------------\n"
+            << "To make this permanent for ALL future terminal sessions in bash or zsh,\n"
+            << "add the same line to your ~/.bashrc or ~/.zshrc file:\n\n"
+            << "  echo 'export " << keyName << "=\"" << keyValue << "\"' >> ~/.bashrc\n"
+            << "------------------------------------------------------------\n"
+            << "Once added, reload your shell or open a new terminal.\n"
+            << "Then re-run this program, and it will see that environment variable.\n\n";
+}
+
diff --git a/src/key_storage_factory.cpp b/src/key_storage_factory.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a250c87161c9f0d563d87f6afe2b4069a9d904d7
--- /dev/null
+++ b/src/key_storage_factory.cpp
@@ -0,0 +1,30 @@
+/**
+ * @file KeyStorageFactory.cpp
+ * @brief Implementation of the KeyStorageFactory class.
+ */
+
+#include <tadah/mlip/key_storage/environment_key_storage.h>
+#include <tadah/mlip/key_storage/plain_text_key_storage.h>
+#include <tadah/mlip/key_storage/fallback_key_storage.h>
+#include <tadah/mlip/key_storage/key_storage_factory.h>
+#include <vector>
+
+std::unique_ptr<IKeyStorage> KeyStorageFactory::createEnvThenFile()
+{
+  // Define storages in order: environment first, then file
+  std::vector<std::unique_ptr<IKeyStorage>> storages;
+  storages.push_back(std::make_unique<EnvironmentKeyStorage>());
+  storages.push_back(std::make_unique<PlainTextKeyStorage>());
+  return std::make_unique<FallbackKeyStorage>(std::move(storages));
+}
+
+std::unique_ptr<IKeyStorage> KeyStorageFactory::createPlainTextOnly()
+{
+  return std::make_unique<PlainTextKeyStorage>();
+}
+
+std::unique_ptr<IKeyStorage> KeyStorageFactory::createEnvironmentOnly()
+{
+  return std::make_unique<EnvironmentKeyStorage>();
+}
+
diff --git a/src/materials_project_reader.cpp b/src/materials_project_reader.cpp
index dc688bd4b0af0b1e24854e8cdd44118018f72b66..74f6e52ea4fb0b19924f2838d9c9e0b603cfffea 100644
--- a/src/materials_project_reader.cpp
+++ b/src/materials_project_reader.cpp
@@ -1,122 +1,242 @@
-#include <sstream>
-#include <stdexcept>
-#include <tadah/mlip/structure_readers/materials_project_reader.h>
+/**
+  MaterialsProjectReader.cpp
+  Implements functionality declared in MaterialsProjectReader.h.
+
+  Example usage:
+  --------------------------------------------------------------------
+  // 1) The request sets multiple query parameters:
+  //      material_ids=mp-35
+  //      deprecated=false
+  //      _per_page=100
+  //      _skip=0
+  //      _limit=100
+  //      _all_fields=true
+  //      license=BY-NC
+  //
+  // 2) The request sets the header:
+  //      X-API-KEY: <some-api-key>
+  //
+  // 3) The server is expected to return JSON data with an array
+  //    under "data", each item a "MaterialsDoc" from which we parse
+  //    the "structure" field's lattice and sites.
+  //
+  // 4) This code is integrated with the rest of the codebase by
+  //    using the MaterialsProjectReader constructor to store the
+  //    API key. The read() method then forms the correct URL
+  //    with the additional query parameters and a custom header.
+*/
 
-#include <curl/curl.h>       // libcurl
-#include <nlohmann/json.hpp> // nlohmann JSON
+#include <tadah/mlip/structure_readers/materials_project_reader.h>
+#include <curl/curl.h>
+#include <nlohmann/json.hpp>
+#include <stdexcept>
+#include <sstream>
+#include <iostream>
 
-static Element parse_element_mp(const std::string &elemName);
+// parse_element_mp helps parse site labels or species info to retrieve
+// an Element from a known PeriodicTable utility.
+static Element parse_element_mp(const std::string &elemName) {
+  return PeriodicTable().find_by_symbol(elemName);
+}
 
+// Constructor: stores user-provided Materials Project API key.
 MaterialsProjectReader::MaterialsProjectReader(const std::string &apiKey)
-    : m_apiKey(apiKey) {}
+  : m_apiKey(apiKey) {
+}
 
+// read fetches and parses structure data for a given mpID (e.g. "mp-35").
 void MaterialsProjectReader::read(const std::string &mpID) {
   fetchAndParseMP(mpID);
-  m_structure.label = "MaterialsProject ID: " + mpID;
 }
 
-Structure MaterialsProjectReader::getStructure() const { return m_structure; }
+// getStructure returns the last structure retrieved.
+Structure MaterialsProjectReader::getStructure() const {
+  return m_structure;
+}
 
+// fetchAndParseMP forms a query URL that includes the user example query params,
+// calls httpGet with a relevant header, then parses the JSON in parseMpJson.
 void MaterialsProjectReader::fetchAndParseMP(const std::string &mpID) {
-  std::string url = "https://materialsproject.org/rest/v2/materials/";
-  url += mpID + "/structure?API_KEY=" + m_apiKey;
-
+  const std::string url =
+    "https://api.materialsproject.org/materials/core/"
+    "?material_ids="
+    + mpID +
+    "&deprecated=false"
+    "&_per_page=100"
+    "&_skip=0"
+    "&_limit=100"
+    "&_all_fields=true"
+    "&license=BY-NC";
+
+  // Execute an HTTP GET request via cURL
   std::string response = httpGet(url);
+
+  // Parse the JSON response
   parseMpJson(response);
 }
+// This callback accumulates data in a std::string.
+size_t writeCallback(char* ptr, size_t size, size_t nmemb, void* userdata) {
+  auto* response = static_cast<std::string*>(userdata);
+  size_t totalBytes = size * nmemb;
+  response->append(ptr, totalBytes);
+  return totalBytes;
+}
 
+// httpGet uses cURL to perform an HTTP GET on `url`, including
+// the X-API-KEY header matching the Materials Project specification.
 std::string MaterialsProjectReader::httpGet(const std::string &url) {
-  CURL *curl = curl_easy_init();
+
+  // cURL initialization
+  CURL* curl = curl_easy_init();
   if (!curl) {
-    throw std::runtime_error(
-        "MaterialsProjectReader::httpGet: Failed to init libcurl");
+    std::cerr << "Error: Failed to init cURL\n";
   }
 
-  auto writeCallback = [](char *ptr, size_t size, size_t nmemb,
-                          void *userdata) -> size_t {
-    std::string *resp = static_cast<std::string *>(userdata);
-    size_t totalSize = size * nmemb;
-    resp->append(ptr, totalSize);
-    return totalSize;
-  };
-
+  // The server response will be stored here.
   std::string response;
+
+  // Prepare custom headers:
+  struct curl_slist* headers = nullptr;
+  {
+    std::stringstream ss;
+    ss << "X-API-KEY: " << m_apiKey;
+    headers = curl_slist_append(headers, ss.str().c_str());
+  }
+  headers = curl_slist_append(headers, "accept: application/json");
+
+  // cURL basic configuration:
   curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
+  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
+
+  // User-Agent string is required by the Materials Project API
+  // to identify the client. Otherwise, the request will be rejected.
+  curl_easy_setopt(curl, CURLOPT_USERAGENT,
+    "Tadah! (https://tadah.readthedocs.io)");
+
+  // The write callback to collect the HTTP response data.
   curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
   curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
+
+  // Follow redirects if needed
   curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
 
+  // Perform the HTTP GET
   CURLcode res = curl_easy_perform(curl);
   if (res != CURLE_OK) {
+    std::cerr << "[DEBUG] cURL perform failed with error code: " << res << std::endl;
+    // Cleanup on error
+    curl_slist_free_all(headers);
     curl_easy_cleanup(curl);
     throw std::runtime_error(
-        std::string("httpGet: curl_easy_perform() failed: ") +
-        curl_easy_strerror(res));
+      std::string("httpGet: curl_easy_perform() failed: ") +
+      curl_easy_strerror(res));
   }
+
+  // Cleanup
+  curl_slist_free_all(headers);
   curl_easy_cleanup(curl);
+
   return response;
 }
 
+// parseMpJson processes the JSON from the Materials Project
+// "materials/core" endpoint. "data" is an array of results. The first
+// result is a "MaterialsDoc" containing a "structure" sub-field. The
+// "structure" sub-field is a JSON representation of a Pymatgen structure
+// that includes a "lattice.matrix" and a "sites" list. Lattice vectors
+// and atomic positions are then stored in m_structure.
 void MaterialsProjectReader::parseMpJson(const std::string &jsonContent) {
   using json = nlohmann::json;
   json root = json::parse(jsonContent);
 
-  if (!root.contains("response") || !root["response"].is_array() ||
-      root["response"].empty()) {
-    throw std::runtime_error("parseMpJson: No response array in JSON.");
+  if (!root.contains("data") || !root["data"].is_array() ||
+      root["data"].empty()) {
+    throw std::runtime_error("parseMpJson: Missing or empty 'data' array.");
   }
 
-  auto data = root["response"][0];
-  if (!data.contains("lattice") || !data["lattice"].contains("matrix")) {
-    throw std::runtime_error("parseMpJson: Missing lattice.matrix");
+  auto doc = root["data"][0];
+  if (!doc.contains("structure")) {
+    throw std::runtime_error("parseMpJson: 'structure' not found.");
   }
 
-  auto mat = data["lattice"]["matrix"];
-  if (!mat.is_array() || mat.size() != 3) {
-    throw std::runtime_error("parseMpJson: matrix is not 3x3");
+  auto structureJson = doc["structure"];
+  if (!structureJson.contains("lattice") ||
+      !structureJson["lattice"].contains("matrix")) {
+    throw std::runtime_error("parseMpJson: Missing 'lattice.matrix'.");
   }
 
+  // Build a label for the structure
+  auto symbol = to_string(doc["symmetry"]["symbol"]);
+  auto volume = to_string(doc["volume"]);
+  auto density = to_string(doc["density"]);
+  auto elements = to_string(doc["elements"]);
+  auto mid = to_string(doc["material_id"]);
+  m_structure.label = "MaterialsProject ID: " + mid+ " | " +
+                      "Symmetry: " + symbol+ " | " +
+                      "Volume: " + volume+ " | " +
+                      "Density: " + density+ " | " +
+                      "Elements: " + elements+ " | ";
+
+  // Extract the 3x3 lattice matrix
+  auto mat = structureJson["lattice"]["matrix"];
+  if (!mat.is_array() || mat.size() != 3) {
+    throw std::runtime_error("parseMpJson: 'matrix' must be array of length 3.");
+  }
   for (int i = 0; i < 3; i++) {
     if (!mat[i].is_array() || mat[i].size() != 3) {
-      throw std::runtime_error("parseMpJson: matrix row is not size 3");
+      throw std::runtime_error("parseMpJson: matrix row must be length 3.");
     }
     m_structure.cell(i, 0) = mat[i][0].get<double>();
     m_structure.cell(i, 1) = mat[i][1].get<double>();
     m_structure.cell(i, 2) = mat[i][2].get<double>();
   }
 
-  if (!data.contains("sites") || !data["sites"].is_array()) {
-    throw std::runtime_error("parseMpJson: No sites array");
+  // 'sites' array: each site has fractional coordinates "abc"
+  if (!structureJson.contains("sites") || !structureJson["sites"].is_array()) {
+    throw std::runtime_error("parseMpJson: 'sites' is missing or not an array.");
   }
-  auto sites = data["sites"];
+  auto sites = structureJson["sites"];
+
   for (auto &site : sites) {
     if (!site.contains("abc") || !site["abc"].is_array() ||
         site["abc"].size() != 3) {
-      throw std::runtime_error("parseMpJson: site missing abc array");
+      throw std::runtime_error("parseMpJson: Site missing valid 'abc'.");
     }
     double fx = site["abc"][0].get<double>();
     double fy = site["abc"][1].get<double>();
     double fz = site["abc"][2].get<double>();
 
-    // fractional -> cart
-    double x = fx * m_structure.cell(0, 0) + fy * m_structure.cell(1, 0) +
+    // Convert fractional -> cart
+    double x = fx * m_structure.cell(0, 0) +
+               fy * m_structure.cell(1, 0) +
                fz * m_structure.cell(2, 0);
-    double y = fx * m_structure.cell(0, 1) + fy * m_structure.cell(1, 1) +
+    double y = fx * m_structure.cell(0, 1) +
+               fy * m_structure.cell(1, 1) +
                fz * m_structure.cell(2, 1);
-    double z = fx * m_structure.cell(0, 2) + fy * m_structure.cell(1, 2) +
+    double z = fx * m_structure.cell(0, 2) +
+               fy * m_structure.cell(1, 2) +
                fz * m_structure.cell(2, 2);
 
+    // The element can be found in either 'label' or 'species[0].element'
+    std::string elemSymbol;
+    if (site.contains("label") && site["label"].is_string()) {
+      elemSymbol = site["label"].get<std::string>();
+    } else if (site.contains("species") && site["species"].is_array() &&
+               !site["species"].empty() &&
+               site["species"][0].contains("element")) {
+      elemSymbol = site["species"][0]["element"].get<std::string>();
+    } else {
+      throw std::runtime_error(
+        "parseMpJson: No recognized element data in site.");
+    }
+
     Atom a;
-    std::string lbl = site["label"].get<std::string>();
-    Element e = parse_element_mp(lbl);
+    Element e = parse_element_mp(elemSymbol);
     static_cast<Element &>(a) = e;
     a.position[0] = x;
     a.position[1] = y;
     a.position[2] = z;
+
     m_structure.atoms.push_back(a);
   }
 }
-
-static Element parse_element_mp(const std::string &elemName) {
-  return PeriodicTable().find_by_symbol(elemName);
-}
diff --git a/src/nomad_reader.cpp b/src/nomad_reader.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/plain_text_key_storage.cpp b/src/plain_text_key_storage.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..014ba23191d0275bbb26c6de0bc8c39fe9235dea
--- /dev/null
+++ b/src/plain_text_key_storage.cpp
@@ -0,0 +1,140 @@
+/**
+ * @file PlainTextKeyStorage.cpp
+ * @brief Implementation file for PlainTextKeyStorage class.
+ */
+
+#include <tadah/mlip/key_storage/plain_text_key_storage.h>
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <cstdlib>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+PlainTextKeyStorage::PlainTextKeyStorage()
+{
+  m_configDir = prepareConfigDirectory();
+}
+
+std::string PlainTextKeyStorage::retrieveKey(const std::string& keyName)
+{
+  return readKeyFromFile(keyName);
+}
+
+bool PlainTextKeyStorage::storeKey(const std::string& keyName, const std::string& keyValue)
+{
+  auto pairs = loadAllKeys();
+  bool updated = false;
+
+  // Update existing or append
+  for (auto &kv : pairs) {
+    if (kv.first == keyName) {
+      kv.second = keyValue; // update
+      updated = true;
+      break;
+    }
+  }
+  if (!updated) {
+    pairs.emplace_back(keyName, keyValue);
+  }
+
+  return writeAllKeys(pairs);
+}
+
+std::string PlainTextKeyStorage::prepareConfigDirectory()
+{
+  const char* homeDir = std::getenv("HOME");
+  if (!homeDir) {
+    std::cerr << "Warning: HOME not set; using current directory for keys file.\n";
+    return ".";
+  }
+
+  std::string configDir = std::string(homeDir) + "/.config/tadah";
+  struct stat sb;
+  if (stat(configDir.c_str(), &sb) != 0) {
+    // Directory does not exist -> mkdir
+    if (mkdir(configDir.c_str(), 0700) != 0) {
+      std::cerr << "Error: Could not create config directory: " << configDir
+                << "\nFalling back to current directory.\n";
+      return ".";
+    }
+  } else if (!S_ISDIR(sb.st_mode)) {
+    // Path exists but is not a directory
+    std::cerr << "Error: " << configDir << " is not a directory.\n"
+              << "Falling back to current directory.\n";
+    return ".";
+  }
+
+  return configDir;
+}
+
+std::string PlainTextKeyStorage::getKeysFilePath() const
+{
+  return m_configDir + "/keys";
+}
+
+std::string PlainTextKeyStorage::readKeyFromFile(const std::string& keyName)
+{
+  std::ifstream fileIn(getKeysFilePath());
+  if (!fileIn.good()) {
+    return "";
+  }
+
+  std::string line;
+  while (std::getline(fileIn, line)) {
+    if (line.empty()) continue;
+
+    auto spacePos = line.find(' ');
+    if (spacePos == std::string::npos) {
+      continue; // skip malformed
+    }
+
+    std::string k = line.substr(0, spacePos);
+    std::string v = line.substr(spacePos + 1);
+
+    if (k == keyName) {
+      return v;
+    }
+  }
+
+  return "";
+}
+
+std::vector<std::pair<std::string, std::string>> PlainTextKeyStorage::loadAllKeys()
+{
+  std::vector<std::pair<std::string, std::string>> kvPairs;
+  std::ifstream fileIn(getKeysFilePath());
+  if (!fileIn.good()) {
+    return kvPairs;
+  }
+
+  std::string line;
+  while (std::getline(fileIn, line)) {
+    if (line.empty()) continue;
+    auto spacePos = line.find(' ');
+    if (spacePos == std::string::npos) {
+      continue;
+    }
+    std::string k = line.substr(0, spacePos);
+    std::string v = line.substr(spacePos + 1);
+    kvPairs.emplace_back(k, v);
+  }
+
+  return kvPairs;
+}
+
+bool PlainTextKeyStorage::writeAllKeys(const std::vector<std::pair<std::string, std::string>>& kvPairs)
+{
+  std::ofstream fileOut(getKeysFilePath(), std::ios::trunc);
+  if (!fileOut.good()) {
+    std::cerr << "Could not write to file: " << getKeysFilePath() << "\n";
+    return false;
+  }
+
+  for (const auto &kv : kvPairs) {
+    fileOut << kv.first << " " << kv.second << "\n";
+  }
+  return true;
+}
+
diff --git a/src/structure_reader_selector.cpp b/src/structure_reader_selector.cpp
index 684d2ce3c92aeb1228fe30c15c18d5ccdea2dd4a..44ecbbf8f5bf1cdd9d8997c83d09913dfeccc6c6 100644
--- a/src/structure_reader_selector.cpp
+++ b/src/structure_reader_selector.cpp
@@ -1,114 +1,118 @@
-#include <tadah/mlip/structure_readers/structure_reader_selector.h>
+#include <tadah/mlip/structure_readers/castep_cell_reader.h>
 #include <tadah/mlip/structure_readers/cif_reader.h>
+#include <tadah/mlip/structure_readers/structure_reader_selector.h>
 #include <tadah/mlip/structure_readers/vasp_poscar_reader.h>
-#include <tadah/mlip/structure_readers/castep_cell_reader.h>
 /*#include <tadah/mlip/structure_readers/extended_xyz_reader.h>*/
 /*#include <tadah/mlip/structure_readers/xsf_reader.h>*/
 #include <tadah/mlip/structure_readers/materials_project_reader.h>
 /*#include <tadah/mlip/structure_readers/cod_reader.h>*/
 /*#include <tadah/mlip/structure_readers/aflow_reader.h>*/
 /*#include <tadah/mlip/structure_readers/nomad_reader.h>*/
+#include <tadah/mlip/key_storage/key_manager.h>
 
-#include <fstream>
 #include <algorithm>
 #include <cctype>
+#include <fstream>
 #include <stdexcept>
 
-std::unique_ptr<StructureReader> StructureReaderSelector::getReader(const std::string& pathOrId)
-{
-  std::cout << "Reading structure from: " << pathOrId << std::endl;
-    std::string format = guessFormat(pathOrId);
-    std::cout << "Format: " << format << std::endl;
+std::unique_ptr<StructureReader>
+StructureReaderSelector::getReader(const std::string &pathOrId) {
+  std::string format = guessFormat(pathOrId);
 
-    if (format == "MP") {
-        return std::make_unique<MaterialsProjectReader>("MY_MP_API_KEY"); // Provide your real key
-    }
-    else if (format == "CIF") {
-      std::cout << "CIF Reader Selected" << std::endl;
-        return std::make_unique<CifReader>();
-    }
-    else if (format == "POSCAR") {
-        return std::make_unique<VaspPoscarReader>();
-    }
-    else if (format == "CELL") {
-        return std::make_unique<CastepCellReader>();
-    }
-    /*else if (format == "XYZ") {*/
-    /*    return std::make_unique<ExtendedXyzReader>();*/
-    /*}*/
-    /*else if (format == "XSF") {*/
-    /*    return std::make_unique<XsfReader>();*/
-    /*}*/
-    /*else if (format == "COD") {*/
-    /*    return std::make_unique<CODReader>();*/
-    /*}*/
-    /*else if (format == "AFLOW") {*/
-    /*    return std::make_unique<AflowReader>();*/
-    /*}*/
-    /*else if (format == "NOMAD") {*/
-    /*    return std::make_unique<NomadReader>();*/
-    /*}*/
-    else {
-        throw std::runtime_error("StructureReaderSelector::getReader - Unknown format for " + pathOrId);
+  if (format == "MP") {
+    std::string mpKey =
+        KeyManager::getServiceKey(ServiceType::MP, /*allowPrompt=*/true);
+    if (mpKey.empty()) {
+      throw std::runtime_error(
+          "No valid Materials Project key provided. Cannot continue.");
     }
+    return std::make_unique<MaterialsProjectReader>(mpKey);
+  } else if (format == "CIF") {
+    return std::make_unique<CifReader>();
+  } else if (format == "POSCAR") {
+    return std::make_unique<VaspPoscarReader>();
+  } else if (format == "CELL") {
+    return std::make_unique<CastepCellReader>();
+  }
+  /*else if (format == "XYZ") {*/
+  /*    return std::make_unique<ExtendedXyzReader>();*/
+  /*}*/
+  /*else if (format == "XSF") {*/
+  /*    return std::make_unique<XsfReader>();*/
+  /*}*/
+  /*else if (format == "COD") {*/
+  /*    return std::make_unique<CODReader>();*/
+  /*}*/
+  /*else if (format == "AFLOW") {*/
+  /*    return std::make_unique<AflowReader>();*/
+  /*}*/
+  /*else if (format == "NOMAD") {*/
+  /*    return std::make_unique<NomadReader>();*/
+  /*}*/
+  else {
+    throw std::runtime_error(
+        "StructureReaderSelector::getReader - Unknown format for " + pathOrId);
+  }
 }
 
-std::string StructureReaderSelector::guessFormat(const std::string& pathOrId)
-{
-    // Check known online ID patterns:
-    if (pathOrId.rfind("mp-", 0) == 0) {
-        return "MP";
-    }
-    if (pathOrId.rfind("cod-", 0) == 0) {
-        return "COD";
-    }
-    if (pathOrId.rfind("aflow-", 0) == 0) {
-        return "AFLOW";
-    }
-    if (pathOrId.rfind("nomad-", 0) == 0) {
-        return "NOMAD";
-    }
+std::string StructureReaderSelector::guessFormat(const std::string &pathOrId) {
+  // Check known online ID patterns:
+  if (pathOrId.rfind("mp-", 0) == 0) {
+    return "MP";
+  }
+  if (pathOrId.rfind("cod-", 0) == 0) {
+    return "COD";
+  }
+  if (pathOrId.rfind("aflow-", 0) == 0) {
+    return "AFLOW";
+  }
+  if (pathOrId.rfind("nomad-", 0) == 0) {
+    return "NOMAD";
+  }
 
-    // Check file extension:
-    auto dotPos = pathOrId.find_last_of('.');
-    if (dotPos != std::string::npos) {
-        std::string ext = pathOrId.substr(dotPos + 1);
-        std::transform(ext.begin(), ext.end(), ext.begin(),
-                       [](unsigned char c){ return std::tolower(c); });
+  // Check file extension:
+  auto dotPos = pathOrId.find_last_of('.');
+  if (dotPos != std::string::npos) {
+    std::string ext = pathOrId.substr(dotPos + 1);
+    std::transform(ext.begin(), ext.end(), ext.begin(),
+                   [](unsigned char c) { return std::tolower(c); });
 
-        if (ext == "cif")  return "CIF";
-        if (ext == "cell") return "CELL";
-        if (ext == "xsf")  return "XSF";
-        if (ext == "xyz")  return "XYZ";
-        // Some VASP files may not have an extension
-    }
+    if (ext == "cif")
+      return "CIF";
+    if (ext == "cell")
+      return "CELL";
+    if (ext == "xsf")
+      return "XSF";
+    if (ext == "xyz")
+      return "XYZ";
+    // Some VASP files may not have an extension
+  }
 
-    return guessFormatFromContent(pathOrId);
+  return guessFormatFromContent(pathOrId);
 }
 
-std::string StructureReaderSelector::guessFormatFromContent(const std::string& filePath)
-{
-    std::ifstream ifs(filePath);
-    if (!ifs.is_open()) {
-        // If can't open, guess POSCAR (commonly no extension).
-        return "POSCAR";
-    }
+std::string
+StructureReaderSelector::guessFormatFromContent(const std::string &filePath) {
+  std::ifstream ifs(filePath);
+  if (!ifs.is_open()) {
+    // If can't open, guess POSCAR (commonly no extension).
+    return "POSCAR";
+  }
 
-    std::string line;
-    if (std::getline(ifs, line)) {
-        // Some naive checks
-        if (line.find("lattice_vector") != std::string::npos) {
-            return "XSF";
-        }
-        if (line.find("CELL_PARAMETERS") != std::string::npos) {
-            return "CIF";
-        }
-        if (line.find("%BLOCK LATTICE_") != std::string::npos) {
-            return "CELL";
-        }
+  std::string line;
+  if (std::getline(ifs, line)) {
+    // Some naive checks
+    if (line.find("lattice_vector") != std::string::npos) {
+      return "XSF";
+    }
+    if (line.find("CELL_PARAMETERS") != std::string::npos) {
+      return "CIF";
+    }
+    if (line.find("%BLOCK LATTICE_") != std::string::npos) {
+      return "CELL";
     }
+  }
 
-    // fallback
-    return "POSCAR";
+  // fallback
+  return "POSCAR";
 }
-