summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/account.py43
-rw-r--r--src/crypto.py45
-rw-r--r--src/database.py125
-rw-r--r--src/process.py225
-rw-r--r--src/yoshi_christian_cleberg.egg-info/PKG-INFO198
-rw-r--r--src/yoshi_christian_cleberg.egg-info/SOURCES.txt11
-rw-r--r--src/yoshi_christian_cleberg.egg-info/dependency_links.txt1
-rw-r--r--src/yoshi_christian_cleberg.egg-info/top_level.txt4
-rw-r--r--src/yoshi_cli.egg-info/PKG-INFO198
-rw-r--r--src/yoshi_cli.egg-info/SOURCES.txt11
-rw-r--r--src/yoshi_cli.egg-info/dependency_links.txt1
-rw-r--r--src/yoshi_cli.egg-info/top_level.txt4
-rw-r--r--src/yoshi_cli_christian_cleberg.egg-info/PKG-INFO198
-rw-r--r--src/yoshi_cli_christian_cleberg.egg-info/SOURCES.txt11
-rw-r--r--src/yoshi_cli_christian_cleberg.egg-info/dependency_links.txt1
-rw-r--r--src/yoshi_cli_christian_cleberg.egg-info/top_level.txt4
16 files changed, 1080 insertions, 0 deletions
diff --git a/src/account.py b/src/account.py
new file mode 100644
index 0000000..79fcc13
--- /dev/null
+++ b/src/account.py
@@ -0,0 +1,43 @@
+"""
+This script imports necessary modules for database interactions.
+
+Modules imported:
+ - database: A custom module providing database functionality.
+"""
+
+import database
+
+
+class Account:
+ """Represents a login account."""
+
+ def __init__(self, uuid: str, application: str, #pylint: disable=R0913,R0917
+ username: str, #pylint: disable=R0913,R0917
+ password: str, url: str) -> None: #pylint: disable=R0913,R0917
+ self.uuid = uuid
+ self.application = application
+ self.username = username
+ self.password = password
+ self.url = url
+
+ def display_account(self) -> None:
+ """Print the account details."""
+ print('ID:', self.uuid)
+ print('Application:', self.application)
+ print('Username:', self.username)
+ print('Password:', self.password)
+ print('URL:', self.url)
+
+ def save_account(self) -> None:
+ """Save the account details to the database."""
+ database.add_account(
+ self.uuid, self.application, self.username, self.password, self.url)
+
+ def delete_account(self) -> bool:
+ """Delete the account from the database.
+
+ Returns:
+ bool: True if the deletion was successful.
+ """
+ database.delete_account(self.uuid)
+ return True
diff --git a/src/crypto.py b/src/crypto.py
new file mode 100644
index 0000000..9b0a423
--- /dev/null
+++ b/src/crypto.py
@@ -0,0 +1,45 @@
+"""
+This module imports the Fernet symmetric encryption algorithm from the cryptography library.
+
+It allows for secure encryption and decryption of data using a secret key.
+"""
+
+from cryptography.fernet import Fernet
+
+VAULT_FILE = 'vault.sqlite'
+
+
+def generate_key() -> bytes:
+ """Generates a new encryption key."""
+ return Fernet.generate_key()
+
+
+def load_key(key_file: str) -> bytes:
+ """
+ Loads an existing encryption key from the file.
+
+ Args:
+ key_file (str): Path to the key file.
+ """
+ with open(key_file, 'rb') as key:
+ return key.read()
+
+
+def encrypt(key: bytes, filename: str = VAULT_FILE) -> None:
+ """Encrypts the data in the specified file using the provided key."""
+ f = Fernet(key)
+ with open(filename, 'rb') as vault:
+ data = vault.read()
+ encrypted_data = f.encrypt(data)
+ with open(filename, 'wb') as vault:
+ vault.write(encrypted_data)
+
+
+def decrypt(key: bytes, filename: str = VAULT_FILE) -> None:
+ """Decrypts the data in the specified file using the provided key."""
+ f = Fernet(key)
+ with open(filename, 'rb') as vault:
+ encrypted_data = vault.read()
+ decrypted_data = f.decrypt(encrypted_data)
+ with open(filename, 'wb') as vault:
+ vault.write(decrypted_data)
diff --git a/src/database.py b/src/database.py
new file mode 100644
index 0000000..e1e2e78
--- /dev/null
+++ b/src/database.py
@@ -0,0 +1,125 @@
+"""
+This module provides a basic interface for connecting to and interacting with a SQLite database.
+It includes functions for creating connections, executing queries, and retrieving results.
+"""
+
+import sqlite3
+import sys
+import os
+
+VAULT_DECRYPTED = 'vault.sqlite'
+VAULT_ENCRYPTED = 'vault.sqlite.aes'
+
+
+def create_table() -> None:
+ """Create the accounts table within the vault database."""
+ db_connection = sqlite3.connect(VAULT_DECRYPTED)
+ cursor = db_connection.cursor()
+ cursor.execute(
+ ''' CREATE TABLE IF NOT EXISTS accounts (uuid text, application text,
+ username text, password text, url text) '''
+ )
+ db_connection.commit()
+ db_connection.close()
+
+
+def check_table() -> bool:
+ """Check if the 'accounts' table exists within the vault database."""
+ check = False
+ db_connection = sqlite3.connect(VAULT_DECRYPTED)
+ cursor = db_connection.cursor()
+ cursor.execute(
+ ''' SELECT count(name) FROM sqlite_master WHERE type='table'
+ AND name='accounts' '''
+ )
+ if cursor.fetchone()[0] != 1:
+ user_choice = input(
+ 'Password vault does not exist. Would you like to create it now? (y/n): ')
+ if user_choice.lower() == 'y':
+ create_table()
+ check = True
+ else:
+ sys.exit('Program aborted upon user request.')
+ else:
+ check = True
+ db_connection.commit()
+ db_connection.close()
+ return check
+
+
+def add_account(uuid: str, application: str, username: str, password: str,
+ url: str) -> None:
+ """Add a new account within the vault database."""
+ db_connection = sqlite3.connect(VAULT_DECRYPTED)
+ cursor = db_connection.cursor()
+ cursor.execute(
+ ''' INSERT INTO accounts VALUES (:uuid,:application,:username,
+ :password,:url) ''', {
+ 'uuid': uuid, 'application': application, 'username': username,
+ 'password': password, 'url': url
+ }
+ )
+ db_connection.commit()
+ db_connection.close()
+
+
+def delete_account(uuid: str) -> None:
+ """Delete an account within the vault database by its unique ID."""
+ db_connection = sqlite3.connect(VAULT_DECRYPTED)
+ cursor = db_connection.cursor()
+ cursor.execute(
+ ''' DELETE FROM accounts WHERE uuid = :uuid ''', {'uuid': uuid}
+ )
+ db_connection.commit()
+ db_connection.close()
+
+
+def find_account(uuid: str) -> list:
+ """Find an account within the vault database by its unique ID."""
+ db_connection = sqlite3.connect(VAULT_DECRYPTED)
+ cursor = db_connection.cursor()
+ cursor.execute(
+ ''' SELECT * FROM accounts WHERE uuid = :uuid ''', {'uuid': uuid}
+ )
+ account = cursor.fetchall()
+ db_connection.close()
+ return account
+
+
+def find_accounts() -> list:
+ """Return all accounts stored within the vault database."""
+ db_connection = sqlite3.connect(VAULT_DECRYPTED)
+ cursor = db_connection.cursor()
+ cursor.execute(''' SELECT * FROM accounts ''')
+ accounts = cursor.fetchall()
+ db_connection.close()
+ return accounts
+
+
+def update_account(field_name: str, new_value: str, uuid: str) -> None:
+ """Update an account within the vault database by its unique ID."""
+ queries = {
+ 'application': 'UPDATE accounts SET application = :new_value WHERE uuid = :uuid',
+ 'username': 'UPDATE accounts SET username = :new_value WHERE uuid = :uuid',
+ 'password': 'UPDATE accounts SET password = :new_value WHERE uuid = :uuid',
+ 'url': 'UPDATE accounts SET url = :new_value WHERE uuid = :uuid'
+ }
+ db_connection = sqlite3.connect(VAULT_DECRYPTED)
+ cursor = db_connection.cursor()
+ cursor.execute(queries[field_name], {'new_value': new_value, 'uuid': uuid})
+ db_connection.commit()
+ db_connection.close()
+
+
+def purge_table() -> None:
+ """Purge the 'accounts' table within the vault database."""
+ db_connection = sqlite3.connect(VAULT_DECRYPTED)
+ cursor = db_connection.cursor()
+ cursor.execute(''' DROP TABLE accounts ''')
+ db_connection.commit()
+ db_connection.close()
+
+
+def purge_database() -> None:
+ """Purge the entire vault database."""
+ os.remove(VAULT_DECRYPTED)
diff --git a/src/process.py b/src/process.py
new file mode 100644
index 0000000..0888a78
--- /dev/null
+++ b/src/process.py
@@ -0,0 +1,225 @@
+"""
+Password Vault Manager
+
+This script provides various functions for managing password vaults.
+It allows users to create, list, edit and delete accounts.
+
+The `Account` class represents an individual account, with attributes for
+the application name, username, password, and URL. The database module is used
+to interact with the SQLite database file (`vault.sqlite`) that stores the
+accounts data.
+
+Functions:
+ generate_characters(n): generates a list of random characters
+ shuffle_characters(characters): shuffles the characters to create a password
+ generate_passphrase(n, sep): generates an XKCD-style passphrase with n words and separator
+ list_accounts(): lists all saved accounts in the database
+ delete_account(uuid): deletes an account by its UUID
+ purge_accounts(): purges the entire database (irreversible)
+ create_account(): creates a new account by prompting user for details
+ edit_account(uuid, edit_parameter): edits an existing account's details
+
+Usage:
+ Run this script in your terminal to access these functions.
+"""
+
+from string import ascii_letters, punctuation, digits
+import random
+import uuid
+from prettytable import PrettyTable
+from account import Account
+import database
+
+
+def generate_characters(n: int) -> list:
+ """
+ Generates a list of n random characters from the set of ASCII letters,
+ punctuation and digits.
+
+ Args:
+ n (int): The number of characters to generate
+
+ Returns:
+ list: A list of n random characters
+ """
+ characters = []
+ password_format = ascii_letters + punctuation + digits
+ for _ in range(n):
+ characters.append(random.choice(password_format))
+ return characters
+
+
+def shuffle_characters(characters: list) -> str:
+ """
+ Shuffles the characters to create a password.
+
+ Args:
+ characters (list): The list of characters
+
+ Returns:
+ str: A string representation of the shuffled characters
+ """
+ random.shuffle(characters)
+ character_string = ''.join(characters)
+ return character_string
+
+
+def generate_passphrase(n: int, sep: str) -> str:
+ """
+ Generates an XKCD-style passphrase with n words and separator.
+
+ Args:
+ n (int): The number of words to include
+ sep (str): The separator symbol
+
+ Returns:
+ str: A string representation of the passphrase
+ """
+ phrases = []
+ lucky_number = random.choice(range(0, n))
+ for _ in range(n):
+ with open('wordlist.txt', 'r', encoding='utf-8') as file:
+ line = random.choice(file.readlines())
+ line = line.replace('\n', '')
+ if _ == lucky_number:
+ phrases.append(line.strip().capitalize() + str(_))
+ else:
+ phrases.append(line.strip().capitalize())
+ passphrase = sep.join(phrases)
+ return passphrase
+
+
+def list_accounts() -> None:
+ """
+ Lists all saved accounts in the database.
+
+ Returns:
+ None
+ """
+ accounts = database.find_accounts()
+ t = PrettyTable(['UUID', 'Application', 'Username', 'Password', 'URL'])
+ for account in accounts:
+ t.add_row([account[0], account[1], account[2], account[3], account[4]])
+ print(t)
+
+
+def delete_account(account_uuid: str) -> None:
+ """
+ Deletes an account by its UUID.
+
+ Args:
+ account_uuid (str): The UUID of the account to delete
+
+ Returns:
+ None
+ """
+ account_record = database.find_account(account_uuid)
+ account = Account(account_record[0][0],
+ account_record[0][1],
+ account_record[0][2],
+ account_record[0][3],
+ account_record[0][4])
+ if account.delete_account():
+ print('Account successfully deleted.')
+
+
+def purge_accounts() -> None:
+ """
+ Purges the entire database (irreversible).
+
+ Returns:
+ None
+ """
+ check = input(
+ '''Are you absolutely sure you want to delete your password vault?
+ This action is irreversible. (y/n): ''')
+ if check.lower() == 'y':
+ database.purge_table()
+ database.purge_database()
+ print('The password vault has been purged. You may now exit or create a new one.')
+
+
+def create_account() -> None:
+ """
+ Creates a new account by prompting user for details.
+
+ Returns:
+ None
+ """
+ application_string = input('Please enter a name for this account: ')
+ username_string = input('Please enter your username for this account: ')
+ url_string = input('(Optional) Please enter a URL for this account: ')
+
+ password_type = input(
+ '''Do you want a random character password (p), an XKCD-style passphrase
+(x), or a custom password (c)? (p|x|c): '''
+ )
+ if password_type not in ['p', 'x', 'c']:
+ print('Error: Invalid choice. Please choose p, x, or c.')
+ return
+
+ if password_type == 'x':
+ password_length = int(
+ input('Please enter number of words to include (min. 2): ')
+ )
+ if password_length < 3:
+ print('Error: Your passphrase length must be at least 3 words.')
+ return
+ password_separator = input(
+ 'Please enter your desired separator symbol (_,-, ~, etc.): '
+ )
+ password_string = generate_passphrase(password_length, password_separator)
+ elif password_type == 'p':
+ password_length = int(
+ input('Please enter your desired password length (min. 8): ')
+ )
+ if password_length < 8:
+ print('Error: Your password length must be at least 8 characters.')
+ return
+ password_string = generate_password(password_length) # pylint: disable=undefined-variable
+ else:
+ password_string = input('Please enter your desired password: ')
+
+ account = Account(str(uuid.uuid4()), application_string,
+ username_string, password_string, url_string)
+ account.save_account()
+ print('Account saved to the vault. Use `--list` to see all saved accounts.')
+
+
+
+def edit_account(account_uuid: str, edit_parameter: int) -> None:
+ """
+ Allow users to edit any account information except the UUID.
+
+ Args:
+ account_uuid (str): Unique identifier of the account.
+ edit_parameter (int): Parameter indicating which field to edit.
+ Valid values are 1 for application name, 2 for username,
+ 3 for password, and 4 for URL.
+ """
+ field_name, new_value = ''
+ if edit_parameter == 1:
+ field_name = 'application'
+ new_value = input('Please enter your desired Application name: ')
+ elif edit_parameter == 2:
+ field_name = 'username'
+ new_value = input('Please enter your desired username: ')
+ elif edit_parameter == 3:
+ field_name = 'password'
+ type_check = input(
+ 'Do you want a new random password or to enter a custom password? '
+ '(random/custom): ').lower()
+ if type_check == 'random':
+ password_length = int(input('Please enter your desired password length: '))
+ if password_length < 8:
+ print('Error: Your password length must be at least 8 characters.')
+ else:
+ password_characters = generate_characters(password_length)
+ new_value = shuffle_characters(password_characters)
+ else:
+ new_value = input('Please enter your desired password: ')
+ elif edit_parameter == 4:
+ field_name = 'url'
+ new_value = input('Please enter your desired URL: ')
+ database.update_account(field_name, new_value, account_uuid)
+ print('Account successfully updated.')
diff --git a/src/yoshi_christian_cleberg.egg-info/PKG-INFO b/src/yoshi_christian_cleberg.egg-info/PKG-INFO
new file mode 100644
index 0000000..233fcce
--- /dev/null
+++ b/src/yoshi_christian_cleberg.egg-info/PKG-INFO
@@ -0,0 +1,198 @@
+Metadata-Version: 2.1
+Name: yoshi_christian-cleberg
+Version: 0.1.0
+Summary: A password manager for the command line.
+Author-email: Christian Cleberg <hello@cleberg.net>
+Project-URL: Homepage, https://github.com/ccleberg/yoshi
+Project-URL: Issues, https://github.com/ccleberg/yoshi/issues
+Classifier: Programming Language :: Python :: 3
+Classifier: License :: OSI Approved :: GPL-3.0
+Classifier: Operating System :: OS Independent
+Requires-Python: >=3.8
+Description-Content-Type: text/markdown
+License-File: LICENSE
+
+# Yoshi: A Password Manager
+
+A simple command-line pass manager, writtin in Python + SQLite3. This tool
+allows you to manage accounts and generate random passwords containing ASCII
+letters, numbers, and punctuation (min. 8 characters) or XKCD-like passphrases
+(min. 3 words).
+
+Please note that the script is written in Python 3 - you may need to run the
+script with the `python3` command instead of `python` if your system uses a
+default of Python 2. See the Installation & Usage sections below for more
+information.
+
+# Table of Contents
+
+- [Installation](#installation)
+- [Usage](#usage)
+ - [Arguments](#arguments)
+- [Contributing](#contributing)
+
+# Installation
+
+[(Back to top)](#table-of-contents)
+
+To run the script locally, run the following commands:
+
+```bash
+git clone REPO_URL
+```
+
+```bash
+cd yoshi
+```
+
+```bash
+pip3 install -r requirements.txt
+```
+
+```bash
+python3 main.py --help
+```
+
+# Usage
+
+[(Back to top)](#table-of-contents)
+
+All commands can be passed to the program with the following template:
+`python3 main.py <COMMAND> <FLAG> <PARAMETER>`
+
+![Yoshi CLI Help](./examples/yoshi-help.png)
+
+## Arguments
+
+### Summary
+
+<table>
+ <thead>
+ <tr>
+ <td><b>Argument</b></td>
+ <td><b>Shortcut</b></td>
+ <td><b>Explanation</b></td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>help</td>
+ <td>h</td>
+ <td>Print the welcome message</td>
+ </tr>
+ <tr>
+ <td>new</td>
+ <td>n</td>
+ <td>Create a new account</td>
+ </tr>
+ <tr>
+ <td>list</td>
+ <td>l</td>
+ <td>List all saved accounts</td>
+ </tr>
+ <tr>
+ <td>edit</td>
+ <td>e</td>
+ <td>Edit a saved account (see below for required flags)</td>
+ </tr>
+ <tr>
+ <td>delete</td>
+ <td>d</td>
+ <td>Delete a saved account (see below for required flags)</td>
+ </tr>
+ <tr>
+ <td>purge</td>
+ <td>N/A</td>
+ <td>Purge all accounts and delete the vault</td>
+ </tr>
+ <tr>
+ <td>encrypt</td>
+ <td>N/A</td>
+ <td>Encrypt the vault database (see below for required flags)</td>
+ </tr>
+ <tr>
+ <td>decrypt</td>
+ <td>N/A</td>
+ <td>Decrypt the vault database (see below for required flags)</td>
+ </tr>
+ </tbody>
+</table>
+
+#### Flags
+
+Flags for the `edit`, `e` command - both are required:
+
+<table>
+ <thead>
+ <tr>
+ <td><b>Argument</b></td>
+ <td><b>Shortcut</b></td>
+ <td><b>Explanation</b></td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>--uuid</td>
+ <td>-u</td>
+ <td>Provide the account UUID to edit</td>
+ </tr>
+ <tr>
+ <td>--field</td>
+ <td>-f</td>
+ <td>Provide the account field to edit</td>
+ </tr>
+ </tbody>
+</table>
+
+Flags for the `delete`, `d` command - this flag is required:
+
+<table>
+ <thead>
+ <tr>
+ <td><b>Argument</b></td>
+ <td><b>Shortcut</b></td>
+ <td><b>Explanation</b></td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>--uuid</td>
+ <td>-u</td>
+ <td>Provide the account UUID to delete</td>
+ </tr>
+ </tbody>
+</table>
+
+Flags for the `encrypt` or `decrypt` command - you must provide at least one
+when encrypting, none are required when decrypting:
+
+<table>
+ <thead>
+ <tr>
+ <td><b>Argument</b></td>
+ <td><b>Shortcut</b></td>
+ <td><b>Explanation</b></td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>--generate</td>
+ <td>-g</td>
+ <td>When encrypting, generate a new key</td>
+ </tr>
+ <tr>
+ <td>--keyfile</td>
+ <td>-k</td>
+ <td>When encrypting or decrypting, provide the path to a saved key file</td>
+ </tr>
+ </tbody>
+</table>
+
+![Yoshi CLI New Account](./examples/yoshi-example.png)
+
+# Contributing
+
+[(Back to top)](#table-of-contents)
+
+Any and all contributions are welcome. Feel free to fork the project, add
+features, and submit a pull request.
diff --git a/src/yoshi_christian_cleberg.egg-info/SOURCES.txt b/src/yoshi_christian_cleberg.egg-info/SOURCES.txt
new file mode 100644
index 0000000..9e4bbfe
--- /dev/null
+++ b/src/yoshi_christian_cleberg.egg-info/SOURCES.txt
@@ -0,0 +1,11 @@
+LICENSE
+README.md
+pyproject.toml
+src/account.py
+src/crypto.py
+src/database.py
+src/process.py
+src/yoshi_christian_cleberg.egg-info/PKG-INFO
+src/yoshi_christian_cleberg.egg-info/SOURCES.txt
+src/yoshi_christian_cleberg.egg-info/dependency_links.txt
+src/yoshi_christian_cleberg.egg-info/top_level.txt \ No newline at end of file
diff --git a/src/yoshi_christian_cleberg.egg-info/dependency_links.txt b/src/yoshi_christian_cleberg.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/yoshi_christian_cleberg.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/src/yoshi_christian_cleberg.egg-info/top_level.txt b/src/yoshi_christian_cleberg.egg-info/top_level.txt
new file mode 100644
index 0000000..e855a8f
--- /dev/null
+++ b/src/yoshi_christian_cleberg.egg-info/top_level.txt
@@ -0,0 +1,4 @@
+account
+crypto
+database
+process
diff --git a/src/yoshi_cli.egg-info/PKG-INFO b/src/yoshi_cli.egg-info/PKG-INFO
new file mode 100644
index 0000000..e545db5
--- /dev/null
+++ b/src/yoshi_cli.egg-info/PKG-INFO
@@ -0,0 +1,198 @@
+Metadata-Version: 2.1
+Name: yoshi-cli
+Version: 0.1.0
+Summary: A password manager for the command line.
+Author-email: Christian Cleberg <hello@cleberg.net>
+Project-URL: Homepage, https://github.com/ccleberg/yoshi
+Project-URL: Issues, https://github.com/ccleberg/yoshi/issues
+Classifier: Programming Language :: Python :: 3
+Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
+Classifier: Operating System :: OS Independent
+Requires-Python: >=3.8
+Description-Content-Type: text/markdown
+License-File: LICENSE
+
+# Yoshi: A Password Manager
+
+A simple command-line pass manager, writtin in Python + SQLite3. This tool
+allows you to manage accounts and generate random passwords containing ASCII
+letters, numbers, and punctuation (min. 8 characters) or XKCD-like passphrases
+(min. 3 words).
+
+Please note that the script is written in Python 3 - you may need to run the
+script with the `python3` command instead of `python` if your system uses a
+default of Python 2. See the Installation & Usage sections below for more
+information.
+
+# Table of Contents
+
+- [Installation](#installation)
+- [Usage](#usage)
+ - [Arguments](#arguments)
+- [Contributing](#contributing)
+
+# Installation
+
+[(Back to top)](#table-of-contents)
+
+To run the script locally, run the following commands:
+
+```bash
+git clone REPO_URL
+```
+
+```bash
+cd yoshi
+```
+
+```bash
+pip3 install -r requirements.txt
+```
+
+```bash
+python3 main.py --help
+```
+
+# Usage
+
+[(Back to top)](#table-of-contents)
+
+All commands can be passed to the program with the following template:
+`python3 main.py <COMMAND> <FLAG> <PARAMETER>`
+
+![Yoshi CLI Help](./examples/yoshi-help.png)
+
+## Arguments
+
+### Summary
+
+<table>
+ <thead>
+ <tr>
+ <td><b>Argument</b></td>
+ <td><b>Shortcut</b></td>
+ <td><b>Explanation</b></td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>help</td>
+ <td>h</td>
+ <td>Print the welcome message</td>
+ </tr>
+ <tr>
+ <td>new</td>
+ <td>n</td>
+ <td>Create a new account</td>
+ </tr>
+ <tr>
+ <td>list</td>
+ <td>l</td>
+ <td>List all saved accounts</td>
+ </tr>
+ <tr>
+ <td>edit</td>
+ <td>e</td>
+ <td>Edit a saved account (see below for required flags)</td>
+ </tr>
+ <tr>
+ <td>delete</td>
+ <td>d</td>
+ <td>Delete a saved account (see below for required flags)</td>
+ </tr>
+ <tr>
+ <td>purge</td>
+ <td>N/A</td>
+ <td>Purge all accounts and delete the vault</td>
+ </tr>
+ <tr>
+ <td>encrypt</td>
+ <td>N/A</td>
+ <td>Encrypt the vault database (see below for required flags)</td>
+ </tr>
+ <tr>
+ <td>decrypt</td>
+ <td>N/A</td>
+ <td>Decrypt the vault database (see below for required flags)</td>
+ </tr>
+ </tbody>
+</table>
+
+#### Flags
+
+Flags for the `edit`, `e` command - both are required:
+
+<table>
+ <thead>
+ <tr>
+ <td><b>Argument</b></td>
+ <td><b>Shortcut</b></td>
+ <td><b>Explanation</b></td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>--uuid</td>
+ <td>-u</td>
+ <td>Provide the account UUID to edit</td>
+ </tr>
+ <tr>
+ <td>--field</td>
+ <td>-f</td>
+ <td>Provide the account field to edit</td>
+ </tr>
+ </tbody>
+</table>
+
+Flags for the `delete`, `d` command - this flag is required:
+
+<table>
+ <thead>
+ <tr>
+ <td><b>Argument</b></td>
+ <td><b>Shortcut</b></td>
+ <td><b>Explanation</b></td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>--uuid</td>
+ <td>-u</td>
+ <td>Provide the account UUID to delete</td>
+ </tr>
+ </tbody>
+</table>
+
+Flags for the `encrypt` or `decrypt` command - you must provide at least one
+when encrypting, none are required when decrypting:
+
+<table>
+ <thead>
+ <tr>
+ <td><b>Argument</b></td>
+ <td><b>Shortcut</b></td>
+ <td><b>Explanation</b></td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>--generate</td>
+ <td>-g</td>
+ <td>When encrypting, generate a new key</td>
+ </tr>
+ <tr>
+ <td>--keyfile</td>
+ <td>-k</td>
+ <td>When encrypting or decrypting, provide the path to a saved key file</td>
+ </tr>
+ </tbody>
+</table>
+
+![Yoshi CLI New Account](./examples/yoshi-example.png)
+
+# Contributing
+
+[(Back to top)](#table-of-contents)
+
+Any and all contributions are welcome. Feel free to fork the project, add
+features, and submit a pull request.
diff --git a/src/yoshi_cli.egg-info/SOURCES.txt b/src/yoshi_cli.egg-info/SOURCES.txt
new file mode 100644
index 0000000..c8ca832
--- /dev/null
+++ b/src/yoshi_cli.egg-info/SOURCES.txt
@@ -0,0 +1,11 @@
+LICENSE
+README.md
+pyproject.toml
+src/account.py
+src/crypto.py
+src/database.py
+src/process.py
+src/yoshi_cli.egg-info/PKG-INFO
+src/yoshi_cli.egg-info/SOURCES.txt
+src/yoshi_cli.egg-info/dependency_links.txt
+src/yoshi_cli.egg-info/top_level.txt \ No newline at end of file
diff --git a/src/yoshi_cli.egg-info/dependency_links.txt b/src/yoshi_cli.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/yoshi_cli.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/src/yoshi_cli.egg-info/top_level.txt b/src/yoshi_cli.egg-info/top_level.txt
new file mode 100644
index 0000000..e855a8f
--- /dev/null
+++ b/src/yoshi_cli.egg-info/top_level.txt
@@ -0,0 +1,4 @@
+account
+crypto
+database
+process
diff --git a/src/yoshi_cli_christian_cleberg.egg-info/PKG-INFO b/src/yoshi_cli_christian_cleberg.egg-info/PKG-INFO
new file mode 100644
index 0000000..9081232
--- /dev/null
+++ b/src/yoshi_cli_christian_cleberg.egg-info/PKG-INFO
@@ -0,0 +1,198 @@
+Metadata-Version: 2.1
+Name: yoshi-cli_christian-cleberg
+Version: 0.1.0
+Summary: A password manager for the command line.
+Author-email: Christian Cleberg <hello@cleberg.net>
+Project-URL: Homepage, https://github.com/ccleberg/yoshi
+Project-URL: Issues, https://github.com/ccleberg/yoshi/issues
+Classifier: Programming Language :: Python :: 3
+Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
+Classifier: Operating System :: OS Independent
+Requires-Python: >=3.8
+Description-Content-Type: text/markdown
+License-File: LICENSE
+
+# Yoshi: A Password Manager
+
+A simple command-line pass manager, writtin in Python + SQLite3. This tool
+allows you to manage accounts and generate random passwords containing ASCII
+letters, numbers, and punctuation (min. 8 characters) or XKCD-like passphrases
+(min. 3 words).
+
+Please note that the script is written in Python 3 - you may need to run the
+script with the `python3` command instead of `python` if your system uses a
+default of Python 2. See the Installation & Usage sections below for more
+information.
+
+# Table of Contents
+
+- [Installation](#installation)
+- [Usage](#usage)
+ - [Arguments](#arguments)
+- [Contributing](#contributing)
+
+# Installation
+
+[(Back to top)](#table-of-contents)
+
+To run the script locally, run the following commands:
+
+```bash
+git clone REPO_URL
+```
+
+```bash
+cd yoshi
+```
+
+```bash
+pip3 install -r requirements.txt
+```
+
+```bash
+python3 main.py --help
+```
+
+# Usage
+
+[(Back to top)](#table-of-contents)
+
+All commands can be passed to the program with the following template:
+`python3 main.py <COMMAND> <FLAG> <PARAMETER>`
+
+![Yoshi CLI Help](./examples/yoshi-help.png)
+
+## Arguments
+
+### Summary
+
+<table>
+ <thead>
+ <tr>
+ <td><b>Argument</b></td>
+ <td><b>Shortcut</b></td>
+ <td><b>Explanation</b></td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>help</td>
+ <td>h</td>
+ <td>Print the welcome message</td>
+ </tr>
+ <tr>
+ <td>new</td>
+ <td>n</td>
+ <td>Create a new account</td>
+ </tr>
+ <tr>
+ <td>list</td>
+ <td>l</td>
+ <td>List all saved accounts</td>
+ </tr>
+ <tr>
+ <td>edit</td>
+ <td>e</td>
+ <td>Edit a saved account (see below for required flags)</td>
+ </tr>
+ <tr>
+ <td>delete</td>
+ <td>d</td>
+ <td>Delete a saved account (see below for required flags)</td>
+ </tr>
+ <tr>
+ <td>purge</td>
+ <td>N/A</td>
+ <td>Purge all accounts and delete the vault</td>
+ </tr>
+ <tr>
+ <td>encrypt</td>
+ <td>N/A</td>
+ <td>Encrypt the vault database (see below for required flags)</td>
+ </tr>
+ <tr>
+ <td>decrypt</td>
+ <td>N/A</td>
+ <td>Decrypt the vault database (see below for required flags)</td>
+ </tr>
+ </tbody>
+</table>
+
+#### Flags
+
+Flags for the `edit`, `e` command - both are required:
+
+<table>
+ <thead>
+ <tr>
+ <td><b>Argument</b></td>
+ <td><b>Shortcut</b></td>
+ <td><b>Explanation</b></td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>--uuid</td>
+ <td>-u</td>
+ <td>Provide the account UUID to edit</td>
+ </tr>
+ <tr>
+ <td>--field</td>
+ <td>-f</td>
+ <td>Provide the account field to edit</td>
+ </tr>
+ </tbody>
+</table>
+
+Flags for the `delete`, `d` command - this flag is required:
+
+<table>
+ <thead>
+ <tr>
+ <td><b>Argument</b></td>
+ <td><b>Shortcut</b></td>
+ <td><b>Explanation</b></td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>--uuid</td>
+ <td>-u</td>
+ <td>Provide the account UUID to delete</td>
+ </tr>
+ </tbody>
+</table>
+
+Flags for the `encrypt` or `decrypt` command - you must provide at least one
+when encrypting, none are required when decrypting:
+
+<table>
+ <thead>
+ <tr>
+ <td><b>Argument</b></td>
+ <td><b>Shortcut</b></td>
+ <td><b>Explanation</b></td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>--generate</td>
+ <td>-g</td>
+ <td>When encrypting, generate a new key</td>
+ </tr>
+ <tr>
+ <td>--keyfile</td>
+ <td>-k</td>
+ <td>When encrypting or decrypting, provide the path to a saved key file</td>
+ </tr>
+ </tbody>
+</table>
+
+![Yoshi CLI New Account](./examples/yoshi-example.png)
+
+# Contributing
+
+[(Back to top)](#table-of-contents)
+
+Any and all contributions are welcome. Feel free to fork the project, add
+features, and submit a pull request.
diff --git a/src/yoshi_cli_christian_cleberg.egg-info/SOURCES.txt b/src/yoshi_cli_christian_cleberg.egg-info/SOURCES.txt
new file mode 100644
index 0000000..a482d76
--- /dev/null
+++ b/src/yoshi_cli_christian_cleberg.egg-info/SOURCES.txt
@@ -0,0 +1,11 @@
+LICENSE
+README.md
+pyproject.toml
+src/account.py
+src/crypto.py
+src/database.py
+src/process.py
+src/yoshi_cli_christian_cleberg.egg-info/PKG-INFO
+src/yoshi_cli_christian_cleberg.egg-info/SOURCES.txt
+src/yoshi_cli_christian_cleberg.egg-info/dependency_links.txt
+src/yoshi_cli_christian_cleberg.egg-info/top_level.txt \ No newline at end of file
diff --git a/src/yoshi_cli_christian_cleberg.egg-info/dependency_links.txt b/src/yoshi_cli_christian_cleberg.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/yoshi_cli_christian_cleberg.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/src/yoshi_cli_christian_cleberg.egg-info/top_level.txt b/src/yoshi_cli_christian_cleberg.egg-info/top_level.txt
new file mode 100644
index 0000000..e855a8f
--- /dev/null
+++ b/src/yoshi_cli_christian_cleberg.egg-info/top_level.txt
@@ -0,0 +1,4 @@
+account
+crypto
+database
+process