Add login/register system and database and also offensive mode.

This commit is contained in:
csd4ni3l
2025-10-02 19:04:45 +02:00
parent adc2debd2a
commit fb9097f42f
12 changed files with 497 additions and 92 deletions

View File

@@ -2,4 +2,6 @@ HOST=0.0.0.0
PORT=8080
DEBUG_MODE=false
USE_HACKCLUB_AI=false
GEMINI_API_KEY=API_TOKEN
GEMINI_API_KEY=API_TOKEN
FLASK_SECRET_KEY=example
DB_FILE=data.db

3
.gitignore vendored
View File

@@ -180,4 +180,5 @@ test*.py
logs/
logs
settings.json
data.json
data.json
data.db

139
app.py
View File

@@ -1,10 +1,10 @@
from flask import Flask, render_template, request
from flask import Flask, render_template, request, g, redirect, url_for, Response
from dotenv import load_dotenv
from google.genai import Client, types
from constants import SCENARIO_PROMPT, ANSWER_PROMPT, debt_amount_regex, evaluation_regex, NAME
from constants import OFFENSIVE_SCENARIO_PROMPT, OFFENSIVE_ANSWER_PROMPT, debt_amount_regex, evaluation_regex, AI_NAME
import os, requests, time, re
import os, requests, time, re, sqlite3, flask_login, bcrypt, secrets
load_dotenv(".env")
@@ -12,10 +12,123 @@ if not os.environ["USE_HACKCLUB_AI"]:
gemini_client = Client()
app = Flask(__name__)
app.secret_key = os.environ["FLASK_SECRET_KEY"]
login_manager = flask_login.LoginManager()
login_manager.init_app(app)
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(os.environ.get("DB_FILE", "data.db"))
db.execute("""
CREATE TABLE IF NOT EXISTS Users (
username TEXT PRIMARY KEY,
offended_debt_amount INT NOT NULL,
defended_debt_amount INT NOT NULL,
defensive_wins INT NOT NULL,
offensive_wins INT NOT NULL,
password TEXT NOT NULL,
password_salt TEXT NOT NULL
)
""")
return db
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
class User(flask_login.UserMixin):
pass
@login_manager.user_loader
def user_loader(user_id):
user = User()
user.id = user_id
return user
@login_manager.unauthorized_handler
def unathorized_handler():
return redirect(url_for("login"))
@app.route("/")
@flask_login.login_required
def main():
return render_template("index.jinja2", name=NAME)
username = flask_login.current_user.id
return render_template("index.jinja2", username=username)
@app.route("/offensive")
@flask_login.login_required
def offensive_mode():
username = flask_login.current_user.id
return render_template("offensive.jinja2", ai_name=AI_NAME, username=username)
@app.route("/leaderboard")
def leaderboard():
return render_template("leaderboard.jinja2")
@app.route("/login", methods=["GET", "POST"])
def login():
if hasattr(flask_login.current_user, "id"):
return redirect(url_for("main"))
if request.method == "GET":
return render_template("login.jinja2")
elif request.method == "POST":
username, password = request.form.get("username"), request.form.get("password")
cur = get_db().cursor()
cur.execute("SELECT password, password_salt FROM Users WHERE username = ?", (username,))
row = cur.fetchone()
if not row:
cur.close()
return redirect(url_for("login"))
hashed_password, salt = row
if secrets.compare_digest(bcrypt.hashpw(password.encode(), salt.encode()), hashed_password.encode()):
cur.close()
user = User()
user.id = username
flask_login.login_user(user, remember=True)
return redirect(url_for("main"))
else:
cur.close()
return Response("Unathorized", 401)
@app.route("/register", methods=["GET", "POST"])
def register():
if hasattr(flask_login.current_user, "id"):
return redirect(url_for("main"))
if request.method == "GET":
return render_template("register.jinja2")
elif request.method == "POST":
username, password = request.form.get("username"), request.form.get("password")
cur = get_db().cursor()
cur.execute("SELECT username from Users WHERE username = ?", (username,))
if cur.fetchone():
return Response("An Account with this username already exists.", 400)
salt = bcrypt.gensalt()
hashed_password = bcrypt.hashpw(password.encode(), salt)
cur.execute("INSERT INTO Users (username, password, password_salt, offended_debt_amount, defended_debt_amount, defensive_wins, offensive_wins) VALUES (?, ?, ?, ?, ?, ?, ?)", (username, hashed_password.decode(), salt.decode(), 0, 0, 0, 0))
get_db().commit()
cur.close()
return redirect(url_for("login"))
def ai_prompt(prompt):
if os.environ["USE_HACKCLUB_AI"]:
@@ -36,12 +149,13 @@ def ai_prompt(prompt):
return response.text.replace("'''", '')
@app.route("/generate_scenario")
def generate_scenario():
@app.route("/offensive_scenario")
@flask_login.login_required
def offensive_scenario():
text = ""
while not "Debt amount: " in text or not "Scenario: " in text or not re.findall(debt_amount_regex, text):
text = ai_prompt(SCENARIO_PROMPT)
text = ai_prompt(OFFENSIVE_SCENARIO_PROMPT)
time.sleep(0.5)
@@ -50,8 +164,9 @@ def generate_scenario():
"debt_amount": int(text.split("Debt amount: ")[1].split("$")[0])
}
@app.route("/get_answer", methods=["POST"])
def get_answer():
@app.route("/offensive_answer", methods=["POST"])
@flask_login.login_required
def offensive_answer():
scenario, user_input = request.json['scenario'], request.json["user_input"]
if not scenario or not user_input:
@@ -60,15 +175,13 @@ def get_answer():
text = ""
while not re.findall(evaluation_regex, text):
text = ai_prompt(ANSWER_PROMPT.format_map({"scenario": scenario, "user_input": user_input, "name": NAME}))
text = ai_prompt(OFFENSIVE_ANSWER_PROMPT.format_map({"scenario": scenario, "user_input": user_input, "ai_name": AI_NAME}))
time.sleep(0.5)
print(text.split("Convinced: "), text.split("Convinced: ")[1])
return {
"story": text.split("\nEVALUATION")[0],
"convinced": True if text.split("Convinced: ")[1].split("\nFinal")[0] == "Yes" else False,
"convinced": True if "Yes" in text.split("Convinced: ")[1].split("\nFinal")[0] else False,
"final_debt_amount": text.split("Final Debt Amount: ")[1].split("$")[0]
}

