Upload current progress

This commit is contained in:
2026-01-03 10:23:05 -06:00
commit ec57d94cc0
66 changed files with 4031 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@@ -0,0 +1,15 @@
.states
*.py[cod]
assets/external/
*.db
.web
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.14

56
00 - Home/Homepage.md Normal file
View File

@@ -0,0 +1,56 @@
---
cssclasses:
- homepage-callouts
---
<div class="header-container">
<h1>Zwei</h1>
<p>Your Second Brain</p>
</div>
<div class="main-container">
<div class="card">
<h2>Navigation</h2>
<div class="button-grid">
<a href="obsidian://open?path=01%20-%20Projects%2F">Projects</a>
<a href="obsidian://open?path=02%20-%20Areas%2F">Areas</a>
<a href="obsidian://open?path=03%20-%20Resources%2F">Resources</a>
<a href="obsidian://open?path=04%20-%20Permanent%2F">Permanent</a>
<a href="obsidian://open?path=05%20-%20Fleeting%2F">Fleeting</a>
<a href="obsidian://open?path=06%20-%20Daily%2F">Daily</a>
</div>
</div>
<div class="card">
<h2>Create New</h2>
<div class="button-grid">
<a href="obsidian://new?template=99%20-%20Meta%2F00%20-%20Templates%2F(TEMPLATE)%20Daily.md&path=06%20-%20Daily%2F">New Daily Note</a>
<a href="obsidian://new?template=99%20-%20Meta%2F00%20-%20Templates%2F(TEMPLATE)%20Unique.md&path=05%20-%20Fleeting%2F">New Fleeting Note</a>
</div>
</div>
<div class="card">
<h2>Active Projects</h2>
```dataview
LIST
FROM "01 - Projects"
SORT file.name asc
```
</div>
<div class="card">
<h2>Recently Modified</h2>
```dataview
TABLE WITHOUT ID file.link as "File", file.mtime as "Modified"
FROM ""
WHERE file.path != this.file.path
SORT file.mtime DESC
LIMIT 10
```
</div>
</div>
> [!IMPORTANT]
> This page requires the **Dataview** and **Templater** plugins to be installed and enabled. If the content below the cards does not render, please ensure these plugins are working correctly and restart Obsidian.

View File

@@ -0,0 +1,56 @@
---
cssclasses:
- homepage-callouts
---
<div class="header-container">
<h1>Zwei</h1>
<p>Your Second Brain</p>
</div>
<div class="main-container">
<div class="card">
<h2>Navigation</h2>
<div class="button-grid">
<a href="obsidian://open?path=01%20-%20Projects%2F">Projects</a>
<a href="obsidian://open?path=02%20-%20Areas%2F">Areas</a>
<a href="obsidian://open?path=03%20-%20Resources%2F">Resources</a>
<a href="obsidian://open?path=04%20-%20Permanent%2F">Permanent</a>
<a href="obsidian://open?path=05%20-%20Fleeting%2F">Fleeting</a>
<a href="obsidian://open?path=06%20-%20Daily%2F">Daily</a>
</div>
</div>
<div class="card">
<h2>Create New</h2>
<div class="button-grid">
<a href="obsidian://new?template=99%20-%20Meta%2F00%20-%20Templates%2F(TEMPLATE)%20Daily.md&path=06%20-%20Daily%2F">New Daily Note</a>
<a href="obsidian://new?template=99%20-%20Meta%2F00%20-%20Templates%2F(TEMPLATE)%20Unique.md&path=05%20-%20Fleeting%2F">New Fleeting Note</a>
</div>
</div>
<div class="card">
<h2>Active Projects</h2>
```dataview
LIST
FROM "01 - Projects"
SORT file.name asc
```
</div>
<div class="card">
<h2>Recently Modified</h2>
```dataview
TABLE WITHOUT ID file.link as "File", file.mtime as "Modified"
FROM ""
WHERE file.path != this.file.path
SORT file.mtime DESC
LIMIT 10
```
</div>
</div>
> [!IMPORTANT]
> This page requires the **Dataview** and **Templater** plugins to be installed and enabled. If the content below the cards does not render, please ensure these plugins are working correctly and restart Obsidian.

56
99 - Meta/Homepage.md Normal file
View File

@@ -0,0 +1,56 @@
---
cssclasses:
- homepage-callouts
---
<div class="header-container">
<h1>Zwei</h1>
<p>Your Second Brain</p>
</div>
<div class="main-container">
<div class="card">
<h2>Navigation</h2>
<div class="button-grid">
<a href="obsidian://open?path=01%20-%20Projects%2F">Projects</a>
<a href="obsidian://open?path=02%20-%20Areas%2F">Areas</a>
<a href="obsidian://open?path=03%20-%20Resources%2F">Resources</a>
<a href="obsidian://open?path=04%20-%20Permanent%2F">Permanent</a>
<a href="obsidian://open?path=05%20-%20Fleeting%2F">Fleeting</a>
<a href="obsidian://open?path=06%20-%20Daily%2F">Daily</a>
</div>
</div>
<div class="card">
<h2>Create New</h2>
<div class="button-grid">
<a href="obsidian://new?template=99%20-%20Meta%2F00%20-%20Templates%2F(TEMPLATE)%20Daily.md&path=06%20-%20Daily%2F">New Daily Note</a>
<a href="obsidian://new?template=99%20-%20Meta%2F00%20-%20Templates%2F(TEMPLATE)%20Unique.md&path=05%20-%20Fleeting%2F">New Fleeting Note</a>
</div>
</div>
<div class="card">
<h2>Active Projects</h2>
```dataview
LIST
FROM "01 - Projects"
SORT file.name asc
```
</div>
<div class="card">
<h2>Recently Modified</h2>
```dataview
TABLE WITHOUT ID file.link as "File", file.mtime as "Modified"
FROM ""
WHERE file.path != this.file.path
SORT file.mtime DESC
LIMIT 10
```
</div>
</div>
> [!IMPORTANT]
> This page requires the **Dataview** and **Templater** plugins to be installed and enabled. If the content below the cards does not render, please ensure these plugins are working correctly and restart Obsidian.

View File

@@ -0,0 +1,5 @@
Tetrapods dominate because they have bones, instead of exoskeleton which needs to be replaced and leaves them vulnerable and consumes a lot of energy
They aren't ginormous because of this! This makes more sense than oxygen [[How bugs breathe]]
#rewrite This is a fleeting note containing a raw insight. It lacks a formal structure, a clear title, and supporting evidence. It needs to be expanded into a permanent note that details the metabolic costs of molting and the structural advantages of endoskeletons compared to the oxygen-limitation hypothesis.

View File

@@ -0,0 +1,9 @@
In Delphi (Pascal) there are prefixes for each class and variable declaration this is useful to know where they are from
| Prefix | Meaning | Example | Context |
| ------ | --------- | ------------------------- | ----------------------------------- |
| **T** | Type | `TButton`, `TStringList` | Class, Record or Enum definitions |
| **L** | Local | `LCount`, `LTempResult` | Variables defined inside a function |
| **F** | Field | `FName`, `FID` | Private variables inside a class |
| **A** | Argument | `AName`, `AValue` | Parameters passed into a function |
| **I** | Interface | `IUnkown`, `ISeralizable` | Interface definitions |

130
FIX_SUMMARY.md Normal file
View File

@@ -0,0 +1,130 @@
# Obsidian CouchDB Sync - Problem & Solution
## What Was Wrong
Your setup had **three separate pieces** that don't work together:
1.**CouchDB** - Storing files (WORKING)
2.**Obsidian Self-Hosted Sync Plugin** - Reading from CouchDB (WORKING)
3.**Filesystem Sync** - Writing files to disk (MISSING!)
### Why Files Weren't Showing
The Obsidian plugin can READ files from CouchDB through its API, but:
- Files weren't being **written to the filesystem**
- Your `watch_changes.py` only **printed** changes, didn't save files
- Your `couch_sync.py` CAN sync, but it's not running continuously
## The Fix
I created `sync_daemon.py` which:
1. ✅ Connects to CouchDB
2. ✅ Does initial full sync (downloads all files)
3. ✅ Watches for changes in real-time
4. ✅ Writes updated files to disk immediately
5. ✅ Reconstructs files from LiveSync's chunk format
6. ✅ Skips encrypted files automatically
## Quick Start
### Step 1: Test Connection
```bash
cd obsidian_automator
python3 test_connection.py
```
This verifies CouchDB is accessible and shows your files.
### Step 2: Configure Target Folder
Edit `sync_daemon.py`, line 16:
```python
TARGET_FOLDER = "/path/to/your/obsidian/vault"
```
Or keep it syncing to current directory:
```python
TARGET_FOLDER = "/home/diego/Scratchpad/obsidian-magic/obsidian_automator"
```
### Step 3: Run Sync Daemon
```bash
python3 sync_daemon.py
```
You should see:
- Initial sync downloading all files
- Real-time updates as you edit in Obsidian
### Step 4 (Optional): Run as Background Service
See `SYNC_SETUP.md` for systemd service setup.
## How It Works
```
┌─────────────┐
│ Obsidian │ ← Edit files
└──────┬──────┘
┌─────────────────┐
│ Self-Hosted │ ← Uploads to CouchDB
│ Sync Plugin │
└──────┬──────────┘
┌──────────────┐
│ CouchDB │ ← Stores in chunks
└──────┬───────┘
↓ _changes feed
┌──────────────┐
│ sync_daemon │ ← NEW! Watches & writes files
└──────┬───────┘
┌──────────────┐
│ Filesystem │ ← Files appear here!
└──────────────┘
```
## Files Changed/Created
-**sync_daemon.py** (NEW) - Main sync daemon
-**test_connection.py** (NEW) - Connection tester
-**SYNC_SETUP.md** (NEW) - Detailed setup guide
-**FIX_SUMMARY.md** (NEW) - This file
Existing files (not modified):
- `couch_sync.py` - Still used by web UI
- `couch_manager.py` - Still used for operations
- `watch_changes.py` - Keep for debugging
## Next Steps
1. Run `test_connection.py` to verify setup
2. Edit `TARGET_FOLDER` in `sync_daemon.py`
3. Run `sync_daemon.py`
4. Check that files appear in target folder
5. Make changes in Obsidian and watch them sync
6. (Optional) Set up as systemd service for auto-start
## Troubleshooting
**No files appearing?**
- Check `TARGET_FOLDER` path is correct and writable
- Verify CouchDB connection with `test_connection.py`
- Check daemon output for errors
**Connection refused?**
- Verify CouchDB is running: `curl http://100.100.112.48:5984/`
- Check credentials in `sync_daemon.py`
- Check firewall/network settings
**Files are encrypted?**
- Daemon skips encrypted files by default
- To sync encrypted files, need to add passphrase decryption
Need help? Check `SYNC_SETUP.md` for more details!

3
Inbox/Teleport.md Normal file
View File

@@ -0,0 +1,3 @@
Research the use of teleport
#rewrite The note is incomplete and lacks substantive content or research details, functioning more as a task than a knowledge note.

View File

@@ -0,0 +1,18 @@
---
copilot-command-context-menu-enabled: false
copilot-command-slash-enabled: false
copilot-command-context-menu-order: 1120
copilot-command-model-key: ""
copilot-command-last-used: 0
---
Convert {} into a Twitter thread following these rules:
1. Each tweet must be under 240 characters
2. Start with "THREAD START" on its own line
3. Separate tweets with "
---
"
4. End with "THREAD END" on its own line
5. Make content engaging and clear
Return only the formatted thread.

View File

@@ -0,0 +1,9 @@
Bugs breathe thorough tiny holed called spiracles and apparently bugs dont have a circulatory system
Apparently they are not just holes, they are an actual system.
Spiders and Scorpions have booklongs and they apparently are an old adaptation of gills.
The oxygen content is a myth bugs arent ginormous because of more oxygen, the actual explanation is that there were no animals on land
#rewrite The note contains several typographical errors ('thorough' instead of 'through', 'holed' instead of 'holes', 'booklongs' instead of 'book lungs') and a factual inaccuracy regarding the circulatory system (insects have an open circulatory system, it's just not used for gas exchange). The claim about oxygen content being a 'myth' is also a simplified take on a complex scientific debate and needs better sourcing/nuance.

View File

@@ -0,0 +1,9 @@
Numbers:
```
i16, u16, i32, u32, i64, u64
```
Boolean is declared with bool
#rewrite The note is extremely sparse and lacks context. It lists primitive number types and the boolean type but does not specify the programming language (likely Rust) or provide any explanation or usage examples.

0
README.md Normal file
View File

View File

@@ -0,0 +1,7 @@
To clear the confiugration on a port on huawei routers you do
```
clear configuration interface GE0/0/6
```
#rewrite Note contains a typo ('confiugration') and is extremely brief, lacking context regarding the specific OS version (VRP) or related prerequisites (like the 'shutdown' command).

View File

@@ -0,0 +1,15 @@
---
copilot-command-context-menu-enabled: true
copilot-command-slash-enabled: true
copilot-command-context-menu-order: 1050
copilot-command-model-key: ""
copilot-command-last-used: 0
---
Add relevant emojis to enhance {}. Follow these rules:
1. Insert emojis at natural breaks in the text
2. Never place two emojis next to each other
3. Keep all original text unchanged
4. Choose emojis that match the context and tone
Return only the emojified text.
#rewrite This note appears to be a raw Copilot command configuration/prompt template. It lacks a descriptive title, context, and proper documentation for use within a knowledge base. It should be rewritten to include usage instructions and integrated into a structured Prompts or Templates folder.

View File

@@ -0,0 +1,8 @@
---
copilot-command-context-menu-enabled: true
copilot-command-slash-enabled: true
copilot-command-context-menu-order: 1030
copilot-command-model-key: ""
copilot-command-last-used: 0
---
Simplify {} to a 6th-grade reading level (ages 11-12). Use simple sentences, common words, and clear explanations. Maintain the original key concepts. Return only the simplified text.

View File

@@ -0,0 +1,3 @@
FireDaemon has a way to download OpenSSL Binaries for Windows https://www.firedaemon.com/download-firedaemon-openssl
#rewrite The note is too brief, consisting only of a single sentence and a URL. It lacks context regarding why this specific source is recommended, installation steps, or use cases for OpenSSL on Windows.

112
SYNC_SETUP.md Normal file
View File

