Upload current progress
This commit is contained in:
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal 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
1
.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.14
|
||||||
56
00 - Home/Homepage.md
Normal file
56
00 - Home/Homepage.md
Normal 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/Dashboards/Homepage.md
Normal file
56
99 - Meta/Dashboards/Homepage.md
Normal 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
56
99 - Meta/Homepage.md
Normal 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.
|
||||||
5
Biology/Evolution/Why tetrapods dominate and not bugs.md
Normal file
5
Biology/Evolution/Why tetrapods dominate and not bugs.md
Normal 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.
|
||||||
9
Coding/Delphi/Delphi Prefixes.md
Normal file
9
Coding/Delphi/Delphi Prefixes.md
Normal 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
130
FIX_SUMMARY.md
Normal 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
3
Inbox/Teleport.md
Normal 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.
|
||||||
18
Meta/Prompts/Rewrite as tweet thread.md
Normal file
18
Meta/Prompts/Rewrite as tweet thread.md
Normal 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.
|
||||||
9
Notes/Science/Biology/How bugs breathe.md
Normal file
9
Notes/Science/Biology/How bugs breathe.md
Normal 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.
|
||||||
9
Programming/Zig/Zig basic types.md
Normal file
9
Programming/Zig/Zig basic types.md
Normal 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,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).
|
||||||
15
Resources/Prompts/Emojify.md
Normal file
15
Resources/Prompts/Emojify.md
Normal 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.
|
||||||
8
Resources/Prompts/Simplify.md
Normal file
8
Resources/Prompts/Simplify.md
Normal 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.
|
||||||
3
Resources/Tools/FireDaemon.md
Normal file
3
Resources/Tools/FireDaemon.md
Normal 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
112
SYNC_SETUP.md
Normal 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)
|
||||||
8
System/Prompts/Summarize.md
Normal file
8
System/Prompts/Summarize.md
Normal 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.
|
||||||
13
Templates/AI Prompts/Emojify.md
Normal file
13
Templates/AI Prompts/Emojify.md
Normal 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.
|
||||||
12
Templates/AI Prompts/Explain like I am 5.md
Normal file
12
Templates/AI Prompts/Explain like I am 5.md
Normal 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.
|
||||||
12
Templates/AI Prompts/Rewrite as tweet.md
Normal file
12
Templates/AI Prompts/Rewrite as tweet.md
Normal 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.
|
||||||
8
Templates/AI Prompts/Summarize.md
Normal file
8
Templates/AI Prompts/Summarize.md
Normal 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.
|
||||||
8
Templates/AI/Summarize.md
Normal file
8
Templates/AI/Summarize.md
Normal 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.
|
||||||
12
Templates/Prompts/AI/Make shorter.md
Normal file
12
Templates/Prompts/AI/Make shorter.md
Normal 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.
|
||||||
15
Templates/Prompts/Emojify.md
Normal file
15
Templates/Prompts/Emojify.md
Normal 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.
|
||||||
10
Templates/Prompts/Fix grammar and spelling.md
Normal file
10
Templates/Prompts/Fix grammar and spelling.md
Normal 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.
|
||||||
10
Templates/Prompts/Generate table of contents.md
Normal file
10
Templates/Prompts/Generate table of contents.md
Normal 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.
|
||||||
14
Templates/Prompts/Make longer.md
Normal file
14
Templates/Prompts/Make longer.md
Normal 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.
|
||||||
10
Templates/Prompts/Remove URLs.md
Normal file
10
Templates/Prompts/Remove URLs.md
Normal 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.
|
||||||
8
Templates/Prompts/Summarize.md
Normal file
8
Templates/Prompts/Summarize.md
Normal 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.
|
||||||
12
Templates/Prompts/Translate to Chinese.md
Normal file
12
Templates/Prompts/Translate to Chinese.md
Normal 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
147
alembic.ini
Normal 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
1
alembic/README
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Generic single-database configuration.
|
||||||
78
alembic/env.py
Normal file
78
alembic/env.py
Normal 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
28
alembic/script.py.mako
Normal 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"}
|
||||||
58
alembic/versions/29a55764f5a5_.py
Normal file
58
alembic/versions/29a55764f5a5_.py
Normal 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
87
cleanup_db.py
Executable 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
62
dump_all_notes.py
Normal 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
76
fix_sizes.py
Executable 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
51
force_sync.py
Executable 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
38
inspect_metadata.py
Normal 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
6
main.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
def main():
|
||||||
|
print("Hello from obsidian-automator!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
1
obsidian_automator/__init__.py
Normal file
1
obsidian_automator/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Package initialization
|
||||||
122
obsidian_automator/agent.py
Normal file
122
obsidian_automator/agent.py
Normal 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}"
|
||||||
58
obsidian_automator/components/layout.py
Normal file
58
obsidian_automator/components/layout.py
Normal 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",
|
||||||
|
)
|
||||||
50
obsidian_automator/components/terminal.py
Normal file
50
obsidian_automator/components/terminal.py
Normal 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%"
|
||||||
|
)
|
||||||
216
obsidian_automator/couch_manager.py
Normal file
216
obsidian_automator/couch_manager.py
Normal 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."
|
||||||
108
obsidian_automator/couch_sync.py
Normal file
108
obsidian_automator/couch_sync.py
Normal 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
|
||||||
25
obsidian_automator/models.py
Normal file
25
obsidian_automator/models.py
Normal 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)
|
||||||
136
obsidian_automator/note_server.py
Normal file
136
obsidian_automator/note_server.py
Normal 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)
|
||||||
21
obsidian_automator/obsidian_automator.py
Normal file
21
obsidian_automator/obsidian_automator.py
Normal 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")
|
||||||
165
obsidian_automator/pages/dashboard.py
Normal file
165
obsidian_automator/pages/dashboard.py
Normal 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%"
|
||||||
|
)
|
||||||
|
)
|
||||||
26
obsidian_automator/pages/login.py
Normal file
26
obsidian_automator/pages/login.py
Normal 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)"
|
||||||
|
)
|
||||||
44
obsidian_automator/pages/setup.py
Normal file
44
obsidian_automator/pages/setup.py
Normal 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)"
|
||||||
|
)
|
||||||
270
obsidian_automator/state/app_state.py
Normal file
270
obsidian_automator/state/app_state.py
Normal 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)
|
||||||
69
obsidian_automator/state/auth.py
Normal file
69
obsidian_automator/state/auth.py
Normal 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
16
pyproject.toml
Normal 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
5
rxconfig.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import reflex as rx
|
||||||
|
|
||||||
|
config = rx.Config(
|
||||||
|
app_name="obsidian_automator",
|
||||||
|
)
|
||||||
191
sync_daemon.py
Executable file
191
sync_daemon.py
Executable 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
71
test_connection.py
Executable 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
64
test_couch.py
Normal 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
43
test_couch_debug.py
Normal 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
47
test_couch_ip.py
Normal 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
953
uv.lock
generated
Normal 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
34
watch_changes.py
Normal 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)
|
||||||
Reference in New Issue
Block a user