diff --git a/.env.example b/.env.example index ab9420f..a0a035c 100644 --- a/.env.example +++ b/.env.example @@ -6,7 +6,29 @@ # 2. Fill in your API key below # 3. Never commit your .env file — keep it local # -# Get your Google API key at: https://aistudio.google.com/apikey -# (Free tier available — Gemini 3 Flash is very cheap for file processing) +# ───────────────────────────────────────────────────────────── +# Gemini Authentication — Choose ONE method: +# ───────────────────────────────────────────────────────────── +# METHOD 1: API Key (recommended for individuals) +# Get your free key at: https://aistudio.google.com/apikey GOOGLE_API_KEY=your_google_api_key_here + +# METHOD 2: Vertex AI (for Google Cloud users) +# Use this if you want to use your GCP project for billing/quota management +# 1. Enable Vertex AI in your Google Cloud project +# 2. Set GOOGLE_GENAI_USE_VERTEXAI=true below +# 3. Fill in your project ID and location +# 4. Authenticate: gcloud auth application-default login +GOOGLE_GENAI_USE_VERTEXAI=false +GOOGLE_CLOUD_PROJECT=your_google_cloud_project_here +GOOGLE_CLOUD_LOCATION=us-central1 + +# Model Selection (applies to both authentication methods): +# - gemini-3-flash-preview (fast, cheap, recommended) +# - gemini-3-pro-preview (slower, higher quality) +# - gemini-2.0-flash-exp (experimental, Vertex AI) +MODEL=gemini-3-flash-preview + +# How to check which method is active: +# Run: python scripts/gemini_auth.py \ No newline at end of file diff --git a/scripts/README.md b/scripts/README.md index 27f14d2..8551978 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -19,15 +19,45 @@ If you want a fast overview of what's in a folder before deciding what to keep, ## Prerequisites +**Step 1: Install dependencies** + ```bash -# 1. Get a free Google API key -# https://aistudio.google.com/apikey +# The setup scripts do this automatically, or run manually: +pip install google-genai python-dotenv pdfplumber python-pptx python-docx openpyxl +``` -# 2. Add it to your vault's .env file -echo "GOOGLE_API_KEY=your_key_here" > .env +**Step 2: Choose your authentication method** -# 3. Install dependencies (setup.sh / setup.ps1 does this automatically) -pip install google-genai python-dotenv pdfplumber python-pptx python-docx openpyxl +### Authentication: Choose Your Method + +Both scripts support two authentication methods. Choose the one that fits your needs: + +**Option 1: Google AI Studio API Key** *(Recommended for individuals)* + +```bash +# 1. Get a free key at: https://aistudio.google.com/apikey +# 2. Add to your .env file: +echo "GOOGLE_API_KEY=your_key_here" >> .env +``` + +**Option 2: Vertex AI** *(For Google Cloud users)* + +```bash +# 1. Enable Vertex AI in your Google Cloud project +# 2. Add to your .env file: +cat >> .env << 'EOF' +GOOGLE_GENAI_USE_VERTEXAI=true +GOOGLE_CLOUD_PROJECT=your-project-id +GOOGLE_CLOUD_LOCATION=us-central1 +EOF +# 3. Authenticate with Google Cloud: +gcloud auth application-default login +``` + +**How it works:** The scripts auto-detect which method to use based on your `.env` settings. You can verify your setup by running: + +```bash +python scripts/gemini_auth.py ``` --- @@ -168,7 +198,28 @@ process_files_with_gemini.py → outputs/file_summaries/YYYY-MM-DD/ ## Troubleshooting -**`GOOGLE_API_KEY not set`** — check your `.env` file is in the vault root and contains `GOOGLE_API_KEY=your_key` +### Authentication Issues + +**`GOOGLE_API_KEY not set`** or **`GOOGLE_CLOUD_PROJECT is not set`** + +Check which authentication method you're using: +```bash +python scripts/gemini_auth.py +``` + +For **API Key method**: +- Verify your `.env` file is in the vault root +- Check it contains `GOOGLE_API_KEY=your_key` (no spaces around `=`) +- Verify the key is valid at https://aistudio.google.com/apikey + +For **Vertex AI method**: +- Verify `.env` contains `GOOGLE_GENAI_USE_VERTEXAI=true` +- Check `GOOGLE_CLOUD_PROJECT` and `GOOGLE_CLOUD_LOCATION` are set +- Ensure you've run: `gcloud auth application-default login` +- Verify Vertex AI API is enabled in your GCP project +- Check your GCP account has permission to use Vertex AI + +### Other Issues **`No module named 'google'`** — run `pip install google-genai` diff --git a/scripts/gemini_auth.py b/scripts/gemini_auth.py new file mode 100644 index 0000000..709fc6d --- /dev/null +++ b/scripts/gemini_auth.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +""" +Centralized Gemini authentication for all scripts. + +Supports two authentication methods: +1. API Key (via GOOGLE_API_KEY) — for Google AI Studio +2. Vertex AI (via GOOGLE_GENAI_USE_VERTEXAI) — for Google Cloud Platform + +The google-genai library auto-detects which method to use based on environment variables. +This module validates that required variables are set and provides clear error messages. +""" + +import os +import sys +from typing import Literal + +try: + from google import genai +except ImportError: + print("Error: google-genai library not installed") + print("Install with: pip install google-genai") + sys.exit(1) + + +def get_auth_method() -> Literal["vertex_ai", "api_key"]: + """ + Determine which authentication method is configured. + + Returns: + "vertex_ai" if GOOGLE_GENAI_USE_VERTEXAI is set to true + "api_key" otherwise (default) + """ + use_vertex = os.environ.get("GOOGLE_GENAI_USE_VERTEXAI", "").lower() + return "vertex_ai" if use_vertex in ("true", "1", "yes") else "api_key" + + +def _validate_api_key_auth() -> None: + """Validate that API key authentication is properly configured.""" + api_key = os.environ.get("GOOGLE_API_KEY") or os.environ.get("GEMINI_API_KEY") + + if not api_key: + print("Error: GOOGLE_API_KEY not set in environment") + print() + print("To use API Key authentication:") + print("1. Get a free key at: https://aistudio.google.com/apikey") + print("2. Add to your .env file:") + print(" GOOGLE_API_KEY=your_key_here") + print() + print("Alternatively, use Vertex AI authentication (see README.md)") + sys.exit(1) + + +def _validate_vertex_auth() -> None: + """Validate that Vertex AI authentication is properly configured.""" + project = os.environ.get("GOOGLE_CLOUD_PROJECT") + location = os.environ.get("GOOGLE_CLOUD_LOCATION") + + errors = [] + if not project: + errors.append("GOOGLE_CLOUD_PROJECT is not set") + if not location: + errors.append("GOOGLE_CLOUD_LOCATION is not set") + + if errors: + print("Error: Vertex AI authentication is enabled but incomplete") + print() + for error in errors: + print(f" ✗ {error}") + print() + print("To use Vertex AI authentication:") + print("1. Set up a Google Cloud project with Vertex AI enabled") + print("2. Add to your .env file:") + print(" GOOGLE_GENAI_USE_VERTEXAI=true") + print(" GOOGLE_CLOUD_PROJECT=your-project-id") + print(" GOOGLE_CLOUD_LOCATION=us-central1") + print("3. Authenticate with: gcloud auth application-default login") + print() + print("Alternatively, use API Key authentication (see README.md)") + sys.exit(1) + + +def get_gemini_client() -> genai.Client: + """ + Get a configured Gemini client with automatic authentication. + + Auto-detects which authentication method to use based on environment variables: + - If GOOGLE_GENAI_USE_VERTEXAI=true → uses Vertex AI with GCP project/location + - Otherwise → uses API key from GOOGLE_API_KEY or GEMINI_API_KEY + + Returns: + genai.Client: Configured client ready to use + + Raises: + SystemExit: If authentication is not properly configured + """ + method = get_auth_method() + + # Validate that required environment variables are set for chosen method + if method == "vertex_ai": + _validate_vertex_auth() + else: + _validate_api_key_auth() + + # Let the library's auto-detection handle the rest + # It will use the appropriate auth method based on environment variables + try: + client = genai.Client() + return client + except Exception as e: + print(f"Error: Failed to create Gemini client") + print(f"Details: {e}") + print() + if method == "vertex_ai": + print("If you see authentication errors, try:") + print(" gcloud auth application-default login") + else: + print("Check that your GOOGLE_API_KEY is valid") + sys.exit(1) + + +if __name__ == "__main__": + # Quick test of authentication setup + print("Testing Gemini authentication...") + print() + + method = get_auth_method() + print(f"Detected method: {method}") + print() + + if method == "vertex_ai": + print("Vertex AI configuration:") + print(f" Project: {os.environ.get('GOOGLE_CLOUD_PROJECT', '(not set)')}") + print(f" Location: {os.environ.get('GOOGLE_CLOUD_LOCATION', '(not set)')}") + else: + print("API Key configuration:") + api_key = os.environ.get("GOOGLE_API_KEY", "") + if api_key: + print(f" Key: {api_key[:10]}...{api_key[-4:] if len(api_key) > 14 else ''}") + else: + print(" Key: (not set)") + + print() + print("Attempting to create client...") + + client = get_gemini_client() + print("✓ Success! Client created successfully.") + print() + print(f"You can now use the Gemini scripts with {method} authentication.") diff --git a/scripts/process_docs_to_obsidian.py b/scripts/process_docs_to_obsidian.py index cc06e75..9df0dc7 100644 --- a/scripts/process_docs_to_obsidian.py +++ b/scripts/process_docs_to_obsidian.py @@ -5,10 +5,14 @@ clean compressed Markdown notes ready for Obsidian ingestion. Usage: - python process_docs_to_obsidian.py + python process_docs_to_obsidian.py [--recursive] Example: python process_docs_to_obsidian.py ~/Documents/company_files ~/vault/inbox + python process_docs_to_obsidian.py ~/Documents/company_files ~/vault/inbox --recursive + +Options: + --recursive, -r Search for files recursively in subdirectories Supported file types: PDF, PPTX, DOCX, TXT, MD """ @@ -29,13 +33,25 @@ print("Install with: pip install google-genai python-dotenv") sys.exit(1) +# Import centralized auth module +try: + from gemini_auth import get_gemini_client +except ImportError: + # If running from different directory, try to import from scripts/ + sys.path.insert(0, str(Path(__file__).parent)) + from gemini_auth import get_gemini_client + # ───────────────────────────────────────────── # CONFIG — edit these to customise behaviour # ───────────────────────────────────────────── # # MODEL: which Gemini model to use for synthesis -# "gemini-3-flash-preview" — fast, cheap, great for most files ← default -# "gemini-3-pro-preview" — slower, higher quality for dense/complex docs +# Default: reads from MODEL environment variable (.env file) +# Fallback: "gemini-3-flash-preview" if MODEL not set +# Options: +# "gemini-3-flash-preview" — fast, cheap, great for most files +# "gemini-3-pro-preview" — slower, higher quality for dense/complex docs +# "gemini-2.0-flash-exp" — (Vertex AI) experimental 2.0 model # # SUPPORTED: file extensions to process — add or remove as needed # @@ -46,8 +62,7 @@ # and it will rewrite it for you. # ───────────────────────────────────────────── -MODEL = "gemini-3-flash-preview" -API_KEY = os.environ.get("GOOGLE_API_KEY") +MODEL = os.environ.get("MODEL", "gemini-3-flash-preview") TODAY = date.today().isoformat() SUPPORTED = {".pdf", ".pptx", ".ppt", ".docx", ".doc", ".txt", ".md"} @@ -185,20 +200,22 @@ def process_file(file_path: Path, client: genai.Client) -> str | None: # BATCH RUNNER # ───────────────────────────────────────────── -def process_folder(input_folder: str, output_folder: str): - if not API_KEY: - print("Error: GOOGLE_API_KEY not set in .env") - sys.exit(1) - - client = genai.Client(api_key=API_KEY) +def process_folder(input_folder: str, output_folder: str, recursive: bool = False): + # Get authenticated client (auto-detects API Key or Vertex AI) + client = get_gemini_client() input_path = Path(input_folder).expanduser() output_path = Path(output_folder).expanduser() output_path.mkdir(parents=True, exist_ok=True) - files = [f for f in input_path.iterdir() if f.suffix.lower() in SUPPORTED] + # Scan for files (recursively or top-level only) + if recursive: + files = [f for f in input_path.rglob('*') if f.is_file() and f.suffix.lower() in SUPPORTED] + else: + files = [f for f in input_path.iterdir() if f.is_file() and f.suffix.lower() in SUPPORTED] if not files: - print(f"No supported files found in {input_path}") + search_type = "recursively" if recursive else "in" + print(f"No supported files found {search_type} {input_path}") print(f"Supported types: {', '.join(SUPPORTED)}") return @@ -232,7 +249,16 @@ def process_folder(input_folder: str, output_folder: str): if __name__ == "__main__": + # Parse command-line arguments if len(sys.argv) < 3: print(__doc__) sys.exit(1) - process_folder(sys.argv[1], sys.argv[2]) + + input_folder = sys.argv[1] + output_folder = sys.argv[2] + recursive = "--recursive" in sys.argv or "-r" in sys.argv + + if recursive: + print("🔄 Recursive mode enabled: scanning subdirectories") + + process_folder(input_folder, output_folder, recursive) diff --git a/scripts/process_files_with_gemini.py b/scripts/process_files_with_gemini.py index 4d8a279..4b3d66e 100644 --- a/scripts/process_files_with_gemini.py +++ b/scripts/process_files_with_gemini.py @@ -5,6 +5,10 @@ Usage: python scripts/process_files_with_gemini.py # processes demo_files/ python scripts/process_files_with_gemini.py path/to/folder # processes custom folder + python scripts/process_files_with_gemini.py path/to/folder --recursive # scan subdirectories + +Options: + --recursive, -r Search for files recursively in subdirectories Supported: PDF, PPTX, XLSX, DOCX, CSV, JSON, XML, MD, TXT, PY, JS, HTML, CSS, any text file @@ -31,18 +35,26 @@ print("Install: pip install google-genai python-dotenv") sys.exit(1) -API_KEY = os.environ.get("GOOGLE_API_KEY") -if not API_KEY: - print("Missing GOOGLE_API_KEY in .env") - sys.exit(1) +# Import centralized auth module +try: + from gemini_auth import get_gemini_client +except ImportError: + # If running from different directory, try to import from scripts/ + import sys + sys.path.insert(0, str(Path(__file__).parent)) + from gemini_auth import get_gemini_client # ───────────────────────────────────────────── # CONFIG — edit these to customise behaviour # ───────────────────────────────────────────── # # MODEL: which Gemini model to use -# "gemini-3-flash-preview" — fast, cheap, great for most files ← default -# "gemini-3-pro-preview" — slower, higher quality on dense docs +# Default: reads from MODEL environment variable (.env file) +# Fallback: "gemini-3-flash-preview" if MODEL not set +# Options: +# "gemini-3-flash-preview" — fast, cheap, great for most files +# "gemini-3-pro-preview" — slower, higher quality on dense docs +# "gemini-2.0-flash-exp" — (Vertex AI) experimental 2.0 model # # OUTPUT_DIR: where summaries are saved (default: outputs/file_summaries/YYYY-MM-DD/) # @@ -55,7 +67,7 @@ # "Always extract the author name and date if present" # ───────────────────────────────────────────── -MODEL = "gemini-3-flash-preview" +MODEL = os.environ.get("MODEL", "gemini-3-flash-preview") BASE_DIR = Path(__file__).parent.parent TODAY = date.today().isoformat() @@ -274,19 +286,29 @@ def analyse_with_gemini(client, filename: str, file_type: str, content: str) -> # ── Main Pipeline ───────────────────────────────────────────────────────────── -def process_folder(folder: Path): - client = genai.Client(api_key=API_KEY) +def process_folder(folder: Path, recursive: bool = False): + # Get authenticated client (auto-detects API Key or Vertex AI) + client = get_gemini_client() - files = sorted([ - f for f in folder.iterdir() - if f.is_file() and not f.name.startswith(".") - ]) + # Scan for files (recursively or top-level only) + if recursive: + files = sorted([ + f for f in folder.rglob('*') + if f.is_file() and not f.name.startswith(".") + ]) + else: + files = sorted([ + f for f in folder.iterdir() + if f.is_file() and not f.name.startswith(".") + ]) if not files: - print(f"No files found in {folder}") + search_type = "recursively in" if recursive else "in" + print(f"No files found {search_type} {folder}") sys.exit(1) - print(f"\n🔍 Processing {len(files)} files from: {folder}") + search_mode = " (recursive)" if recursive else "" + print(f"\n🔍 Processing {len(files)} files from: {folder}{search_mode}") print(f"📂 Output folder: {OUTPUT_DIR}\n") print("─" * 60) @@ -351,16 +373,22 @@ def process_folder(folder: Path): def main(): - if len(sys.argv) > 1: + # Parse command-line arguments + if len(sys.argv) > 1 and sys.argv[1] not in ("--recursive", "-r"): folder = Path(sys.argv[1]) else: folder = BASE_DIR / "demo_files" + recursive = "--recursive" in sys.argv or "-r" in sys.argv + if not folder.exists(): print(f"Folder not found: {folder}") sys.exit(1) - process_folder(folder) + if recursive: + print("🔄 Recursive mode enabled: scanning subdirectories\n") + + process_folder(folder, recursive) if __name__ == "__main__": diff --git a/setup.ps1 b/setup.ps1 index 2e44b6f..edda006 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -113,7 +113,7 @@ Write-Host " =====================================================" Write-Host "" # === STEP 1: Check winget =================================================== -Write-Host "${White}Step 1/7 -- Checking winget${Reset}" +Write-Host "${White}Step 1/8 -- Checking winget${Reset}" if (-not (Get-Command winget -ErrorAction SilentlyContinue)) { Write-Host " ${Red}FAIL${Reset} winget not found." Write-Host " Install 'App Installer' from the Microsoft Store," @@ -124,7 +124,7 @@ Write-Host " ${Green}OK${Reset} winget available" # === STEP 2: Obsidian ======================================================== Write-Host "" -Write-Host "${White}Step 2/7 -- Installing Obsidian${Reset}" +Write-Host "${White}Step 2/8 -- Installing Obsidian${Reset}" $obsCheck = (winget list --id Obsidian.Obsidian 2>$null | Select-String "Obsidian.Obsidian") if (-not $obsCheck) { Write-Host " Installing Obsidian..." @@ -142,7 +142,7 @@ if (-not $obsCheck) { # === STEP 3: Claude Code ===================================================== Write-Host "" -Write-Host "${White}Step 3/7 -- Installing Claude Code${Reset}" +Write-Host "${White}Step 3/8 -- Installing Claude Code${Reset}" if (-not (Get-Command claude -ErrorAction SilentlyContinue)) { Write-Host " Installing Claude Code via winget..." winget install --id Anthropic.ClaudeCode --silent --accept-package-agreements --accept-source-agreements @@ -161,7 +161,7 @@ if (-not (Get-Command claude -ErrorAction SilentlyContinue)) { # === STEP 4: Python deps ===================================================== Write-Host "" -Write-Host "${White}Step 4/7 -- Installing Python dependencies${Reset}" +Write-Host "${White}Step 4/8 -- Installing Python dependencies${Reset}" $pipInstalled = $false @@ -214,7 +214,7 @@ if (-not $pipInstalled) { # === STEP 5: Vault setup ===================================================== Write-Host "" -Write-Host "${White}Step 5/7 -- Setting up your vault${Reset}" +Write-Host "${White}Step 5/8 -- Setting up your vault${Reset}" Write-Host "" Write-Host " Where should your second brain live?" Write-Host " ${Dim}Press Enter for default: $env:USERPROFILE\second-brain${Reset}" @@ -232,6 +232,14 @@ if ($vaultPath.Length -gt 3 -and $vaultPath.EndsWith('\')) { $vaultPath = $vaultPath.TrimEnd('\') } +# Guard: don't let vault = the repo folder (causes identical file cp errors) +$realVault = if (Test-Path $vaultPath) { (Resolve-Path $vaultPath).Path } else { $vaultPath } +$realScript = (Resolve-Path $scriptDir).Path +if ($realVault -eq $realScript) { + Write-Host " ${Orange}WARNING${Reset} Vault can't be the same folder as the repo. Using $env:USERPROFILE\second-brain instead." + $vaultPath = "$env:USERPROFILE\second-brain" +} + # Guard: check if path is an existing file (not a directory) if ((Test-Path $vaultPath) -and -not (Test-Path $vaultPath -PathType Container)) { Write-Host " ${Red}ERROR${Reset} That path points to a file, not a folder: $vaultPath" @@ -324,7 +332,7 @@ if (Test-Path "$scriptDir\memory.md") { } # Copy scripts (optional -- file processing won't work without them but vault still works) -foreach ($script in @("process_docs_to_obsidian.py", "process_files_with_gemini.py")) { +foreach ($script in @("gemini_auth.py", "process_docs_to_obsidian.py", "process_files_with_gemini.py")) { if (Test-Path "$scriptDir\scripts\$script") { Copy-Item "$scriptDir\scripts\$script" "$vaultPath\scripts\" -Force } @@ -352,34 +360,191 @@ if ($isExistingVault) { } Write-Host " ${Green}OK${Reset} Skills installed globally -- work in any folder" -# API key (masked input) +# === Step 6: Configure Gemini Authentication ================================ +Write-Host "" +Write-Host "${White}Step 6/8 -- Configure Gemini Authentication${Reset}" +Write-Host "" +Write-Host " Gemini 3 Flash processes your files (PDFs, docs, slides) into Markdown." +Write-Host " ${Dim}Choose how to authenticate:${Reset}" Write-Host "" -Write-Host " ${Dim}Get your free Google API key: https://aistudio.google.com/apikey${Reset}" -Write-Host " ${Dim}Your key will NOT be visible as you paste -- this is normal. Press Enter when done.${Reset}" -$secureKey = Read-Host " Paste your Google API key (or press Enter to skip)" -AsSecureString -$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureKey) -$apiKey = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) -[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR) +Write-Host " ${Cyan}1.${Reset} Google API Key ${Dim}(recommended for individuals -- free tier works)${Reset}" +Write-Host " ${Cyan}2.${Reset} Vertex AI ${Dim}(for Google Cloud users)${Reset}" +Write-Host " ${Cyan}3.${Reset} Skip ${Dim}(configure later by editing .env)${Reset}" +Write-Host "" + +# Prompt for authentication method choice +$authChoice = "" +while ($authChoice -notmatch "^[123]$") { + $authInput = Read-Host " Choose authentication method [1-3, default 3]" + if (-not $authInput) { $authInput = "3" } + $authChoice = $authInput + if ($authChoice -notmatch "^[123]$") { + Write-Host " ${Orange}Please enter 1, 2, or 3${Reset}" + } +} + +# Prompt for model selection (if not skipping) +$geminiModel = "" +if ($authChoice -ne "3") { + Write-Host "" + Write-Host " ${White}Which Gemini model should file processing use?${Reset}" + Write-Host " ${Dim}(Same models available for both API Key and Vertex AI)${Reset}" + Write-Host "" + Write-Host " ${Cyan}1.${Reset} gemini-3-flash-preview ${Dim}(fast, cheap, recommended)${Reset}" + Write-Host " ${Cyan}2.${Reset} gemini-3-pro-preview ${Dim}(slower, higher quality)${Reset}" + Write-Host " ${Cyan}3.${Reset} Custom model name ${Dim}(e.g., gemini-2.0-flash-exp)${Reset}" + Write-Host "" + $modelInput = Read-Host " Choose model [default: 1]" + if (-not $modelInput) { $modelInput = "1" } + + switch ($modelInput) { + "1" { $geminiModel = "gemini-3-flash-preview" } + "2" { $geminiModel = "gemini-3-pro-preview" } + "3" { + $customModel = Read-Host " Enter model name" + $geminiModel = $customModel.Trim() + if (-not $geminiModel) { + Write-Host " ${Orange}Empty input, using default: gemini-3-flash-preview${Reset}" + $geminiModel = "gemini-3-flash-preview" + } + } + default { + Write-Host " ${Orange}Invalid choice, using default: gemini-3-flash-preview${Reset}" + $geminiModel = "gemini-3-flash-preview" + } + } +} + +# Check for existing .env file +if (Test-Path "$vaultPath\.env") { + Write-Host "" + Write-Host " ${Orange}WARNING${Reset} Found existing .env file at $vaultPath\.env" + $overwriteAnswer = Read-Host " Overwrite with new configuration? [y/N]" + if (-not $overwriteAnswer) { $overwriteAnswer = "N" } + if ($overwriteAnswer -notmatch "^[Yy]") { + Write-Host " ${Dim} Keeping existing .env file${Reset}" + $authChoice = "skip" + } +} + +Write-Host "" + +# Execute the chosen authentication flow +if ($authChoice -eq "1") { + # ─── Option 1: Google API Key ──────────────────────────────────────────── + Write-Host " ${Cyan}Get your free Google API key at: https://aistudio.google.com/apikey${Reset}" + Write-Host " ${Dim}Your key will NOT be visible as you paste -- this is normal. Press Enter when done.${Reset}" + Write-Host "" + $secureKey = Read-Host " Paste your Google API key (or press Enter to skip)" -AsSecureString + $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureKey) + $apiKey = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) + [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR) + + # Trim whitespace from API key (clipboard paste often adds spaces) + if ($apiKey) { $apiKey = $apiKey.Trim() } + + if ($apiKey) { + # Write without BOM (PS5.1's Out-File -Encoding UTF8 adds a BOM that breaks Python dotenv) + $envContent = "GOOGLE_API_KEY=$apiKey`nMODEL=$geminiModel`n" + [System.IO.File]::WriteAllText("$vaultPath\.env", $envContent, (New-Object System.Text.UTF8Encoding $false)) + Write-Host " ${Green}OK${Reset} API key saved (hidden from display)" + } else { + if (-not (Test-Path "$vaultPath\.env")) { + if (Test-Path "$scriptDir\.env.example") { + Copy-Item "$scriptDir\.env.example" "$vaultPath\.env" -Force + } + } + Write-Host " ${Orange}WARNING${Reset} Skipped -- add your key to $vaultPath\.env before processing files" + } + +} elseif ($authChoice -eq "2") { + # ─── Option 2: Vertex AI ───────────────────────────────────────────────── + Write-Host " ${Cyan}Vertex AI requires a Google Cloud project. Get started at:${Reset}" + Write-Host " ${Cyan}https://console.cloud.google.com${Reset}" + Write-Host "" + + # Step 1: Collect project ID + $gcpProject = Read-Host " Google Cloud Project ID (or press Enter to skip)" + $gcpProject = $gcpProject.Trim() + + if (-not $gcpProject) { + Write-Host " ${Orange}WARNING${Reset} Skipped -- Vertex AI setup cancelled" + if (-not (Test-Path "$vaultPath\.env")) { + if (Test-Path "$scriptDir\.env.example") { + Copy-Item "$scriptDir\.env.example" "$vaultPath\.env" -Force + } + } + } else { + # Step 2: Collect location/region + Write-Host "" + $gcpLocationInput = Read-Host " Google Cloud Location [default: us-central1]" + $gcpLocation = if ($gcpLocationInput) { $gcpLocationInput.Trim() } else { "us-central1" } + + # Step 3: Write Vertex AI configuration + $envContent = @" +GOOGLE_GENAI_USE_VERTEXAI=true +GOOGLE_CLOUD_PROJECT=$gcpProject +GOOGLE_CLOUD_LOCATION=$gcpLocation +MODEL=$geminiModel +"@ + [System.IO.File]::WriteAllText("$vaultPath\.env", $envContent, (New-Object System.Text.UTF8Encoding $false)) -# Trim whitespace from API key (clipboard paste often adds spaces) -if ($apiKey) { $apiKey = $apiKey.Trim() } + Write-Host "" + Write-Host " ${Green}OK${Reset} Vertex AI configuration saved" + + # Step 4: gcloud authentication (optional) + Write-Host "" + Write-Host " ${White}GCloud Authentication Required:${Reset}" + Write-Host " Vertex AI requires application-default credentials." + Write-Host "" + + if (Get-Command gcloud -ErrorAction SilentlyContinue) { + Write-Host " ${Green}OK${Reset} gcloud CLI detected" + Write-Host "" + $gcloudAuthAnswer = Read-Host " Run 'gcloud auth application-default login' now? [Y/n]" + if (-not $gcloudAuthAnswer) { $gcloudAuthAnswer = "Y" } + + if ($gcloudAuthAnswer -match "^[Yy]") { + Write-Host "" + Write-Host " Opening browser for authentication..." + try { + gcloud auth application-default login + if ($LASTEXITCODE -eq 0) { + Write-Host "" + Write-Host " ${Green}OK${Reset} gcloud authentication successful" + } else { + Write-Host "" + Write-Host " ${Orange}WARNING${Reset} gcloud authentication failed" + Write-Host " You can run it manually later: ${Dim}gcloud auth application-default login${Reset}" + } + } catch { + Write-Host "" + Write-Host " ${Orange}WARNING${Reset} gcloud authentication failed" + Write-Host " You can run it manually later: ${Dim}gcloud auth application-default login${Reset}" + } + } else { + Write-Host " ${Dim} Skipped -- run later: gcloud auth application-default login${Reset}" + } + } else { + Write-Host " ${Orange}WARNING${Reset} gcloud CLI not found" + Write-Host " Install it: ${Cyan}https://cloud.google.com/sdk/docs/install${Reset}" + Write-Host " Then run: ${Dim}gcloud auth application-default login${Reset}" + } + } -if ($apiKey) { - # Write without BOM (PS5.1's Out-File -Encoding UTF8 adds a BOM that breaks Python dotenv) - [System.IO.File]::WriteAllText("$vaultPath\.env", "GOOGLE_API_KEY=$apiKey`n", (New-Object System.Text.UTF8Encoding $false)) - Write-Host " ${Green}OK${Reset} API key saved (hidden from display)" } else { + # ─── Option 3: Skip ────────────────────────────────────────────────────── if (-not (Test-Path "$vaultPath\.env")) { if (Test-Path "$scriptDir\.env.example") { Copy-Item "$scriptDir\.env.example" "$vaultPath\.env" -Force } } - Write-Host " ${Orange}WARNING${Reset} Add your key to $vaultPath\.env before processing files" + Write-Host " ${Orange}WARNING${Reset} Skipped -- configure authentication by editing $vaultPath\.env" } -# === STEP 6: Import existing files ========================================== +# === STEP 7: Import existing files ========================================== Write-Host "" -Write-Host "${White}Step 6/7 -- Import existing files (optional)${Reset}" +Write-Host "${White}Step 7/8 -- Import existing files (optional)${Reset}" Write-Host "" Write-Host " Do you have existing files to import? (PDFs, Word docs, slides)" Write-Host " ${Dim}Gemini 3 Flash will synthesize them into clean Markdown notes${Reset}" @@ -389,35 +554,55 @@ if ($importFolder) { $importFolder = $importFolder.Trim().Trim('"').Trim("'") } if ($importFolder -and (Test-Path $importFolder)) { + # Ask about recursive scanning + Write-Host "" + Write-Host " ${White}Search for files recursively in subdirectories?${Reset}" + Write-Host " ${Dim}Yes: scan all nested folders | No: only top-level files${Reset}" + $recursiveAnswer = Read-Host " Scan recursively? [y/N]" + if (-not $recursiveAnswer) { $recursiveAnswer = "N" } + + $recursiveFlag = "" + if ($recursiveAnswer -match "^[Yy]") { + $recursiveFlag = "--recursive" + Write-Host " ${Dim}Will scan subdirectories recursively${Reset}" + } else { + Write-Host " ${Dim}Will scan top-level files only${Reset}" + } + if (-not $pipInstalled) { Write-Host " ${Orange}WARNING${Reset} Python packages were not installed in Step 4." - Write-Host " File processing may fail. Install Python first, then run manually:" - Write-Host " python `"$vaultPath\scripts\process_docs_to_obsidian.py`" `"$importFolder`" `"$vaultPath\inbox`"" + Write-Host " File processing may fail. Install Python + deps first, then run manually:" + Write-Host " python `"$vaultPath\scripts\process_docs_to_obsidian.py`" `"$importFolder`" `"$vaultPath\inbox`" $recursiveFlag" } else { + Write-Host "" Write-Host " Processing files with Gemini 3 Flash..." $pythonCmd = $null if (Get-Command python -ErrorAction SilentlyContinue) { $pythonCmd = "python" } elseif (Get-Command py -ErrorAction SilentlyContinue) { $pythonCmd = "py" } if ($pythonCmd) { - & $pythonCmd "$vaultPath\scripts\process_docs_to_obsidian.py" $importFolder "$vaultPath\inbox" + $cmdArgs = @("$vaultPath\scripts\process_docs_to_obsidian.py", $importFolder, "$vaultPath\inbox") + if ($recursiveFlag) { $cmdArgs += $recursiveFlag } + & $pythonCmd $cmdArgs if ($LASTEXITCODE -eq 0) { + Write-Host "" Write-Host " ${Green}OK${Reset} Files processed -> saved to $vaultPath\inbox" Write-Host " ${Dim}Open Claude Code and say: 'Sort everything in inbox/ into the right folders'${Reset}" } else { - Write-Host " ${Orange}WARNING${Reset} File processing had errors -- check your API key in .env" + Write-Host "" + Write-Host " ${Orange}WARNING${Reset} File processing failed -- check your API key in .env and try again manually" } } else { Write-Host " ${Orange}WARNING${Reset} Python not found -- install Python first, then run:" - Write-Host " python `"$vaultPath\scripts\process_docs_to_obsidian.py`" `"$importFolder`" `"$vaultPath\inbox`"" + Write-Host " python `"$vaultPath\scripts\process_docs_to_obsidian.py`" `"$importFolder`" `"$vaultPath\inbox`" $recursiveFlag" } } } elseif ($importFolder) { Write-Host " ${Orange}WARNING${Reset} Folder not found: $importFolder" } -# === STEP 7: Kepano Obsidian Skills (optional) ============================== +# === STEP 8: Kepano Obsidian Skills (optional) ============================== Write-Host "" -Write-Host "${White}Step 7/7 -- Obsidian Skills by Kepano (optional)${Reset}" +Write-Host "${White}Step 8/8 -- Obsidian Skills by Kepano (optional)${Reset}" Write-Host "" Write-Host " Kepano (Steph Ango) is the CEO of Obsidian. He published a set of" Write-Host " official agent skills that teach Claude Code to natively read, write," diff --git a/setup.sh b/setup.sh index e75d22c..92804af 100755 --- a/setup.sh +++ b/setup.sh @@ -100,7 +100,7 @@ if [[ "$OSTYPE" != "darwin"* ]]; then exit 1 fi -echo -e "${WHITE}Step 1/7 — Checking dependencies + Homebrew${RESET}" +echo -e "${WHITE}Step 1/8 — Checking dependencies + Homebrew${RESET}" # ─── STEP 2: Homebrew ──────────────────────────────────────────────────────── if ! command -v brew &>/dev/null; then @@ -112,7 +112,7 @@ fi # ─── STEP 3: Obsidian ──────────────────────────────────────────────────────── echo "" -echo -e "${WHITE}Step 2/7 — Installing Obsidian${RESET}" +echo -e "${WHITE}Step 2/8 — Installing Obsidian${RESET}" if ! brew list --cask obsidian &>/dev/null 2>&1; then echo " Installing Obsidian..." brew install --cask obsidian @@ -123,7 +123,7 @@ fi # ─── STEP 4: Claude Code ───────────────────────────────────────────────────── echo "" -echo -e "${WHITE}Step 3/7 — Installing Claude Code CLI${RESET}" +echo -e "${WHITE}Step 3/8 — Installing Claude Code CLI${RESET}" if ! command -v claude &>/dev/null; then echo " Installing Claude Code..." curl -fsSL https://claude.ai/install.sh | sh @@ -135,7 +135,7 @@ fi # ─── STEP 5: Python deps (venv to avoid PEP 668 on modern macOS) ───────────── echo "" -echo -e "${WHITE}Step 4/7 — Installing Python dependencies${RESET}" +echo -e "${WHITE}Step 4/8 — Installing Python dependencies${RESET}" PIP_OK=false if command -v python3 &>/dev/null; then VENV_DIR="$HOME/.second-brain-venv" @@ -153,7 +153,7 @@ fi # ─── STEP 6: Vault setup ───────────────────────────────────────────────────── echo "" -echo -e "${WHITE}Step 5/7 — Setting up your vault${RESET}" +echo -e "${WHITE}Step 5/8 — Setting up your vault${RESET}" echo "" echo -e " Where should your second brain live?" echo -e " ${DIM}Press Enter for default: ~/second-brain${RESET}" @@ -255,6 +255,7 @@ safe_cp "$SCRIPT_DIR/skills/vault-setup/SKILL.md" "$VAULT_PATH/.claude/skills/va safe_cp "$SCRIPT_DIR/skills/daily/SKILL.md" "$VAULT_PATH/.claude/skills/daily/SKILL.md" safe_cp "$SCRIPT_DIR/skills/tldr/SKILL.md" "$VAULT_PATH/.claude/skills/tldr/SKILL.md" safe_cp "$SCRIPT_DIR/skills/file-intel/SKILL.md" "$VAULT_PATH/.claude/skills/file-intel/SKILL.md" +safe_cp "$SCRIPT_DIR/scripts/gemini_auth.py" "$VAULT_PATH/scripts/gemini_auth.py" safe_cp "$SCRIPT_DIR/scripts/process_docs_to_obsidian.py" "$VAULT_PATH/scripts/process_docs_to_obsidian.py" safe_cp "$SCRIPT_DIR/scripts/process_files_with_gemini.py" "$VAULT_PATH/scripts/process_files_with_gemini.py" @@ -273,32 +274,184 @@ else fi echo -e " ${GREEN}✓${RESET} Skills installed globally — work in any folder" -# ─── API key (masked input) ────────────────────────────────────────────────── +# ─── Step 6/8: Configure Gemini Authentication ────────────────────────────── echo "" -echo -e " ${CYAN}Get your free Google API key at: https://aistudio.google.com/apikey${RESET}" -echo -e " ${DIM}(Used by Gemini 3 Flash to process your existing files — free tier works)${RESET}" -echo -e " ${DIM}Your key will NOT be visible as you paste — this is normal. Press Enter when done.${RESET}" +echo -e "${WHITE}Step 6/8 — Configure Gemini Authentication${RESET}" +echo "" +echo -e " Gemini 3 Flash processes your files (PDFs, docs, slides) into Markdown." +echo -e " ${DIM}Choose how to authenticate:${RESET}" +echo "" +echo -e " ${CYAN}1.${RESET} Google API Key ${DIM}(recommended for individuals — free tier works)${RESET}" +echo -e " ${CYAN}2.${RESET} Vertex AI ${DIM}(for Google Cloud users)${RESET}" +echo -e " ${CYAN}3.${RESET} Skip ${DIM}(configure later by editing .env)${RESET}" echo "" -read -rsp " Paste your Google API key (or press Enter to skip): " GOOGLE_KEY -echo "" # newline after hidden input -# Trim whitespace from API key (clipboard paste often adds spaces) -GOOGLE_KEY="$(echo "$GOOGLE_KEY" | tr -d '[:space:]')" +# Prompt for authentication method choice +AUTH_CHOICE="" +while [[ ! "$AUTH_CHOICE" =~ ^[123]$ ]]; do + read -rp " Choose authentication method [1-3, default 3]: " AUTH_CHOICE + AUTH_CHOICE="${AUTH_CHOICE:-3}" + if [[ ! "$AUTH_CHOICE" =~ ^[123]$ ]]; then + echo -e " ${ORANGE}Please enter 1, 2, or 3${RESET}" + fi +done -if [ -n "$GOOGLE_KEY" ]; then - # Use printf to safely write the key (handles special characters) - printf 'GOOGLE_API_KEY=%s\n' "$GOOGLE_KEY" > "$VAULT_PATH/.env" - echo -e " ${GREEN}✓${RESET} API key saved (hidden from display)" +# Prompt for model selection (if not skipping) +if [ "$AUTH_CHOICE" != "3" ]; then + echo "" + echo -e " ${WHITE}Which Gemini model should file processing use?${RESET}" + echo -e " ${DIM}(Same models available for both API Key and Vertex AI)${RESET}" + echo "" + echo -e " ${CYAN}1.${RESET} gemini-3-flash-preview ${DIM}(fast, cheap, recommended)${RESET}" + echo -e " ${CYAN}2.${RESET} gemini-3-pro-preview ${DIM}(slower, higher quality)${RESET}" + echo -e " ${CYAN}3.${RESET} Custom model name ${DIM}(e.g., gemini-2.0-flash-exp)${RESET}" + echo "" + read -rp " Choose model [default: 1]: " MODEL_CHOICE + MODEL_CHOICE="${MODEL_CHOICE:-1}" + + # Map choice to model name + case "$MODEL_CHOICE" in + 1) + GEMINI_MODEL="gemini-3-flash-preview" + ;; + 2) + GEMINI_MODEL="gemini-3-pro-preview" + ;; + 3) + read -rp " Enter model name: " GEMINI_MODEL + GEMINI_MODEL="$(echo "$GEMINI_MODEL" | xargs)" + if [ -z "$GEMINI_MODEL" ]; then + echo -e " ${ORANGE}Empty input, using default: gemini-3-flash-preview${RESET}" + GEMINI_MODEL="gemini-3-flash-preview" + fi + ;; + *) + echo -e " ${ORANGE}Invalid choice, using default: gemini-3-flash-preview${RESET}" + GEMINI_MODEL="gemini-3-flash-preview" + ;; + esac else + # Skip mode: will copy .env.example which has MODEL set + GEMINI_MODEL="" +fi + +# Check for existing .env file +if [ -f "$VAULT_PATH/.env" ]; then + echo "" + echo -e " ${ORANGE}⚠${RESET} Found existing .env file at $VAULT_PATH/.env" + read -rp " Overwrite with new configuration? [y/N]: " OVERWRITE_ANSWER + OVERWRITE_ANSWER="${OVERWRITE_ANSWER:-N}" + if [[ ! "$OVERWRITE_ANSWER" =~ ^[Yy] ]]; then + echo -e " ${DIM} Keeping existing .env file${RESET}" + AUTH_CHOICE="skip" + fi +fi + +echo "" + +# Execute the chosen authentication flow +if [ "$AUTH_CHOICE" = "1" ]; then + # ─── Option 1: Google API Key ────────────────────────────────────────────── + echo -e " ${CYAN}Get your free Google API key at: https://aistudio.google.com/apikey${RESET}" + echo -e " ${DIM}Your key will NOT be visible as you paste — this is normal. Press Enter when done.${RESET}" + echo "" + read -rsp " Paste your Google API key (or press Enter to skip): " GOOGLE_KEY + echo "" # newline after hidden input + + # Trim whitespace from API key (clipboard paste often adds spaces) + GOOGLE_KEY="$(echo "$GOOGLE_KEY" | tr -d '[:space:]')" + + if [ -n "$GOOGLE_KEY" ]; then + # Use printf to safely write the key (handles special characters) + { + printf 'GOOGLE_API_KEY=%s\n' "$GOOGLE_KEY" + printf 'MODEL=%s\n' "$GEMINI_MODEL" + } > "$VAULT_PATH/.env" + echo -e " ${GREEN}✓${RESET} API key saved (hidden from display)" + else + if [ ! -f "$VAULT_PATH/.env" ]; then + safe_cp "$SCRIPT_DIR/.env.example" "$VAULT_PATH/.env" + fi + echo -e " ${ORANGE}⚠${RESET} Skipped — add your key to $VAULT_PATH/.env before processing files" + fi + +elif [ "$AUTH_CHOICE" = "2" ]; then + # ─── Option 2: Vertex AI ─────────────────────────────────────────────────── + echo -e " ${CYAN}Vertex AI requires a Google Cloud project. Get started at:${RESET}" + echo -e " ${CYAN}https://console.cloud.google.com${RESET}" + echo "" + + # Step 1: Collect project ID + read -rp " Google Cloud Project ID (or press Enter to skip): " GCP_PROJECT + GCP_PROJECT="$(echo "$GCP_PROJECT" | xargs)" # trim whitespace + + if [ -z "$GCP_PROJECT" ]; then + echo -e " ${ORANGE}⚠${RESET} Skipped — Vertex AI setup cancelled" + if [ ! -f "$VAULT_PATH/.env" ]; then + safe_cp "$SCRIPT_DIR/.env.example" "$VAULT_PATH/.env" + fi + else + # Step 2: Collect location/region + echo "" + read -rp " Google Cloud Location [default: us-central1]: " GCP_LOCATION + GCP_LOCATION="${GCP_LOCATION:-us-central1}" + GCP_LOCATION="$(echo "$GCP_LOCATION" | xargs)" # trim whitespace + + # Step 3: Write Vertex AI configuration + { + printf 'GOOGLE_GENAI_USE_VERTEXAI=%s\n' "true" + printf 'GOOGLE_CLOUD_PROJECT=%s\n' "$GCP_PROJECT" + printf 'GOOGLE_CLOUD_LOCATION=%s\n' "$GCP_LOCATION" + printf 'MODEL=%s\n' "$GEMINI_MODEL" + } > "$VAULT_PATH/.env" + + echo "" + echo -e " ${GREEN}✓${RESET} Vertex AI configuration saved" + + # Step 4: gcloud authentication (optional) + echo "" + echo -e " ${WHITE}GCloud Authentication Required:${RESET}" + echo -e " Vertex AI requires application-default credentials." + echo "" + + if command -v gcloud &>/dev/null; then + echo -e " ${GREEN}✓${RESET} gcloud CLI detected" + echo "" + read -rp " Run 'gcloud auth application-default login' now? [Y/n]: " GCLOUD_AUTH_ANSWER + GCLOUD_AUTH_ANSWER="${GCLOUD_AUTH_ANSWER:-Y}" + + if [[ "$GCLOUD_AUTH_ANSWER" =~ ^[Yy] ]]; then + echo "" + echo " Opening browser for authentication..." + if gcloud auth application-default login; then + echo "" + echo -e " ${GREEN}✓${RESET} gcloud authentication successful" + else + echo "" + echo -e " ${ORANGE}⚠${RESET} gcloud authentication failed" + echo -e " You can run it manually later: ${DIM}gcloud auth application-default login${RESET}" + fi + else + echo -e " ${DIM} Skipped — run later: gcloud auth application-default login${RESET}" + fi + else + echo -e " ${ORANGE}⚠${RESET} gcloud CLI not found" + echo -e " Install it: ${CYAN}https://cloud.google.com/sdk/docs/install${RESET}" + echo -e " Then run: ${DIM}gcloud auth application-default login${RESET}" + fi + fi + +else + # ─── Option 3: Skip ──────────────────────────────────────────────────────── if [ ! -f "$VAULT_PATH/.env" ]; then safe_cp "$SCRIPT_DIR/.env.example" "$VAULT_PATH/.env" fi - echo -e " ${ORANGE}⚠${RESET} Skipped — add your key to $VAULT_PATH/.env before processing files" + echo -e " ${ORANGE}⚠${RESET} Skipped — configure authentication by editing $VAULT_PATH/.env" fi # ─── Import existing files ─────────────────────────────────────────────────── echo "" -echo -e "${WHITE}Step 6/7 — Import existing files (optional)${RESET}" +echo -e "${WHITE}Step 7/8 — Import existing files (optional)${RESET}" echo "" echo -e " Do you have existing files to import? (PDFs, Word docs, slides)" echo -e " ${DIM}Gemini 3 Flash will synthesize them into clean Markdown notes${RESET}" @@ -306,15 +459,30 @@ echo "" read -rp " Folder path to import (or press Enter to skip): " IMPORT_FOLDER if [ -n "$IMPORT_FOLDER" ] && [ -d "$IMPORT_FOLDER" ]; then + # Ask about recursive scanning + echo "" + echo -e " ${WHITE}Search for files recursively in subdirectories?${RESET}" + echo -e " ${DIM}Yes: scan all nested folders | No: only top-level files${RESET}" + read -rp " Scan recursively? [y/N]: " RECURSIVE_ANSWER + RECURSIVE_ANSWER="${RECURSIVE_ANSWER:-N}" + + RECURSIVE_FLAG="" + if [[ "$RECURSIVE_ANSWER" =~ ^[Yy] ]]; then + RECURSIVE_FLAG="--recursive" + echo -e " ${DIM}Will scan subdirectories recursively${RESET}" + else + echo -e " ${DIM}Will scan top-level files only${RESET}" + fi + if [ "$PIP_OK" = false ]; then echo -e " ${ORANGE}⚠${RESET} Python packages were not installed in Step 4." echo -e " File processing may fail. Install Python + deps first, then run manually:" - echo -e " ${DIM}python3 \"$VAULT_PATH/scripts/process_docs_to_obsidian.py\" \"$IMPORT_FOLDER\" \"$VAULT_PATH/inbox\"${RESET}" + echo -e " ${DIM}python3 \"$VAULT_PATH/scripts/process_docs_to_obsidian.py\" \"$IMPORT_FOLDER\" \"$VAULT_PATH/inbox\" $RECURSIVE_FLAG${RESET}" else echo "" echo " Processing files with Gemini 3 Flash..." if "$HOME/.second-brain-venv/bin/python3" "$VAULT_PATH/scripts/process_docs_to_obsidian.py" \ - "$IMPORT_FOLDER" "$VAULT_PATH/inbox"; then + "$IMPORT_FOLDER" "$VAULT_PATH/inbox" $RECURSIVE_FLAG; then echo "" echo -e " ${GREEN}✓${RESET} Files processed → saved to $VAULT_PATH/inbox" echo -e " ${DIM}Open Claude Code and say: \"Sort everything in inbox/ into the right folders\"${RESET}" @@ -329,7 +497,7 @@ fi # ─── Kepano Obsidian Skills (optional) ────────────────────────────────────── echo "" -echo -e "${WHITE}Step 7/7 — Obsidian Skills by Kepano (optional)${RESET}" +echo -e "${WHITE}Step 8/8 — Obsidian Skills by Kepano (optional)${RESET}" echo "" echo -e " Kepano (Steph Ango) is the CEO of Obsidian. He published a set of" echo -e " official agent skills that teach Claude Code to natively read, write,"