@@ -0,0 +1,112 @@
# CouchDB Sync Daemon Setup
## The Problem
Files in CouchDB can be "read" by Obsidian's self-hosted sync plugin, but they don't automatically sync to the filesystem. The daemon only monitors changes but doesn't write files.
## The Solution
The `sync_daemon.py` script watches CouchDB for changes and **writes files to disk in real-time**.
## Setup
### 1. Configure Your Vault Path
Edit `sync_daemon.py` and set your Obsidian vault path:
```python
TARGET_FOLDER = os.path.expanduser("~/ObsidianVault") # Change this!
```
Or use the current directory structure (seems like you have notes here):
```python
TARGET_FOLDER = "/home/diego/Scratchpad/obsidian-magic/obsidian_automator"
```
### 2. Run the Daemon
```bash
cd obsidian_automator
source .venv/bin/activate # or use your virtual environment
python3 sync_daemon.py
```
### 3. What It Does
1. **Initial Sync**: Downloads all notes from CouchDB to filesystem
2. **Real-time Watch**: Monitors CouchDB changes and updates files immediately
3. **Handles Chunks**: Reconstructs files from LiveSync's chunked storage format
4. **Skips Encryption**: Ignores encrypted files automatically
## Running as a Service (Optional)
### Using systemd (Linux)
Create `/etc/systemd/system/obsidian-sync.service`:
```ini
[Unit]
Description=Obsidian CouchDB Sync Daemon
After=network.target
[Service]
Type=simple
User=diego
WorkingDirectory=/home/diego/Scratchpad/obsidian-magic/obsidian_automator
ExecStart=/home/diego/Scratchpad/obsidian-magic/obsidian_automator/.venv/bin/python3 sync_daemon.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
Then:
```bash
sudo systemctl daemon-reload
sudo systemctl enable obsidian-sync
sudo systemctl start obsidian-sync
sudo systemctl status obsidian-sync
```
### Using screen/tmux (Quick method)
```bash
screen -S obsidian-sync
cd obsidian_automator
python3 sync_daemon.py
# Press Ctrl+A, then D to detach
```
To reattach: `screen -r obsidian-sync`
## Troubleshooting
### Files Not Appearing
- Check that `TARGET_FOLDER` path is correct
- Ensure CouchDB credentials are correct
- Check file permissions on target folder
### Connection Issues
- Verify CouchDB is accessible: `curl http://100.100.112.48:5984/`
- Check firewall rules
- Verify database name is correct
### Encrypted Files
The daemon automatically skips encrypted files. If you need to sync encrypted files, you'll need to configure the passphrase in the code.
## Architecture
```
CouchDB (LiveSync Format)
sync_daemon.py (watches _changes feed)
Filesystem (Obsidian Vault)
Obsidian App (reads local files)
```
## Key Files
- `sync_daemon.py` - Main sync daemon (NEW)
- `couch_sync.py` - CouchDBSync class (used by web UI)
- `couch_manager.py` - CouchDB operations (move, flag, etc.)
- `watch_changes.py` - Simple change monitor (doesn't write files)

View File

@@ -0,0 +1,8 @@
---
copilot-command-context-menu-enabled: true
copilot-command-slash-enabled: true
copilot-command-context-menu-order: 1020
copilot-command-model-key: ""
copilot-command-last-used: 0
---
Create a bullet-point summary of {}. Each bullet point should capture a key point. Return only the bullet-point summary.

View File

@@ -0,0 +1,13 @@
---
copilot-command-context-menu-enabled: true
copilot-command-slash-enabled: true
copilot-command-context-menu-order: 1050
copilot-command-model-key: ""
copilot-command-last-used: 0
---
Add relevant emojis to enhance {}. Follow these rules:
1. Insert emojis at natural breaks in the text
2. Never place two emojis next to each other
3. Keep all original text unchanged
4. Choose emojis that match the context and tone
Return only the emojified text.

View File

@@ -0,0 +1,12 @@
---
copilot-command-context-menu-enabled: true
copilot-command-slash-enabled: true
copilot-command-context-menu-order: 1040
copilot-command-model-key: ""
copilot-command-last-used: 0
---
Explain {} in simple terms that a 5-year-old would understand:
1. Use basic vocabulary
2. Include simple analogies
3. Break down complex concepts
Return only the simplified explanation.

View File

@@ -0,0 +1,12 @@
---
copilot-command-context-menu-enabled: false
copilot-command-slash-enabled: false
copilot-command-context-menu-order: 1110
copilot-command-model-key: ""
copilot-command-last-used: 0
---
Rewrite {} as a single tweet with these requirements:
1. Maximum 280 characters
2. Use concise, impactful language
3. Maintain the core message
Return only the tweet text.

View File

@@ -0,0 +1,8 @@
---
copilot-command-context-menu-enabled: true
copilot-command-slash-enabled: true
copilot-command-context-menu-order: 1020
copilot-command-model-key: ""
copilot-command-last-used: 0
---
Create a bullet-point summary of {}. Each bullet point should capture a key point. Return only the bullet-point summary.

View File

@@ -0,0 +1,8 @@
---
copilot-command-context-menu-enabled: true
copilot-command-slash-enabled: true
copilot-command-context-menu-order: 1020
copilot-command-model-key: ""
copilot-command-last-used: 0
---
Create a bullet-point summary of {}. Each bullet point should capture a key point. Return only the bullet-point summary.

View File

@@ -0,0 +1,12 @@
---
copilot-command-context-menu-enabled: true
copilot-command-slash-enabled: true
copilot-command-context-menu-order: 1060
copilot-command-model-key: ""
copilot-command-last-used: 0
---
Reduce {} to half its length while preserving these elements:
1. Main ideas and key points
2. Essential details
3. Original tone and style
Return only the shortened text.

View File

@@ -0,0 +1,15 @@
---
copilot-command-context-menu-enabled: true
copilot-command-slash-enabled: true
copilot-command-context-menu-order: 1050
copilot-command-model-key: ""
copilot-command-last-used: 0
---
Add relevant emojis to enhance {}. Follow these rules:
1. Insert emojis at natural breaks in the text
2. Never place two emojis next to each other
3. Keep all original text unchanged
4. Choose emojis that match the context and tone
Return only the emojified text.
#rewrite The note appears to be a raw configuration snippet and a prompt template without a clear title, context, or organizational structure. It should be rewritten to include a proper header, description of use, and categorized correctly.

View File

@@ -0,0 +1,10 @@
---
copilot-command-context-menu-enabled: true
copilot-command-slash-enabled: true
copilot-command-context-menu-order: 1000
copilot-command-model-key: ""
copilot-command-last-used: 0
---
Fix the grammar and spelling of {}. Preserve all formatting, line breaks, and special characters. Do not add or remove any content. Return only the corrected text.
#rewrite The note is incomplete as a knowledge entry; it contains raw plugin configuration and a prompt but lacks a descriptive title, explanation of use, or versioning metadata.

View File

@@ -0,0 +1,10 @@
---
copilot-command-context-menu-enabled: false
copilot-command-slash-enabled: false
copilot-command-context-menu-order: 1080
copilot-command-model-key: ""
copilot-command-last-used: 0
---
Generate a hierarchical table of contents for {}. Use appropriate heading levels (H1, H2, H3, etc.). Include page numbers if present. Return only the table of contents.
#rewrite The note contains only metadata and a single prompt template instruction with a placeholder ({}). It lacks substantive content or context, making it incomplete as a standalone knowledge note.

View File

@@ -0,0 +1,14 @@
---
copilot-command-context-menu-enabled: true
copilot-command-slash-enabled: true
copilot-command-context-menu-order: 1070
copilot-command-model-key: ""
copilot-command-last-used: 0
---
Expand {} to twice its length by:
1. Adding relevant details and examples
2. Elaborating on key points
3. Maintaining the original tone and style
Return only the expanded text.
#rewrite The note appears to be a prompt template or configuration snippet lacking context, a proper title, or substantive knowledge content. It should be expanded to include its purpose, usage instructions, or integrated into a more comprehensive document on AI prompting.

View File

@@ -0,0 +1,10 @@
---
copilot-command-context-menu-enabled: false
copilot-command-slash-enabled: false
copilot-command-context-menu-order: 1100
copilot-command-model-key: ""
copilot-command-last-used: 0
---
Remove all URLs from {}. Preserve all other content and formatting. URLs may be in various formats (http, https, www). Return only the text with URLs removed.
#rewrite The note contains a raw LLM prompt instruction and plugin configuration but lacks a title, description, or context for its intended use within the vault. It should be expanded to include metadata and usage instructions.

View File

@@ -0,0 +1,8 @@
---
copilot-command-context-menu-enabled: true
copilot-command-slash-enabled: true
copilot-command-context-menu-order: 1020
copilot-command-model-key: ""
copilot-command-last-used: 0
---
Create a bullet-point summary of {}. Each bullet point should capture a key point. Return only the bullet-point summary.

View File

@@ -0,0 +1,12 @@
---
copilot-command-context-menu-enabled: true
copilot-command-slash-enabled: true
copilot-command-context-menu-order: 1010
copilot-command-model-key: ""
copilot-command-last-used: 0
---
Translate {} into Chinese:
1. Preserve the meaning and tone
2. Maintain appropriate cultural context
3. Keep formatting and structure
Return only the translated text.

147
alembic.ini Normal file
View File

@@ -0,0 +1,147 @@
# A generic, single database configuration.
[alembic]
# path to migration scripts.
# this is typically a path given in POSIX (e.g. forward slashes)
# format, relative to the token %(here)s which refers to the location of this
# ini file
script_location = %(here)s/alembic
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
# Uncomment the line below if you want the files to be prepended with date and time
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
# for all available tokens
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
# sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory. for multiple paths, the path separator
# is defined by "path_separator" below.
prepend_sys_path = .
# timezone to use when rendering the date within the migration file
# as well as the filename.
# If specified, requires the tzdata library which can be installed by adding
# `alembic[tz]` to the pip requirements.
# string value is passed to ZoneInfo()
# leave blank for localtime
# timezone =
# max length of characters to apply to the "slug" field
# truncate_slug_length = 40
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false
# version location specification; This defaults
# to <script_location>/versions. When using multiple version
# directories, initial revisions must be specified with --version-path.
# The path separator used here should be the separator specified by "path_separator"
# below.
# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions
# path_separator; This indicates what character is used to split lists of file
# paths, including version_locations and prepend_sys_path within configparser
# files such as alembic.ini.
# The default rendered in new alembic.ini files is "os", which uses os.pathsep
# to provide os-dependent path splitting.
#
# Note that in order to support legacy alembic.ini files, this default does NOT
# take place if path_separator is not present in alembic.ini. If this
# option is omitted entirely, fallback logic is as follows:
#
# 1. Parsing of the version_locations option falls back to using the legacy
# "version_path_separator" key, which if absent then falls back to the legacy
# behavior of splitting on spaces and/or commas.
# 2. Parsing of the prepend_sys_path option falls back to the legacy
# behavior of splitting on spaces, commas, or colons.
#
# Valid values for path_separator are:
#
# path_separator = :
# path_separator = ;
# path_separator = space
# path_separator = newline
#
# Use os.pathsep. Default configuration used for new projects.
path_separator = os
# set to 'true' to search source files recursively
# in each "version_locations" directory
# new in Alembic version 1.10
# recursive_version_locations = false
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
# database URL. This is consumed by the user-maintained env.py script only.
# other means of configuring database URLs may be customized within the env.py
# file.
sqlalchemy.url = driver://user:pass@localhost/dbname
[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples
# format using "black" - use the console_scripts runner, against the "black" entrypoint
# hooks = black
# black.type = console_scripts
# black.entrypoint = black
# black.options = -l 79 REVISION_SCRIPT_FILENAME
# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module
# hooks = ruff
# ruff.type = module
# ruff.module = ruff
# ruff.options = check --fix REVISION_SCRIPT_FILENAME
# Alternatively, use the exec runner to execute a binary found on your PATH
# hooks = ruff
# ruff.type = exec
# ruff.executable = ruff
# ruff.options = check --fix REVISION_SCRIPT_FILENAME
# Logging configuration. This is also consumed by the user-maintained
# env.py script only.
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARNING
handlers = console
qualname =
[logger_sqlalchemy]
level = WARNING
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

1
alembic/README Normal file
View File

@@ -0,0 +1 @@
Generic single-database configuration.

78
alembic/env.py Normal file
View File

@@ -0,0 +1,78 @@
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = None
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

28
alembic/script.py.mako Normal file
View File

@@ -0,0 +1,28 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision: str = ${repr(up_revision)}
down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
def upgrade() -> None:
"""Upgrade schema."""
${upgrades if upgrades else "pass"}
def downgrade() -> None:
"""Downgrade schema."""
${downgrades if downgrades else "pass"}

View File

@@ -0,0 +1,58 @@
"""empty message
Revision ID: 29a55764f5a5
Revises:
Create Date: 2025-12-28 20:20:32.876599
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import sqlmodel
# revision identifiers, used by Alembic.
revision: str = '29a55764f5a5'
down_revision: Union[str, Sequence[str], None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('configuration',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('gemini_api_key', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.Column('input_folder', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.Column('rewrite_tag', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.Column('philosophy', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.Column('couchdb_url', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.Column('couchdb_user', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.Column('couchdb_password', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.Column('couchdb_passphrase', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.Column('couchdb_db_name', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.Column('is_configured', sa.Boolean(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_table('user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('username', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('password_hash', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_user_username'), ['username'], unique=False)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_user_username'))
op.drop_table('user')
op.drop_table('configuration')
# ### end Alembic commands ###

87
cleanup_db.py Executable file
View File

@@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""
Clean up LiveSync database - removes orphaned docs and fixes corruption
"""
import couchdb
import time
from urllib.parse import quote
USER = "admin"
PASSWORD = "DonCucarach0!?"
IP_ADDRESS = "100.100.112.48"
PORT = "5984"
DB_NAME = "obsidiandb"
def cleanup_db():
safe_password = quote(PASSWORD, safe="")
safe_username = quote(USER, safe="")
url = f"http://{safe_username}:{safe_password}@{IP_ADDRESS}:{PORT}/"
server = couchdb.Server(url)
db = server[DB_NAME]
print("Scanning for issues...")
files_to_fix = []
# Find all file metadata docs
for row in db.view('_all_docs', include_docs=True):
doc = row.doc
# Skip non-file docs
if 'children' not in doc:
continue
# Check if missing required LiveSync fields
missing_fields = []
if 'type' not in doc:
missing_fields.append('type')
if 'size' not in doc:
missing_fields.append('size')
if 'ctime' not in doc:
missing_fields.append('ctime')
if 'eden' not in doc:
missing_fields.append('eden')
if missing_fields:
files_to_fix.append((doc['_id'], doc, missing_fields))
print(f"{doc.get('path', doc['_id'])}: missing {missing_fields}")
if not files_to_fix:
print("\n✅ No issues found!")
return
print(f"\nFound {len(files_to_fix)} files to fix")
print("Fixing...")
for doc_id, doc, missing in files_to_fix:
# Add missing fields with sensible defaults
if 'type' in missing:
doc['type'] = 'plain'
if 'size' in missing:
# Calculate size from chunks
doc['size'] = 0
for chunk_id in doc.get('children', []):
if chunk_id in db:
chunk = db[chunk_id]
data = chunk.get('data', chunk.get('content', ''))
doc['size'] += len(str(data))
if 'ctime' in missing:
doc['ctime'] = doc.get('mtime', int(time.time() * 1000))
if 'eden' in missing:
doc['eden'] = {}
# Update mtime to trigger sync
doc['mtime'] = int(time.time() * 1000)
try:
db.save(doc)
print(f"✓ Fixed: {doc.get('path', doc_id)}")
except Exception as e:
print(f"✗ Failed to fix {doc_id}: {e}")
print("\n✅ Cleanup complete!")
print("Now run force_sync.py to trigger Obsidian re-sync")
if __name__ == "__main__":
cleanup_db()

62
dump_all_notes.py Normal file
View File

@@ -0,0 +1,62 @@
import couchdb
from urllib.parse import quote
# Configuration
user = "admin"
password = "DonCucarach0!?"
ip_address = "100.100.112.48"
port = "5984"
db_name = "obsidiandb"
# Safe URL Construction
safe_password = quote(password, safe="")
url = f"http://{user}:{safe_password}@{ip_address}:{port}/"
print(f"Connecting to {db_name}...")
try:
server = couchdb.Server(url)
db = server[db_name]
# Get all docs, skipping design docs
# map_fun not needed if we just iterate db
count = 0
encrypted_count = 0
print(f"\n--- 📚 Dumping Notes from {db_name} ---\n")
for doc_id in db:
if doc_id.startswith("_design"):
continue
doc = db[doc_id]
# Try to find content
content = doc.get("data") or doc.get("content")
print(f"📄 [{count+1}] {doc_id}")
if content:
content_str = str(content)
# Check for encryption markers
if content_str.startswith("%=") or content_str.startswith("%") or doc.get("e_"):
print(" 🔒 [ENCRYPTED DATA DETECTED]")
encrypted_count += 1
else:
# Show preview
preview = content_str[:200].replace("\n", "")
print(f" 📝 Content: {preview}...")
else:
print(" ❌ [No content field found]")
print("-" * 40)
count += 1
print(f"\n✅ Scan Complete.")
print(f"Total Notes: {count}")
print(f"Encrypted Notes: {encrypted_count}")
except Exception as e:
print(f"❌ Error: {e}")

76
fix_sizes.py Executable file
View File

@@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""
Fix LiveSync corruption by recalculating file sizes
The corruption happens when size field doesn't match actual chunk content size
"""
import couchdb
import time
from urllib.parse import quote
USER = "admin"
PASSWORD = "DonCucarach0!?"
IP_ADDRESS = "100.100.112.48"
PORT = "5984"
DB_NAME = "obsidiandb"
def fix_sizes():
safe_password = quote(PASSWORD, safe="")
safe_username = quote(USER, safe="")
url = f"http://{safe_username}:{safe_password}@{IP_ADDRESS}:{PORT}/"
server = couchdb.Server(url)
db = server[DB_NAME]
print("Scanning for size mismatches...")
fixed = []
correct = []
for row in db.view('_all_docs', include_docs=True):
doc = row.doc
# Only process file metadata docs
if 'children' not in doc or not isinstance(doc.get('children'), list):
continue
path = doc.get('path', doc['_id'])
stored_size = doc.get('size', 0)
# Calculate actual size from chunks
actual_size = 0
for chunk_id in doc['children']:
if chunk_id in db:
chunk = db[chunk_id]
data = chunk.get('data', chunk.get('content', ''))
actual_size += len(str(data))
if stored_size != actual_size:
print(f"{path}")
print(f" Stored: {stored_size}, Actual: {actual_size}")
# Fix it
doc['size'] = actual_size
doc['mtime'] = int(time.time() * 1000) # Trigger sync
try:
db.save(doc)
fixed.append(path)
print(f" ✓ Fixed")
except Exception as e:
print(f" ✗ Failed: {e}")
else:
correct.append(path)
print(f"\n{'='*60}")
print(f"✅ Correct: {len(correct)} files")
print(f"🔧 Fixed: {len(fixed)} files")
if fixed:
print(f"\nFixed files:")
for f in fixed:
print(f" - {f}")
print(f"\n✅ All sizes corrected! Obsidian should now sync properly.")
if __name__ == "__main__":
fix_sizes()

51
force_sync.py Executable file
View File

@@ -0,0 +1,51 @@
#!/usr/bin/env python3
"""
Force Obsidian LiveSync to re-sync by touching all documents
This updates mtime to trigger the _changes feed
"""
import couchdb
import time
from urllib.parse import quote
USER = "admin"
PASSWORD = "DonCucarach0!?"
IP_ADDRESS = "100.100.112.48"
PORT = "5984"
DB_NAME = "obsidiandb"
def force_sync():
safe_password = quote(PASSWORD, safe="")
safe_username = quote(USER, safe="")
url = f"http://{safe_username}:{safe_password}@{IP_ADDRESS}:{PORT}/"
server = couchdb.Server(url)
db = server[DB_NAME]
print("Force syncing all files by updating mtime...")
current_time = int(time.time() * 1000)
count = 0
for row in db.view('_all_docs', include_docs=True):
doc = row.doc
# Only update file metadata docs
if 'children' in doc and isinstance(doc.get('children'), list):
path = doc.get('path', doc['_id'])
# Update mtime to current time to trigger sync
doc['mtime'] = current_time
try:
db.save(doc)
count += 1
print(f"{path}")
time.sleep(2) # Rate limit: 2 seconds between updates
except Exception as e:
print(f"{path}: {e}")
print(f"\nUpdated {count} files. Obsidian should now re-sync.")
print("Check your Obsidian sync plugin - it should detect changes now.")
if __name__ == "__main__":
force_sync()

38
inspect_metadata.py Normal file
View File

@@ -0,0 +1,38 @@
import couchdb
from urllib.parse import quote
import json
# Configuration
user = "admin"
password = "DonCucarach0!?"
ip_address = "100.100.112.48"
port = "5984"
db_name = "obsidiandb"
# Target Doc (one that looked like a filename in the dump)
target_id = "05 - fleeting/zig.md"
safe_password = quote(password, safe="")
url = f"http://{user}:{safe_password}@{ip_address}:{port}/"
print(f"Inspecting: {target_id}")
try:
server = couchdb.Server(url)
db = server[db_name]
if target_id in db:
doc = db[target_id]
print(json.dumps(doc, indent=2))
else:
print("Document not found. Trying another one...")
# Fallback to finding ANY doc with a path-like ID
for doc_id in db:
if "/" in doc_id and not doc_id.startswith("_"):
print(f"Found alternative: {doc_id}")
doc = db[doc_id]
print(json.dumps(doc, indent=2))
break
except Exception as e:
print(f"Error: {e}")

6
main.py Normal file
View File

@@ -0,0 +1,6 @@
def main():
print("Hello from obsidian-automator!")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1 @@
# Package initialization

122
obsidian_automator/agent.py Normal file
View File

@@ -0,0 +1,122 @@
from google import genai
from google.genai import types
import os
import time
from .note_server import NoteServer
class ObsidianAgent:
def __init__(self, api_key: str, server: NoteServer):
self.client = genai.Client(api_key=api_key)
self.server = server
self.model_id = "gemini-3-flash-preview"
def process_vault(self, input_folder: str, philosophy: str, rewrite_tag: str) -> list[str]:
"""
Scans the input folder and processes each note using the LLM.
Returns a list of action logs.
"""
logs = []
notes = self.server.list_notes(input_folder)
# Derive Vault Root (Assuming input_folder is inside the vault, e.g. .../Vault/Inbox)
# We take the parent directory of the input folder.
vault_root = os.path.dirname(input_folder.rstrip(os.sep))
if not notes:
return ["No notes found in input folder."]
logs.append(f"Found {len(notes)} notes. Processing (Slow mode for Rate Limits)...")
for i, note_path in enumerate(notes):
# RATE LIMITING: Sleep to respect 5 RPM (1 request every 12s + buffer)
if i > 0:
time.sleep(15)
content = self.server.read_note(note_path)
if not content:
continue
log = self._process_single_note_with_retry(note_path, content, philosophy, rewrite_tag, vault_root)
logs.append(log)
return logs
def _process_single_note_with_retry(self, note_path: str, content: str, philosophy: str, rewrite_tag: str, vault_root: str) -> str:
"""Wraps processing with retry logic for 429 errors."""
max_retries = 3
for attempt in range(max_retries):
try:
return self._process_single_note(note_path, content, philosophy, rewrite_tag, vault_root)
except Exception as e:
if "429" in str(e) or "RESOURCE_EXHAUSTED" in str(e):
if attempt < max_retries - 1:
wait_time = 60 * (attempt + 1)
print(f"Rate limit hit. Waiting {wait_time}s...")
time.sleep(wait_time)
continue
return f"Error processing {os.path.basename(note_path)}: {str(e)}"
return f"Failed after retries: {os.path.basename(note_path)}"
def _process_single_note(self, note_path: str, content: str, philosophy: str, rewrite_tag: str, vault_root: str) -> str:
# 1. Define Tools
def move_note(target_folder: str):
"""Moves the note to a folder relative to Vault Root. E.g. 'Science/Biology'."""
# Ensure we don't treat target_folder as absolute unless it starts with vault_root
if not target_folder.startswith(vault_root):
full_target = os.path.join(vault_root, target_folder.strip(os.sep))
else:
full_target = target_folder
return self.server.move_note(note_path, full_target)
def flag_rewrite(reason: str):
"""Flags the current note for rewrite by appending a tag and reason."""
return self.server.flag_rewrite(note_path, reason, rewrite_tag)
# 2. Construct Prompt
prompt = f"""
You are an expert Knowledge Manager for an Obsidian Vault.
PHILOSOPHY:
{philosophy}
CURRENT NOTE CONTENT:
{content[:10000]} # Truncate to avoid massive context if note is huge
TASK:
Analyze the note content against the Philosophy.
1. If it fits well into a specific folder in the vault structure, move it there.
2. If it is low quality, incomplete, or violates the philosophy, flag it for rewrite.
You MUST call a tool.
"""
# 3. Call Gemini
response = self.client.models.generate_content(
model=self.model_id,
contents=prompt,
config=types.GenerateContentConfig(
tools=[move_note, flag_rewrite],
tool_config=types.ToolConfig(
function_calling_config=types.FunctionCallingConfig(
mode="ANY" # Force the model to use a tool
)
)
)
)
# 4. Execute Tool
if not response.function_calls:
return f"Skipped: {os.path.basename(note_path)} (Model failed to call tool)"
# Execute the first function call found
fc = response.function_calls[0]
if fc.name == "move_note":
result = move_note(**fc.args)
return f"[AI] {result}"
elif fc.name == "flag_rewrite":
result = flag_rewrite(**fc.args)
return f"[AI] {result}"
else:
return f"Error: Unknown tool called {fc.name}"

View File

@@ -0,0 +1,58 @@
import reflex as rx
from ..state.auth import AuthState
def sidebar_item(text: str, icon: str, href: str):
return rx.link(
rx.hstack(
rx.icon(icon, size=18),
rx.text(text, size="3", weight="medium"),
spacing="3",
align_items="center",
padding="12px",
border_radius="8px",
_hover={"bg": "var(--gray-3)"},
color="var(--gray-11)"
),
href=href,
width="100%",
text_decoration="none"
)
def dashboard_layout(content: rx.Component):
return rx.flex(
# Sidebar
rx.vstack(
rx.heading("Obsidian Automator", size="4", margin_bottom="6", color="var(--accent-9)"),
sidebar_item("Dashboard", "layout-dashboard", "/"),
sidebar_item("Settings", "settings", "/settings"),
rx.spacer(),
rx.button(
rx.hstack(rx.icon("log-out"), rx.text("Logout")),
on_click=AuthState.logout,
variant="ghost",
width="100%",
justify_content="start"
),
width="250px",
height="100vh",
padding="6",
bg="var(--gray-2)",
border_right="1px solid var(--gray-4)",
),
# Main Content
rx.box(
content,
flex="1",
height="100vh",
bg="var(--gray-1)",
padding="8",
overflow="auto",
),
width="100vw",
height="100vh",
)

View File

@@ -0,0 +1,50 @@
import reflex as rx
def terminal_window(logs: list[str], title="System Output"):
return rx.card(
rx.vstack(
rx.hstack(
rx.icon("terminal", size=18, color="var(--gray-11)"),
rx.text(title, size="2", weight="bold", color="var(--gray-11)"),
rx.spacer(),
# Minimal header controls (visual only for now)
rx.box(width="10px", height="10px", bg="var(--red-9)", border_radius="50%"),
rx.box(width="10px", height="10px", bg="var(--yellow-9)", border_radius="50%"),
rx.box(width="10px", height="10px", bg="var(--green-9)", border_radius="50%"),
align_items="center",
width="100%",
padding_bottom="3",
border_bottom="1px solid var(--gray-4)"
),
rx.scroll_area(
rx.vstack(
rx.foreach(
logs,
lambda log: rx.box(
rx.text(
log,
font_family="JetBrains Mono, monospace",
font_size="13px",
color="var(--gray-12)",
white_space="pre-wrap" # Handle multi-line logs
),
padding_y="1"
)
),
align_items="start",
spacing="0",
),
height="400px", # Fixed height for scroll
type="always",
scrollbars="vertical",
bg="var(--gray-2)",
padding="4",
border_radius="0 0 8px 8px"
),
spacing="0",
width="100%"
),
padding="0",
variant="surface",
width="100%"
)

View File

@@ -0,0 +1,216 @@
import couchdb
import os
import json
import time
from urllib.parse import quote
class CouchDBManager:
def __init__(self, url, username, password, db_name):
self.url = url.rstrip("/")
self.username = username.strip()
self.password = password.strip()
self.db_name = db_name
def connect(self):
try:
if self.username and self.password:
if "://" in self.url:
protocol, rest = self.url.split("://", 1)
else:
protocol, rest = "http", self.url
safe_user = quote(self.username, safe="")
safe_pass = quote(self.password, safe="")
full_url = f"{protocol}://{safe_user}:{safe_pass}@{rest}"
else:
full_url = self.url
server = couchdb.Server(full_url)
return server[self.db_name]
except Exception as e:
raise Exception(f"CouchDB Connection Error: {e}")
def _calculate_size(self, db, children):
"""Calculate actual content size from chunks (LiveSync compatible)"""
size = 0
for chunk_id in children:
if chunk_id in db:
chunk = db[chunk_id]
data = chunk.get("data", chunk.get("content", ""))
size += len(str(data))
return size
def list_files(self, prefix_filter=""):
"""Returns a dict: {id: path} for all 'file' documents."""
db = self.connect()
files = {}
# Use _all_docs to scan quickly
for row in db.view('_all_docs', include_docs=True):
doc = row.doc
# Identify file metadata docs (has children array)
if "children" in doc and isinstance(doc["children"], list):
path = doc.get("path", doc["_id"])
# Filter by prefix (folder) if requested
# Normalizing path separators to /
norm_path = path.replace("\\", "/")
norm_prefix = prefix_filter.replace("\\", "/")
# Debug print for first few items to diagnose filtering mismatch
if len(files) < 3:
print(f"DEBUG: Found path '{norm_path}'. Filter: '{norm_prefix}'")
if not prefix_filter or norm_path.startswith(norm_prefix):
files[doc["_id"]] = path
print(f"DEBUG: list_files returning {len(files)} files after filtering.")
return files
def read_file_content(self, doc_id):
"""Reconstructs file content from chunks."""
db = self.connect()
doc = db[doc_id]
content = []
for chunk_id in doc.get("children", []):
if chunk_id in db:
chunk = db[chunk_id]
data = chunk.get("data") or chunk.get("content") or ""
# Check encryption
if str(data).startswith("%") or chunk.get("e_"):
return "[ENCRYPTED_CONTENT]"
content.append(str(data))
return "".join(content)
def move_file(self, doc_id, target_folder):
"""
LiveSync-compatible file move:
Updates the path and _id in place without breaking chunk references.
NOTE: This approach updates the document but LiveSync may still need
to reconcile the change. For best results, use Obsidian's built-in move.
"""
db = self.connect()
print(f"DEBUG: Attempting to move doc_id: {repr(doc_id)} to '{target_folder}'")
doc = None
actual_doc_id = None
try:
# Try direct lookup first (faster)
if doc_id in db:
doc = db[doc_id]
actual_doc_id = doc_id
else:
# Fallback: case-insensitive scan
doc_id_lower = doc_id.lower()
for row in db.view('_all_docs', include_docs=True):
if row.id.lower() == doc_id_lower:
doc = row.doc
actual_doc_id = row.id
break
if not doc:
return f"Error: Document {doc_id} not found in DB (Scan failed)."
except Exception as e:
return f"Error fetching document {doc_id}: {e}"
old_path = doc.get("path", actual_doc_id)
filename = os.path.basename(old_path)
# Construct new path
target_folder = target_folder.strip("/")
new_path = f"{target_folder}/{filename}"
new_doc_id = new_path.lower()
# Check if already at target
if actual_doc_id == new_doc_id:
return f"Already at {new_path}"
# IMPORTANT: For LiveSync compatibility, we need to:
# 1. Update path field (tells LiveSync where file should be)
# 2. Update mtime (triggers sync)
# 3. Keep all other metadata intact
# Update in place first
doc["path"] = new_path
doc["mtime"] = int(time.time() * 1000)
db.save(doc)
# If the doc_id needs to change, create new doc and delete old
if actual_doc_id != new_doc_id:
# Check if target exists
if new_doc_id in db:
# Target exists - merge/update it instead
target_doc = db[new_doc_id]
target_doc["path"] = new_path
target_doc["mtime"] = int(time.time() * 1000)
target_doc["children"] = doc["children"] # Use latest chunks
target_doc["size"] = self._calculate_size(db, doc["children"]) # Recalculate size
target_doc["ctime"] = doc.get("ctime", target_doc.get("ctime"))
target_doc["type"] = doc.get("type", "plain")
target_doc["eden"] = doc.get("eden", {})
db.save(target_doc)
# Delete old
db.delete(doc)
return f"Merged and moved to {new_path}"
else:
# Create new doc with new ID
new_doc = doc.copy()
new_doc["_id"] = new_doc_id
new_doc["path"] = new_path
new_doc["size"] = self._calculate_size(db, new_doc["children"]) # Recalculate size
del new_doc["_rev"]
db.save(new_doc)
# Delete old
old_doc = db[actual_doc_id] # Refresh to get latest _rev
db.delete(old_doc)
return f"Moved to {new_path}"
def flag_rewrite(self, doc_id, reason, tag):
"""
Appends a rewrite tag.
"""
db = self.connect()
print(f"DEBUG: Flagging rewrite for doc_id: {repr(doc_id)}")
doc = None
try:
# Try direct lookup first (faster)
if doc_id in db:
doc = db[doc_id]
else:
# Fallback: case-insensitive scan
doc_id_lower = doc_id.lower()
for row in db.view('_all_docs', include_docs=True):
if row.id.lower() == doc_id_lower:
doc = row.doc
break
if not doc:
return f"Error: Document {doc_id} not found (Scan failed)."
except Exception as e:
return f"Error fetching doc for rewrite {doc_id}: {e}"
# Create new chunk
chunk_id = f"h:{int(time.time())}" # Simple ID generation
chunk_content = f"\n\n{tag} {reason}\n"
chunk_doc = {
"_id": chunk_id,
"data": chunk_content,
"type": "chunk" # LiveSync convention
}
db.save(chunk_doc)
# Update metadata to point to new chunk
doc["children"].append(chunk_id)
doc["mtime"] = int(time.time() * 1000)
# CRITICAL: Update size to match actual content size
doc["size"] = self._calculate_size(db, doc["children"])
db.save(doc)
return f"Flagged {doc.get('path')} for rewrite."

View File

@@ -0,0 +1,108 @@
import couchdb
import os
import base64
import json
from urllib.parse import quote
class CouchDBSync:
def __init__(self, url, username, password, db_name, passphrase=""):
self.url = url.rstrip("/")
self.username = username.strip()
self.password = password.strip()
self.db_name = db_name
self.passphrase = passphrase
def connect(self):
"""Establishes connection to CouchDB."""
try:
if self.username and self.password:
if "://" in self.url:
protocol, rest = self.url.split("://", 1)
else:
protocol, rest = "http", self.url
safe_user = quote(self.username, safe="")
safe_pass = quote(self.password, safe="")
full_url = f"{protocol}://{safe_user}:{safe_pass}@{rest}"
else:
full_url = self.url
server = couchdb.Server(full_url)
_ = server.version()
return server
except Exception as e:
raise Exception(f"Connection Failed: {str(e)}")
def fetch_notes(self, target_folder: str) -> list[str]:
logs = []
try:
server = self.connect()
if self.db_name not in server:
return [f"Error: Database '{self.db_name}' not found."]
db = server[self.db_name]
logs.append(f"Connected to {self.db_name}.")
if not os.path.exists(target_folder):
os.makedirs(target_folder, exist_ok=True)
# 1. Fetch all docs with content in ONE request
logs.append("Fetching all documents...")
all_docs = {}
# include_docs=True is much faster than 278 individual requests
for row in db.view('_all_docs', include_docs=True):
doc = row.doc
if not doc['_id'].startswith("_design"):
all_docs[doc['_id']] = doc
logs.append(f"Retrieved {len(all_docs)} documents. Reconstructing notes...")
note_count = 0
# 2. Iterate to find "Metadata" documents
for doc_id, doc in all_docs.items():
# Self-hosted LiveSync uses 'children' for files
if "children" in doc and isinstance(doc["children"], list):
relative_path = doc.get("path", doc_id)
# Construct full local path
safe_path = relative_path.replace(":", "-").replace("|", "-")
full_local_path = os.path.join(target_folder, safe_path)
# 3. Reconstruct Content
full_content = []
is_encrypted = False
for chunk_id in doc["children"]:
chunk = all_docs.get(chunk_id)
if not chunk:
continue
chunk_data = chunk.get("data") or chunk.get("content") or ""
if str(chunk_data).startswith("%") or chunk.get("e_"):
is_encrypted = True
break
full_content.append(str(chunk_data))
if is_encrypted:
continue
note_text = "".join(full_content)
# Only save if there is content or it's a known empty file
if note_text.strip() or len(doc["children"]) > 0:
os.makedirs(os.path.dirname(full_local_path), exist_ok=True)
with open(full_local_path, "w", encoding="utf-8") as f:
f.write(note_text)
note_count += 1
if note_count % 10 == 0:
logs.append(f"Reconstructed {note_count} notes...")
logs.append(f"Sync Complete. Reconstructed {note_count} total notes.")
except Exception as e:
logs.append(f"Sync Error: {str(e)}")
return logs

View File

@@ -0,0 +1,25 @@
import reflex as rx
from sqlmodel import Field
from typing import Optional
class User(rx.Model, table=True):
"""Admin user credentials."""
username: str = Field(index=True)
password_hash: str
class Configuration(rx.Model, table=True):
"""Persistent application settings."""
# Gemini
gemini_api_key: Optional[str] = Field(default="")
input_folder: Optional[str] = Field(default="")
rewrite_tag: Optional[str] = Field(default="#rewrite")
philosophy: Optional[str] = Field(default="Keep notes atomic. Use Zettelkasten principles.")
# CouchDB
couchdb_url: Optional[str] = Field(default="")
couchdb_user: Optional[str] = Field(default="")
couchdb_password: Optional[str] = Field(default="")
couchdb_passphrase: Optional[str] = Field(default="")
couchdb_db_name: Optional[str] = Field(default="obsidian_livesync")
is_configured: bool = Field(default=False)

View File

@@ -0,0 +1,136 @@
import os
import shutil
from abc import ABC, abstractmethod
from typing import List
class NoteServer(ABC):
@abstractmethod
def list_notes(self, directory: str) -> List[str]:
"""List all markdown files in the directory."""
pass
@abstractmethod
def read_note(self, file_path: str) -> str:
"""Read content of a note."""
pass
@abstractmethod
def move_note(self, file_path: str, target_folder: str) -> str:
"""Move a note to a target folder."""
pass
@abstractmethod
def flag_rewrite(self, file_path: str, reason: str, rewrite_tag: str) -> str:
"""Append a rewrite tag and reason to the note."""
pass
@abstractmethod
def list_subfolders(self, directory: str) -> List[str]:
"""List immediate subdirectories."""
pass
class FileSystemServer(NoteServer):
def list_subfolders(self, directory: str) -> List[str]:
if not os.path.exists(directory):
return []
try:
return [d for d in os.listdir(directory) if os.path.isdir(os.path.join(directory, d)) and not d.startswith(".")]
except Exception:
return []
def list_notes(self, directory: str) -> List[str]:
notes = []
if not os.path.exists(directory):
return []
for root, _, files in os.walk(directory):
for file in files:
if file.endswith(".md"):
# Only return absolute paths to keep things simple
notes.append(os.path.abspath(os.path.join(root, file)))
return notes
def read_note(self, file_path: str) -> str:
try:
with open(file_path, "r", encoding="utf-8") as f:
return f.read()
except Exception as e:
return f"Error reading file: {str(e)}"
def move_note(self, file_path: str, target_folder: str) -> str:
try:
filename = os.path.basename(file_path)
# Ensure target folder exists
if not os.path.exists(target_folder):
os.makedirs(target_folder)
new_path = os.path.join(target_folder, filename)
# Prevent overwriting by appending timestamp if exists, or just fail safely
if os.path.exists(new_path):
return f"Error: File {filename} already exists in {target_folder}"
shutil.move(file_path, new_path)
return f"Moved {filename} to {target_folder}"
except Exception as e:
return f"Error moving file: {str(e)}"
def flag_rewrite(self, file_path: str, reason: str, rewrite_tag: str) -> str:
try:
# Check if file ends with newline to avoid appending on same line
with open(file_path, "r+", encoding="utf-8") as f:
content = f.read()
prefix = "\n" if content and not content.endswith("\n") else ""
f.write(f"{prefix}\n{rewrite_tag} {reason}\n")
return f"Flagged {os.path.basename(file_path)} for rewrite: {reason}"
except Exception as e:
return f"Error flagging file: {str(e)}"
from .couch_manager import CouchDBManager
class CouchDBNoteServer(NoteServer):
def __init__(self, url, user, password, db_name):
self.manager = CouchDBManager(url, user, password, db_name)
def list_notes(self, directory: str) -> List[str]:
# Directory here acts as a prefix filter
files_dict = self.manager.list_files(prefix_filter=directory)
return list(files_dict.keys())
def list_subfolders(self, directory: str) -> List[str]:
# CouchDB is flat. We simulate folders by looking at paths.
# This is expensive (scan all), but accurate.
all_files_dict = self.manager.list_files()
subfolders = set()
# If directory is "(All Notes)", we look at root folders
# If directory is "Inbox", we look at "Inbox/Subfolder"
prefix = directory if directory != "(All Notes)" else ""
prefix = prefix.strip("/")
for doc_id, path in all_files_dict.items():
# Normalize path
path = path.replace("\\", "/")
if prefix and not path.startswith(prefix + "/"):
continue
# Strip prefix
relative_path = path[len(prefix)+1:] if prefix else path
if "/" in relative_path:
top_level = relative_path.split("/")[0]
subfolders.add(top_level)
return sorted(list(subfolders))
def read_note(self, file_path: str) -> str:
return self.manager.read_file_content(file_path)
def move_note(self, file_path: str, target_folder: str) -> str:
return self.manager.move_file(file_path, target_folder)
def flag_rewrite(self, file_path: str, reason: str, rewrite_tag: str) -> str:
return self.manager.flag_rewrite(file_path, reason, rewrite_tag)

View File

@@ -0,0 +1,21 @@
import reflex as rx
from .pages.dashboard import dashboard_page, settings_page
from .pages.login import login_page
from .pages.setup import setup_page
from .state.auth import AuthState
from .state.app_state import AppState
app = rx.App(
theme=rx.theme(
appearance="dark",
accent_color="iris",
gray_color="slate",
radius="medium",
)
)
# Routes
app.add_page(dashboard_page, route="/", on_load=[AuthState.on_load_check, AppState.start_scheduler, AppState.load_config, AppState.reset_state])
app.add_page(settings_page, route="/settings", on_load=[AuthState.on_load_check, AppState.load_config])
app.add_page(login_page, route="/login")
app.add_page(setup_page, route="/setup")

View File

@@ -0,0 +1,165 @@
import reflex as rx
from ..state.app_state import AppState
from ..components.layout import dashboard_layout
from ..components.terminal import terminal_window
def dashboard_page():
return dashboard_layout(
rx.vstack(
rx.hstack(
rx.heading("Dashboard", size="8"),
rx.spacer(),
rx.button(
rx.hstack(rx.icon("download"), rx.text("Sync from CouchDB")),
on_click=AppState.run_couch_sync,
loading=AppState.is_running,
variant="soft",
color_scheme="orange",
size="3",
margin_right="2",
disabled=AppState.is_running
),
rx.select(
AppState.available_subfolders,
value=AppState.selected_subfolder,
on_change=AppState.set_selected_subfolder,
placeholder="Select Target Folder",
size="3",
margin_right="2",
width="200px"
),
rx.icon_button(
rx.icon("refresh-cw"),
on_click=AppState.refresh_subfolders,
variant="soft",
size="3",
margin_right="2"
),
rx.button(
rx.hstack(rx.icon("play"), rx.text("Run Agent Now")),
on_click=AppState.run_agent_process,
loading=AppState.is_running,
size="3",
disabled=AppState.is_running
),
width="100%",
align_items="center",
margin_bottom="6"
),
rx.grid(
rx.card(
rx.vstack(
rx.text("Scheduler", size="2", color="gray.11", weight="bold"),
rx.text("Active", size="6", weight="bold"),
rx.text("Daily @ 02:00", size="1", color="gray.10"),
spacing="1"
)
),
rx.card(
rx.vstack(
rx.text("Model", size="2", color="gray.11", weight="bold"),
rx.text("Gemini Flash", size="6", weight="bold"),
rx.text("v3 Preview", size="1", color="gray.10"),
spacing="1"
)
),
columns="2",
spacing="4",
width="100%"
),
rx.box(
terminal_window(AppState.logs, title="Agent Activity Log"),
width="100%",
box_shadow="0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)"
),
spacing="6",
width="100%"
)
)
def settings_page():
return dashboard_layout(
rx.vstack(
rx.heading("Settings", size="8", margin_bottom="6"),
rx.tabs.root(
rx.tabs.list(
rx.tabs.trigger("General", value="general"),
rx.tabs.trigger("CouchDB Sync", value="couchdb"),
),
rx.tabs.content(
rx.card(
rx.vstack(
rx.text("Gemini API Key", weight="bold"),
rx.input(value=AppState.api_key, on_change=AppState.set_api_key, type="password", width="100%"),
rx.text("Input Folder Path", weight="bold"),
rx.input(value=AppState.input_folder, on_change=AppState.set_input_folder, width="100%"),
rx.text("Rewrite Tag", weight="bold"),
rx.input(value=AppState.rewrite_tag, on_change=AppState.set_rewrite_tag, width="100%"),
rx.text("Vault Philosophy", weight="bold"),
rx.text_area(value=AppState.philosophy, on_change=AppState.set_philosophy, height="200px", width="100%"),
rx.button("Save Changes", on_click=AppState.save_config, size="3", margin_top="4"),
spacing="4",
align_items="start",
padding="6"
),
width="100%",
max_width="800px",
margin_top="4"
),
value="general"
),
rx.tabs.content(
rx.card(
rx.vstack(
rx.heading("CouchDB Configuration", size="4"),
rx.text("Connect to your Obsidian LiveSync database to download notes.", color="gray.11", size="2"),
rx.text("Database URL", weight="bold", margin_top="2"),
rx.input(placeholder="http://localhost:5984", value=AppState.couch_url, on_change=AppState.set_couch_url, width="100%"),
rx.text("Database Name", weight="bold"),
rx.input(placeholder="obsidian_livesync", value=AppState.couch_db, on_change=AppState.set_couch_db, width="100%"),
rx.text("Username (Optional)", weight="bold"),
rx.input(value=AppState.couch_user, on_change=AppState.set_couch_user, width="100%"),
rx.text("Password (Optional)", weight="bold"),
rx.input(type="password", value=AppState.couch_pass, on_change=AppState.set_couch_pass, width="100%"),
rx.text("Decryption Passphrase (Experimental)", weight="bold"),
rx.input(type="password", value=AppState.couch_passphrase, on_change=AppState.set_couch_passphrase, width="100%"),
rx.button("Save Changes", on_click=AppState.save_config, size="3", margin_top="4"),
spacing="4",
align_items="start",
padding="6"
),
width="100%",
max_width="800px",
margin_top="4"
),
value="couchdb"
),
defaultValue="general",
width="100%"
),
width="100%"
)
)

View File

@@ -0,0 +1,26 @@
import reflex as rx
from ..state.auth import AuthState
def login_page():
return rx.center(
rx.card(
rx.vstack(
rx.heading("Login", size="7", margin_bottom="4"),
rx.text("Username", size="2", weight="bold"),
rx.input(value=AuthState.username, on_change=AuthState.set_username, width="100%"),
rx.text("Password", size="2", weight="bold"),
rx.input(type="password", value=AuthState.password, on_change=AuthState.set_password, width="100%"),
rx.button("Sign In", size="3", width="100%", on_click=AuthState.check_login, margin_top="4"),
padding="6",
spacing="3",
align_items="start"
),
width="400px",
),
height="100vh",
bg="var(--gray-1)"
)

View File

@@ -0,0 +1,44 @@
import reflex as rx
from ..state.auth import AuthState
def setup_step(title: str, children: list):
return rx.vstack(
rx.heading(title, size="6", margin_bottom="4"),
*children,
width="100%",
max_width="500px",
spacing="4",
align_items="start"
)
def setup_page():
return rx.center(
rx.card(
rx.vstack(
rx.heading("Welcome to Obsidian Automator", size="8", color="var(--accent-9)"),
rx.text("Let's secure your agent and configure your vault.", color="gray.11", margin_bottom="6"),
rx.separator(margin_bottom="6"),
rx.text("Create Admin Account", weight="bold"),
rx.input(placeholder="Username", value=AuthState.username, on_change=AuthState.set_username, width="100%"),
rx.input(placeholder="Password", type="password", value=AuthState.password, on_change=AuthState.set_password, width="100%"),
rx.input(placeholder="Confirm Password", type="password", value=AuthState.confirm_password, on_change=AuthState.set_confirm_password, width="100%"),
rx.separator(margin_y="4"),
rx.text("Agent Configuration", weight="bold"),
rx.input(placeholder="Gemini API Key", value=AuthState.setup_api_key, on_change=AuthState.set_setup_api_key, width="100%", type="password"),
rx.input(placeholder="Vault Input Path", value=AuthState.setup_input_folder, on_change=AuthState.set_setup_input_folder, width="100%"),
rx.text_area(placeholder="Vault Philosophy (e.g. Zettelkasten rules)", value=AuthState.setup_philosophy, on_change=AuthState.set_setup_philosophy, width="100%", height="100px"),
rx.button("Complete Setup", size="4", width="100%", on_click=AuthState.create_admin_account, margin_top="4"),
padding="8",
),
size="4",
width="600px"
),
height="100vh",
bg="var(--gray-2)"
)

View File

@@ -0,0 +1,270 @@
import reflex as rx
import asyncio
import schedule
import os
from sqlmodel import select
from ..models import Configuration
from ..agent import ObsidianAgent
from ..note_server import FileSystemServer, CouchDBNoteServer
from ..couch_sync import CouchDBSync
class AppState(rx.State):
# Live Status
logs: list[str] = ["Waiting for agent..."]
is_running: bool = False
_scheduler_task: bool = False
# Configuration (Loaded from DB)
api_key: str = ""
input_folder: str = "" # Used as Prefix for CouchDB mode
philosophy: str = ""
rewrite_tag: str = ""
# CouchDB Config
couch_url: str = ""
couch_user: str = ""
couch_pass: str = ""
couch_passphrase: str = ""
couch_db: str = ""
# Subfolder Selection
available_subfolders: list[str] = []
selected_subfolder: str = ""
def _get_active_server(self):
"""Returns the appropriate NoteServer based on config."""
if self.couch_url:
return CouchDBNoteServer(self.couch_url, self.couch_user, self.couch_pass, self.couch_db)
else:
return FileSystemServer()
def load_config(self):
"""Loads configuration from the database into State."""
with rx.session() as session:
config = session.exec(select(Configuration)).first()
if config:
self.api_key = config.gemini_api_key
self.input_folder = config.input_folder
self.philosophy = config.philosophy
self.rewrite_tag = config.rewrite_tag
# CouchDB
self.couch_url = config.couchdb_url or ""
self.couch_user = config.couchdb_user or ""
self.couch_pass = config.couchdb_password or ""
self.couch_passphrase = config.couchdb_passphrase or ""
self.couch_db = config.couchdb_db_name or "obsidian_livesync"
# Auto-refresh folders on load
# Use asyncio.create_task to run async refresh if needed,
# but refresh_subfolders is sync in implementation for now (though server calls might block)
# We will call it directly but handle errors gracefully
try:
self.refresh_subfolders()
except:
pass
def refresh_subfolders(self):
"""Scans the input folder for subdirectories."""
try:
server = self._get_active_server()
# If using CouchDB, input_folder acts as a root prefix (e.g. "Inbox")
# If using FS, it's a path.
root = self.input_folder or ""
folders = server.list_subfolders(root)
self.available_subfolders = ["(All Notes)"] + sorted(folders)
if not self.selected_subfolder:
self.selected_subfolder = "(All Notes)"
except Exception as e:
print(f"Error refreshing folders: {e}")
self.available_subfolders = ["(Error)"]
def save_config(self):
"""Persists current state config back to DB."""
with rx.session() as session:
config = session.exec(select(Configuration)).first()
if not config:
config = Configuration()
session.add(config)
config.gemini_api_key = self.api_key
config.input_folder = self.input_folder
config.philosophy = self.philosophy
config.rewrite_tag = self.rewrite_tag
# CouchDB
config.couchdb_url = self.couch_url
config.couchdb_user = self.couch_user
config.couchdb_password = self.couch_pass
config.couchdb_passphrase = self.couch_passphrase
config.couchdb_db_name = self.couch_db
session.add(config)
session.commit()
try:
self.refresh_subfolders()
except:
pass
return rx.window_alert("Configuration saved.")
async def run_couch_sync(self):
"""Triggers the CouchDB download."""
async with self:
if self.is_running: return
self.is_running = True
self.logs.append("--- Starting CouchDB Sync ---")
self.load_config()
if not self.couch_url:
self.logs.append("Error: CouchDB URL not configured.")
self.is_running = False
return
try:
syncer = CouchDBSync(self.couch_url, self.couch_user, self.couch_pass, self.couch_db, self.couch_passphrase)
sync_logs = await asyncio.to_thread(
syncer.fetch_notes,
self.input_folder
)
async with self:
self.logs.extend(sync_logs)
self.logs.append("--- CouchDB Sync Complete ---")
self.refresh_subfolders() # Auto-refresh after sync
except Exception as e:
async with self:
self.logs.append(f"Sync Error: {str(e)}")
finally:
async with self:
self.is_running = False
def reset_state(self):
"""Force reset running state on load."""
self.is_running = False
async def run_agent_process(self):
"""Runs the agent and provides real-time feedback."""
if self.is_running:
yield rx.window_alert("Agent is already running!")
return
self.is_running = True
self.logs.append("--- Starting AI Agent ---")
yield # Force UI update immediately
async with self:
self.load_config()
# Strict check only for API Key. Input folder is optional for CouchDB (root scan)
if not self.api_key:
self.logs.append("Error: Missing API Key.")
self.is_running = False
return
# If FS mode, input_folder is required.
if not self.couch_url and not self.input_folder:
self.logs.append("Error: Input Folder required for File System mode.")
self.is_running = False
return
# Determine target path
target_path = self.input_folder
if self.selected_subfolder and self.selected_subfolder != "(All Notes)":
target_path = os.path.join(self.input_folder, self.selected_subfolder)
self.logs.append(f"Targeting specific folder: {self.selected_subfolder}")
# Derive Vault Root (Parent of the configured Inbox)
vault_root = os.path.dirname(self.input_folder.rstrip(os.sep))
try:
# yield again to ensure user sees "Starting"
yield
# Use active server (CouchDB or FS)
server = self._get_active_server()
# For CouchDB, we don't need a vault_root calculator like FS,
# because paths are relative to the DB root already.
# But agent expects one. We can pass "" for CouchDB.
vault_root = ""
if isinstance(server, FileSystemServer):
vault_root = os.path.dirname(self.input_folder.rstrip(os.sep))
agent = ObsidianAgent(api_key=self.api_key, server=server)
# 1. Get the list of notes first
notes = await asyncio.to_thread(server.list_notes, target_path)
async with self:
self.logs.append(f"Found {len(notes)} notes to process.")
yield
if not notes:
async with self:
self.logs.append("No notes found in that scope.")
self.is_running = False
return
# 2. Process them one by one in the thread, but updating state in between
for i, note_path in enumerate(notes):
# Rate Limit Sleep (except for the first one)
if i > 0:
async with self:
self.logs.append(" ...Waiting 15s (Free Tier Rate Limit)...")
yield
await asyncio.sleep(15)
filename = os.path.basename(note_path)
content = server.read_note(note_path)
preview = (content[:50].replace("\n", " ") + "...") if content else "[EMPTY NOTE]"
async with self:
self.logs.append(f"[{i+1}/{len(notes)}] Analyzing: {filename}")
self.logs.append(f" Context: \"{preview}\"")
yield # Push update to UI
# Process single note in thread
result = await asyncio.to_thread(
agent._process_single_note_with_retry,
note_path,
content,
self.philosophy,
self.rewrite_tag,
vault_root
)
async with self:
self.logs.append(f" -> {result}")
yield # Push result
async with self:
self.logs.append("--- All Notes Processed ---")
except Exception as e:
async with self:
self.logs.append(f"Critical Error: {str(e)}")
finally:
async with self:
self.is_running = False
yield
async def start_scheduler(self):
async with self:
if self._scheduler_task: return
self._scheduler_task = True
self.logs.append("Scheduler active (Daily @ 02:00).")
asyncio.create_task(self._run_scheduler_loop())
async def _run_scheduler_loop(self):
def job():
# In a real deployment, we'd need a way to trigger the async task from here
# For now, we print to stdout which will show in the server logs
print("Scheduler Triggered")
schedule.every().day.at("02:00").do(job)
while True:
schedule.run_pending()
await asyncio.sleep(60)

View File

@@ -0,0 +1,69 @@
import reflex as rx
from passlib.context import CryptContext
from ..models import User, Configuration
from sqlmodel import select
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
class AuthState(rx.State):
username: str = ""
password: str = ""
confirm_password: str = ""
is_logged_in: bool = False
# Wizard Config Fields
setup_api_key: str = ""
setup_input_folder: str = ""
setup_philosophy: str = ""
def check_login(self):
"""Validates credentials."""
with rx.session() as session:
user = session.exec(select(User).where(User.username == self.username)).first()
if user and pwd_context.verify(self.password, user.password_hash):
self.is_logged_in = True
return rx.redirect("/")
return rx.window_alert("Invalid credentials.")
def logout(self):
self.is_logged_in = False
self.username = ""
self.password = ""
return rx.redirect("/login")
def create_admin_account(self):
"""Creates the initial admin account during wizard setup."""
if self.password != self.confirm_password:
return rx.window_alert("Passwords do not match.")
if len(self.password) < 8:
return rx.window_alert("Password must be at least 8 characters.")
hashed = pwd_context.hash(self.password)
user = User(username=self.username, password_hash=hashed)
# Also save the initial config
config = Configuration(
gemini_api_key=self.setup_api_key,
input_folder=self.setup_input_folder,
philosophy=self.setup_philosophy,
is_configured=True
)
with rx.session() as session:
session.add(user)
session.add(config)
session.commit()
self.is_logged_in = True
return rx.redirect("/")
def on_load_check(self):
"""Redirects to setup if no user exists, or login if not authenticated."""
with rx.session() as session:
user = session.exec(select(User)).first()
if not user:
return rx.redirect("/setup")
elif not self.is_logged_in:
return rx.redirect("/login")

16
pyproject.toml Normal file
View File

@@ -0,0 +1,16 @@
[project]
name = "obsidian-automator"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.14"
dependencies = [
"argon2-cffi>=25.1.0",
"bcrypt>=5.0.0",
"couchdb>=1.2",
"google-genai>=1.56.0",
"passlib>=1.7.4",
"reflex>=0.8.24.post1",
"requests>=2.32.5",
"schedule>=1.2.2",
]

5
rxconfig.py Normal file
View File

@@ -0,0 +1,5 @@
import reflex as rx
config = rx.Config(
app_name="obsidian_automator",
)

191
sync_daemon.py Executable file
View File

@@ -0,0 +1,191 @@
#!/usr/bin/env python3
"""
CouchDB to Filesystem Sync Daemon
Monitors CouchDB changes and writes updated files to disk in real-time.
"""
import couchdb
import os
import time
import json
from urllib.parse import quote
from pathlib import Path
# Configuration
USER = "admin"
PASSWORD = "DonCucarach0!?"
IP_ADDRESS = "100.100.112.48"
PORT = "5984"
DB_NAME = "obsidiandb"
TARGET_FOLDER = os.path.expanduser("~/ObsidianVault") # Change this to your Obsidian vault path
class CouchDBSyncDaemon:
def __init__(self, url, username, password, db_name, target_folder):
self.url = url
self.username = username
self.password = password
self.db_name = db_name
self.target_folder = target_folder
self.server = None
self.db = None
def connect(self):
"""Connect to CouchDB"""
safe_password = quote(self.password, safe="")
safe_username = quote(self.username, safe="")
full_url = f"{self.url.split('://')[0]}://{safe_username}:{safe_password}@{self.url.split('://')[1]}"
self.server = couchdb.Server(full_url)
self.db = self.server[self.db_name]
print(f"✅ Connected to CouchDB: {self.db_name}")
def get_all_docs_dict(self):
"""Fetch all documents into a dictionary for efficient lookup"""
all_docs = {}
for row in self.db.view('_all_docs', include_docs=True):
doc = row.doc
if not doc['_id'].startswith("_design"):
all_docs[doc['_id']] = doc
return all_docs
def write_file_to_disk(self, doc_id, all_docs):
"""Reconstruct and write a single file to disk"""
doc = all_docs.get(doc_id)
if not doc:
print(f"⚠️ Document {doc_id} not found")
return False
# Check if this is a file metadata document
if "children" not in doc or not isinstance(doc.get("children"), list):
# This might be a chunk or other document type - skip
return False
relative_path = doc.get("path", doc_id)
safe_path = relative_path.replace(":", "-").replace("|", "-")
full_local_path = os.path.join(self.target_folder, safe_path)
# Reconstruct content from chunks
full_content = []
is_encrypted = False
for chunk_id in doc["children"]:
chunk = all_docs.get(chunk_id)
if not chunk:
print(f"⚠️ Chunk {chunk_id} not found for {relative_path}")
continue
chunk_data = chunk.get("data") or chunk.get("content") or ""
# Check if encrypted
if str(chunk_data).startswith("%") or chunk.get("e_"):
is_encrypted = True
break
full_content.append(str(chunk_data))
if is_encrypted:
print(f"🔒 Skipping encrypted file: {relative_path}")
return False
note_text = "".join(full_content)
# Create parent directory if needed
os.makedirs(os.path.dirname(full_local_path), exist_ok=True)
# Write file
with open(full_local_path, "w", encoding="utf-8") as f:
f.write(note_text)
print(f"📝 Wrote: {relative_path}")
return True
def handle_change(self, change, all_docs):
"""Handle a single change notification"""
doc_id = change['id']
is_deleted = change.get('deleted', False)
if is_deleted:
print(f"🗑️ Deleted: {doc_id}")
# TODO: Could implement file deletion here
return
# Try to write the file
self.write_file_to_disk(doc_id, all_docs)
def initial_sync(self):
"""Perform initial full sync"""
print("🔄 Starting initial sync...")
all_docs = self.get_all_docs_dict()
count = 0
for doc_id, doc in all_docs.items():
if "children" in doc and isinstance(doc.get("children"), list):
if self.write_file_to_disk(doc_id, all_docs):
count += 1
if count % 10 == 0:
print(f" Synced {count} files...")
print(f"✅ Initial sync complete: {count} files")
return count
def watch_changes(self):
"""Watch for changes and sync in real-time"""
print("👀 Watching for changes...")
# Get current sequence
since = self.db.info()['update_seq']
while True:
try:
# Use longpoll to wait for changes
changes = self.db.changes(since=since, feed='longpoll', timeout=30000, include_docs=True)
since = changes['last_seq']
if changes['results']:
# Fetch all docs for chunk lookup
all_docs = self.get_all_docs_dict()
for change in changes['results']:
self.handle_change(change, all_docs)
time.sleep(0.5)
except KeyboardInterrupt:
print("\n⛔ Stopping sync daemon...")
break
except Exception as e:
print(f"❌ Error: {e}")
print(" Retrying in 5 seconds...")
time.sleep(5)
try:
self.connect() # Reconnect
except Exception as reconnect_error:
print(f"❌ Reconnection failed: {reconnect_error}")
def main():
# Ensure target folder exists
os.makedirs(TARGET_FOLDER, exist_ok=True)
print("=" * 60)
print("CouchDB → Filesystem Sync Daemon")
print("=" * 60)
print(f"Database: {DB_NAME}")
print(f"Target: {TARGET_FOLDER}")
print("=" * 60)
# Build URL
url = f"http://{IP_ADDRESS}:{PORT}/"
# Create daemon
daemon = CouchDBSyncDaemon(url, USER, PASSWORD, DB_NAME, TARGET_FOLDER)
# Connect
daemon.connect()
# Initial sync
daemon.initial_sync()
# Watch for changes
daemon.watch_changes()
if __name__ == "__main__":
main()

71
test_connection.py Executable file
View File

@@ -0,0 +1,71 @@
#!/usr/bin/env python3
"""
Quick test to verify CouchDB connection and show available files
"""
import couchdb
from urllib.parse import quote
# Configuration
USER = "admin"
PASSWORD = "DonCucarach0!?"
IP_ADDRESS = "100.100.112.48"
PORT = "5984"
DB_NAME = "obsidiandb"
def test_connection():
print("Testing CouchDB Connection...")
print(f"URL: http://{IP_ADDRESS}:{PORT}/")
print(f"Database: {DB_NAME}")
print("-" * 60)
try:
# Build URL with credentials
safe_password = quote(PASSWORD, safe="")
safe_username = quote(USER, safe="")
url = f"http://{safe_username}:{safe_password}@{IP_ADDRESS}:{PORT}/"
# Connect
server = couchdb.Server(url)
print(f"✅ Server Version: {server.version()}")
# List databases
dbs = list(server)
print(f"✅ Available Databases: {dbs}")
# Check if our database exists
if DB_NAME not in dbs:
print(f"❌ Database '{DB_NAME}' not found!")
return False
# Connect to database
db = server[DB_NAME]
info = db.info()
print(f"✅ Database '{DB_NAME}' found")
print(f" - Document Count: {info['doc_count']}")
print(f" - Update Sequence: {info['update_seq']}")
# List some files
print("\n📁 Sample Files:")
count = 0
for row in db.view('_all_docs', include_docs=True, limit=10):
doc = row.doc
if "children" in doc and isinstance(doc.get("children"), list):
path = doc.get("path", doc["_id"])
num_chunks = len(doc["children"])
print(f" - {path} ({num_chunks} chunks)")
count += 1
if count == 0:
print(" ⚠️ No file metadata documents found (showing first 10 docs)")
for row in db.view('_all_docs', limit=10):
print(f" - {row.id}")
print("\n✅ Connection test successful!")
return True
except Exception as e:
print(f"❌ Connection failed: {e}")
return False
if __name__ == "__main__":
test_connection()

64
test_couch.py Normal file
View File

@@ -0,0 +1,64 @@
import couchdb
from urllib.parse import quote
# Configuration
user = "admin"
password = "DonCucarach0!?"
# Note: Removed /_utils/ as that is the UI path, not the API root
base_host = "diego-server.tailc07c18.ts.net"
db_name = "obsidiandb"
# Safe URL Construction
safe_password = quote(password, safe="")
# Assuming HTTPS since it's a tailscale domain
url = f"https://{user}:{safe_password}@{base_host}/"
print(f"Attempting connection to: https://{base_host}/")
print(f"Target Database: {db_name}")
try:
# 1. Connect
server = couchdb.Server(url)
print(f"✅ Server Connected! Version: {server.version()}")
# 2. Check Database
if db_name in server:
db = server[db_name]
print(f"✅ Database '{db_name}' found.")
print(f"📊 Total Documents: {len(db)}")
# 3. Peek at Data
print("\n--- Sample Notes (First 5) ---")
count = 0
for doc_id in db:
# Skip internal CouchDB design documents
if doc_id.startswith("_design"):
continue
if count >= 5: break
doc = db[doc_id]
print(f"📄 ID: {doc_id}")
# Check for content fields common in Obsidian LiveSync
content = doc.get("data") or doc.get("content")
if content:
preview = str(content)[:100].replace("\n", " ")
print(f" Content: {preview}...")
else:
print(" [No 'data' or 'content' field found - might be a metadata chunk]")
print("-" * 30)
count += 1
if count == 0:
print("Database is empty or only contains design docs.")
else:
print(f"❌ Database '{db_name}' NOT found.")
print("Available Databases:", list(server))
except Exception as e:
print(f"\n❌ Connection Failed: {e}")
print("Tip: Ensure you are connected to Tailscale and the URL includes https://")

43
test_couch_debug.py Normal file
View File

@@ -0,0 +1,43 @@
import requests
from requests.auth import HTTPBasicAuth
# Configuration
user = "admin"
password = "DonCucarach0?"
ip_address = "100.100.112.48"
port = "5984"
url = f"http://{ip_address}:{port}/"
print(f"--- CouchDB Debug Tool ---")
print(f"Target: {url}")
try:
# 1. Test Network Connectivity (No Auth)
print("\n1. Testing basic connectivity...")
resp = requests.get(url, timeout=5)
print(f" Success! Server responded: {resp.json().get('couchdb')}")
except Exception as e:
print(f" Network Failed: {e}")
try:
# 2. Test Authentication
print("\n2. Testing Authentication (Basic Auth)...")
resp = requests.get(url + "_session", auth=HTTPBasicAuth(user, password), timeout=5)
if resp.status_code == 200:
print(f" ✅ Auth Success! Session info: {resp.json().get('userCtx')}")
else:
print(f" ❌ Auth Failed (Status {resp.status_code}): {resp.text}")
except Exception as e:
print(f" Request Error: {e}")
try:
# 3. List Databases (Authenticated)
print("\n3. Listing Databases...")
resp = requests.get(url + "_all_dbs", auth=HTTPBasicAuth(user, password), timeout=5)
if resp.status_code == 200:
print(f" ✅ Found Databases: {resp.json()}")
else:
print(f" ❌ Failed to list DBs: {resp.text}")
except Exception as e:
print(f" Request Error: {e}")

47
test_couch_ip.py Normal file
View File

@@ -0,0 +1,47 @@
import couchdb
from urllib.parse import quote
# Configuration
user = "admin"
password = "DonCucarach0!?"
ip_address = "100.100.112.48"
port = "5984"
db_name = "obsidiandb"
# Safe URL Construction
safe_password = quote(password, safe="")
# Using HTTP as requested for the IP address
url = f"http://{user}:{safe_password}@{ip_address}:{port}/"
print(f"Attempting connection to: http://{ip_address}:{port}/")
print(f"Target Database: {db_name}")
try:
# 1. Connect
server = couchdb.Server(url)
# Testing authentication by getting server info
version = server.version()
print(f"✅ Server Connected! Version: {version}")
# 2. Check Database
if db_name in server:
db = server[db_name]
print(f"✅ Database '{db_name}' found.")
print(f"📊 Total Documents: {len(db)}")
# 3. List some docs
docs = [id for id in db if not id.startswith("_design")]
print(f"Found {len(docs)} non-design documents.")
if docs:
print("First doc ID:", docs[0])
else:
print(f"❌ Database '{db_name}' NOT found.")
print("Available Databases:", list(server))
except Exception as e:
print(f"\n❌ Connection Failed: {e}")
print("\nTroubleshooting tips:")
print("1. Verify Tailscale is active and pinging 100.100.112.48 works.")
print("2. Check if CouchDB is configured to allow 'admin' login via HTTP.")
print("3. Ensure the password 'DonCucarach0?' is exactly correct.")

953
uv.lock generated Normal file
View File

@@ -0,0 +1,953 @@
version = 1
revision = 3
requires-python = ">=3.14"
[[package]]
name = "alembic"
version = "1.17.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mako" },
{ name = "sqlalchemy" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/02/a6/74c8cadc2882977d80ad756a13857857dbcf9bd405bc80b662eb10651282/alembic-1.17.2.tar.gz", hash = "sha256:bbe9751705c5e0f14877f02d46c53d10885e377e3d90eda810a016f9baa19e8e", size = 1988064, upload-time = "2025-11-14T20:35:04.057Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ba/88/6237e97e3385b57b5f1528647addea5cc03d4d65d5979ab24327d41fb00d/alembic-1.17.2-py3-none-any.whl", hash = "sha256:f483dd1fe93f6c5d49217055e4d15b905b425b6af906746abb35b69c1996c4e6", size = 248554, upload-time = "2025-11-14T20:35:05.699Z" },
]
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
]
[[package]]
name = "anyio"
version = "4.12.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload-time = "2025-11-28T23:37:38.911Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" },
]
[[package]]
name = "argon2-cffi"
version = "25.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "argon2-cffi-bindings" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" },
]
[[package]]
name = "argon2-cffi-bindings"
version = "25.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" },
{ url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" },
{ url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" },
{ url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" },
{ url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" },
{ url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" },
{ url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" },
{ url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" },
{ url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" },
{ url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" },
{ url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" },
{ url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" },
{ url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" },
{ url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" },
{ url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" },
{ url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" },
{ url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" },
{ url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" },
{ url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" },
{ url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" },
]
[[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/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" },
]
[[package]]
name = "bidict"
version = "0.23.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/9a/6e/026678aa5a830e07cd9498a05d3e7e650a4f56a42f267a53d22bcda1bdc9/bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71", size = 29093, upload-time = "2024-02-18T19:09:05.748Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764, upload-time = "2024-02-18T19:09:04.156Z" },
]
[[package]]
name = "cachetools"
version = "6.2.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/bc/1d/ede8680603f6016887c062a2cf4fc8fdba905866a3ab8831aa8aa651320c/cachetools-6.2.4.tar.gz", hash = "sha256:82c5c05585e70b6ba2d3ae09ea60b79548872185d2f24ae1f2709d37299fd607", size = 31731, upload-time = "2025-12-15T18:24:53.744Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/fc/1d7b80d0eb7b714984ce40efc78859c022cd930e402f599d8ca9e39c78a4/cachetools-6.2.4-py3-none-any.whl", hash = "sha256:69a7a52634fed8b8bf6e24a050fb60bff1c9bd8f6d24572b99c32d4e71e62a51", size = 11551, upload-time = "2025-12-15T18:24:52.332Z" },
]
[[package]]
name = "certifi"
version = "2025.11.12"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" },
]
[[package]]
name = "cffi"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
{ url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
{ url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
{ url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
{ url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
{ url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
{ url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
{ url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
{ url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
{ url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
{ url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
{ url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
{ url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
{ url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
{ url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
{ url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
{ url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
{ url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
{ url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
{ url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
{ url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
{ url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
]
[[package]]
name = "charset-normalizer"
version = "3.4.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
{ url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
{ url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
{ url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
{ url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
{ url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
{ url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
{ url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
{ url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
{ url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
{ url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
{ url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
{ url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
{ url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
{ url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
{ url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
{ url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
]
[[package]]
name = "click"
version = "8.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "couchdb"
version = "1.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7c/c8/f94a107eca0c178e5d74c705dad1a5205c0f580840bd1b155cd8a258cb7c/CouchDB-1.2.tar.gz", hash = "sha256:1386a1a43f25bed3667e3b805222054940d674fa1967fa48e9d2012a18630ab7", size = 65880, upload-time = "2018-02-09T13:12:52.727Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ff/35/6660f7526c5d509b13264b27642de73754bd3d0addf56b175601c8b951e1/CouchDB-1.2-py2.py3-none-any.whl", hash = "sha256:13a28a1159c49f8346732e8724b9a4d65cba54bec017c4a7eeb1499fe88151d1", size = 67954, upload-time = "2018-02-09T13:12:50.581Z" },
]
[[package]]
name = "distro"
version = "1.9.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
]
[[package]]
name = "google-auth"
version = "2.45.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cachetools" },
{ name = "pyasn1-modules" },
{ name = "rsa" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e5/00/3c794502a8b892c404b2dea5b3650eb21bfc7069612fbfd15c7f17c1cb0d/google_auth-2.45.0.tar.gz", hash = "sha256:90d3f41b6b72ea72dd9811e765699ee491ab24139f34ebf1ca2b9cc0c38708f3", size = 320708, upload-time = "2025-12-15T22:58:42.889Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c6/97/451d55e05487a5cd6279a01a7e34921858b16f7dc8aa38a2c684743cd2b3/google_auth-2.45.0-py2.py3-none-any.whl", hash = "sha256:82344e86dc00410ef5382d99be677c6043d72e502b625aa4f4afa0bdacca0f36", size = 233312, upload-time = "2025-12-15T22:58:40.777Z" },
]
[package.optional-dependencies]
requests = [
{ name = "requests" },
]
[[package]]
name = "google-genai"
version = "1.56.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "distro" },
{ name = "google-auth", extra = ["requests"] },
{ name = "httpx" },
{ name = "pydantic" },
{ name = "requests" },
{ name = "sniffio" },
{ name = "tenacity" },
{ name = "typing-extensions" },
{ name = "websockets" },
]
sdist = { url = "https://files.pythonhosted.org/packages/70/ad/d3ac5a102135bd3f1e4b1475ca65d2bd4bcc22eb2e9348ac40fe3fadb1d6/google_genai-1.56.0.tar.gz", hash = "sha256:0491af33c375f099777ae207d9621f044e27091fafad4c50e617eba32165e82f", size = 340451, upload-time = "2025-12-17T12:35:05.412Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/84/93/94bc7a89ef4e7ed3666add55cd859d1483a22737251df659bf1aa46e9405/google_genai-1.56.0-py3-none-any.whl", hash = "sha256:9e6b11e0c105ead229368cb5849a480e4d0185519f8d9f538d61ecfcf193b052", size = 426563, upload-time = "2025-12-17T12:35:03.717Z" },
]
[[package]]
name = "granian"
version = "2.6.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ea/1e/0a33c4b68b054b9d5f7963371dd06978da5f4f58f58ddcb77854018abfdb/granian-2.6.0.tar.gz", hash = "sha256:d9b773633e411c7bf51590704e608e757dab09cd452fb18971a50a7d7c439677", size = 115955, upload-time = "2025-11-16T16:07:27.082Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bb/53/9ed1a1f710a78eaad2897b9264bb6ae1190dc251af463b87be41f1963dfe/granian-2.6.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cba1d4ac5b101c41fa916fb1ca5d5c359892b63d1470a9587055605c68850df8", size = 3072924, upload-time = "2025-11-16T16:06:43.949Z" },
{ url = "https://files.pythonhosted.org/packages/b1/58/8fa09896c88937a95b92185a1377b09f7cd1b8ac1e0f06a251e02ce96164/granian-2.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2c829ece96a49d431c01553e0c020a675624798659c74e3d800867a415376fef", size = 2800675, upload-time = "2025-11-16T16:06:45.831Z" },
{ url = "https://files.pythonhosted.org/packages/4f/53/779e15fb6372cf00d2c66f392d754e0816bf0597e8346459c22bde9de219/granian-2.6.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:637153b653a85e1bb0cba2e10e321e2cbb1af1e9abab0feafd34eb612fe3fcdd", size = 3323029, upload-time = "2025-11-16T16:06:47.682Z" },
{ url = "https://files.pythonhosted.org/packages/32/ad/3af7388f51b4df3a781ecfc6f1ec18331ec74ea413fb2c62fe24c65e7935/granian-2.6.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6c09792ca3807059ef8e58a7c7bc5620586414f03ebd4bb2e6cd044044f0165", size = 3142617, upload-time = "2025-11-16T16:06:48.964Z" },
{ url = "https://files.pythonhosted.org/packages/4c/4d/6a7766fd9fe09f3f887c2168d5607cc2eb2ee9fe5c9364a877942c05de41/granian-2.6.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79e2f68d99f454d1c51aacc86bed346693c074f27c51fb19b8afe5dc375e1b70", size = 3383669, upload-time = "2025-11-16T16:06:50.363Z" },
{ url = "https://files.pythonhosted.org/packages/b8/1c/b4bbdcd6bbe9c3290a2ac76eac3ae8916fdb38269f9f981e5b933ff02664/granian-2.6.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:995f7b496b16484c97e8da9f44ead66307d6f532135a9890b0d27c88b8232df3", size = 3233040, upload-time = "2025-11-16T16:06:52.787Z" },
{ url = "https://files.pythonhosted.org/packages/75/26/ca7afabab2b31101eabc78df26772bd679e0a2bc879c58e8fcbb9732d57e/granian-2.6.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:6d5db194527d52183b2dc17be9d68c59647bc66459c01a960407d446aa686c98", size = 3302090, upload-time = "2025-11-16T16:06:54.112Z" },
{ url = "https://files.pythonhosted.org/packages/e8/3b/3e6992ac60f8d2e7f6eb5ae7845ba8f77d9373379e7d8ec7dbdfac89c00b/granian-2.6.0-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:c9fd40f3db242eece303ab4e9da4c7c398c865d628d58ff747680c54775ea9e4", size = 3469619, upload-time = "2025-11-16T16:06:55.488Z" },
{ url = "https://files.pythonhosted.org/packages/5d/96/8e78858630d7ca51751502c323f22841a56847db827a73d946a9303108c1/granian-2.6.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:99dfa23b29e6a4f8cc2ec9b55946746f94ce305e474defef5c3c0e496471821e", size = 3479330, upload-time = "2025-11-16T16:06:56.989Z" },
{ url = "https://files.pythonhosted.org/packages/41/c1/2ea5fa5e28a9b400549494ce091dfb1a6122893b318c3fbc4cf7693cc398/granian-2.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:22138a906503cb62cc25e26baa6d9e424ae26f9b46d7f852126d0a92fd9b3194", size = 2345011, upload-time = "2025-11-16T16:06:58.745Z" },
{ url = "https://files.pythonhosted.org/packages/5a/5c/5770f1270c2e59b7d27e25792ed62f3164b8b962ccf19b4a351429fd34fe/granian-2.6.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:a8b356e8d96481c0fa67c2646a516b1f8985171995c0a40c5548352b75cae513", size = 3026090, upload-time = "2025-11-16T16:07:00.105Z" },
{ url = "https://files.pythonhosted.org/packages/28/89/85b40c55ddd270a31e047b368b4d82f32c0f6388511a0affcf6c8459821b/granian-2.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f026d7a2f1395b02cba2b59613edfd463d9ef43aae33b3c5e41f2ac8d0752507", size = 2752890, upload-time = "2025-11-16T16:07:01.507Z" },
{ url = "https://files.pythonhosted.org/packages/bf/4e/369700caefaad0526fc36d43510e9274f430a5bdeea54b97f907e2dd387d/granian-2.6.0-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:15c888605406c9b29b7da8e3afa0ce31dabad7d446cf42a2033d1f184e280ef3", size = 2965483, upload-time = "2025-11-16T16:07:02.836Z" },
{ url = "https://files.pythonhosted.org/packages/75/47/d6d95615b94a8bac94efca7a634cb3160fb7cd3235039e4d1708e0399453/granian-2.6.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d9ce9ff8d4d9da73eb2e90c72eae986f823ab46b2c8c7ee091ec06e3c835a94e", size = 3313071, upload-time = "2025-11-16T16:07:04.128Z" },
{ url = "https://files.pythonhosted.org/packages/6a/76/f9098765797adfc142d503ee8a18fe137324558a028db6322753d88305d9/granian-2.6.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:681f44fa950b50721250536477b068315322c447f60b6a7018a9d61385202d67", size = 3271503, upload-time = "2025-11-16T16:07:05.482Z" },
{ url = "https://files.pythonhosted.org/packages/7f/f9/55be32f079af772054284aa917eb7bd77f1f8ba840f0773db9ac47279149/granian-2.6.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:cf23f25826e7c87c2cd9d984a358c14106d589febcd71af0f5392bb65fafb07a", size = 3106398, upload-time = "2025-11-16T16:07:07.396Z" },
{ url = "https://files.pythonhosted.org/packages/79/ab/e63f54a8432b2b877d83c5f2921a54791a420685002854dc7005bbd48817/granian-2.6.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:559985641cc1f0497f2c35d75126214f9cf9286ec6cea083fb1d0324712dbc47", size = 3296156, upload-time = "2025-11-16T16:07:08.858Z" },
{ url = "https://files.pythonhosted.org/packages/ef/3c/a37c038be10441a27cfde65a64c4406556ee64ab5deba4a782eaaa5ce7cf/granian-2.6.0-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:98718c08713d4afdf0e444f6236eeac6d01fdf08d0587f3c15da37fd12ee03f6", size = 3460301, upload-time = "2025-11-16T16:07:10.476Z" },
{ url = "https://files.pythonhosted.org/packages/44/05/bcc03661028df91808440f24ae9923cda4fc53938c6bb85a87e3d47540a5/granian-2.6.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:c115726904be2186b1e621a2b4a292f8d0ccc4b0f41ac89dcbe4b50cbaa67414", size = 3474889, upload-time = "2025-11-16T16:07:11.873Z" },
{ url = "https://files.pythonhosted.org/packages/fa/e2/69a3263a7415993c46561521de93213d2c7e04f2675b627a14c6dd69334c/granian-2.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5bf42d8b4240f95a0edd227175161c0c93d465d6b8bb23abd65c2b82c37cfc44", size = 2344006, upload-time = "2025-11-16T16:07:13.314Z" },
]
[package.optional-dependencies]
reload = [
{ name = "watchfiles" },
]
[[package]]
name = "greenlet"
version = "3.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload-time = "2025-12-04T14:49:44.05Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d7/7c/f0a6d0ede2c7bf092d00bc83ad5bafb7e6ec9b4aab2fbdfa6f134dc73327/greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:60c2ef0f578afb3c8d92ea07ad327f9a062547137afe91f38408f08aacab667f", size = 275671, upload-time = "2025-12-04T14:23:05.267Z" },
{ url = "https://files.pythonhosted.org/packages/44/06/dac639ae1a50f5969d82d2e3dd9767d30d6dbdbab0e1a54010c8fe90263c/greenlet-3.3.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5d554d0712ba1de0a6c94c640f7aeba3f85b3a6e1f2899c11c2c0428da9365", size = 646360, upload-time = "2025-12-04T14:50:10.026Z" },
{ url = "https://files.pythonhosted.org/packages/e0/94/0fb76fe6c5369fba9bf98529ada6f4c3a1adf19e406a47332245ef0eb357/greenlet-3.3.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a898b1e9c5f7307ebbde4102908e6cbfcb9ea16284a3abe15cab996bee8b9b3", size = 658160, upload-time = "2025-12-04T14:57:45.41Z" },
{ url = "https://files.pythonhosted.org/packages/93/79/d2c70cae6e823fac36c3bbc9077962105052b7ef81db2f01ec3b9bf17e2b/greenlet-3.3.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dcd2bdbd444ff340e8d6bdf54d2f206ccddbb3ccfdcd3c25bf4afaa7b8f0cf45", size = 671388, upload-time = "2025-12-04T15:07:15.789Z" },
{ url = "https://files.pythonhosted.org/packages/b8/14/bab308fc2c1b5228c3224ec2bf928ce2e4d21d8046c161e44a2012b5203e/greenlet-3.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5773edda4dc00e173820722711d043799d3adb4f01731f40619e07ea2750b955", size = 660166, upload-time = "2025-12-04T14:26:05.099Z" },
{ url = "https://files.pythonhosted.org/packages/4b/d2/91465d39164eaa0085177f61983d80ffe746c5a1860f009811d498e7259c/greenlet-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac0549373982b36d5fd5d30beb8a7a33ee541ff98d2b502714a09f1169f31b55", size = 1615193, upload-time = "2025-12-04T15:04:27.041Z" },
{ url = "https://files.pythonhosted.org/packages/42/1b/83d110a37044b92423084d52d5d5a3b3a73cafb51b547e6d7366ff62eff1/greenlet-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d198d2d977460358c3b3a4dc844f875d1adb33817f0613f663a656f463764ccc", size = 1683653, upload-time = "2025-12-04T14:27:32.366Z" },
{ url = "https://files.pythonhosted.org/packages/7c/9a/9030e6f9aa8fd7808e9c31ba4c38f87c4f8ec324ee67431d181fe396d705/greenlet-3.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:73f51dd0e0bdb596fb0417e475fa3c5e32d4c83638296e560086b8d7da7c4170", size = 305387, upload-time = "2025-12-04T14:26:51.063Z" },
{ url = "https://files.pythonhosted.org/packages/a0/66/bd6317bc5932accf351fc19f177ffba53712a202f9df10587da8df257c7e/greenlet-3.3.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d6ed6f85fae6cdfdb9ce04c9bf7a08d666cfcfb914e7d006f44f840b46741931", size = 282638, upload-time = "2025-12-04T14:25:20.941Z" },
{ url = "https://files.pythonhosted.org/packages/30/cf/cc81cb030b40e738d6e69502ccbd0dd1bced0588e958f9e757945de24404/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9125050fcf24554e69c4cacb086b87b3b55dc395a8b3ebe6487b045b2614388", size = 651145, upload-time = "2025-12-04T14:50:11.039Z" },
{ url = "https://files.pythonhosted.org/packages/9c/ea/1020037b5ecfe95ca7df8d8549959baceb8186031da83d5ecceff8b08cd2/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:87e63ccfa13c0a0f6234ed0add552af24cc67dd886731f2261e46e241608bee3", size = 654236, upload-time = "2025-12-04T14:57:47.007Z" },
{ url = "https://files.pythonhosted.org/packages/69/cc/1e4bae2e45ca2fa55299f4e85854606a78ecc37fead20d69322f96000504/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2662433acbca297c9153a4023fe2161c8dcfdcc91f10433171cf7e7d94ba2221", size = 662506, upload-time = "2025-12-04T15:07:16.906Z" },
{ url = "https://files.pythonhosted.org/packages/57/b9/f8025d71a6085c441a7eaff0fd928bbb275a6633773667023d19179fe815/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c6e9b9c1527a78520357de498b0e709fb9e2f49c3a513afd5a249007261911b", size = 653783, upload-time = "2025-12-04T14:26:06.225Z" },
{ url = "https://files.pythonhosted.org/packages/f6/c7/876a8c7a7485d5d6b5c6821201d542ef28be645aa024cfe1145b35c120c1/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:286d093f95ec98fdd92fcb955003b8a3d054b4e2cab3e2707a5039e7b50520fd", size = 1614857, upload-time = "2025-12-04T15:04:28.484Z" },
{ url = "https://files.pythonhosted.org/packages/4f/dc/041be1dff9f23dac5f48a43323cd0789cb798342011c19a248d9c9335536/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c10513330af5b8ae16f023e8ddbfb486ab355d04467c4679c5cfe4659975dd9", size = 1676034, upload-time = "2025-12-04T14:27:33.531Z" },
]
[[package]]
name = "h11"
version = "0.16.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
]
[[package]]
name = "httpcore"
version = "1.0.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
]
[[package]]
name = "httpx"
version = "0.28.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "certifi" },
{ name = "httpcore" },
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
]
[[package]]
name = "idna"
version = "3.11"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
]
[[package]]
name = "mako"
version = "1.3.10"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markupsafe" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" },
]
[[package]]
name = "markdown-it-py"
version = "4.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mdurl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
]
[[package]]
name = "markupsafe"
version = "3.0.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
{ url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
{ url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
{ url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
{ url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
{ url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
{ url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
{ url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
{ url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
{ url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
{ url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
{ url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
{ url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
{ url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
{ url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
{ url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
{ url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
{ url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
{ url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
{ url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
{ url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
]
[[package]]
name = "mdurl"
version = "0.1.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
]
[[package]]
name = "obsidian-automator"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "argon2-cffi" },
{ name = "bcrypt" },
{ name = "couchdb" },
{ name = "google-genai" },
{ name = "passlib" },
{ name = "reflex" },
{ name = "requests" },
{ name = "schedule" },
]
[package.metadata]
requires-dist = [
{ name = "argon2-cffi", specifier = ">=25.1.0" },
{ name = "bcrypt", specifier = ">=5.0.0" },
{ name = "couchdb", specifier = ">=1.2" },
{ name = "google-genai", specifier = ">=1.56.0" },
{ name = "passlib", specifier = ">=1.7.4" },
{ name = "reflex", specifier = ">=0.8.24.post1" },
{ name = "requests", specifier = ">=2.32.5" },
{ name = "schedule", specifier = ">=1.2.2" },
]
[[package]]
name = "packaging"
version = "25.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
]
[[package]]
name = "passlib"
version = "1.7.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b6/06/9da9ee59a67fae7761aab3ccc84fa4f3f33f125b370f1ccdb915bf967c11/passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04", size = 689844, upload-time = "2020-10-08T19:00:52.121Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/a4/ab6b7589382ca3df236e03faa71deac88cae040af60c071a78d254a62172/passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1", size = 525554, upload-time = "2020-10-08T19:00:49.856Z" },
]
[[package]]
name = "platformdirs"
version = "4.5.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" },
]
[[package]]
name = "psutil"
version = "7.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/be/7c/31d1c3ceb1260301f87565f50689dc6da3db427ece1e1e012af22abca54e/psutil-7.2.0.tar.gz", hash = "sha256:2e4f8e1552f77d14dc96fb0f6240c5b34a37081c0889f0853b3b29a496e5ef64", size = 489863, upload-time = "2025-12-23T20:26:24.616Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/28/d2feadc7f18e501c5ce687c377db7dca924585418fd694272b8e488ea99f/psutil-7.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:45f6b91f7ad63414d6454fd609e5e3556d0e1038d5d9c75a1368513bdf763f57", size = 140372, upload-time = "2025-12-23T20:26:49.334Z" },
{ url = "https://files.pythonhosted.org/packages/b2/1d/48381f5fd0425aa054c4ee3de24f50de3d6c347019f3aec75f357377d447/psutil-7.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87b18a19574139d60a546e88b5f5b9cbad598e26cdc790d204ab95d7024f03ee", size = 135400, upload-time = "2025-12-23T20:26:51.585Z" },
{ url = "https://files.pythonhosted.org/packages/01/a2/5b39e08bd9b27476bc7cce7e21c71a481ad60b81ffac49baf02687a50d7f/psutil-7.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:abdb74137ca232d20250e9ad471f58d500e7743bc8253ba0bfbf26e570c0e437", size = 136910, upload-time = "2025-12-23T20:27:05.289Z" },
{ url = "https://files.pythonhosted.org/packages/59/54/53839db1258c1eaeb4ded57ff202144ebc75b23facc05a74fd98d338b0c6/psutil-7.2.0-cp37-abi3-win_arm64.whl", hash = "sha256:284e71038b3139e7ab3834b63b3eb5aa5565fcd61a681ec746ef9a0a8c457fd2", size = 133807, upload-time = "2025-12-23T20:27:06.825Z" },
]
[[package]]
name = "pyasn1"
version = "0.6.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" },
]
[[package]]
name = "pyasn1-modules"
version = "0.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyasn1" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" },
]
[[package]]
name = "pycparser"
version = "2.23"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" },
]
[[package]]
name = "pydantic"
version = "2.12.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" },
]
[[package]]
name = "pydantic-core"
version = "2.41.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" },
{ url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" },
{ url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" },
{ url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" },
{ url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" },
{ url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" },
{ url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" },
{ url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" },
{ url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" },
{ url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" },
{ url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" },
{ url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" },
{ url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" },
{ url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" },
{ url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" },
{ url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" },
{ url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" },
{ url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" },
{ url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" },
{ url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" },
{ url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" },
{ url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" },
{ url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" },
{ url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" },
{ url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" },
{ url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" },
{ url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" },
{ url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
]
[[package]]
name = "pygments"
version = "2.19.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
]
[[package]]
name = "python-engineio"
version = "4.13.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "simple-websocket" },
]
sdist = { url = "https://files.pythonhosted.org/packages/42/5a/349caac055e03ef9e56ed29fa304846063b1771ee54ab8132bf98b29491e/python_engineio-4.13.0.tar.gz", hash = "sha256:f9c51a8754d2742ba832c24b46ed425fdd3064356914edd5a1e8ffde76ab7709", size = 92194, upload-time = "2025-12-24T22:38:05.111Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/50/74/c655a6eda0fd188d490c14142a0f0380655ac7099604e1fbf8fa1a97f0a1/python_engineio-4.13.0-py3-none-any.whl", hash = "sha256:57b94eac094fa07b050c6da59f48b12250ab1cd920765f4849963e3d89ad9de3", size = 59676, upload-time = "2025-12-24T22:38:03.56Z" },
]
[[package]]
name = "python-multipart"
version = "0.0.21"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/78/96/804520d0850c7db98e5ccb70282e29208723f0964e88ffd9d0da2f52ea09/python_multipart-0.0.21.tar.gz", hash = "sha256:7137ebd4d3bbf70ea1622998f902b97a29434a9e8dc40eb203bbcf7c2a2cba92", size = 37196, upload-time = "2025-12-17T09:24:22.446Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/aa/76/03af049af4dcee5d27442f71b6924f01f3efb5d2bd34f23fcd563f2cc5f5/python_multipart-0.0.21-py3-none-any.whl", hash = "sha256:cf7a6713e01c87aa35387f4774e812c4361150938d20d232800f75ffcf266090", size = 24541, upload-time = "2025-12-17T09:24:21.153Z" },
]
[[package]]
name = "python-socketio"
version = "5.16.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "bidict" },
{ name = "python-engineio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b8/55/5d8af5884283b58e4405580bcd84af1d898c457173c708736e065f10ca4a/python_socketio-5.16.0.tar.gz", hash = "sha256:f79403c7f1ba8b84460aa8fe4c671414c8145b21a501b46b676f3740286356fd", size = 127120, upload-time = "2025-12-24T23:51:48.826Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/28/d2/2ccc2b69a187b80fda3152745670cfba936704f296a9fa54c6c8ac694d12/python_socketio-5.16.0-py3-none-any.whl", hash = "sha256:d95802961e15c7bd54ecf884c6e7644f81be8460f0a02ee66b473df58088ee8a", size = 79607, upload-time = "2025-12-24T23:51:47.2Z" },
]
[[package]]
name = "redis"
version = "7.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/43/c8/983d5c6579a411d8a99bc5823cc5712768859b5ce2c8afe1a65b37832c81/redis-7.1.0.tar.gz", hash = "sha256:b1cc3cfa5a2cb9c2ab3ba700864fb0ad75617b41f01352ce5779dabf6d5f9c3c", size = 4796669, upload-time = "2025-11-19T15:54:39.961Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/89/f0/8956f8a86b20d7bb9d6ac0187cf4cd54d8065bc9a1a09eb8011d4d326596/redis-7.1.0-py3-none-any.whl", hash = "sha256:23c52b208f92b56103e17c5d06bdc1a6c2c0b3106583985a76a18f83b265de2b", size = 354159, upload-time = "2025-11-19T15:54:38.064Z" },
]
[[package]]
name = "reflex"
version = "0.8.24.post1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "alembic" },
{ name = "click" },
{ name = "granian", extra = ["reload"] },
{ name = "httpx" },
{ name = "packaging" },
{ name = "platformdirs" },
{ name = "psutil", marker = "sys_platform == 'win32'" },
{ name = "pydantic" },
{ name = "python-multipart" },
{ name = "python-socketio" },
{ name = "redis" },
{ name = "reflex-hosting-cli" },
{ name = "rich" },
{ name = "sqlmodel" },
{ name = "starlette" },
{ name = "typing-extensions" },
{ name = "wrapt" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/f9d4880d8de98f7faaf987550384bce52871bf6dd5ae9bd3992f6120578e/reflex-0.8.24.post1.tar.gz", hash = "sha256:a016e844846ffc5ef460ed098a17bcf02b11acc9b15192fa234f29294586b080", size = 571019, upload-time = "2025-12-22T20:22:24.499Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/73/ba/2ef17f78a5fcea091fb509a550ae03a93515cd774f969ae89a8b51025e1f/reflex-0.8.24.post1-py3-none-any.whl", hash = "sha256:c1023a754d0eee1161351688dbf9e05d11f570c082859e34c03e354d324deb58", size = 813241, upload-time = "2025-12-22T20:22:26.113Z" },
]
[[package]]
name = "reflex-hosting-cli"
version = "0.1.61"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "httpx" },
{ name = "packaging" },
{ name = "platformdirs" },
{ name = "rich" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2f/8e/10d095093ac7f08f5a0c286b5be289bfee978ebe51f66884cae6df080b7c/reflex_hosting_cli-0.1.61.tar.gz", hash = "sha256:064520951559e7382777bb28bc03b95e0418fd97c0f69751a2f00920453b82d8", size = 35774, upload-time = "2025-12-23T23:50:47.94Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/96/97/2b058a398a9ebaa75b5220801c0d0c997b0e743bc3d2045de0fb19580d10/reflex_hosting_cli-0.1.61-py3-none-any.whl", hash = "sha256:e4cc91aaef63027c0ec175aeb54e6c6d25eb7b79214039d3fc2790c3fdb8c3d4", size = 45339, upload-time = "2025-12-23T23:50:46.617Z" },
]
[[package]]
name = "requests"
version = "2.32.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "charset-normalizer" },
{ name = "idna" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
]
[[package]]
name = "rich"
version = "14.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" },
]
[[package]]
name = "rsa"
version = "4.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyasn1" },
]
sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" },
]
[[package]]
name = "schedule"
version = "1.2.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/0c/91/b525790063015759f34447d4cf9d2ccb52cdee0f1dd6ff8764e863bcb74c/schedule-1.2.2.tar.gz", hash = "sha256:15fe9c75fe5fd9b9627f3f19cc0ef1420508f9f9a46f45cd0769ef75ede5f0b7", size = 26452, upload-time = "2024-06-18T20:03:14.633Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/a7/84c96b61fd13205f2cafbe263cdb2745965974bdf3e0078f121dfeca5f02/schedule-1.2.2-py3-none-any.whl", hash = "sha256:5bef4a2a0183abf44046ae0d164cadcac21b1db011bdd8102e4a0c1e91e06a7d", size = 12220, upload-time = "2024-05-25T18:41:59.121Z" },
]
[[package]]
name = "simple-websocket"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "wsproto" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b0/d4/bfa032f961103eba93de583b161f0e6a5b63cebb8f2c7d0c6e6efe1e3d2e/simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4", size = 17300, upload-time = "2024-10-10T22:39:31.412Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c", size = 13842, upload-time = "2024-10-10T22:39:29.645Z" },
]
[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
]
[[package]]
name = "sqlalchemy"
version = "2.0.45"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/be/f9/5e4491e5ccf42f5d9cfc663741d261b3e6e1683ae7812114e7636409fcc6/sqlalchemy-2.0.45.tar.gz", hash = "sha256:1632a4bda8d2d25703fdad6363058d882541bdaaee0e5e3ddfa0cd3229efce88", size = 9869912, upload-time = "2025-12-09T21:05:16.737Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/64/4e1913772646b060b025d3fc52ce91a58967fe58957df32b455de5a12b4f/sqlalchemy-2.0.45-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f46ec744e7f51275582e6a24326e10c49fbdd3fc99103e01376841213028774", size = 3272404, upload-time = "2025-12-09T22:11:09.662Z" },
{ url = "https://files.pythonhosted.org/packages/b3/27/caf606ee924282fe4747ee4fd454b335a72a6e018f97eab5ff7f28199e16/sqlalchemy-2.0.45-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:883c600c345123c033c2f6caca18def08f1f7f4c3ebeb591a63b6fceffc95cce", size = 3277057, upload-time = "2025-12-09T22:13:56.213Z" },
{ url = "https://files.pythonhosted.org/packages/85/d0/3d64218c9724e91f3d1574d12eb7ff8f19f937643815d8daf792046d88ab/sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2c0b74aa79e2deade948fe8593654c8ef4228c44ba862bb7c9585c8e0db90f33", size = 3222279, upload-time = "2025-12-09T22:11:11.1Z" },
{ url = "https://files.pythonhosted.org/packages/24/10/dd7688a81c5bc7690c2a3764d55a238c524cd1a5a19487928844cb247695/sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a420169cef179d4c9064365f42d779f1e5895ad26ca0c8b4c0233920973db74", size = 3244508, upload-time = "2025-12-09T22:13:57.932Z" },
{ url = "https://files.pythonhosted.org/packages/aa/41/db75756ca49f777e029968d9c9fee338c7907c563267740c6d310a8e3f60/sqlalchemy-2.0.45-cp314-cp314-win32.whl", hash = "sha256:e50dcb81a5dfe4b7b4a4aa8f338116d127cb209559124f3694c70d6cd072b68f", size = 2113204, upload-time = "2025-12-09T21:39:38.365Z" },
{ url = "https://files.pythonhosted.org/packages/89/a2/0e1590e9adb292b1d576dbcf67ff7df8cf55e56e78d2c927686d01080f4b/sqlalchemy-2.0.45-cp314-cp314-win_amd64.whl", hash = "sha256:4748601c8ea959e37e03d13dcda4a44837afcd1b21338e637f7c935b8da06177", size = 2138785, upload-time = "2025-12-09T21:39:39.503Z" },
{ url = "https://files.pythonhosted.org/packages/42/39/f05f0ed54d451156bbed0e23eb0516bcad7cbb9f18b3bf219c786371b3f0/sqlalchemy-2.0.45-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd337d3526ec5298f67d6a30bbbe4ed7e5e68862f0bf6dd21d289f8d37b7d60b", size = 3522029, upload-time = "2025-12-09T22:13:32.09Z" },
{ url = "https://files.pythonhosted.org/packages/54/0f/d15398b98b65c2bce288d5ee3f7d0a81f77ab89d9456994d5c7cc8b2a9db/sqlalchemy-2.0.45-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9a62b446b7d86a3909abbcd1cd3cc550a832f99c2bc37c5b22e1925438b9367b", size = 3475142, upload-time = "2025-12-09T22:13:33.739Z" },
{ url = "https://files.pythonhosted.org/packages/bf/e1/3ccb13c643399d22289c6a9786c1a91e3dcbb68bce4beb44926ac2c557bf/sqlalchemy-2.0.45-py3-none-any.whl", hash = "sha256:5225a288e4c8cc2308dbdd874edad6e7d0fd38eac1e9e5f23503425c8eee20d0", size = 1936672, upload-time = "2025-12-09T21:54:52.608Z" },
]
[[package]]
name = "sqlmodel"
version = "0.0.31"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "sqlalchemy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/56/b8/e7cd6def4a773f25d6e29ffce63ccbfd6cf9488b804ab6fb9b80d334b39d/sqlmodel-0.0.31.tar.gz", hash = "sha256:2d41a8a9ee05e40736e2f9db8ea28cbfe9b5d4e5a18dd139e80605025e0c516c", size = 94952, upload-time = "2025-12-28T12:35:01.436Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6c/72/5aa5be921800f6418a949a73c9bb7054890881143e6bc604a93d228a95a3/sqlmodel-0.0.31-py3-none-any.whl", hash = "sha256:6d946d56cac4c2db296ba1541357cee2e795d68174e2043cd138b916794b1513", size = 27093, upload-time = "2025-12-28T12:35:00.108Z" },
]
[[package]]
name = "starlette"
version = "0.50.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" },
]
[[package]]
name = "tenacity"
version = "9.1.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]
[[package]]
name = "typing-inspection"
version = "0.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
]
[[package]]
name = "urllib3"
version = "2.6.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" },
]
[[package]]
name = "watchfiles"
version = "1.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" },
{ url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" },
{ url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" },
{ url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" },
{ url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" },
{ url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" },
{ url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" },
{ url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" },
{ url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" },
{ url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" },
{ url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" },
{ url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" },
{ url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" },
{ url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" },
{ url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" },
{ url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" },
{ url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" },
{ url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" },
{ url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" },
{ url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" },
{ url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" },
{ url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" },
{ url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" },
]
[[package]]
name = "websockets"
version = "15.0.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
]
[[package]]
name = "wrapt"
version = "2.0.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/49/2a/6de8a50cb435b7f42c46126cf1a54b2aab81784e74c8595c8e025e8f36d3/wrapt-2.0.1.tar.gz", hash = "sha256:9c9c635e78497cacb81e84f8b11b23e0aacac7a136e73b8e5b2109a1d9fc468f", size = 82040, upload-time = "2025-11-07T00:45:33.312Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/73/81/d08d83c102709258e7730d3cd25befd114c60e43ef3891d7e6877971c514/wrapt-2.0.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:5e53b428f65ece6d9dad23cb87e64506392b720a0b45076c05354d27a13351a1", size = 78290, upload-time = "2025-11-07T00:44:34.691Z" },
{ url = "https://files.pythonhosted.org/packages/f6/14/393afba2abb65677f313aa680ff0981e829626fed39b6a7e3ec807487790/wrapt-2.0.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ad3ee9d0f254851c71780966eb417ef8e72117155cff04821ab9b60549694a55", size = 61255, upload-time = "2025-11-07T00:44:35.762Z" },
{ url = "https://files.pythonhosted.org/packages/c4/10/a4a1f2fba205a9462e36e708ba37e5ac95f4987a0f1f8fd23f0bf1fc3b0f/wrapt-2.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7b822c61ed04ee6ad64bc90d13368ad6eb094db54883b5dde2182f67a7f22c0", size = 61797, upload-time = "2025-11-07T00:44:37.22Z" },
{ url = "https://files.pythonhosted.org/packages/12/db/99ba5c37cf1c4fad35349174f1e38bd8d992340afc1ff27f526729b98986/wrapt-2.0.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7164a55f5e83a9a0b031d3ffab4d4e36bbec42e7025db560f225489fa929e509", size = 120470, upload-time = "2025-11-07T00:44:39.425Z" },
{ url = "https://files.pythonhosted.org/packages/30/3f/a1c8d2411eb826d695fc3395a431757331582907a0ec59afce8fe8712473/wrapt-2.0.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e60690ba71a57424c8d9ff28f8d006b7ad7772c22a4af432188572cd7fa004a1", size = 122851, upload-time = "2025-11-07T00:44:40.582Z" },
{ url = "https://files.pythonhosted.org/packages/b3/8d/72c74a63f201768d6a04a8845c7976f86be6f5ff4d74996c272cefc8dafc/wrapt-2.0.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3cd1a4bd9a7a619922a8557e1318232e7269b5fb69d4ba97b04d20450a6bf970", size = 117433, upload-time = "2025-11-07T00:44:38.313Z" },
{ url = "https://files.pythonhosted.org/packages/c7/5a/df37cf4042cb13b08256f8e27023e2f9b3d471d553376616591bb99bcb31/wrapt-2.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b4c2e3d777e38e913b8ce3a6257af72fb608f86a1df471cb1d4339755d0a807c", size = 121280, upload-time = "2025-11-07T00:44:41.69Z" },
{ url = "https://files.pythonhosted.org/packages/54/34/40d6bc89349f9931e1186ceb3e5fbd61d307fef814f09fbbac98ada6a0c8/wrapt-2.0.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3d366aa598d69416b5afedf1faa539fac40c1d80a42f6b236c88c73a3c8f2d41", size = 116343, upload-time = "2025-11-07T00:44:43.013Z" },
{ url = "https://files.pythonhosted.org/packages/70/66/81c3461adece09d20781dee17c2366fdf0cb8754738b521d221ca056d596/wrapt-2.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c235095d6d090aa903f1db61f892fffb779c1eaeb2a50e566b52001f7a0f66ed", size = 119650, upload-time = "2025-11-07T00:44:44.523Z" },
{ url = "https://files.pythonhosted.org/packages/46/3a/d0146db8be8761a9e388cc9cc1c312b36d583950ec91696f19bbbb44af5a/wrapt-2.0.1-cp314-cp314-win32.whl", hash = "sha256:bfb5539005259f8127ea9c885bdc231978c06b7a980e63a8a61c8c4c979719d0", size = 58701, upload-time = "2025-11-07T00:44:48.277Z" },
{ url = "https://files.pythonhosted.org/packages/1a/38/5359da9af7d64554be63e9046164bd4d8ff289a2dd365677d25ba3342c08/wrapt-2.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:4ae879acc449caa9ed43fc36ba08392b9412ee67941748d31d94e3cedb36628c", size = 60947, upload-time = "2025-11-07T00:44:46.086Z" },
{ url = "https://files.pythonhosted.org/packages/aa/3f/96db0619276a833842bf36343685fa04f987dd6e3037f314531a1e00492b/wrapt-2.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:8639b843c9efd84675f1e100ed9e99538ebea7297b62c4b45a7042edb84db03e", size = 59359, upload-time = "2025-11-07T00:44:47.164Z" },
{ url = "https://files.pythonhosted.org/packages/71/49/5f5d1e867bf2064bf3933bc6cf36ade23505f3902390e175e392173d36a2/wrapt-2.0.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:9219a1d946a9b32bb23ccae66bdb61e35c62773ce7ca6509ceea70f344656b7b", size = 82031, upload-time = "2025-11-07T00:44:49.4Z" },
{ url = "https://files.pythonhosted.org/packages/2b/89/0009a218d88db66ceb83921e5685e820e2c61b59bbbb1324ba65342668bc/wrapt-2.0.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fa4184e74197af3adad3c889a1af95b53bb0466bced92ea99a0c014e48323eec", size = 62952, upload-time = "2025-11-07T00:44:50.74Z" },
{ url = "https://files.pythonhosted.org/packages/ae/18/9b968e920dd05d6e44bcc918a046d02afea0fb31b2f1c80ee4020f377cbe/wrapt-2.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c5ef2f2b8a53b7caee2f797ef166a390fef73979b15778a4a153e4b5fedce8fa", size = 63688, upload-time = "2025-11-07T00:44:52.248Z" },
{ url = "https://files.pythonhosted.org/packages/a6/7d/78bdcb75826725885d9ea26c49a03071b10c4c92da93edda612910f150e4/wrapt-2.0.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e042d653a4745be832d5aa190ff80ee4f02c34b21f4b785745eceacd0907b815", size = 152706, upload-time = "2025-11-07T00:44:54.613Z" },
{ url = "https://files.pythonhosted.org/packages/dd/77/cac1d46f47d32084a703df0d2d29d47e7eb2a7d19fa5cbca0e529ef57659/wrapt-2.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2afa23318136709c4b23d87d543b425c399887b4057936cd20386d5b1422b6fa", size = 158866, upload-time = "2025-11-07T00:44:55.79Z" },
{ url = "https://files.pythonhosted.org/packages/8a/11/b521406daa2421508903bf8d5e8b929216ec2af04839db31c0a2c525eee0/wrapt-2.0.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6c72328f668cf4c503ffcf9434c2b71fdd624345ced7941bc6693e61bbe36bef", size = 146148, upload-time = "2025-11-07T00:44:53.388Z" },
{ url = "https://files.pythonhosted.org/packages/0c/c0/340b272bed297baa7c9ce0c98ef7017d9c035a17a6a71dce3184b8382da2/wrapt-2.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3793ac154afb0e5b45d1233cb94d354ef7a983708cc3bb12563853b1d8d53747", size = 155737, upload-time = "2025-11-07T00:44:56.971Z" },
{ url = "https://files.pythonhosted.org/packages/f3/93/bfcb1fb2bdf186e9c2883a4d1ab45ab099c79cbf8f4e70ea453811fa3ea7/wrapt-2.0.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fec0d993ecba3991645b4857837277469c8cc4c554a7e24d064d1ca291cfb81f", size = 144451, upload-time = "2025-11-07T00:44:58.515Z" },
{ url = "https://files.pythonhosted.org/packages/d2/6b/dca504fb18d971139d232652656180e3bd57120e1193d9a5899c3c0b7cdd/wrapt-2.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:949520bccc1fa227274da7d03bf238be15389cd94e32e4297b92337df9b7a349", size = 150353, upload-time = "2025-11-07T00:44:59.753Z" },
{ url = "https://files.pythonhosted.org/packages/1d/f6/a1de4bd3653afdf91d250ca5c721ee51195df2b61a4603d4b373aa804d1d/wrapt-2.0.1-cp314-cp314t-win32.whl", hash = "sha256:be9e84e91d6497ba62594158d3d31ec0486c60055c49179edc51ee43d095f79c", size = 60609, upload-time = "2025-11-07T00:45:03.315Z" },
{ url = "https://files.pythonhosted.org/packages/01/3a/07cd60a9d26fe73efead61c7830af975dfdba8537632d410462672e4432b/wrapt-2.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:61c4956171c7434634401db448371277d07032a81cc21c599c22953374781395", size = 64038, upload-time = "2025-11-07T00:45:00.948Z" },
{ url = "https://files.pythonhosted.org/packages/41/99/8a06b8e17dddbf321325ae4eb12465804120f699cd1b8a355718300c62da/wrapt-2.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:35cdbd478607036fee40273be8ed54a451f5f23121bd9d4be515158f9498f7ad", size = 60634, upload-time = "2025-11-07T00:45:02.087Z" },
{ url = "https://files.pythonhosted.org/packages/15/d1/b51471c11592ff9c012bd3e2f7334a6ff2f42a7aed2caffcf0bdddc9cb89/wrapt-2.0.1-py3-none-any.whl", hash = "sha256:4d2ce1bf1a48c5277d7969259232b57645aae5686dba1eaeade39442277afbca", size = 44046, upload-time = "2025-11-07T00:45:32.116Z" },
]
[[package]]
name = "wsproto"
version = "1.3.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c7/79/12135bdf8b9c9367b8701c2c19a14c913c120b882d50b014ca0d38083c2c/wsproto-1.3.2.tar.gz", hash = "sha256:b86885dcf294e15204919950f666e06ffc6c7c114ca900b060d6e16293528294", size = 50116, upload-time = "2025-11-20T18:18:01.871Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl", hash = "sha256:61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584", size = 24405, upload-time = "2025-11-20T18:18:00.454Z" },
]

34
watch_changes.py Normal file
View File

@@ -0,0 +1,34 @@
import couchdb
import time
import json
from urllib.parse import quote
# Configuration
user = "admin"
password = "DonCucarach0!?"
ip_address = "100.100.112.48"
port = "5984"
db_name = "obsidiandb"
safe_password = quote(password, safe="")
url = f"http://{user}:{safe_password}@{ip_address}:{port}/"
print("Listening for changes on CouchDB...")
server = couchdb.Server(url)
db = server[db_name]
# Get current update sequence
since = db.info()['update_seq']
print(f"Current Seq: {since}")
# Poll for changes
while True:
changes = db.changes(since=since, feed='longpoll', timeout=10000)
since = changes['last_seq']
for change in changes['results']:
print(f"Change detected! ID: {change['id']}, Deleted: {change.get('deleted', False)}")
# Check if it's one of our moved files
if "05 - fleeting" in change['id'].lower() or "science" in change['id'].lower():
print(" -> RELEVANT FILE CHANGED")
time.sleep(1)