Compare commits

..

2 Commits

8 changed files with 269 additions and 4 deletions
+63
View File
@@ -72,6 +72,54 @@ def detect_browser_from_profile(profile_path: str) -> str | None:
return None
def find_firefox_profile() -> str | None:
"""Auto-detect Firefox profile directory cross-platform."""
import sys
home = os.path.expanduser("~")
candidates = []
if sys.platform == "win32":
appdata = os.environ.get("APPDATA", "")
if appdata:
profiles_dir = os.path.join(appdata, "Mozilla", "Firefox", "Profiles")
else:
profiles_dir = os.path.join(home, "AppData", "Roaming", "Mozilla", "Firefox", "Profiles")
elif sys.platform == "darwin":
profiles_dir = os.path.join(home, "Library", "Application Support", "Firefox", "Profiles")
else:
profiles_dir = os.path.join(home, ".mozilla", "firefox")
if os.path.isdir(profiles_dir):
for entry in os.listdir(profiles_dir):
full = os.path.join(profiles_dir, entry)
if os.path.isdir(full) and (".default" in entry.lower() or ".dev-edition" in entry.lower()):
candidates.append(full)
candidates.sort(key=lambda p: "default-release" in p.lower(), reverse=True)
if candidates:
logger.info("Auto-detected Firefox profile: %s", candidates[0])
return candidates[0]
return None
def find_chrome_profile() -> str | None:
"""Auto-detect Chrome/Chromium profile directory cross-platform."""
import sys
home = os.path.expanduser("~")
if sys.platform == "win32":
base = os.path.join(os.environ.get("LOCALAPPDATA", ""), "Google", "Chrome", "User Data")
elif sys.platform == "darwin":
base = os.path.join(home, "Library", "Application Support", "Google", "Chrome")
else:
base = os.path.join(home, ".config", "google-chrome")
default_profile = os.path.join(base, "Default")
if os.path.isdir(default_profile):
logger.info("Auto-detected Chrome profile: %s", default_profile)
return default_profile
return None
def copy_firefox_profile(src_path: str) -> str:
"""Copy essential Firefox profile files to a temp dir for Playwright."""
essential = ['cookies.sqlite', 'webappsstore.sqlite', 'permissions.sqlite']
@@ -745,12 +793,27 @@ def cleanup_chrome():
async def scrape_facebook(profile_path: str | None = None, force: bool = False) -> dict:
"""Dispatcher — Firefox primary, browser-use Agent fallback."""
effective_path = profile_path or FX_PROFILE
# Auto-detect Firefox profile if none provided
if not effective_path:
detected = find_firefox_profile()
if detected:
effective_path = detected
os.environ["FX_PROFILE"] = detected
browser_type = detect_browser_from_profile(effective_path)
if not browser_type and CHROME_PROFILE:
browser_type = detect_browser_from_profile(CHROME_PROFILE)
effective_path = CHROME_PROFILE
# Auto-detect Chrome profile if still nothing
if not browser_type:
detected_chrome = find_chrome_profile()
if detected_chrome:
effective_path = detected_chrome
browser_type = 'chromium'
logger.info("Detected browser: %s (profile: %s)", browser_type or "none", effective_path)
# Firefox primary (raw Playwright, stealth)
+5
View File
@@ -0,0 +1,5 @@
fastapi>=0.115.0
uvicorn>=0.34.0
playwright>=1.49.0
browser-use>=0.1.0
langchain-ollama>=0.2.0
+5 -4
View File
@@ -5,13 +5,14 @@
"scripts": {
"dev": "npm run dev:precheck & npm run dev:ollama & npm run dev:start",
"dev:signaling": "node signaling-server.mjs",
"dev:open": "powershell -NoProfile -Command \"Start-Sleep 8; Start-Process 'http://localhost:3001/splash'\"",
"dev:open": "node scripts/open-browser.mjs",
"dev:start": "concurrently -n AI,BROWSE,SIGNAL,NEXT,OPEN -c cyan,magenta,yellow,green,white \"npm run dev:rust\" \"npm run dev:browser-use\" \"npm run dev:signaling\" \"npm run dev:next\" \"npm run dev:open\"",
"dev:next": "next dev -p 3006",
"dev:precheck": "powershell -NoProfile -Command \"Get-NetTCPConnection -State Listen | Where-Object { $_.LocalPort -in 3001,3006,3007,3008 } | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue; Write-Host ('Freed port '+$_.LocalPort) }\"",
"dev:ollama": "powershell -NoProfile -Command \"$ollama = if (Get-Command ollama -ErrorAction SilentlyContinue) { 'ollama' } else { Join-Path $env:LOCALAPPDATA 'Programs\\Ollama\\ollama.exe' }; if (-not (Get-Process ollama -ErrorAction SilentlyContinue)) { Start-Process $ollama -ArgumentList 'serve' -WindowStyle Hidden; Start-Sleep 3 }; exit 0\"",
"dev:precheck": "node scripts/precheck.mjs",
"dev:ollama": "node scripts/ensure-ollama.mjs",
"dev:rust": "node ai-server/index.mjs",
"dev:browser-use": "set FX_PROFILE=C:\\Users\\USER-PC\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\h8p11vlj.default-release && cd browser-use-service && python main.py",
"dev:browser-use": "cd browser-use-service && node ../scripts/run-python.mjs main.py",
"setup": "node scripts/setup.mjs",
"build": "next build",
"start": "next start -p 3006",
"lint": "eslint"
+47
View File
@@ -0,0 +1,47 @@
import { execSync, spawn } from "node:child_process"
import { platform } from "node:os"
function isRunning() {
try {
if (platform() === "win32") {
execSync('tasklist /FI "IMAGENAME eq ollama.exe" 2>nul | findstr ollama', { stdio: "pipe", timeout: 3000 })
} else {
execSync('pgrep -x ollama', { stdio: "pipe", timeout: 3000 })
}
return true
} catch {
return false
}
}
function findOllama() {
if (platform() === "win32") {
try {
return execSync("where ollama", { encoding: "utf8", timeout: 3000 }).trim().split("\n")[0]
} catch {}
const local = `${process.env.LOCALAPPDATA}\\Programs\\Ollama\\ollama.exe`
const programFiles = `${process.env.PROGRAMFILES}\\Ollama\\ollama.exe`
if (local) try { execSync(`"${local}" --version`, { stdio: "ignore", timeout: 2000 }); return local } catch {}
if (programFiles) try { execSync(`"${programFiles}" --version`, { stdio: "ignore", timeout: 2000 }); return programFiles } catch {}
} else {
try { return execSync("which ollama", { encoding: "utf8", timeout: 3000 }).trim() } catch {}
}
return null
}
if (!isRunning()) {
const bin = findOllama()
if (!bin) {
console.error("Ollama not found. Install from https://ollama.com")
process.exit(1)
}
console.log("Starting Ollama...")
if (platform() === "win32") {
spawn(bin, ["serve"], { stdio: "ignore", detached: true, windowsHide: true }).unref()
} else {
spawn(bin, ["serve"], { stdio: "ignore", detached: true }).unref()
}
execSync("sleep 3", { stdio: "ignore", timeout: 5000 })
} else {
console.log("Ollama already running")
}
+23
View File
@@ -0,0 +1,23 @@
import { execSync } from "node:child_process"
import { platform } from "node:os"
const url = process.argv[2] || "http://localhost:3001/splash"
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
async function main() {
await sleep(8000)
try {
if (platform() === "win32") {
execSync(`start "" "${url}"`, { stdio: "ignore", timeout: 5000 })
} else if (platform() === "darwin") {
execSync(`open "${url}"`, { stdio: "ignore", timeout: 5000 })
} else {
execSync(`xdg-open "${url}"`, { stdio: "ignore", timeout: 5000 })
}
} catch (e) {
console.error("Failed to open browser:", e.message)
}
}
main()
+30
View File
@@ -0,0 +1,30 @@
import { execSync } from "node:child_process"
import { platform } from "node:os"
const PORTS = [3001, 3006, 3007, 3008]
if (platform() === "win32") {
for (const port of PORTS) {
try {
const out = execSync(`netstat -ano | findstr "LISTENING" | findstr ":${port} "`, { encoding: "utf8", timeout: 5000 })
const lines = out.trim().split("\n").filter(Boolean)
for (const line of lines) {
const parts = line.trim().split(/\s+/)
const pid = parts[parts.length - 1]
if (pid) {
try { execSync(`taskkill /F /PID ${pid}`, { stdio: "ignore", timeout: 3000 }); console.log(`Freed port ${port} (PID ${pid})`) } catch {}
}
}
} catch {}
}
} else {
for (const port of PORTS) {
try {
const pid = execSync(`lsof -ti:${port} 2>/dev/null`, { encoding: "utf8", timeout: 5000 }).trim()
if (pid) {
execSync(`kill -9 ${pid}`, { stdio: "ignore", timeout: 3000 })
console.log(`Freed port ${port} (PID ${pid})`)
}
} catch {}
}
}
+27
View File
@@ -0,0 +1,27 @@
import { execSync, spawn } from "node:child_process"
import { platform } from "node:os"
function detectPython() {
const candidates = platform() === "win32" ? ["python", "python3"] : ["python3", "python"]
for (const cmd of candidates) {
try {
const out = execSync(platform() === "win32" ? `where ${cmd}` : `which ${cmd}`, { encoding: "utf8", timeout: 5000 })
const path = out.trim().split("\n")[0].replace(/\r$/, "")
if (path) return path
} catch {}
}
console.error("Python not found. Install Python 3 from https://python.org")
process.exit(1)
}
const PYTHON = detectPython()
const script = process.argv[2]
const args = process.argv.slice(3)
if (!script) {
console.error("Usage: node run-python.mjs <script> [args...]")
process.exit(1)
}
const proc = spawn(PYTHON, [script, ...args], { stdio: "inherit" })
proc.on("exit", (code) => process.exit(code ?? 1))
+69
View File
@@ -0,0 +1,69 @@
import { execSync } from "node:child_process"
import { existsSync, copyFileSync } from "node:fs"
import { platform } from "node:os"
const SEP = platform() === "win32" ? "&" : ";"
function detectPython() {
const candidates = platform() === "win32" ? ["python", "python3"] : ["python3", "python"]
for (const cmd of candidates) {
try {
execSync(`${cmd} --version`, { stdio: "pipe", timeout: 5000 })
return cmd
} catch {}
}
console.error("Python not found. Install Python 3 from https://python.org")
process.exit(1)
}
function detectPip(python) {
const candidates = platform() === "win32" ? ["pip", "pip3"] : ["pip3", "pip"]
for (const cmd of candidates) {
try {
execSync(`${cmd} --version`, { stdio: "pipe", timeout: 5000 })
return cmd
} catch {}
}
return `${python} -m pip`
}
const PY = detectPython()
const PIP = detectPip(PY)
function run(cmd, label) {
console.log(`\n── ${label} ──`)
try {
execSync(cmd, { stdio: "inherit", timeout: 120000 })
} catch (e) {
console.error(` ✗ Failed: ${e.message}`)
process.exit(1)
}
}
console.log("=== CoastIT CRM Setup ===\n")
// 1. Node dependencies
run("npm install", "Installing Node.js dependencies")
// 2. Python dependencies
run(`cd browser-use-service ${SEP} ${PIP} install -r requirements.txt`, "Installing Python dependencies")
// 3. Playwright browsers
run(`${PY} -m playwright install firefox chromium`, "Installing Playwright browsers")
// 4. .env file
if (!existsSync(".env.local")) {
console.log("\n── Creating .env.local ──")
copyFileSync(".env.example", ".env.local")
console.log(" ✓ Created .env.local from .env.example — edit it with your settings")
} else {
console.log("\n── .env.local already exists, skipping ──")
}
// 5. Ollama model
console.log("\n── Next steps ──")
console.log(" 1. Make sure PostgreSQL is running with database 'crm'")
console.log(" 2. Pull the Ollama model: ollama pull dolphin-llama3:8b")
console.log(" 3. Edit .env.local with your settings")
console.log(" 4. Run: npm run dev")
console.log("\n=== Setup complete! ===")