diff --git a/.claude/commands/daily-operator.md b/.claude/commands/daily-operator.md new file mode 100644 index 0000000..a18f1cf --- /dev/null +++ b/.claude/commands/daily-operator.md @@ -0,0 +1,22 @@ +# Daily Operator + +Run the weekday live-to-sheet workflow, review result, and append a stable status record. + +## Steps +1. Ensure log folder exists: + - `powershell -NoProfile -Command "New-Item -ItemType Directory -Force output/logs | Out-Null"` +2. Run: + - `cmd /c scripts\\daily_market_intel_to_sheet.bat` +3. Review: + - latest `output/logs/daily_market_intel_*.log` + - CSV/sync outputs expected for this run +4. Append status line to `output/logs/daily-operator-status.log`: + - `powershell -NoProfile -Command "$ts=Get-Date -Format 'yyyy-MM-dd HH:mm:ss'; Add-Content -Path 'output/logs/daily-operator-status.log' -Value \"$ts | PASS|FAIL | summary | next_action\""` +5. Return: + - `PASS` or `FAIL` + - one-line reason + - immediate next action if failed + +## Notes +- Always append exactly one status line per operator run. +- Escalate credential errors explicitly. diff --git a/.claude/commands/run-content-intel.md b/.claude/commands/run-content-intel.md new file mode 100644 index 0000000..4a1e83a --- /dev/null +++ b/.claude/commands/run-content-intel.md @@ -0,0 +1,20 @@ +# Run Content Intel + +Run the existing live automation path with minimal manual orchestration. + +## Steps +1. Ensure log folder exists: + - `powershell -NoProfile -Command "New-Item -ItemType Directory -Force output/logs | Out-Null"` +2. Check whether `scripts/run_pipeline_live_to_google_sheets.bat` exists. +3. If it exists, run: + - `cmd /c scripts\\run_pipeline_live_to_google_sheets.bat` +4. If it does not exist, run: + - `cmd /c scripts\\run_pipeline_live.bat` +5. Return a short completion summary: + - script used + - pass/fail + - latest log file path in `output/logs` (if available) + +## Notes +- Do not modify the underlying pipeline. +- Reuse existing scripts exactly as-is. diff --git a/.claude/commands/sync-market-sheet.md b/.claude/commands/sync-market-sheet.md new file mode 100644 index 0000000..b32975e --- /dev/null +++ b/.claude/commands/sync-market-sheet.md @@ -0,0 +1,21 @@ +# Sync Market Sheet + +Run only the Google Sheets sync setup/sync pathway without rerunning the full live content pipeline. + +## Steps +1. Ensure log folder exists: + - `powershell -NoProfile -Command "New-Item -ItemType Directory -Force output/logs | Out-Null"` +2. Run sync-only script: + - `cmd /c scripts\\setup_google_sheets_sync.bat` +3. Validate expected sync outputs still exist: + - `output/` + - `output/logs/` + - latest sync artifacts referenced by `docs/google-sheets-sync-setup.md` (if present) +4. Return: + - pass/fail + - missing artifacts (if any) + - next action + +## Notes +- This command is intentionally sync-only. +- Do not trigger `run_pipeline_live.bat` from this command. diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..2d52e2f --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,31 @@ +{ + "permissions": { + "allow": [ + "Bash(scripts/*.bat)", + "Bash(powershell -File scripts/*.ps1)", + "Bash(cmd /c scripts/*.bat)", + "Read(*)", + "Write(output/**)", + "Write(.claude/**)", + "Write(docs/**)", + "Write(scripts/**)" + ], + "deny": [ + "Bash(rm -rf *)", + "Bash(git reset --hard *)" + ] + }, + "hooks": { + "notification": { + "command": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/claude_notify.ps1", + "events": [ + "needs_input", + "run_completed", + "run_failed" + ] + } + }, + "env": { + "BIZDEEDZ_LOG_DIR": "output/logs" + } +} diff --git a/.claude/subagents/output-reviewer.md b/.claude/subagents/output-reviewer.md new file mode 100644 index 0000000..f9d559e --- /dev/null +++ b/.claude/subagents/output-reviewer.md @@ -0,0 +1,30 @@ +# Subagent: output-reviewer + +You are a focused quality-review subagent for pipeline outputs. + +## Responsibilities +- Review CSV and sheet-sync artifacts for completeness. +- Surface missing/blank key fields and suspicious row counts. +- Flag records needing manual review. +- Keep findings short and actionable for a daily operator. + +## Review checklist +1. File presence + - Required files exist in `output/` (or configured output path). +2. Structural quality + - Header row present + - Expected columns present +3. Data quality + - No unexpected fully blank rows + - Key fields (company/deed/contact/market signal) not mostly empty +4. Volume sanity + - Row count is non-zero and within expected recent range + +## Output format +- `PASS` or `FAIL` +- bullets of findings (max 5) +- `Next action:` one line + +## Guardrails +- Do not mutate source outputs during review. +- If uncertainty is high, mark `REVIEW` and request human check. diff --git a/.claude/subagents/repo-runner.md b/.claude/subagents/repo-runner.md new file mode 100644 index 0000000..22b03e9 --- /dev/null +++ b/.claude/subagents/repo-runner.md @@ -0,0 +1,27 @@ +# Subagent: repo-runner + +You are a focused execution subagent for BizDeedz operations. + +## Responsibilities +- Run existing repository scripts (primarily in `scripts/*.bat` and `scripts/*.ps1`). +- Capture exit codes and summarize failures in plain language. +- Verify expected output paths exist after runs. +- Never redesign or replace the underlying pipeline. + +## Workflow +1. Confirm target script exists. +2. Execute with `cmd /c` for `.bat` or `powershell -File` for `.ps1`. +3. Record: + - command + - exit code + - key stderr/stdout failure lines +4. Check output directories/files expected by the operator command. +5. Return concise report: + - PASS/FAIL + - root cause guess + - next operator action + +## Guardrails +- Prefer existing wrappers over direct internal steps. +- Keep changes non-destructive. +- If credentials fail, clearly call out credential remediation. diff --git a/docs/automation-runbook.md b/docs/automation-runbook.md new file mode 100644 index 0000000..2e0cf60 --- /dev/null +++ b/docs/automation-runbook.md @@ -0,0 +1,53 @@ +# BizDeedz Automation Runbook (Windows-first) + +## What runs automatically +- Scheduled task runs `scripts/daily_market_intel_to_sheet.bat` each weekday morning. +- Wrapper prefers `scripts/run_pipeline_live_to_google_sheets.bat` when available. +- If that wrapper is unavailable, it falls back to `scripts/run_pipeline_live.bat`. +- Logs are always written under `output/logs`. + +## Claude operator commands +- `run-content-intel`: runs live path (prefers live-to-sheet wrapper). +- `sync-market-sheet`: sync-only path (does **not** run full live pipeline). +- `daily-operator`: runs wrapper + appends stable status line in `output/logs/daily-operator-status.log`. + +## Notifications +- Claude notifications are handled by: + - `scripts/claude_notify.ps1` +- The hook is configured in: + - `.claude/settings.json` + +## Install weekday schedule +Run in PowerShell: + +```powershell +powershell -ExecutionPolicy Bypass -File scripts/install_task_scheduler.ps1 +``` + +Optional custom time: + +```powershell +powershell -ExecutionPolicy Bypass -File scripts/install_task_scheduler.ps1 -RunTime 07:30 +``` + +## Manual fallback commands +- Full live-to-sheet path: + - `cmd /c scripts\\daily_market_intel_to_sheet.bat` +- Existing live pipeline only: + - `cmd /c scripts\\run_pipeline_live.bat` +- Existing live-to-sheet wrapper directly: + - `cmd /c scripts\\run_pipeline_live_to_google_sheets.bat` +- Google Sheets sync setup/sync-only path: + - `cmd /c scripts\\setup_google_sheets_sync.bat` + +## If credentials fail +1. Re-run `scripts/setup_google_sheets_sync.bat`. +2. Confirm local credential files/tokens are still valid. +3. Re-run `scripts/daily_market_intel_to_sheet.bat`. +4. If still failing, open latest `output/logs` file and escalate first auth-related error line. + +## Quick remove/edit scheduler task +- Edit start time: + - `schtasks /Change /TN "BizDeedz Daily Market Intel" /ST HH:MM` +- Remove task: + - `schtasks /Delete /TN "BizDeedz Daily Market Intel" /F` diff --git a/scripts/claude_notify.ps1 b/scripts/claude_notify.ps1 new file mode 100644 index 0000000..6fa4f2a --- /dev/null +++ b/scripts/claude_notify.ps1 @@ -0,0 +1,24 @@ +[CmdletBinding()] +param( + [string]$Event, + [string]$Title, + [string]$Message +) + +$ErrorActionPreference = 'SilentlyContinue' + +if (-not $Title) { $Title = if ($env:CLAUDE_HOOK_EVENT) { "Claude Code: $($env:CLAUDE_HOOK_EVENT)" } else { 'Claude Code' } } +if (-not $Message) { $Message = if ($env:CLAUDE_HOOK_MESSAGE) { $env:CLAUDE_HOOK_MESSAGE } else { 'Automation event triggered.' } } +if (-not $Event) { $Event = if ($env:CLAUDE_HOOK_EVENT) { $env:CLAUDE_HOOK_EVENT } else { 'event' } } + +Add-Type -AssemblyName System.Windows.Forms | Out-Null +Add-Type -AssemblyName System.Drawing | Out-Null + +$icon = New-Object System.Windows.Forms.NotifyIcon +$icon.Icon = [System.Drawing.SystemIcons]::Information +$icon.BalloonTipTitle = $Title +$icon.BalloonTipText = "[$Event] $Message" +$icon.Visible = $true +$icon.ShowBalloonTip(8000) +Start-Sleep -Seconds 9 +$icon.Dispose() diff --git a/scripts/daily_market_intel_to_sheet.bat b/scripts/daily_market_intel_to_sheet.bat new file mode 100644 index 0000000..c6f3bf2 --- /dev/null +++ b/scripts/daily_market_intel_to_sheet.bat @@ -0,0 +1,36 @@ +@echo off +setlocal enabledelayedexpansion + +set "SCRIPT_DIR=%~dp0" +for %%I in ("%SCRIPT_DIR%..") do set "REPO_ROOT=%%~fI" +set "LOG_DIR=%REPO_ROOT%\output\logs" + +if not exist "%LOG_DIR%" ( + mkdir "%LOG_DIR%" >nul 2>&1 +) + +for /f %%I in ('powershell -NoProfile -Command "Get-Date -Format yyyyMMdd_HHmmss"') do set "RUN_TS=%%I" +set "LOG_FILE=%LOG_DIR%\daily_market_intel_%RUN_TS%.log" + +set "TARGET_SCRIPT=%SCRIPT_DIR%run_pipeline_live_to_google_sheets.bat" +if not exist "%TARGET_SCRIPT%" ( + set "TARGET_SCRIPT=%SCRIPT_DIR%run_pipeline_live.bat" +) + +if not exist "%TARGET_SCRIPT%" ( + echo [%date% %time%] ERROR: No runnable pipeline script found.>>"%LOG_FILE%" + echo Expected one of: run_pipeline_live_to_google_sheets.bat or run_pipeline_live.bat>>"%LOG_FILE%" + exit /b 1 +) + +echo [%date% %time%] INFO: Running "%TARGET_SCRIPT%">>"%LOG_FILE%" +cmd /c ""%TARGET_SCRIPT%"" >>"%LOG_FILE%" 2>&1 +set "EXIT_CODE=%ERRORLEVEL%" + +if not "%EXIT_CODE%"=="0" ( + echo [%date% %time%] ERROR: Daily market intel run failed with exit code %EXIT_CODE%.>>"%LOG_FILE%" + exit /b %EXIT_CODE% +) + +echo [%date% %time%] INFO: Daily market intel run completed successfully.>>"%LOG_FILE%" +exit /b 0 diff --git a/scripts/install_task_scheduler.ps1 b/scripts/install_task_scheduler.ps1 new file mode 100644 index 0000000..09d3931 --- /dev/null +++ b/scripts/install_task_scheduler.ps1 @@ -0,0 +1,30 @@ +[CmdletBinding()] +param( + [string]$TaskName = "BizDeedz Daily Market Intel", + [string]$RunTime = "08:00" +) + +$ErrorActionPreference = 'Stop' + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$repoRoot = Resolve-Path (Join-Path $scriptDir "..") +$wrapperScript = Join-Path $scriptDir "daily_market_intel_to_sheet.bat" + +if (-not (Test-Path $wrapperScript)) { + throw "Wrapper script not found: $wrapperScript" +} + +$action = New-ScheduledTaskAction -Execute "cmd.exe" -Argument "/c \"$wrapperScript\"" -WorkingDirectory $repoRoot +$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday,Tuesday,Wednesday,Thursday,Friday -At $RunTime +$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Hours 2) + +Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Settings $settings -Description "Runs BizDeedz daily market intel pipeline and sheet sync wrapper." -Force | Out-Null + +Write-Host "Installed task: $TaskName" +Write-Host "Run time (local): $RunTime on weekdays" +Write-Host "Wrapper: $wrapperScript" +Write-Host "" +Write-Host "Edit task schedule:" +Write-Host " schtasks /Change /TN \"$TaskName\" /ST HH:MM" +Write-Host "Remove task:" +Write-Host " schtasks /Delete /TN \"$TaskName\" /F"