ASTRAI - API Key Manager Tool

A simple, local, and secure way to manage your AI API keys.

Opis obrazka

Overview

The ASTRAI API Key Manager is a lightweight desktop application built with Python's Tkinter, designed to help you securely store and quickly access your various AI API keys.

It's particularly useful for developers, AI enthusiasts, and anyone working with multiple AI services (like OpenAI, Google Gemini, Claude, etc.) who needs a convenient way to keep their API keys organized and ready for use.

How it Works

This Python script creates a simple GUI where you can:

Get the Tool

To use the API Key Manager, you will need Python installed on your system. You'll also need the pyperclip library for clipboard functionality.

  1. Install Python: If you don't have Python, download it from python.org.
  2. Install pyperclip: Open your terminal or command prompt and run: pip install pyperclip
  3. Save the Code: Copy the Python script provided below and save it as a .py file (e.g., api_key_manager.py).
  4. Run the Script: Execute the script from your terminal: python api_key_manager.py

The manager will automatically create an api_keys.json file in the same directory where the script is run, to store your keys.

Show Python API Key Manager Script
import tkinter as tk
from tkinter import simpledialog, messagebox, ttk
import json
import os
import pyperclip

class APIKeyManager:
    def __init__(self, root):
        self.root = root
        self.root.title("API Key Manager")
        self.root.geometry("550x560") 
        self.root.resizable(False, False) 

        self.style_dark_mode()

        self.keys_file = "api_keys.json"
        self.api_keys = self.load_keys()

        main_frame = ttk.Frame(root, padding="20 20 20 20")
        main_frame.pack(fill=tk.BOTH, expand=True)

        ascii_logo = """
          _____ _______ _____            _____ _____ _    _       _______ 
     /\    / ____|__   __|  __ \     /\   |_   _/ ____| |  | |   /\|__   __|
    /  \  | (___    | |  | |__) |   /  \    | || |    | |__| |  /  \  | |   
   / /\ \  \___ \   | |  |  _  /   / /\ \   | || |    |  __  | / /\ \ | |   
  / ____ \ ____) |  | |  | | \ \  / ____ \ _| || |____| |  | |/ ____ \| |   
 /_/    \_\_____/   |_|  |_|  \_\/_/    \_\_____\_____|_|  |_/_/    \_\_|   
        """
        logo_label = tk.Label(
            main_frame,
            text=ascii_logo,
            font=('Consolas', 8), 
            bg='#1e1e1e',
            fg="#7f1dc0" 
        )
        logo_label.pack(pady=(0, 10)) 

        title_label = ttk.Label(main_frame, text="API Key Manager", 
                                font=('Arial', 18, 'bold'))
        title_label.pack(pady=(0, 20))

        listbox_frame = ttk.Frame(main_frame)
        listbox_frame.pack(pady=10, fill=tk.X)

        self.keys_listbox = tk.Listbox(
            listbox_frame, 
            height=12, 
            bg='#2c2c2c', 
            fg='#ffffff', 
            selectbackground='#007acc', 
            selectforeground='#ffffff',
            font=('Consolas', 10),
            bd=0, 
            highlightthickness=0 # 
        )
        self.keys_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        scrollbar = ttk.Scrollbar(listbox_frame, orient=tk.VERTICAL, command=self.keys_listbox.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.keys_listbox.config(yscrollcommand=scrollbar.set)

        self.update_keys_listbox()

        button_frame = ttk.Frame(main_frame)
        button_frame.pack(pady=15)

        add_button = ttk.Button(
            button_frame, 
            text="Add Key", 
            command=self.add_key
        )
        add_button.pack(side=tk.LEFT, padx=5)

        copy_button = ttk.Button(
            button_frame, 
            text="Copy Selected Key", 
            command=self.copy_key
        )
        copy_button.pack(side=tk.LEFT, padx=5)

        delete_button = ttk.Button(
            button_frame, 
            text="Delete Key", 
            command=self.delete_key
        )
        delete_button.pack(side=tk.LEFT, padx=5)

        self.status_label = ttk.Label(
            main_frame, 
            text="", 
            font=('Arial', 10)
        )
        self.status_label.pack(pady=10)

        instruction_label = ttk.Label(
            main_frame,
            text="Tip: Press 1-9 to quickly copy the corresponding key.",
            font=('Arial', 9, 'italic'),
            foreground='#aaaaaa' 
        )
        instruction_label.pack(pady=(0, 5))

        for i in range(1, 10):
            self.root.bind(f"", self._on_number_key_press)

    def style_dark_mode(self):
        """Applies a dark theme to the Tkinter window and widgets."""
        self.root.tk_setPalette(background='#1e1e1e', foreground='white',
                                activeBackground='#4a4a4a', activeForeground='white')
        
        style = ttk.Style()
        style.theme_use('clam') 

        style.configure('.', font=('Arial', 10), background='#1e1e1e', foreground='white')

        style.configure('TFrame', background='#1e1e1e')

        style.configure('TLabel', background='#1e1e1e', foreground='white')
        style.configure('Title.TLabel', font=('Arial', 18, 'bold')) 
        style.configure('Status.TLabel', foreground='#00ff00') 

        style.configure('TButton', 
                        background='#3c3f41', 
                        foreground='white',
                        font=('Arial', 10, 'bold'),
                        borderwidth=0, 
                        focusthickness=3,
                        focuscolor='#007acc') 

        style.map('TButton', 
                  background=[('active', '#007acc'), ('!active', '#3c3f41')], 
                  foreground=[('active', 'white'), ('!active', 'white')])

        style.configure("Vertical.TScrollbar", troughcolor='#1e1e1e', background='#555555', 
                        gripcount=0, borderwidth=0) 
        style.map("Vertical.TScrollbar",
                  background=[('active', '#777777')])

    def show_status(self, message, color='#00ff00'):
        """Displays a temporary status message."""
        self.status_label.config(text=message, foreground=color)
        # Reset color to default (white) after some time
        self.root.after(2000, lambda: self.status_label.config(text="", foreground='white'))

    def load_keys(self):
        """Loads API keys from a JSON file."""
        if os.path.exists(self.keys_file):
            try:
                with open(self.keys_file, 'r') as f:
                    return json.load(f)
            except json.JSONDecodeError:
                messagebox.showerror("Error", "Could not load API keys. The file might be corrupted.")
                return {}
        return {}

    def save_keys(self):
        """Saves API keys to a JSON file."""
        try:
            with open(self.keys_file, 'w') as f:
                json.dump(self.api_keys, f, indent=4)
        except Exception as e:
            messagebox.showerror("Error", f"Failed to save API keys: {e}")

    def update_keys_listbox(self):
        """Updates the listbox with current API keys."""
        self.keys_listbox.delete(0, tk.END)
        if not self.api_keys:
            self.keys_listbox.insert(tk.END, "No API keys stored. Add one using 'Add Key'.")
            self.keys_listbox.itemconfig(0, {'fg': '#888888'}) # Gray out the placeholder text
            return

        for i, (name, key) in enumerate(self.api_keys.items()):
            # Mask the key for display
            masked_key = key[:5] + '*' * (len(key) - 10) + key[-5:] if len(key) > 10 else '*' * len(key)
            self.keys_listbox.insert(tk.END, f"{i+1}. {name}: {masked_key}")

    def add_key(self):
        """Adds a new API key."""
        name = simpledialog.askstring(
            "Add API Key", 
            "Enter key name (e.g., 'OpenAI', 'Google'):", 
            parent=self.root
        )
        if name:
            name = name.strip()
            if not name:
                self.show_status("Key name cannot be empty.", color='#ffcc00')
                return
            if name in self.api_keys:
                messagebox.showwarning("Warning", f"A key with the name '{name}' already exists. Please choose a different name or delete the existing key.")
                return

            key = simpledialog.askstring(
                "Add API Key", 
                "Enter API key:", 
                parent=self.root
            )
            if key:
                key = key.strip()
                if not key:
                    self.show_status("API key cannot be empty.", color='#ffcc00')
                    return
                self.api_keys[name] = key
                self.save_keys()
                self.update_keys_listbox()
                self.show_status("Key added successfully.", color='#00ff00')
            else:
                self.show_status("Key addition cancelled.", color='#ffcc00')
        else:
            self.show_status("Key addition cancelled.", color='#ffcc00')

    def copy_key(self, index=None):
        """Copies the selected key to the clipboard.
           If `index` is provided, copies the key at that index.
           Otherwise, copies the currently selected key in the listbox.
        """
        try:
            if index is None:
                selected_indices = self.keys_listbox.curselection()
                if not selected_indices:
                    self.show_status("Please select a key to copy.", color='#ffcc00')
                    return
                index = selected_indices[0]
            
            if 0 <= index < len(self.api_keys):
                key = list(self.api_keys.values())[index]
                pyperclip.copy(key)
                self.show_status(f"Copied key '{list(self.api_keys.keys())[index]}'.", color='#00ff00')
                self.keys_listbox.selection_clear(0, tk.END)
                self.keys_listbox.selection_set(index)
                self.keys_listbox.activate(index)
            else:
                self.show_status("Invalid key index.", color='#ff0000')

        except IndexError:
            self.show_status("Please select a key to copy.", color='#ffcc00')
        except Exception as e:
            self.show_status(f"Error copying key: {e}", color='#ff0000')

    def delete_key(self):
        """Deletes the selected API key."""
        try:
            selected_index = self.keys_listbox.curselection()
            if not selected_index:
                self.show_status("Please select a key to delete.", color='#ffcc00')
                return

            index = selected_index[0]
            key_name_to_delete = list(self.api_keys.keys())[index]

            if messagebox.askyesno("Confirm Deletion", f"Are you sure you want to delete the key '{key_name_to_delete}'?"):
                del self.api_keys[key_name_to_delete]
                self.save_keys()
                self.update_keys_listbox()
                self.show_status(f"Key '{key_name_to_delete}' deleted.", color='#00ff00')
            else:
                self.show_status("Deletion cancelled.", color='#ffcc00')
        except IndexError:
            self.show_status("Please select a key to delete.", color='#ffcc00')
        except Exception as e:
            self.show_status(f"Error deleting key: {e}", color='#ff0000')

    def _on_number_key_press(self, event):
        """Handles numeric key presses (1-9) to copy a key."""
        try:
            key_number = int(event.char)
            if 1 <= key_number <= 9:
                self.copy_key(key_number - 1)
        except ValueError:
            pass
        except Exception as e:
            self.show_status(f"Key press error: {e}", color='#ff0000')

def main():
    root = tk.Tk()
    app = APIKeyManager(root)
    root.mainloop()

if __name__ == "__main__":
    main()