vault backup: 2026-08-15 15:07:24
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
---
|
||||
created: 2026-08-06T14:49:00
|
||||
tags:
|
||||
- aws
|
||||
- script
|
||||
- openwebUI
|
||||
version: "1.0"
|
||||
---
|
||||
|
||||
```
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REGION="us-east-2"
|
||||
INSTANCE_ID="i-0b6c42e2ff7df81ee"
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 {start|stop|status}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
cmd="${1:-}"
|
||||
[ -n "$cmd" ] || usage
|
||||
|
||||
case "$cmd" in
|
||||
start)
|
||||
echo "Starting Open WebUI server..."
|
||||
aws ec2 start-instances --region "$REGION" --instance-ids "$INSTANCE_ID" \
|
||||
--query 'StartingInstances[].{InstanceId:InstanceId,Previous:PreviousState.Name,Current:CurrentState.Name}' \
|
||||
--output table
|
||||
|
||||
echo "Waiting for instance to run..."
|
||||
aws ec2 wait instance-running --region "$REGION" --instance-ids "$INSTANCE_ID"
|
||||
|
||||
echo "Waiting for status checks..."
|
||||
aws ec2 wait instance-status-ok --region "$REGION" --instance-ids "$INSTANCE_ID"
|
||||
|
||||
echo "Started. Open: https://openwebui.boogerclub.com"
|
||||
;;
|
||||
|
||||
stop)
|
||||
echo "Stopping Open WebUI server..."
|
||||
aws ec2 stop-instances --region "$REGION" --instance-ids "$INSTANCE_ID" \
|
||||
--query 'StoppingInstances[].{InstanceId:InstanceId,Previous:PreviousState.Name,Current:CurrentState.Name}' \
|
||||
--output table
|
||||
|
||||
echo "Waiting for instance to stop..."
|
||||
aws ec2 wait instance-stopped --region "$REGION" --instance-ids "$INSTANCE_ID"
|
||||
|
||||
echo "Stopped."
|
||||
;;
|
||||
|
||||
status)
|
||||
aws ec2 describe-instances --region "$REGION" --instance-ids "$INSTANCE_ID" \
|
||||
--query 'Reservations[0].Instances[0].{InstanceId:InstanceId,State:State.Name,Type:InstanceType,PublicIp:PublicIpAddress}' \
|
||||
--output table
|
||||
;;
|
||||
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
```
|
||||
@@ -0,0 +1,356 @@
|
||||
---
|
||||
created: 2026-08-06T15:15:00
|
||||
tags:
|
||||
- NotebookLM
|
||||
- colab
|
||||
- vector
|
||||
updated: 2026-08-06T15:15:00
|
||||
---
|
||||
|
||||
# REFACTORED COLAB NOTEBOOK - Free Tier Compatible
|
||||
## Kentucky Kernel Archive → Vector DB Pipeline
|
||||
|
||||
---
|
||||
|
||||
## CELL 1: SETUP & MOUNT (DO THIS FIRST)
|
||||
|
||||
```python
|
||||
## @title SETUP (RUN ONCE)
|
||||
|
||||
## Install everything upfront
|
||||
!pip install internetarchive chromadb sentence-transformers
|
||||
|
||||
## Mount Drive once, reuse everywhere
|
||||
from google.colab import drive
|
||||
drive.mount('/content/drive')
|
||||
|
||||
print("✅ Setup complete. Drive mounted.")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CELL 2: CONFIGURE PATHS (USER INPUT)
|
||||
|
||||
```python
|
||||
## @title CONFIG - Enter Your Settings
|
||||
|
||||
## User inputs
|
||||
creator_name = input("Enter creator name (e.g., 'The Mt. Sterling Advocate'): ").strip()
|
||||
test_mode = input("Test mode? (y/n, tests with 50 issues): ").strip().lower() == 'y'
|
||||
|
||||
## Paths
|
||||
BASE_DOWNLOAD = f'/content/{creator_name.replace(" ", "_")}_Archive'
|
||||
CLEANED_JSON = f'/content/drive/MyDrive/{creator_name.replace(" ", "_")}_Cleaned/cleaned_pages.json'
|
||||
VECTORDB_PATH = f'/content/drive/MyDrive/{creator_name.replace(" ", "_")}_VectorDB'
|
||||
|
||||
## Create directories
|
||||
import os
|
||||
os.makedirs(os.path.dirname(CLEANED_JSON), exist_ok=True)
|
||||
os.makedirs(BASE_DOWNLOAD, exist_ok=True)
|
||||
|
||||
print(f"✓ Creator: {creator_name}")
|
||||
print(f"✓ Download to: {BASE_DOWNLOAD}")
|
||||
print(f"✓ Cleaned JSON: {CLEANED_JSON}")
|
||||
print(f"✓ Vector DB: {VECTORDB_PATH}")
|
||||
print(f"✓ Test Mode: {test_mode}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CELL 3: DOWNLOAD FROM ARCHIVE.ORG
|
||||
|
||||
```python
|
||||
## @title DOWNLOAD (This takes 30-45 min)
|
||||
|
||||
from internetarchive import search_items, get_item, get_session
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
ia_session = get_session()
|
||||
ia_session.mount_http_adapter()
|
||||
|
||||
## Search
|
||||
query = f'creator:"{creator_name}"'
|
||||
print(f"Searching: {query}")
|
||||
|
||||
search = search_items(query, archive_session=ia_session)
|
||||
identifiers = [result['identifier'] for result in search]
|
||||
|
||||
if test_mode:
|
||||
identifiers = identifiers[:50] # Test with 50 only
|
||||
|
||||
total_items = len(identifiers)
|
||||
print(f"Found {total_items} issues. Starting download with 2 workers (safe)...\n")
|
||||
|
||||
def fast_download(identifier):
|
||||
target_folder = os.path.join(BASE_DOWNLOAD, identifier)
|
||||
if os.path.exists(target_folder) and len(os.listdir(target_folder)) >= 3:
|
||||
return f"⏭️ Skipped: {identifier}"
|
||||
|
||||
try:
|
||||
item = get_item(identifier, archive_session=ia_session)
|
||||
item.download(
|
||||
destdir=BASE_DOWNLOAD,
|
||||
glob_pattern=['*meta.xml', '*djvu.xml', '*djvu.txt'],
|
||||
ignore_existing=True,
|
||||
retries=3
|
||||
)
|
||||
return f"✅ Downloaded: {identifier}"
|
||||
except Exception as e:
|
||||
return f"❌ Failed: {identifier} - {str(e)[:50]}"
|
||||
|
||||
## Use 2 workers (safer than 4 on Free Tier)
|
||||
completed = 0
|
||||
failed = []
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as executor:
|
||||
futures = {executor.submit(fast_download, identifier): identifier for identifier in identifiers}
|
||||
|
||||
for future in as_completed(futures):
|
||||
completed += 1
|
||||
result = future.result()
|
||||
if "❌" in result:
|
||||
failed.append(result)
|
||||
|
||||
if completed % 25 == 0 or completed == total_items:
|
||||
print(f"[{completed}/{total_items}] {result[:60]}")
|
||||
|
||||
print(f"\n✅ Download complete. {len(failed)} failures.")
|
||||
if failed:
|
||||
print("Failed items:")
|
||||
for f in failed[:5]:
|
||||
print(f" {f}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CELL 4: PARSE XML & CLEAN TEXT (CPU-BASED)
|
||||
|
||||
```python
|
||||
## @title PARSE & CLEAN
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
import json
|
||||
import pandas as pd # ← Use pandas, not cudf
|
||||
import glob
|
||||
|
||||
print("Scanning for DJVU XML files...")
|
||||
djvu_files = glob.glob(os.path.join(BASE_DOWNLOAD, '**/*djvu.xml'), recursive=True)
|
||||
print(f"Found {len(djvu_files)} XML files.")
|
||||
|
||||
page_data_list = []
|
||||
failed_issues = []
|
||||
|
||||
for djvu_path in djvu_files:
|
||||
folder_name = os.path.basename(os.path.dirname(djvu_path))
|
||||
|
||||
try:
|
||||
tree = ET.parse(djvu_path)
|
||||
root = tree.getroot()
|
||||
pages = root.findall('.//OBJECT')
|
||||
|
||||
for page_index, page in enumerate(pages):
|
||||
page_text = []
|
||||
for word in page.findall('.//WORD'):
|
||||
if word.text:
|
||||
page_text.append(word.text)
|
||||
|
||||
raw_page = " ".join(page_text)
|
||||
|
||||
if raw_page.strip():
|
||||
page_data_list.append({
|
||||
"issue_id": folder_name,
|
||||
"page_number": page_index + 1,
|
||||
"text": raw_page
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
failed_issues.append((folder_name, str(e)))
|
||||
|
||||
print(f"Extracted {len(page_data_list)} pages, {len(failed_issues)} failures.")
|
||||
|
||||
## CLEANING: Use pandas (CPU) - faster than GPU for regex
|
||||
print("Cleaning text with regex...")
|
||||
texts_df = pd.DataFrame({'text': [p['text'] for p in page_data_list]})
|
||||
|
||||
## Fix hyphenation
|
||||
texts_df['text'] = texts_df['text'].str.replace(r'-\s*\n\s*', '', regex=True)
|
||||
## Remove garbage OCR chars
|
||||
texts_df['text'] = texts_df['text'].str.replace(r'[^a-zA-Z0-9\s.,;:\'"!?()-]', '', regex=True)
|
||||
## Normalize spaces
|
||||
texts_df['text'] = texts_df['text'].str.replace(r'\s+', ' ', regex=True)
|
||||
|
||||
## Reattach cleaned text
|
||||
for i, clean_text in enumerate(texts_df['text'].tolist()):
|
||||
page_data_list[i]['text'] = clean_text
|
||||
|
||||
## Save
|
||||
print(f"Saving {len(page_data_list)} pages to JSON...")
|
||||
with open(CLEANED_JSON, 'w', encoding='utf-8') as f:
|
||||
json.dump(page_data_list, f, indent=2)
|
||||
|
||||
print(f"✅ Complete! {len(page_data_list)} clean pages saved.")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CELL 5: BUILD VECTOR DB (GPU-Accelerated Embeddings)
|
||||
|
||||
```python
|
||||
## @title BUILD VECTOR DB (30-60 min - may timeout, that's ok)
|
||||
|
||||
import json
|
||||
import chromadb
|
||||
from chromadb.utils import embedding_functions
|
||||
|
||||
print("Loading cleaned pages...")
|
||||
with open(CLEANED_JSON, 'r', encoding='utf-8') as f:
|
||||
pages_data = json.load(f)
|
||||
|
||||
print(f"Loaded {len(pages_data)} pages.")
|
||||
|
||||
## Initialize ChromaDB with GPU embeddings
|
||||
print("Initializing ChromaDB with GPU embeddings...")
|
||||
os.makedirs(VECTORDB_PATH, exist_ok=True)
|
||||
|
||||
gpu_ef = embedding_functions.SentenceTransformerEmbeddingFunction(
|
||||
model_name="all-MiniLM-L6-v2",
|
||||
device="cuda" # GPU accelerated
|
||||
)
|
||||
|
||||
chroma_client = chromadb.PersistentClient(path=VECTORDB_PATH)
|
||||
collection = chroma_client.get_or_create_collection(
|
||||
name="historical_news",
|
||||
embedding_function=gpu_ef
|
||||
)
|
||||
|
||||
## Chunking function
|
||||
def chunk_text(text, chunk_size=800, overlap=150):
|
||||
chunks = []
|
||||
start = 0
|
||||
while start < len(text):
|
||||
end = min(start + chunk_size, len(text))
|
||||
chunks.append(text[start:end])
|
||||
start += chunk_size - overlap
|
||||
return chunks
|
||||
|
||||
## Extract & chunk
|
||||
documents = []
|
||||
metadatas = []
|
||||
ids = []
|
||||
|
||||
print("Chunking and preparing documents...")
|
||||
for page in pages_data:
|
||||
issue_id = page.get('issue_id', 'unknown')
|
||||
page_num = page.get('page_number', 0)
|
||||
text = page.get('text', '')
|
||||
|
||||
if not text.strip():
|
||||
continue
|
||||
|
||||
chunks = chunk_text(text)
|
||||
|
||||
for chunk_idx, chunk_text_data in enumerate(chunks):
|
||||
documents.append(chunk_text_data)
|
||||
metadatas.append({
|
||||
"issue_id": issue_id,
|
||||
"page_number": page_num,
|
||||
"chunk_id": chunk_idx
|
||||
})
|
||||
ids.append(f"{issue_id}_p{page_num}_c{chunk_idx}")
|
||||
|
||||
total_chunks = len(documents)
|
||||
print(f"Prepared {total_chunks} chunks.")
|
||||
|
||||
## Save checkpoint before starting (in case it times out)
|
||||
checkpoint = {
|
||||
'total_chunks': total_chunks,
|
||||
'vectorized': 0,
|
||||
'started_at': str(__import__('datetime').datetime.now())
|
||||
}
|
||||
|
||||
checkpoint_file = os.path.join(VECTORDB_PATH, 'progress.json')
|
||||
|
||||
print("Adding to vector DB...")
|
||||
BATCH_SIZE = 250
|
||||
|
||||
for b in range(0, total_chunks, BATCH_SIZE):
|
||||
try:
|
||||
collection.add(
|
||||
documents=documents[b:b+BATCH_SIZE],
|
||||
metadatas=metadatas[b:b+BATCH_SIZE],
|
||||
ids=ids[b:b+BATCH_SIZE]
|
||||
)
|
||||
checkpoint['vectorized'] = b + BATCH_SIZE
|
||||
|
||||
if (b + BATCH_SIZE) % 1000 == 0 or (b + BATCH_SIZE) >= total_chunks:
|
||||
with open(checkpoint_file, 'w') as f:
|
||||
json.dump(checkpoint, f)
|
||||
print(f"✓ {b + BATCH_SIZE}/{total_chunks} chunks vectorized")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error at batch {b}: {e}")
|
||||
print(f"Saved progress checkpoint. DB is recoverable.")
|
||||
break
|
||||
|
||||
print(f"✅ Vector DB complete! Stored at: {VECTORDB_PATH}")
|
||||
print(f"Query it with: chroma_client.get_collection('historical_news')")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CELL 6: TEST THE VECTOR DB (Optional)
|
||||
|
||||
```python
|
||||
## @title TEST QUERY
|
||||
|
||||
import chromadb
|
||||
|
||||
db_path = VECTORDB_PATH
|
||||
client = chromadb.PersistentClient(path=db_path)
|
||||
collection = client.get_collection("historical_news")
|
||||
|
||||
## Test query
|
||||
test_query = input("Enter a search term (e.g., 'railroad accident'): ").strip()
|
||||
|
||||
results = collection.query(
|
||||
query_texts=[test_query],
|
||||
n_results=5
|
||||
)
|
||||
|
||||
print(f"\n🔍 Top 5 results for: '{test_query}'\n")
|
||||
for i, (doc, metadata, distance) in enumerate(zip(
|
||||
results['documents'][0],
|
||||
results['metadatas'][0],
|
||||
results['distances'][0]
|
||||
)):
|
||||
print(f"{i+1}. Issue: {metadata['issue_id']}, Page {metadata['page_number']}")
|
||||
print(f" Relevance score: {1 - distance:.2f}")
|
||||
print(f" Text: {doc[:150]}...\n")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## KEY CHANGES FROM ORIGINAL
|
||||
|
||||
| Issue | Original | Fixed |
|
||||
|-------|----------|-------|
|
||||
| **cudf** | ❌ Will crash | ✅ Use pandas (faster anyway) |
|
||||
| **Drive mounts** | 3x (wasteful) | 1x (efficient) |
|
||||
| **GPU usage** | Regex on GPU (slow) | Embeddings on GPU (fast) |
|
||||
| **Hardcoded paths** | ❌ Not flexible | ✅ User input + parameterized |
|
||||
| **Error handling** | Silent fails | ✅ Logged failures |
|
||||
| **Checkpointing** | None | ✅ Progress saved |
|
||||
| **Workers** | 4 (risky) | 2 (safe) |
|
||||
| **Test mode** | N/A | ✅ Optional test with 50 issues |
|
||||
| **Memory** | All in RAM | ✅ Batch processing |
|
||||
|
||||
---
|
||||
|
||||
## EXPECTED RESULTS
|
||||
|
||||
- **Download:** 30-45 min (1756 issues)
|
||||
- **Parse & Clean:** 10-15 min
|
||||
- **Vectorize:** 45-90 min (may timeout on Free Tier around 70 min mark)
|
||||
- **Total:** ~2.5 hours
|
||||
|
||||
If vectorization times out, your progress is saved and resumable in next session.
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
created: 2026-08-06T15:15:00
|
||||
tags:
|
||||
- claude
|
||||
- script
|
||||
updated: 2026-08-06T15:16:00
|
||||
---
|
||||
|
||||
```
|
||||
(async function () {
|
||||
const sessionId = "f27f0fa3-e115-4efe-a5a1-ca22bfc283b3";
|
||||
const baseUrl = "/kyuidocuments/Home/GetDocument?sessionId=" + sessionId + "&docId=";
|
||||
|
||||
const allBoxes = Array.from(document.getElementsByClassName('doc-checkbox'));
|
||||
const checked = allBoxes.filter(cb => cb.checked);
|
||||
|
||||
if (checked.length === 0) {
|
||||
alert("No documents are checked. Check the boxes you want first, then re-run.");
|
||||
return;
|
||||
}
|
||||
|
||||
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
const sanitize = s => s.replace(/[\\/:*?"<>|]/g, "_");
|
||||
|
||||
console.log(`Starting download of ${checked.length} document(s)...`);
|
||||
|
||||
for (let i = 0; i < checked.length; i++) {
|
||||
const cb = checked[i];
|
||||
const docName = cb.getAttribute('data-doc-name');
|
||||
const docId = cb.getAttribute('data-doc-id');
|
||||
|
||||
try {
|
||||
const res = await fetch(baseUrl + docId, { credentials: 'include' });
|
||||
const result = await res.json();
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = "data:application/pdf;base64," + result.content;
|
||||
a.download = sanitize(docName + "-" + docId) + ".pdf";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
|
||||
console.log(`[${i + 1}/${checked.length}] Downloaded: ${a.download}`);
|
||||
} catch (err) {
|
||||
console.error(`[${i + 1}/${checked.length}] FAILED docId ${docId}:`, err);
|
||||
}
|
||||
|
||||
await sleep(400);
|
||||
|
||||
if ((i + 1) % 5 === 0 && (i + 1) < checked.length) {
|
||||
const nextPage = Math.floor(i / 5) + 2;
|
||||
if (typeof displayTable === 'function') displayTable(nextPage);
|
||||
await sleep(300);
|
||||
}
|
||||
}
|
||||
|
||||
alert(`Done. Downloaded ${checked.length} document(s).`);
|
||||
})();
|
||||
```
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
tags:
|
||||
- nvm
|
||||
- cmds
|
||||
---
|
||||
|
||||
**Core commands:**
|
||||
|
||||
```powershell
|
||||
nvm list # installed versions
|
||||
nvm list available # versions you can install
|
||||
nvm install <version> # e.g. nvm install 20.18.0
|
||||
nvm use <version> # switch active version
|
||||
nvm uninstall <version> # remove one
|
||||
```
|
||||
|
||||
**Practical stuff you'll actually hit:**
|
||||
|
||||
1. **`nvm use` is per-shell, not per-project.** Every new terminal window defaults back to whatever was last set globally — it does NOT auto-detect per-folder like nvm on Mac/Linux does. nvm-windows has no built-in `.nvmrc` autoswitch.
|
||||
|
||||
2. **Fake it with `.nvmrc` + a wrapper function.** Add this to your PowerShell `$PROFILE` to auto-switch when you `cd` into a folder with a `.nvmrc`:
|
||||
|
||||
```powershell
|
||||
function nvmrc-check {
|
||||
if (Test-Path .nvmrc) {
|
||||
$version = Get-Content .nvmrc -Raw
|
||||
nvm use $version.Trim()
|
||||
}
|
||||
}
|
||||
```
|
||||
You'd need to hook this to directory changes yourself (no native hook in PowerShell) — or just manually run `nvm use` when you switch projects. Not worth automating unless you're juggling many projects daily.
|
||||
|
||||
3. **Global npm packages don't carry over between Node versions.** Installing `npm i -g something` under 24.18.1 means it's gone if you `nvm use 20.x`. Reinstall globals per version you actually use.
|
||||
|
||||
4. **`nvm use` requires admin the FIRST time after a fresh nvm-windows install**, then not after — it's flipping a symlink at `C:\Program Files\nodejs`. If PowerShell isn't elevated the first time, you'll get silent permission failures. You already got it working, so this is a non-issue now, but keep it in mind after Windows updates or profile changes.
|
||||
|
||||
5. **Check `npm config get prefix`** — with nvm-windows this should point to the nvm symlink path, not a per-user AppData path. If a global tool ever "disappears" after switching versions, that's why — it's expected, not broken.
|
||||
|
||||
6. **LTS vs current:** `nvm install lts` grabs whatever's tagged LTS at install time — it does NOT auto-update to newer LTS releases later. You'll need to rerun `nvm install lts` periodically and `nvm use` the new one.
|
||||
|
||||
That covers what actually bites people. Anything specific you're about to do with it (global CLI tools, multiple project versions, etc.) — I can give exact commands.
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
"""Remove YAML frontmatter from all markdown files in the repo.
|
||||
|
||||
Frontmatter is the block delimited by `---` at the very start of a file:
|
||||
---
|
||||
title: ...
|
||||
---
|
||||
The script strips that block (and any immediately following blank lines)
|
||||
from every .md file found recursively under the target directory.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
FRONTMATTER_RE = re.compile(r"\A---\r?\n.*?\r?\n---\r?\n?", re.DOTALL)
|
||||
|
||||
|
||||
def strip_frontmatter(content: str) -> str:
|
||||
"""Return content with leading YAML frontmatter removed."""
|
||||
m = FRONTMATTER_RE.match(content)
|
||||
if not m:
|
||||
return content
|
||||
rest = content[m.end():]
|
||||
# Trim leading blank lines left behind after the frontmatter.
|
||||
return rest.lstrip("\r\n")
|
||||
|
||||
|
||||
def process_file(path: str, dry_run: bool = False) -> bool:
|
||||
"""Strip frontmatter from one file. Returns True if changed."""
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8-sig", newline="") as f:
|
||||
original = f.read()
|
||||
except (UnicodeDecodeError, OSError) as e:
|
||||
print(f" ! skip (read error): {path} ({e})")
|
||||
return False
|
||||
|
||||
updated = strip_frontmatter(original)
|
||||
if updated == original:
|
||||
return False
|
||||
|
||||
if dry_run:
|
||||
print(f" ~ would strip frontmatter: {path}")
|
||||
else:
|
||||
with open(path, "w", encoding="utf-8", newline="") as f:
|
||||
f.write(updated)
|
||||
print(f" ✓ stripped frontmatter: {path}")
|
||||
return True
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = [a for a in sys.argv[1:] if a != "--dry-run"]
|
||||
dry_run = "--dry-run" in sys.argv
|
||||
root = args[0] if args else os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# Only process these subdirectories under root.
|
||||
target_dirs = ["agentss", "promps"]
|
||||
|
||||
print(f"Scanning: {root} (dirs: {', '.join(target_dirs)})"
|
||||
+ (" (dry run)" if dry_run else ""))
|
||||
changed = 0
|
||||
total = 0
|
||||
for sub in target_dirs:
|
||||
sub_root = os.path.join(root, sub)
|
||||
if not os.path.isdir(sub_root):
|
||||
print(f" ! directory not found, skipping: {sub_root}")
|
||||
continue
|
||||
for dirpath, _dirs, files in os.walk(sub_root):
|
||||
# Skip hidden dirs.
|
||||
if os.path.basename(dirpath).startswith("."):
|
||||
continue
|
||||
for name in files:
|
||||
if not name.lower().endswith(".md"):
|
||||
continue
|
||||
total += 1
|
||||
full = os.path.join(dirpath, name)
|
||||
if process_file(full, dry_run=dry_run):
|
||||
changed += 1
|
||||
|
||||
print(f"\nDone. {changed} of {total} markdown file(s) "
|
||||
f"{'would be ' if dry_run else ''}modified.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user