All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 1m25s
- Add DeepSeek, OpenRouter, Mistral, Z.AI, LM Studio as AI providers with editable model names via Combobox in admin settings - Fix OpenRouter broken by normalizeProvider bug in config.ts - Convert agent-created notes from Markdown to HTML (TipTap rich text) - Add Notification model + in-app notifications for agent results - Agent notification click opens the created note directly - Add note count display on notebook and inbox headers - Fix checklist toggle in card view (persist state via localCheckItems) - Add checklist creation option in tabs/list view (dropdown on + button) - Fix image description ENOENT error with HTTP fallback - Improve UI contrast across all themes (input, border, checkbox visibility) - Add font family setting (Inter vs System Default) in Appearance settings - Fix CSS font-sans variable conflict (removed dead Geist references) - Update README with new features and 8 providers Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
511 lines
17 KiB
PowerShell
511 lines
17 KiB
PowerShell
# ============================================================
|
|
# Memento - Local Deploy Script (Windows PowerShell)
|
|
# ============================================================
|
|
# Usage:
|
|
# .\scripts\deploy-local.ps1 # Full setup
|
|
# .\scripts\deploy-local.ps1 -EnvOnly # Generate .env only
|
|
# .\scripts\deploy-local.ps1 -Start # Start the app (dev or prod)
|
|
# .\scripts\deploy-local.ps1 -Migrate # Run database migrations
|
|
# .\scripts\deploy-local.ps1 -Install # Install npm dependencies
|
|
# ============================================================
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[switch]$EnvOnly = $false,
|
|
[switch]$Start = $false,
|
|
[switch]$Migrate = $false,
|
|
[switch]$Install = $false,
|
|
[switch]$Full = $false
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
$ProjectDir = Split-Path -Parent $ScriptDir
|
|
$AppDir = Join-Path $ProjectDir "memento-note"
|
|
$McpDir = Join-Path $ProjectDir "mcp-server"
|
|
$EnvFile = Join-Path $AppDir ".env"
|
|
$McpEnvFile = Join-Path $McpDir ".env"
|
|
|
|
# -----------------------------------------------------------
|
|
# Helpers
|
|
# -----------------------------------------------------------
|
|
function Write-Info($msg) { Write-Host "[INFO] $msg" -ForegroundColor Blue }
|
|
function Write-Ok($msg) { Write-Host "[OK] $msg" -ForegroundColor Green }
|
|
function Write-Warn($msg) { Write-Host "[WARN] $msg" -ForegroundColor Yellow }
|
|
function Write-Err($msg) { Write-Host "[ERROR] $msg" -ForegroundColor Red; exit 1 }
|
|
function Write-Step($msg) { Write-Host "`n==> $msg" -ForegroundColor Cyan }
|
|
|
|
function Get-RandomSecret {
|
|
$bytes = New-Object byte[] 32
|
|
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($bytes)
|
|
[Convert]::ToBase64String($bytes)
|
|
}
|
|
|
|
function Ask-Input {
|
|
param([string]$Prompt, [string]$Default = "")
|
|
if ($Default) {
|
|
Write-Host " ? $Prompt [$Default]: " -ForegroundColor Cyan -NoNewline
|
|
} else {
|
|
Write-Host " ? $Prompt: " -ForegroundColor Cyan -NoNewline
|
|
}
|
|
$result = Read-Host
|
|
if ([string]::IsNullOrWhiteSpace($result)) { $result = $Default }
|
|
return $result
|
|
}
|
|
|
|
function Ask-Required {
|
|
param([string]$Prompt)
|
|
while ($true) {
|
|
Write-Host " ? $Prompt (required): " -ForegroundColor Cyan -NoNewline
|
|
$result = Read-Host
|
|
if (-not [string]::IsNullOrWhiteSpace($result)) { return $result }
|
|
Write-Host " This field is required." -ForegroundColor Red
|
|
}
|
|
}
|
|
|
|
function Ask-Email {
|
|
param([string]$Prompt)
|
|
while ($true) {
|
|
Write-Host " ? $Prompt (required): " -ForegroundColor Cyan -NoNewline
|
|
$result = Read-Host
|
|
if (-not [string]::IsNullOrWhiteSpace($result) -and $result -match '^[^@]+@[^@]+\.[^@]+$') {
|
|
return $result
|
|
}
|
|
Write-Host " Please enter a valid email address." -ForegroundColor Red
|
|
}
|
|
}
|
|
|
|
# -----------------------------------------------------------
|
|
# Check dependencies
|
|
# -----------------------------------------------------------
|
|
function Check-Deps {
|
|
Write-Step "Checking dependencies..."
|
|
|
|
$missing = @()
|
|
|
|
# Node.js
|
|
$node = Get-Command node -ErrorAction SilentlyContinue
|
|
if ($node) {
|
|
$nodeVersion = & node -v 2>$null
|
|
Write-Ok "Node.js $nodeVersion"
|
|
} else {
|
|
$missing += "node"
|
|
}
|
|
|
|
# npm
|
|
$npm = Get-Command npm -ErrorAction SilentlyContinue
|
|
if ($npm) {
|
|
$npmVersion = & npm -v 2>$null
|
|
Write-Ok "npm $npmVersion"
|
|
} else {
|
|
$missing += "npm"
|
|
}
|
|
|
|
# PostgreSQL
|
|
$psql = Get-Command psql -ErrorAction SilentlyContinue
|
|
if ($psql) {
|
|
$pgVersion = & psql --version 2>$null | Select-Object -First 1
|
|
Write-Ok "PostgreSQL client $pgVersion"
|
|
} else {
|
|
$missing += "psql"
|
|
}
|
|
|
|
if ($missing.Count -gt 0) {
|
|
Write-Host ""
|
|
Write-Warn "Missing dependencies: $($missing -join ', ')"
|
|
Write-Host ""
|
|
Write-Host " Installation instructions:" -ForegroundColor White
|
|
Write-Host ""
|
|
|
|
foreach ($dep in $missing) {
|
|
switch ($dep) {
|
|
"node" {
|
|
Write-Host " Node.js + npm:"
|
|
Write-Host " Download: https://nodejs.org/"
|
|
Write-Host " Winget: winget install OpenJS.NodeJS.LTS"
|
|
Write-Host ""
|
|
}
|
|
"npm" {
|
|
Write-Host " npm:"
|
|
Write-Host " Usually included with Node.js installer"
|
|
Write-Host ""
|
|
}
|
|
"psql" {
|
|
Write-Host " PostgreSQL:"
|
|
Write-Host " Download: https://www.postgresql.org/download/windows/"
|
|
Write-Host " Winget: winget install PostgreSQL.PostgreSQL"
|
|
Write-Host ""
|
|
}
|
|
}
|
|
}
|
|
|
|
$cont = Ask-Input "Continue anyway?" "N"
|
|
if ($cont -notmatch "^[Yy]") { exit 1 }
|
|
}
|
|
}
|
|
|
|
# -----------------------------------------------------------
|
|
# Generate .env
|
|
# -----------------------------------------------------------
|
|
function Generate-Env {
|
|
Write-Step "Configuring Memento for local development"
|
|
|
|
if (Test-Path $EnvFile) {
|
|
Write-Warn ".env already exists at $EnvFile"
|
|
$confirm = Ask-Input "Overwrite?" "N"
|
|
if ($confirm -notmatch "^[Yy]") {
|
|
Write-Info "Keeping existing .env"
|
|
return
|
|
}
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host " This wizard will guide you through the configuration." -ForegroundColor White
|
|
Write-Host " Press Enter to accept defaults in [brackets]." -ForegroundColor White
|
|
Write-Host ""
|
|
|
|
# ---- Database ----
|
|
Write-Step "Database configuration"
|
|
|
|
$dbHost = Ask-Input "PostgreSQL host" "localhost"
|
|
$dbPort = Ask-Input "PostgreSQL port" "5432"
|
|
$dbName = Ask-Input "PostgreSQL database name" "memento"
|
|
$dbUser = Ask-Input "PostgreSQL username" "memento"
|
|
$dbPass = Ask-Input "PostgreSQL password" "memento"
|
|
|
|
$dbUrl = "postgresql://${dbUser}:${dbPass}@${dbHost}:${dbPort}/${dbName}"
|
|
|
|
# Check DB connectivity
|
|
if (Get-Command psql -ErrorAction SilentlyContinue) {
|
|
Write-Step "Testing database connection..."
|
|
$env:PGPASSWORD = $dbPass
|
|
try {
|
|
& psql -h $dbHost -p $dbPort -U $dbUser -d $dbName -c "SELECT 1" 2>&1 | Out-Null
|
|
Write-Ok "Database connection successful"
|
|
} catch {
|
|
Write-Warn "Could not connect to database. It may not exist yet."
|
|
Write-Host ""
|
|
Write-Host " Create it with:"
|
|
Write-Host " createdb $dbName"
|
|
Write-Host " psql -c `"CREATE USER $dbUser WITH PASSWORD '$dbPass';`""
|
|
Write-Host " psql -c `"GRANT ALL PRIVILEGES ON DATABASE $dbName TO $dbUser;`""
|
|
Write-Host ""
|
|
$cont = Ask-Input "Continue anyway?" "Y"
|
|
if ($cont -match "^[Nn]") { exit 1 }
|
|
}
|
|
Remove-Item Env:PGPASSWORD
|
|
}
|
|
|
|
# ---- Core ----
|
|
Write-Step "Core configuration"
|
|
|
|
$url = Ask-Input "App URL (NEXTAUTH_URL)" "http://localhost:3000"
|
|
$secret = Get-RandomSecret
|
|
Write-Info "Auto-generated NEXTAUTH_SECRET"
|
|
$adminEmail = Ask-Email "Admin email (first user with this email becomes ADMIN)"
|
|
|
|
$allowReg = Ask-Input "Allow public registration" "true"
|
|
if ($allowReg -match "^[Yy]|^[Yy]es|^true|^1") { $allowReg = "true" } else { $allowReg = "false" }
|
|
|
|
# ---- AI Provider ----
|
|
Write-Step "AI Provider configuration"
|
|
|
|
Write-Host " Choose your AI provider:"
|
|
Write-Host " 1) OpenAI"
|
|
Write-Host " 2) Ollama (local)"
|
|
Write-Host " 3) Custom OpenAI-compatible (OpenRouter, Groq, Together, etc.)"
|
|
Write-Host " 4) Skip AI configuration"
|
|
Write-Host ""
|
|
$aiChoice = Ask-Input "Choice" "4"
|
|
|
|
$aiTagsProvider = $aiTagsModel = $aiEmbedProvider = $aiEmbedModel = ""
|
|
$aiChatProvider = $aiChatModel = $openaiKey = $customKey = $customUrl = $ollamaUrl = ""
|
|
|
|
switch ($aiChoice) {
|
|
"1" {
|
|
$aiTagsProvider = "openai"; $aiTagsModel = "gpt-4o-mini"
|
|
$aiEmbedProvider = "openai"; $aiEmbedModel = "text-embedding-3-small"
|
|
$aiChatProvider = "openai"; $aiChatModel = "gpt-4o-mini"
|
|
$openaiKey = Ask-Required "OpenAI API Key"
|
|
}
|
|
"2" {
|
|
$aiTagsProvider = "ollama"; $aiTagsModel = "granite4:latest"
|
|
$aiEmbedProvider = "ollama"; $aiEmbedModel = "embeddinggemma:latest"
|
|
$aiChatProvider = "ollama"; $aiChatModel = "granite4:latest"
|
|
$ollamaUrl = Ask-Input "Ollama base URL" "http://localhost:11434"
|
|
}
|
|
"3" {
|
|
$aiTagsProvider = "custom"
|
|
$aiEmbedProvider = "custom"
|
|
$aiChatProvider = "custom"
|
|
$customKey = Ask-Required "Custom provider API Key"
|
|
$customUrl = Ask-Required "Custom provider base URL"
|
|
$aiTagsModel = Ask-Input "Model for tags" "gpt-4o-mini"
|
|
$aiEmbedModel = Ask-Input "Model for embeddings" "text-embedding-3-small"
|
|
$aiChatModel = Ask-Input "Model for chat" "gpt-4o-mini"
|
|
}
|
|
"4" {
|
|
Write-Info "Skipping AI configuration. You can configure it later in the admin panel."
|
|
}
|
|
default { Write-Err "Invalid choice" }
|
|
}
|
|
|
|
# ---- MCP ----
|
|
Write-Step "MCP Server configuration (optional)"
|
|
|
|
$mcpEnable = Ask-Input "Configure MCP server?" "no"
|
|
$mcpEnable = ($mcpEnable -match "^[Yy]|^[Yy]es|^true|^1")
|
|
$mcpMode = "sse"; $mcpPort = "3001"; $mcpServerUrl = ""
|
|
|
|
if ($mcpEnable) {
|
|
$mcpMode = Ask-Input "MCP mode (sse or stdio)" "sse"
|
|
$mcpPort = Ask-Input "MCP port" "3001"
|
|
$mcpServerUrl = "http://localhost:${mcpPort}"
|
|
}
|
|
|
|
# ---- Email ----
|
|
Write-Step "Email configuration (optional, needed for password reset)"
|
|
|
|
Write-Host " Choose an email provider:"
|
|
Write-Host " 1) Resend"
|
|
Write-Host " 2) SMTP"
|
|
Write-Host " 3) Skip"
|
|
Write-Host ""
|
|
$emailChoice = Ask-Input "Choice" "3"
|
|
|
|
$resendKey = $smtpHost = $smtpPort = $smtpUser = $smtpPass = $smtpFrom = ""
|
|
|
|
switch ($emailChoice) {
|
|
"1" { $resendKey = Ask-Required "Resend API Key" }
|
|
"2" {
|
|
$smtpHost = Ask-Required "SMTP Host"
|
|
$smtpPort = Ask-Input "SMTP Port" "587"
|
|
$smtpUser = Ask-Required "SMTP Username"
|
|
$smtpPass = Ask-Required "SMTP Password"
|
|
$smtpFrom = Ask-Required "SMTP From email"
|
|
}
|
|
}
|
|
|
|
# ---- Write .env ----
|
|
Write-Step "Writing .env"
|
|
|
|
$envContent = @"
|
|
# =============================================================================
|
|
# Memento - Local Environment (auto-generated by deploy-local.ps1)
|
|
# =============================================================================
|
|
# Generated on $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
|
|
# =============================================================================
|
|
|
|
# Core
|
|
DATABASE_URL="$dbUrl"
|
|
NEXTAUTH_SECRET="$secret"
|
|
NEXTAUTH_URL="$url"
|
|
ADMIN_EMAIL="$adminEmail"
|
|
ALLOW_REGISTRATION="$allowReg"
|
|
"@
|
|
|
|
if ($aiChoice -ne "4") {
|
|
$envContent += @"
|
|
|
|
# AI - Tags
|
|
AI_PROVIDER_TAGS=$aiTagsProvider
|
|
AI_MODEL_TAGS="$aiTagsModel"
|
|
|
|
# AI - Embeddings
|
|
AI_PROVIDER_EMBEDDING=$aiEmbedProvider
|
|
AI_MODEL_EMBEDDING="$aiEmbedModel"
|
|
|
|
# AI - Chat
|
|
AI_PROVIDER_CHAT=$aiChatProvider
|
|
AI_MODEL_CHAT="$aiChatModel"
|
|
"@
|
|
if ($openaiKey) { $envContent += "`nOPENAI_API_KEY=`"$openaiKey`"" }
|
|
if ($customKey) { $envContent += "`nCUSTOM_OPENAI_API_KEY=`"$customKey`"`nCUSTOM_OPENAI_BASE_URL=`"$customUrl`"" }
|
|
if ($ollamaUrl) { $envContent += "`nOLLAMA_BASE_URL=`"$ollamaUrl`"" }
|
|
}
|
|
|
|
if ($mcpEnable) {
|
|
$envContent += @"
|
|
|
|
# MCP Server
|
|
MCP_SERVER_MODE="$mcpMode"
|
|
MCP_SERVER_URL="$mcpServerUrl"
|
|
"@
|
|
} else {
|
|
$envContent += @"
|
|
|
|
# MCP Server (disabled)
|
|
MCP_SERVER_MODE="disabled"
|
|
"@
|
|
}
|
|
|
|
if ($resendKey) { $envContent += "`n`n# Email - Resend`nRESEND_API_KEY=`"$resendKey`"" }
|
|
if ($smtpHost) {
|
|
$envContent += @"
|
|
|
|
# Email - SMTP
|
|
SMTP_HOST="$smtpHost"
|
|
SMTP_PORT="$smtpPort"
|
|
SMTP_USER="$smtpUser"
|
|
SMTP_PASS="$smtpPass"
|
|
SMTP_FROM="$smtpFrom"
|
|
"@
|
|
}
|
|
|
|
$envContent | Set-Content -Path $EnvFile -Encoding UTF8
|
|
Write-Ok ".env created at $EnvFile"
|
|
|
|
# MCP server .env
|
|
if ($mcpEnable) {
|
|
$mcpContent = @"
|
|
# =============================================================================
|
|
# MCP Server - Local Environment (auto-generated by deploy-local.ps1)
|
|
# =============================================================================
|
|
DATABASE_URL="$dbUrl"
|
|
MCP_MODE="$mcpMode"
|
|
PORT="$mcpPort"
|
|
APP_BASE_URL="$url"
|
|
"@
|
|
$mcpContent | Set-Content -Path $McpEnvFile -Encoding UTF8
|
|
Write-Ok ".env created at $McpEnvFile"
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host " Configuration summary:" -ForegroundColor White
|
|
Write-Host " URL: $url"
|
|
Write-Host " Admin email: $adminEmail"
|
|
Write-Host " Database: $dbHost`:$dbPort/$dbName"
|
|
Write-Host " AI provider: $(if ($aiChoice -eq '4') { 'skipped' } else { $aiTagsProvider })"
|
|
Write-Host " MCP server: $(if ($mcpEnable) { "enabled ($mcpMode)" } else { 'disabled' })"
|
|
Write-Host " Email: $(if ($emailChoice -eq '3') { 'skipped' } else { 'configured' })"
|
|
}
|
|
|
|
# -----------------------------------------------------------
|
|
# Install dependencies
|
|
# -----------------------------------------------------------
|
|
function Install-Deps {
|
|
Write-Step "Installing dependencies..."
|
|
Push-Location $AppDir
|
|
|
|
if (-not (Test-Path "node_modules")) {
|
|
Write-Info "Running npm install..."
|
|
& npm install
|
|
Write-Ok "Dependencies installed"
|
|
} else {
|
|
Write-Info "node_modules exists, checking for updates..."
|
|
& npm install
|
|
}
|
|
|
|
Pop-Location
|
|
}
|
|
|
|
# -----------------------------------------------------------
|
|
# Run migrations
|
|
# -----------------------------------------------------------
|
|
function Run-Migrations {
|
|
if (-not (Test-Path $EnvFile)) {
|
|
Write-Err ".env not found. Run: .\scripts\deploy-local.ps1 -EnvOnly"
|
|
}
|
|
|
|
Write-Step "Running database migrations..."
|
|
Push-Location $AppDir
|
|
|
|
try {
|
|
& npx prisma migrate deploy 2>&1
|
|
} catch {
|
|
Write-Warn "migrate deploy failed, trying db push..."
|
|
try {
|
|
& npx prisma db push --skip-generate 2>&1
|
|
} catch {
|
|
Write-Err "Database migration failed"
|
|
}
|
|
}
|
|
|
|
Write-Ok "Database migrations complete"
|
|
Pop-Location
|
|
}
|
|
|
|
# -----------------------------------------------------------
|
|
# Start app
|
|
# -----------------------------------------------------------
|
|
function Start-App {
|
|
if (-not (Test-Path $EnvFile)) {
|
|
Write-Err ".env not found. Run: .\scripts\deploy-local.ps1 -EnvOnly"
|
|
}
|
|
|
|
Push-Location $AppDir
|
|
|
|
Write-Host ""
|
|
Write-Host " Choose mode:"
|
|
Write-Host " 1) Development (npm run dev, with hot reload)"
|
|
Write-Host " 2) Production (npm run build + npm start)"
|
|
Write-Host ""
|
|
$mode = Ask-Input "Choice" "1"
|
|
|
|
switch ($mode) {
|
|
"2" {
|
|
Write-Step "Building for production..."
|
|
& npm run build
|
|
|
|
Write-Step "Starting in production mode..."
|
|
Write-Info "Starting server on http://localhost:3000"
|
|
Write-Info "Press Ctrl+C to stop"
|
|
Write-Host ""
|
|
& npm start
|
|
}
|
|
default {
|
|
Write-Step "Starting in development mode..."
|
|
Write-Info "Starting dev server on http://localhost:3000"
|
|
Write-Info "Press Ctrl+C to stop"
|
|
Write-Host ""
|
|
& npm run dev
|
|
}
|
|
}
|
|
|
|
Pop-Location
|
|
}
|
|
|
|
# -----------------------------------------------------------
|
|
# Full setup
|
|
# -----------------------------------------------------------
|
|
function Full-Setup {
|
|
Check-Deps
|
|
Generate-Env
|
|
Install-Deps
|
|
Run-Migrations
|
|
|
|
Write-Host ""
|
|
Write-Host "============================================" -ForegroundColor Green
|
|
Write-Host " Setup complete!" -ForegroundColor Green
|
|
Write-Host "============================================" -ForegroundColor Green
|
|
Write-Host ""
|
|
|
|
$admEmail = (Select-String -Path $EnvFile -Pattern '^ADMIN_EMAIL=(.+)$').Matches[0].Groups[1].Value -replace '"',''
|
|
$appUrl = (Select-String -Path $EnvFile -Pattern '^NEXTAUTH_URL=(.+)$').Matches[0].Groups[1].Value -replace '"',''
|
|
|
|
Write-Host " Next steps:" -ForegroundColor White
|
|
Write-Host " 1. Start the app: .\scripts\deploy-local.ps1 -Start"
|
|
Write-Host " 2. Open: $appUrl"
|
|
Write-Host " 3. Register with: $admEmail" -ForegroundColor White
|
|
Write-Host " 4. That account will automatically get ADMIN role"
|
|
Write-Host ""
|
|
}
|
|
|
|
# -----------------------------------------------------------
|
|
# Main
|
|
# -----------------------------------------------------------
|
|
if ($EnvOnly) {
|
|
Check-Deps
|
|
Generate-Env
|
|
} elseif ($Start) {
|
|
Start-App
|
|
} elseif ($Migrate) {
|
|
Run-Migrations
|
|
} elseif ($Install) {
|
|
Install-Deps
|
|
} else {
|
|
# Default: full setup
|
|
Full-Setup
|
|
}
|