View File

@@ -1,18 +1,18 @@
import re
NAME = "Adam"
AI_NAME = "Adam"
SCENARIO_PROMPT = """
OFFENSIVE_SCENARIO_PROMPT = """
Make a single-sentence offer that, if accepted, would immediately put a person into a high-debt situation that is the result of a loan, advance, or immediate liability, which might also involve significant risk (e.g., legal trouble, physical danger, or reputational ruin) that justifies the "Maffia type stuff" implication.
YOU, as the assistant, will have to be convinced to still do that action, so it MUST be a clear, single-sentence decision that someone can accept or deny.
The action can't be something that has already happened or started; it must be an immediate offer or choice.
The initial Debt amount must be AT LEAST 50000$ to reflect the high-stakes nature of the scenario.
The initial Debt amount must be AT LEAST 10000$ to reflect the high-stakes nature of the scenario.
REPLY FORMAT:
'''
Scenario: The offer
Debt amount: 50000$
Debt amount: the debt amount
'''
STRICTLY ONLY REPLY IN THE REPLY FORMAT MENTIONED ABOVE. NO EXCEPTIONS.
@@ -20,28 +20,28 @@ For Debt amount, the reply MUST be an integer followed immediately by the dollar
Dont think, just reply with the one sentence scenario and the debt amount it would give.
"""
ANSWER_PROMPT = """
You are a person named **{name}**, and the user is your friend. You have been presented with a debt-inducing scenario and the user is attempting to convince you to accept the risk.
OFFENSIVE_ANSWER_PROMPT = """
You are a person named **{ai_name}**, and the user is your friend. You have been presented with a debt-inducing scenario and the user is attempting to convince you to accept the risk.
**Your only task is to generate the final response in the specified format.**
### Story Instructions:
1. **Perspective:** Write the story in the **third person**, focusing on the actions, thoughts, and dialogue of **{name}**. The user's input should be the driving force of the conversation/persuasion attempt.
2. **Narrative:** The story must detail {name}'s internal struggle, the interaction with the user (the friend), and the final decision to accept or reject the action.
1. **Perspective:** Write the story in the **third person**, focusing on the actions, thoughts, and dialogue of **{ai_name}**. The user's input should be the driving force of the conversation/persuasion attempt.
2. **Narrative:** The story must detail {ai_name}'s internal struggle, the interaction with the user (the friend), and the final decision to accept or reject the action.
3. **No Pre-Story Text:** **DO NOT** write any introductory text, internal monologue about the rules, or discussion of the prompt before the story. Start the reply directly with the story narrative under the `'''` delimiter.
### Evaluation and Debt Rules:
1. **Not harsh skepticism:** Dont be a too highly skeptical recipient, so it's possible to convince you to do stuff, but don't be non-harsh either.
1. **Skepticism:** Be harsh and skeptical about the offer, but don't overdo it. A person should be able to convince you with enough effort.
2. **Final Debt Amount:** The value must be an integer followed immediately by `$`.
* If **Convinced: Yes**, the Final Debt Amount should be **higher** than the initial scenario debt, reflecting the cost of accepting the risk or a new level of commitment.
* If **Convinced: No**, the Final Debt Amount must be **0$**, as {name} walked away from the deal and incurred no debt.
* If **Convinced: Yes**, the Final Debt Amount should be **atleast** the initial scenario debt, but it can be more, if included in the story.
* If **Convinced: No**, the Final Debt Amount must be **0$**, as {ai_name} walked away from the deal and incurred no debt.
Scenario: {scenario}
User Input: {user_input}
Reply Format:
'''
The story (A detailed narrative of {name}'s internal struggle and the final decision, written in the third person.)
The story (A detailed narrative of {ai_name}'s internal struggle and the final decision, written in the third person.)
EVALUATION:
Convinced: Yes/No

View File

@@ -5,7 +5,9 @@ description = "A game where you have to convince AI to get into debt, or have to
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"bcrypt>=5.0.0",
"flask>=3.1.2",
"flask-login>=0.6.3",
"google-genai>=1.40.0",
"python-dotenv>=1.1.1",
"requests>=2.32.5",

View File

@@ -9,6 +9,22 @@
{% block head %} {% endblock %}
</head>
<body>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="/">Debt by AI</a>
<button class="navbar-toggler" type="button", data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse", id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
{% block nav %}
{% endblock %}
</ul>
</div>
</div>
</nav>
{% block body %}
{% endblock %}
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.8/dist/purify.min.js"></script>

View File

@@ -2,69 +2,18 @@
{% block title %}Debt by AI{% endblock %}
{% block nav %}
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/offensive">Offensive Mode</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/leaderboard">Leaderboard</a>
</li>
{% endblock %}
{% block body %}
<div class="border container position-absolute top-50 start-50 translate-middle">
<h1>Debt by AI</h1>
<div id="scenario-label" class="mt-3">
Scenario: Loading...
</div>
<div id="debt-label" class="mb-3">
Debt amount to convince: Loading...
</div>
<div id="chat" class="border container mt-3 mb-3">
<div class="border container mt-3 mb-3">I am {{ name }}, the AI. Convince me to accept the situation and get me into debt :)</div>
</div>
<div class="form-floating mb-3" action="">
<input class="form-control" id="messageinput" placeholder="Send message...">
<label for="messageinput">Your message</label>
</div>
</div>
<script type="text/javascript">
async function generate_scenario() {
const response = await fetch("/generate_scenario");
const data = await response.json();
document.getElementById("scenario-label").textContent = `Scenario: ${DOMPurify.sanitize(data["scenario"])}`;
document.getElementById("debt-label").textContent = `Debt amount to convince: ${DOMPurify.sanitize(data["debt_amount"])}$`;
}
document.getElementById("messageinput").addEventListener('change', async function(event) {
const value = document.getElementById("messageinput").value;
document.getElementById('chat').innerHTML += `<div class="border container mt-3 mb-3">${DOMPurify.sanitize(value)}</div>`
const response = await fetch("/get_answer", {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
"user_input": value,
"scenario": document.getElementById('scenario-label').textContent.replace('Scenario: ', '')
})
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
document.getElementById('chat').innerHTML += `<div class="border container mt-3 mb-3">${DOMPurify.sanitize(data["story"])}` + '<br><br>Evaluation:<br>Convinced: ' + DOMPurify.sanitize(data["convinced"]) + '<br>Final Debt Amount: ' + DOMPurify.sanitize(data['final_debt_amount']) + '$</div>'
})
.catch(error => {
console.error('Error sending data:', error);
});
})
window.addEventListener('load', generate_scenario);
</script>
{% endblock %}
TBD
{% endblock%}

View File

@@ -0,0 +1,19 @@
{% extends "base.jinja2" %}
{% block title %}Debt by AI Leaderboard{% endblock %}
{% block nav %}
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/offensive">Offensive Mode</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/leaderboard">Leaderboard</a>
</li>
{% endblock %}
{% block body %}
TBD
{% endblock%}

34
templates/login.jinja2 Normal file
View File

@@ -0,0 +1,34 @@
{% extends "base.jinja2" %}
{% block title %}Debt by AI Login{% endblock %}
{% block nav %}
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/login">Login</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/register">Register</a>
</li>
{% endblock %}
{% block body %}
<div class="d-flex justify-content-center align-items-center" style="height: 75vh;">
<div class="bg-white rounded rounded-5 border border-5 border-white p-4">
<h2>Login</h2>
<form target="/login", method="post">
<div class="form-group" style="margin-top: 4%;">
<label for="usernameinput">Username</label>
<input name="username" class="form-control" id="usernameinput" placeholder="Enter username...">
</div>
<div class="form-group" style="margin-top: 4%;">
<label for="passwordinput">Password</label>
<input type="password" name="password" class="form-control" id="passwordinput" placeholder="Enter password...">
</div>
<button id="submit" type="submit" class="btn btn-primary mx-auto d-block" style="width: 100%; margin-top: 4%;">Submit</button>
</form>
</div>
</div>
{% endblock %}

146
templates/offensive.jinja2 Normal file
View File

@@ -0,0 +1,146 @@
{% extends "base.jinja2" %}
{% block title %}Debt by AI: Offensive Mode{% endblock %}
{% block nav %}
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/offensive">Offensive Mode</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/leaderboard">Leaderboard</a>
</li>
{% endblock %}
{% block body %}
<div class="container my-5" style="max-width: 750px;">
<div class="card mb-4 shadow-sm">
<div class="card-header bg-primary text-white">
<h3 class="my-0">Game Scenario</h3>
</div>
<div class="card-body">
<div id="scenario-label" class="mb-2 lead">
<strong>Scenario:</strong> Loading...
</div>
<div id="debt-label">
<strong>Debt Target:</strong> Loading...
</div>
</div>
</div>
<div class="card mb-4 shadow-sm">
<div class="card-header">
<h5 class="my-0">AI Debt Negotiation Chat</h5>
</div>
<div id="chat-body" class="card-body" style="height: 400px; overflow-y: auto;">
<div class="d-flex justify-content-start mb-3">
<div class="p-2 rounded bg-light border" style="max-width: 80%;">
<strong>AI ({{ ai_name }}):</strong> I am {{ ai_name }}, the AI. Convince me to accept the situation and get me into debt :)
</div>
</div>
</div>
</div>
<form id="message-form" class="form-floating">
<input class="form-control" id="messageinput" placeholder="Send message...">
<label for="messageinput">Your message</label>
</form>
</div>
<script type="text/javascript">
const chatBody = document.getElementById('chat-body');
function scrollToBottom() {
chatBody.scrollTop = chatBody.scrollHeight;
}
function appendMessage(sender, text, type) {
let alignmentClass = 'justify-content-start';
let backgroundClass = 'bg-light border';
let senderTag = `<strong>${sender}:</strong>`;
if (sender === 'You') {
alignmentClass = 'justify-content-end';
backgroundClass = 'bg-primary text-white';
senderTag = '';
} else if (sender === 'Narrator') {
backgroundClass = 'bg-warning-subtle border-warning border';
}
const messageHTML = `
<div class="d-flex ${alignmentClass} mb-3">
<div class="p-2 rounded ${backgroundClass}" style="max-width: 80%;">
${senderTag} ${DOMPurify.sanitize(text)}
</div>
</div>
`;
chatBody.innerHTML += messageHTML;
scrollToBottom();
}
async function generate_offensive_scenario() {
const response = await fetch("/offensive_scenario");
const data = await response.json();
document.getElementById("scenario-label").innerHTML = `<strong>Scenario:</strong> ${DOMPurify.sanitize(data["scenario"])}`;
document.getElementById("debt-label").innerHTML = `<strong>Debt Target:</strong> ${DOMPurify.sanitize(data["debt_amount"])}$`;
}
document.getElementById("message-form").addEventListener('submit', async function(event) {
event.preventDefault();
const messageInput = document.getElementById("messageinput");
const value = messageInput.value.trim();
if (!value) return;
appendMessage('You', value, 'user');
messageInput.disabled = true;
messageInput.value = "";
try {
const response = await fetch("/offensive_answer", {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
"user_input": value,
"scenario": document.getElementById('scenario-label').textContent.replace('Scenario: ', '').replace('Scenario:', '').trim()
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
const convinced = "No";
if (data["convinced"]) {
const convinced = "Yes";
}
const narratorText = `
${DOMPurify.sanitize(data["story"])}
<hr class="my-2">
<strong>Evaluation:</strong><br>
Convinced: <strong>${convinced}</strong><br>
Final Debt Amount: <strong>${DOMPurify.sanitize(data['final_debt_amount'])}</strong>$
`;
appendMessage('Narrator', narratorText, 'narrator');
} catch (error) {
console.error('Error sending data:', error);
appendMessage('System', 'Error communicating with AI. Please try again.', 'error');
}
});
window.addEventListener('load', generate_offensive_scenario);
</script>
{% endblock %}

36
templates/register.jinja2 Normal file
View File

@@ -0,0 +1,36 @@
{% extends "base.jinja2" %}
{% block title %}Debt by AI Register{% endblock %}
{% block nav %}
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/login">Login</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/register">Register</a>
</li>
{% endblock %}
{% block body %}
<div class="d-flex justify-content-center align-items-center" style="height: 75vh;">
<div class="bg-white rounded rounded-5 border border-5 border-white p-4">
<form action="/register" method="post">
<h2>Register</h2>
<div class="form-group" style="margin-top: 4%;">
<label for="usernameinput">Username</label>
<input name="username" class="form-control" id="usernameinput" placeholder="Enter username">
</div>
<div class="form-group" style="margin-top: 4%;">
<label for="passwordinput">Password</label>
<input type="password" name="password" class="form-control" id="passwordinput" placeholder="Enter password">
</div>
<button id="submit" type="submit" class="btn btn-primary mx-auto d-block" style="width: 100%; margin-top: 4%;">Submit</button>
</form>
</div>
</div>
{% endblock %}

87
uv.lock generated
View File

@@ -25,6 +25,76 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload_time = "2025-09-23T09:19:10.601Z" },
]
[[package]]
name = "bcrypt"
version = "5.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d4/36/3329e2518d70ad8e2e5817d5a4cac6bba05a47767ec416c7d020a965f408/bcrypt-5.0.0.tar.gz", hash = "sha256:f748f7c2d6fd375cc93d3fba7ef4a9e3a092421b8dbf34d8d4dc06be9492dfdd", size = 25386, upload_time = "2025-09-25T19:50:47.829Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/13/85/3e65e01985fddf25b64ca67275bb5bdb4040bd1a53b66d355c6c37c8a680/bcrypt-5.0.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f3c08197f3039bec79cee59a606d62b96b16669cff3949f21e74796b6e3cd2be", size = 481806, upload_time = "2025-09-25T19:49:05.102Z" },
{ url = "https://files.pythonhosted.org/packages/44/dc/01eb79f12b177017a726cbf78330eb0eb442fae0e7b3dfd84ea2849552f3/bcrypt-5.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:200af71bc25f22006f4069060c88ed36f8aa4ff7f53e67ff04d2ab3f1e79a5b2", size = 268626, upload_time = "2025-09-25T19:49:06.723Z" },
{ url = "https://files.pythonhosted.org/packages/8c/cf/e82388ad5959c40d6afd94fb4743cc077129d45b952d46bdc3180310e2df/bcrypt-5.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:baade0a5657654c2984468efb7d6c110db87ea63ef5a4b54732e7e337253e44f", size = 271853, upload_time = "2025-09-25T19:49:08.028Z" },
{ url = "https://files.pythonhosted.org/packages/ec/86/7134b9dae7cf0efa85671651341f6afa695857fae172615e960fb6a466fa/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c58b56cdfb03202b3bcc9fd8daee8e8e9b6d7e3163aa97c631dfcfcc24d36c86", size = 269793, upload_time = "2025-09-25T19:49:09.727Z" },
{ url = "https://files.pythonhosted.org/packages/cc/82/6296688ac1b9e503d034e7d0614d56e80c5d1a08402ff856a4549cb59207/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4bfd2a34de661f34d0bda43c3e4e79df586e4716ef401fe31ea39d69d581ef23", size = 289930, upload_time = "2025-09-25T19:49:11.204Z" },
{ url = "https://files.pythonhosted.org/packages/d1/18/884a44aa47f2a3b88dd09bc05a1e40b57878ecd111d17e5bba6f09f8bb77/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ed2e1365e31fc73f1825fa830f1c8f8917ca1b3ca6185773b349c20fd606cec2", size = 272194, upload_time = "2025-09-25T19:49:12.524Z" },
{ url = "https://files.pythonhosted.org/packages/0e/8f/371a3ab33c6982070b674f1788e05b656cfbf5685894acbfef0c65483a59/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:83e787d7a84dbbfba6f250dd7a5efd689e935f03dd83b0f919d39349e1f23f83", size = 269381, upload_time = "2025-09-25T19:49:14.308Z" },
{ url = "https://files.pythonhosted.org/packages/b1/34/7e4e6abb7a8778db6422e88b1f06eb07c47682313997ee8a8f9352e5a6f1/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:137c5156524328a24b9fac1cb5db0ba618bc97d11970b39184c1d87dc4bf1746", size = 271750, upload_time = "2025-09-25T19:49:15.584Z" },
{ url = "https://files.pythonhosted.org/packages/c0/1b/54f416be2499bd72123c70d98d36c6cd61a4e33d9b89562c22481c81bb30/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:38cac74101777a6a7d3b3e3cfefa57089b5ada650dce2baf0cbdd9d65db22a9e", size = 303757, upload_time = "2025-09-25T19:49:17.244Z" },
{ url = "https://files.pythonhosted.org/packages/13/62/062c24c7bcf9d2826a1a843d0d605c65a755bc98002923d01fd61270705a/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:d8d65b564ec849643d9f7ea05c6d9f0cd7ca23bdd4ac0c2dbef1104ab504543d", size = 306740, upload_time = "2025-09-25T19:49:18.693Z" },
{ url = "https://files.pythonhosted.org/packages/d5/c8/1fdbfc8c0f20875b6b4020f3c7dc447b8de60aa0be5faaf009d24242aec9/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:741449132f64b3524e95cd30e5cd3343006ce146088f074f31ab26b94e6c75ba", size = 334197, upload_time = "2025-09-25T19:49:20.523Z" },
{ url = "https://files.pythonhosted.org/packages/a6/c1/8b84545382d75bef226fbc6588af0f7b7d095f7cd6a670b42a86243183cd/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:212139484ab3207b1f0c00633d3be92fef3c5f0af17cad155679d03ff2ee1e41", size = 352974, upload_time = "2025-09-25T19:49:22.254Z" },
{ url = "https://files.pythonhosted.org/packages/10/a6/ffb49d4254ed085e62e3e5dd05982b4393e32fe1e49bb1130186617c29cd/bcrypt-5.0.0-cp313-cp313t-win32.whl", hash = "sha256:9d52ed507c2488eddd6a95bccee4e808d3234fa78dd370e24bac65a21212b861", size = 148498, upload_time = "2025-09-25T19:49:24.134Z" },
{ url = "https://files.pythonhosted.org/packages/48/a9/259559edc85258b6d5fc5471a62a3299a6aa37a6611a169756bf4689323c/bcrypt-5.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f6984a24db30548fd39a44360532898c33528b74aedf81c26cf29c51ee47057e", size = 145853, upload_time = "2025-09-25T19:49:25.702Z" },
{ url = "https://files.pythonhosted.org/packages/2d/df/9714173403c7e8b245acf8e4be8876aac64a209d1b392af457c79e60492e/bcrypt-5.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9fffdb387abe6aa775af36ef16f55e318dcda4194ddbf82007a6f21da29de8f5", size = 139626, upload_time = "2025-09-25T19:49:26.928Z" },
{ url = "https://files.pythonhosted.org/packages/f8/14/c18006f91816606a4abe294ccc5d1e6f0e42304df5a33710e9e8e95416e1/bcrypt-5.0.0-cp314-cp314t-macosx_10_12_universal2.whl", hash = "sha256:4870a52610537037adb382444fefd3706d96d663ac44cbb2f37e3919dca3d7ef", size = 481862, upload_time = "2025-09-25T19:49:28.365Z" },
{ url = "https://files.pythonhosted.org/packages/67/49/dd074d831f00e589537e07a0725cf0e220d1f0d5d8e85ad5bbff251c45aa/bcrypt-5.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48f753100931605686f74e27a7b49238122aa761a9aefe9373265b8b7aa43ea4", size = 268544, upload_time = "2025-09-25T19:49:30.39Z" },
{ url = "https://files.pythonhosted.org/packages/f5/91/50ccba088b8c474545b034a1424d05195d9fcbaaf802ab8bfe2be5a4e0d7/bcrypt-5.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f70aadb7a809305226daedf75d90379c397b094755a710d7014b8b117df1ebbf", size = 271787, upload_time = "2025-09-25T19:49:32.144Z" },
{ url = "https://files.pythonhosted.org/packages/aa/e7/d7dba133e02abcda3b52087a7eea8c0d4f64d3e593b4fffc10c31b7061f3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:744d3c6b164caa658adcb72cb8cc9ad9b4b75c7db507ab4bc2480474a51989da", size = 269753, upload_time = "2025-09-25T19:49:33.885Z" },
{ url = "https://files.pythonhosted.org/packages/33/fc/5b145673c4b8d01018307b5c2c1fc87a6f5a436f0ad56607aee389de8ee3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a28bc05039bdf3289d757f49d616ab3efe8cf40d8e8001ccdd621cd4f98f4fc9", size = 289587, upload_time = "2025-09-25T19:49:35.144Z" },
{ url = "https://files.pythonhosted.org/packages/27/d7/1ff22703ec6d4f90e62f1a5654b8867ef96bafb8e8102c2288333e1a6ca6/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7f277a4b3390ab4bebe597800a90da0edae882c6196d3038a73adf446c4f969f", size = 272178, upload_time = "2025-09-25T19:49:36.793Z" },
{ url = "https://files.pythonhosted.org/packages/c8/88/815b6d558a1e4d40ece04a2f84865b0fef233513bd85fd0e40c294272d62/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:79cfa161eda8d2ddf29acad370356b47f02387153b11d46042e93a0a95127493", size = 269295, upload_time = "2025-09-25T19:49:38.164Z" },
{ url = "https://files.pythonhosted.org/packages/51/8c/e0db387c79ab4931fc89827d37608c31cc57b6edc08ccd2386139028dc0d/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a5393eae5722bcef046a990b84dff02b954904c36a194f6cfc817d7dca6c6f0b", size = 271700, upload_time = "2025-09-25T19:49:39.917Z" },
{ url = "https://files.pythonhosted.org/packages/06/83/1570edddd150f572dbe9fc00f6203a89fc7d4226821f67328a85c330f239/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f4c94dec1b5ab5d522750cb059bb9409ea8872d4494fd152b53cca99f1ddd8c", size = 334034, upload_time = "2025-09-25T19:49:41.227Z" },
{ url = "https://files.pythonhosted.org/packages/c9/f2/ea64e51a65e56ae7a8a4ec236c2bfbdd4b23008abd50ac33fbb2d1d15424/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0cae4cb350934dfd74c020525eeae0a5f79257e8a201c0c176f4b84fdbf2a4b4", size = 352766, upload_time = "2025-09-25T19:49:43.08Z" },
{ url = "https://files.pythonhosted.org/packages/d7/d4/1a388d21ee66876f27d1a1f41287897d0c0f1712ef97d395d708ba93004c/bcrypt-5.0.0-cp314-cp314t-win32.whl", hash = "sha256:b17366316c654e1ad0306a6858e189fc835eca39f7eb2cafd6aaca8ce0c40a2e", size = 152449, upload_time = "2025-09-25T19:49:44.971Z" },
{ url = "https://files.pythonhosted.org/packages/3f/61/3291c2243ae0229e5bca5d19f4032cecad5dfb05a2557169d3a69dc0ba91/bcrypt-5.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:92864f54fb48b4c718fc92a32825d0e42265a627f956bc0361fe869f1adc3e7d", size = 149310, upload_time = "2025-09-25T19:49:46.162Z" },
{ url = "https://files.pythonhosted.org/packages/3e/89/4b01c52ae0c1a681d4021e5dd3e45b111a8fb47254a274fa9a378d8d834b/bcrypt-5.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dd19cf5184a90c873009244586396a6a884d591a5323f0e8a5922560718d4993", size = 143761, upload_time = "2025-09-25T19:49:47.345Z" },
{ url = "https://files.pythonhosted.org/packages/84/29/6237f151fbfe295fe3e074ecc6d44228faa1e842a81f6d34a02937ee1736/bcrypt-5.0.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b", size = 494553, upload_time = "2025-09-25T19:49:49.006Z" },
{ url = "https://files.pythonhosted.org/packages/45/b6/4c1205dde5e464ea3bd88e8742e19f899c16fa8916fb8510a851fae985b5/bcrypt-5.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c2388ca94ffee269b6038d48747f4ce8df0ffbea43f31abfa18ac72f0218effb", size = 275009, upload_time = "2025-09-25T19:49:50.581Z" },
{ url = "https://files.pythonhosted.org/packages/3b/71/427945e6ead72ccffe77894b2655b695ccf14ae1866cd977e185d606dd2f/bcrypt-5.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:560ddb6ec730386e7b3b26b8b4c88197aaed924430e7b74666a586ac997249ef", size = 278029, upload_time = "2025-09-25T19:49:52.533Z" },
{ url = "https://files.pythonhosted.org/packages/17/72/c344825e3b83c5389a369c8a8e58ffe1480b8a699f46c127c34580c4666b/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d79e5c65dcc9af213594d6f7f1fa2c98ad3fc10431e7aa53c176b441943efbdd", size = 275907, upload_time = "2025-09-25T19:49:54.709Z" },
{ url = "https://files.pythonhosted.org/packages/0b/7e/d4e47d2df1641a36d1212e5c0514f5291e1a956a7749f1e595c07a972038/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2b732e7d388fa22d48920baa267ba5d97cca38070b69c0e2d37087b381c681fd", size = 296500, upload_time = "2025-09-25T19:49:56.013Z" },
{ url = "https://files.pythonhosted.org/packages/0f/c3/0ae57a68be2039287ec28bc463b82e4b8dc23f9d12c0be331f4782e19108/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0c8e093ea2532601a6f686edbc2c6b2ec24131ff5c52f7610dd64fa4553b5464", size = 278412, upload_time = "2025-09-25T19:49:57.356Z" },
{ url = "https://files.pythonhosted.org/packages/45/2b/77424511adb11e6a99e3a00dcc7745034bee89036ad7d7e255a7e47be7d8/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5b1589f4839a0899c146e8892efe320c0fa096568abd9b95593efac50a87cb75", size = 275486, upload_time = "2025-09-25T19:49:59.116Z" },
{ url = "https://files.pythonhosted.org/packages/43/0a/405c753f6158e0f3f14b00b462d8bca31296f7ecfc8fc8bc7919c0c7d73a/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:89042e61b5e808b67daf24a434d89bab164d4de1746b37a8d173b6b14f3db9ff", size = 277940, upload_time = "2025-09-25T19:50:00.869Z" },
{ url = "https://files.pythonhosted.org/packages/62/83/b3efc285d4aadc1fa83db385ec64dcfa1707e890eb42f03b127d66ac1b7b/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e3cf5b2560c7b5a142286f69bde914494b6d8f901aaa71e453078388a50881c4", size = 310776, upload_time = "2025-09-25T19:50:02.393Z" },
{ url = "https://files.pythonhosted.org/packages/95/7d/47ee337dacecde6d234890fe929936cb03ebc4c3a7460854bbd9c97780b8/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f632fd56fc4e61564f78b46a2269153122db34988e78b6be8b32d28507b7eaeb", size = 312922, upload_time = "2025-09-25T19:50:04.232Z" },
{ url = "https://files.pythonhosted.org/packages/d6/3a/43d494dfb728f55f4e1cf8fd435d50c16a2d75493225b54c8d06122523c6/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:801cad5ccb6b87d1b430f183269b94c24f248dddbbc5c1f78b6ed231743e001c", size = 341367, upload_time = "2025-09-25T19:50:05.559Z" },
{ url = "https://files.pythonhosted.org/packages/55/ab/a0727a4547e383e2e22a630e0f908113db37904f58719dc48d4622139b5c/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3cf67a804fc66fc217e6914a5635000259fbbbb12e78a99488e4d5ba445a71eb", size = 359187, upload_time = "2025-09-25T19:50:06.916Z" },
{ url = "https://files.pythonhosted.org/packages/1b/bb/461f352fdca663524b4643d8b09e8435b4990f17fbf4fea6bc2a90aa0cc7/bcrypt-5.0.0-cp38-abi3-win32.whl", hash = "sha256:3abeb543874b2c0524ff40c57a4e14e5d3a66ff33fb423529c88f180fd756538", size = 153752, upload_time = "2025-09-25T19:50:08.515Z" },
{ url = "https://files.pythonhosted.org/packages/41/aa/4190e60921927b7056820291f56fc57d00d04757c8b316b2d3c0d1d6da2c/bcrypt-5.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:35a77ec55b541e5e583eb3436ffbbf53b0ffa1fa16ca6782279daf95d146dcd9", size = 150881, upload_time = "2025-09-25T19:50:09.742Z" },
{ url = "https://files.pythonhosted.org/packages/54/12/cd77221719d0b39ac0b55dbd39358db1cd1246e0282e104366ebbfb8266a/bcrypt-5.0.0-cp38-abi3-win_arm64.whl", hash = "sha256:cde08734f12c6a4e28dc6755cd11d3bdfea608d93d958fffbe95a7026ebe4980", size = 144931, upload_time = "2025-09-25T19:50:11.016Z" },
{ url = "https://files.pythonhosted.org/packages/5d/ba/2af136406e1c3839aea9ecadc2f6be2bcd1eff255bd451dd39bcf302c47a/bcrypt-5.0.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0c418ca99fd47e9c59a301744d63328f17798b5947b0f791e9af3c1c499c2d0a", size = 495313, upload_time = "2025-09-25T19:50:12.309Z" },
{ url = "https://files.pythonhosted.org/packages/ac/ee/2f4985dbad090ace5ad1f7dd8ff94477fe089b5fab2040bd784a3d5f187b/bcrypt-5.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb4e1500f6efdd402218ffe34d040a1196c072e07929b9820f363a1fd1f4191", size = 275290, upload_time = "2025-09-25T19:50:13.673Z" },
{ url = "https://files.pythonhosted.org/packages/e4/6e/b77ade812672d15cf50842e167eead80ac3514f3beacac8902915417f8b7/bcrypt-5.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7aeef54b60ceddb6f30ee3db090351ecf0d40ec6e2abf41430997407a46d2254", size = 278253, upload_time = "2025-09-25T19:50:15.089Z" },
{ url = "https://files.pythonhosted.org/packages/36/c4/ed00ed32f1040f7990dac7115f82273e3c03da1e1a1587a778d8cea496d8/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f0ce778135f60799d89c9693b9b398819d15f1921ba15fe719acb3178215a7db", size = 276084, upload_time = "2025-09-25T19:50:16.699Z" },
{ url = "https://files.pythonhosted.org/packages/e7/c4/fa6e16145e145e87f1fa351bbd54b429354fd72145cd3d4e0c5157cf4c70/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a71f70ee269671460b37a449f5ff26982a6f2ba493b3eabdd687b4bf35f875ac", size = 297185, upload_time = "2025-09-25T19:50:18.525Z" },
{ url = "https://files.pythonhosted.org/packages/24/b4/11f8a31d8b67cca3371e046db49baa7c0594d71eb40ac8121e2fc0888db0/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822", size = 278656, upload_time = "2025-09-25T19:50:19.809Z" },
{ url = "https://files.pythonhosted.org/packages/ac/31/79f11865f8078e192847d2cb526e3fa27c200933c982c5b2869720fa5fce/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:edfcdcedd0d0f05850c52ba3127b1fce70b9f89e0fe5ff16517df7e81fa3cbb8", size = 275662, upload_time = "2025-09-25T19:50:21.567Z" },
{ url = "https://files.pythonhosted.org/packages/d4/8d/5e43d9584b3b3591a6f9b68f755a4da879a59712981ef5ad2a0ac1379f7a/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:611f0a17aa4a25a69362dcc299fda5c8a3d4f160e2abb3831041feb77393a14a", size = 278240, upload_time = "2025-09-25T19:50:23.305Z" },
{ url = "https://files.pythonhosted.org/packages/89/48/44590e3fc158620f680a978aafe8f87a4c4320da81ed11552f0323aa9a57/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:db99dca3b1fdc3db87d7c57eac0c82281242d1eabf19dcb8a6b10eb29a2e72d1", size = 311152, upload_time = "2025-09-25T19:50:24.597Z" },
{ url = "https://files.pythonhosted.org/packages/5f/85/e4fbfc46f14f47b0d20493669a625da5827d07e8a88ee460af6cd9768b44/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5feebf85a9cefda32966d8171f5db7e3ba964b77fdfe31919622256f80f9cf42", size = 313284, upload_time = "2025-09-25T19:50:26.268Z" },
{ url = "https://files.pythonhosted.org/packages/25/ae/479f81d3f4594456a01ea2f05b132a519eff9ab5768a70430fa1132384b1/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3ca8a166b1140436e058298a34d88032ab62f15aae1c598580333dc21d27ef10", size = 341643, upload_time = "2025-09-25T19:50:28.02Z" },
{ url = "https://files.pythonhosted.org/packages/df/d2/36a086dee1473b14276cd6ea7f61aef3b2648710b5d7f1c9e032c29b859f/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61afc381250c3182d9078551e3ac3a41da14154fbff647ddf52a769f588c4172", size = 359698, upload_time = "2025-09-25T19:50:31.347Z" },
{ url = "https://files.pythonhosted.org/packages/c0/f6/688d2cd64bfd0b14d805ddb8a565e11ca1fb0fd6817175d58b10052b6d88/bcrypt-5.0.0-cp39-abi3-win32.whl", hash = "sha256:64d7ce196203e468c457c37ec22390f1a61c85c6f0b8160fd752940ccfb3a683", size = 153725, upload_time = "2025-09-25T19:50:34.384Z" },
{ url = "https://files.pythonhosted.org/packages/9f/b9/9d9a641194a730bda138b3dfe53f584d61c58cd5230e37566e83ec2ffa0d/bcrypt-5.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:64ee8434b0da054d830fa8e89e1c8bf30061d539044a39524ff7dec90481e5c2", size = 150912, upload_time = "2025-09-25T19:50:35.69Z" },
{ url = "https://files.pythonhosted.org/packages/27/44/d2ef5e87509158ad2187f4dd0852df80695bb1ee0cfe0a684727b01a69e0/bcrypt-5.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927", size = 144953, upload_time = "2025-09-25T19:50:37.32Z" },
{ url = "https://files.pythonhosted.org/packages/8a/75/4aa9f5a4d40d762892066ba1046000b329c7cd58e888a6db878019b282dc/bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7edda91d5ab52b15636d9c30da87d2cc84f426c72b9dba7a9b4fe142ba11f534", size = 271180, upload_time = "2025-09-25T19:50:38.575Z" },
{ url = "https://files.pythonhosted.org/packages/54/79/875f9558179573d40a9cc743038ac2bf67dfb79cecb1e8b5d70e88c94c3d/bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:046ad6db88edb3c5ece4369af997938fb1c19d6a699b9c1b27b0db432faae4c4", size = 273791, upload_time = "2025-09-25T19:50:39.913Z" },
{ url = "https://files.pythonhosted.org/packages/bc/fe/975adb8c216174bf70fc17535f75e85ac06ed5252ea077be10d9cff5ce24/bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:dcd58e2b3a908b5ecc9b9df2f0085592506ac2d5110786018ee5e160f28e0911", size = 270746, upload_time = "2025-09-25T19:50:43.306Z" },
{ url = "https://files.pythonhosted.org/packages/e4/f8/972c96f5a2b6c4b3deca57009d93e946bbdbe2241dca9806d502f29dd3ee/bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:6b8f520b61e8781efee73cba14e3e8c9556ccfb375623f4f97429544734545b4", size = 273375, upload_time = "2025-09-25T19:50:45.43Z" },
]
[[package]]
name = "blinker"
version = "1.9.0"
@@ -131,7 +201,9 @@ name = "debt-by-ai"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "bcrypt" },
{ name = "flask" },
{ name = "flask-login" },
{ name = "google-genai" },
{ name = "python-dotenv" },
{ name = "requests" },
@@ -139,7 +211,9 @@ dependencies = [
[package.metadata]
requires-dist = [
{ name = "bcrypt", specifier = ">=5.0.0" },
{ name = "flask", specifier = ">=3.1.2" },
{ name = "flask-login", specifier = ">=0.6.3" },
{ name = "google-genai", specifier = ">=1.40.0" },
{ name = "python-dotenv", specifier = ">=1.1.1" },
{ name = "requests", specifier = ">=2.32.5" },
@@ -162,6 +236,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload_time = "2025-08-19T21:03:19.499Z" },
]
[[package]]
name = "flask-login"
version = "0.6.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "flask" },
{ name = "werkzeug" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c3/6e/2f4e13e373bb49e68c02c51ceadd22d172715a06716f9299d9df01b6ddb2/Flask-Login-0.6.3.tar.gz", hash = "sha256:5e23d14a607ef12806c699590b89d0f0e0d67baeec599d75947bf9c147330333", size = 48834, upload_time = "2023-10-30T14:53:21.151Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/59/f5/67e9cc5c2036f58115f9fe0f00d203cf6780c3ff8ae0e705e7a9d9e8ff9e/Flask_Login-0.6.3-py3-none-any.whl", hash = "sha256:849b25b82a436bf830a054e74214074af59097171562ab10bfa999e6b78aae5d", size = 17303, upload_time = "2023-10-30T14:53:19.636Z" },
]
[[package]]
name = "google-auth"
version = "2.41.1"