Files
Z User e45b68bb8b feat: IMAP mail sync with thread-to-contact matching (PRD §6.1)
Real IMAP connection via imapflow library:
- Connects to IMAP server (IMAPFlow, TLS on port 993)
- Fetches recent messages (configurable limit, default 20)
- Extracts: from, fromEmail, subject, date, snippet (200 chars)
- Thread-to-contact matching (PRD §6.1):
  1. Match sender email against persons table
  2. If no person match, extract domain and match against company.website
  3. Returns matchedCompany + matchedPerson for each email
- Audit-logged (action: crm.mailsync:imap)
- Zod validated (host, port, user, password, folder, limit)
- 502 on IMAP connection failure with detail
- 400 on validation failure

Tested: invalid creds → 502 'IMAP connection failed', missing host → 400
2026-10-27 22:50:50 +00:00

330 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
# ---
# name: pdf-setup
# author: Z.AI
# version: "1.0"
# description: Environment setup for the PDF skill. Checks and installs all required dependencies.
# ---
#
# Installs only dependencies required by the PDF skill.
set -euo pipefail
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
ok() { echo -e " ${GREEN}✓${NC} $1"; }
fail() { echo -e " ${RED}✗${NC} $1"; }
warn() { echo -e " ${YELLOW}○${NC} $1"; }
info() { echo -e " ${BLUE}→${NC} $1"; }
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
echo "============================================"
echo " PDF Skill — Environment Setup"
echo "============================================"
echo ""
# ── Detect platform ──
OS="$(uname -s)"
ARCH="$(uname -m)"
echo "Platform: $OS $ARCH"
echo ""
# ── 0. macOS: Homebrew ──
if [ "$OS" = "Darwin" ]; then
echo "--- Homebrew (macOS package manager) ---"
if command -v brew &>/dev/null; then
BREW_VER=$(brew --version 2>/dev/null | head -1)
ok "brew ($BREW_VER)"
else
fail "brew not found — most dependencies below need Homebrew on macOS"
info "Install: /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
fi
echo ""
fi
# ── 1. Python 3 ──
echo "--- Python ---"
if command -v python3 &>/dev/null; then
PY_VER=$(python3 --version 2>&1)
ok "python3 ($PY_VER)"
# macOS: warn if using system Python
if [ "$OS" = "Darwin" ]; then
PY_PATH=$(which python3 2>/dev/null)
if [[ "$PY_PATH" == "/usr/bin/python3" ]]; then
warn "Using macOS system Python (limited). Recommend: brew install python3"
fi
fi
else
fail "python3 not found"
case "$OS" in
Darwin) info "Install: brew install python3" ;;
Linux) info "Install: sudo apt install python3 python3-pip (Debian/Ubuntu)"
info " sudo dnf install python3 python3-pip (Fedora/RHEL)" ;;
*) info "Install: https://www.python.org/downloads/" ;;
esac
fi
# ── 2. pip ──
echo ""
echo "--- pip ---"
if python3 -m pip --version &>/dev/null 2>&1; then
PIP_VER=$(python3 -m pip --version 2>/dev/null | head -1)
ok "pip ($PIP_VER)"
else
fail "pip not found"
case "$OS" in
Darwin) info "Install: python3 -m ensurepip --upgrade"
info " or: brew install python3 (includes pip)" ;;
Linux) info "Install: sudo apt install python3-pip (Debian/Ubuntu)" ;;
*) info "Install: python3 -m ensurepip --upgrade" ;;
esac
fi
# ── 3. Python packages (pip) ──
echo ""
echo "--- Python Packages ---"
PY_PKGS=(
"pikepdf:pikepdf"
"pdfplumber:pdfplumber"
"pypdf:pypdf"
"reportlab:reportlab"
"pymupdf:PyMuPDF"
)
MISSING_PY=()
for entry in "${PY_PKGS[@]}"; do
mod="${entry%%:*}"
pkg="${entry##*:}"
if python3 -c "import $mod" 2>/dev/null; then
ver=$(python3 -c "import $mod; print(getattr($mod, '__version__', 'installed'))" 2>/dev/null)
ok "$pkg ($ver)"
else
fail "$pkg not installed"
MISSING_PY+=("$pkg")
fi
done
if [ ${#MISSING_PY[@]} -gt 0 ]; then
echo ""
if [ -t 0 ]; then
read -p " Install missing Python packages? [Y/n] " -n 1 -r REPLY
echo ""
REPLY=${REPLY:-Y}
else
warn "Non-interactive mode — skipping auto-install. Run interactively or install manually."
REPLY=N
fi
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
python3 -m pip install -q "${MISSING_PY[@]}" 2>/dev/null \
|| python3 -m pip install -q --user "${MISSING_PY[@]}" 2>/dev/null \
|| python3 -m pip install -q --break-system-packages "${MISSING_PY[@]}" 2>/dev/null \
|| { fail "pip install failed. Try manually: pip install ${MISSING_PY[*]}"; }
ok "Installed: ${MISSING_PY[*]}"
fi
fi
# ── 4. Node.js ──
echo ""
echo "--- Node.js ---"
if command -v node &>/dev/null; then
NODE_VER=$(node --version)
ok "node ($NODE_VER)"
else
fail "node not found"
case "$OS" in
Darwin) info "Install: brew install node" ;;
Linux) info "Install: curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -"
info " sudo apt install -y nodejs" ;;
*) info "Install: https://nodejs.org/" ;;
esac
fi
# ── 5. npm ──
echo ""
echo "--- npm ---"
if command -v npm &>/dev/null; then
NPM_VER=$(npm --version 2>/dev/null)
ok "npm ($NPM_VER)"
else
fail "npm not found"
case "$OS" in
Darwin) info "Install: brew install node (includes npm)" ;;
Linux) info "Install: comes with nodejs" ;;
*) info "Install: https://nodejs.org/" ;;
esac
fi
# ── 6. Playwright + Chromium ──
echo ""
echo "--- Playwright (HTML→PDF engine) ---"
if node -e "require('playwright')" 2>/dev/null; then
PW_VER=$(node -e "console.log(require('playwright/package.json').version)" 2>/dev/null)
ok "playwright ($PW_VER)"
else
fail "playwright not installed"
info "Install: npm install -g playwright@1.50.0"
fi
# Check Chromium
if [ "$OS" = "Darwin" ]; then
PW_CACHE="$HOME/Library/Caches/ms-playwright"
else
PW_CACHE="$HOME/.cache/ms-playwright"
fi
if ls "$PW_CACHE"/chromium-* &>/dev/null 2>&1; then
CR_DIR=$(ls -d "$PW_CACHE"/chromium-* 2>/dev/null | tail -1)
ok "chromium ($(basename "$CR_DIR"))"
else
fail "chromium not installed"
info "Install: npx playwright install chromium"
if [ "$OS" = "Linux" ]; then
info " npx playwright install-deps (system libs, needs sudo)"
fi
fi
# ── 7. Tectonic (LaTeX engine, optional) ──
echo ""
echo "--- Tectonic (LaTeX→PDF, optional) ---"
BUNDLED="$SCRIPT_DIR/tectonic"
if [ -x "$BUNDLED" ]; then
if [ "$OS" = "Darwin" ] && [ "$ARCH" = "arm64" ]; then
ok "tectonic (bundled, macOS arm64)"
else
warn "bundled tectonic is macOS arm64 only — cannot run on $OS $ARCH"
if command -v tectonic &>/dev/null; then
TEC_VER=$(tectonic --version 2>&1 | head -1)
ok "tectonic (system: $TEC_VER)"
else
fail "tectonic not in PATH"
case "$OS" in
Darwin) info "Install: brew install tectonic" ;;
Linux) info "Install: conda install -c conda-forge tectonic"
info " or: conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/ && conda install tectonic" ;;
MINGW*|MSYS*|CYGWIN*) info "Install: scoop install tectonic / choco install tectonic" ;;
esac
fi
fi
elif command -v tectonic &>/dev/null; then
TEC_VER=$(tectonic --version 2>&1 | head -1)
ok "tectonic ($TEC_VER)"
elif [ -x "$HOME/tectonic" ]; then
ok "tectonic (~/tectonic)"
else
warn "tectonic not installed (needed only for LaTeX/academic PDFs)"
case "$OS" in
Darwin) info "Install: brew install tectonic" ;;
Linux) info "Install: conda install -c conda-forge tectonic"
info " or: conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/ && conda install tectonic" ;;
MINGW*|MSYS*|CYGWIN*) info "Install: scoop install tectonic / choco install tectonic" ;;
esac
fi
# ── 8. LibreOffice (optional, for Office→PDF conversion) ──
echo ""
echo "--- LibreOffice (optional, Office→PDF) ---"
if command -v soffice &>/dev/null; then
LO_VER=$(soffice --version 2>/dev/null | head -1)
ok "libreoffice ($LO_VER)"
else
warn "libreoffice not installed (needed only for .docx/.xlsx→PDF conversion)"
case "$OS" in
Darwin) info "Install: brew install --cask libreoffice" ;;
Linux) info "Install: sudo apt install libreoffice-core (Debian/Ubuntu)" ;;
esac
fi
# ── 9. CJK Fonts ──
echo ""
echo "--- CJK Fonts ---"
FONT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)/fonts"
if [ -d "$FONT_DIR" ]; then
FONT_COUNT=$(find "$FONT_DIR" -name "*.ttf" -o -name "*.otf" 2>/dev/null | head -20 | wc -l | tr -d ' ')
ok "fonts directory ($FONT_COUNT font files in $FONT_DIR)"
else
warn "no fonts/ directory found — CJK PDFs may have missing glyphs"
info "Expected at: $FONT_DIR"
fi
# Check system CJK fonts
if [ "$OS" = "Darwin" ]; then
if [ -f "$HOME/Library/Fonts/NotoSansSC[wght].ttf" ] || fc-list :lang=zh 2>/dev/null | head -1 | grep -q . \
|| ls "$HOME/Library/Fonts/"*NotoSansSC* &>/dev/null 2>&1; then
ok "CJK fonts available (Noto Sans SC or system)"
else
warn "no common CJK system fonts found"
fi
elif [ "$OS" = "Linux" ]; then
if fc-list :lang=zh 2>/dev/null | head -1 | grep -q .; then
ok "system CJK fonts available (fc-list)"
else
warn "no CJK fonts found. Install: sudo apt install fonts-noto-cjk"
fi
fi
# ── 9b. Noto Serif SC (Chinese serif for covers / academic docs) ──
echo ""
echo "--- Noto Serif SC (Chinese serif, covers/academic) ---"
ENV_SETUP_FONT="$(cd "$SCRIPT_DIR/../.." && pwd)/env-setup/fonts/noto-serif-sc/NotoSerifSC-Regular.ttf"
NOTO_SERIF_FOUND=0
# Method 1: Check via fc-list
if command -v fc-list &>/dev/null; then
if fc-list | grep -qi "Noto Serif SC"; then
ok "Noto Serif SC (fc-list)"
NOTO_SERIF_FOUND=1
fi
fi
# Method 2: Check common font directories
if [ $NOTO_SERIF_FOUND -eq 0 ]; then
for CHECK_PATH in \
"$HOME/Library/Fonts/NotoSerifSC"*.ttf \
"/Library/Fonts/NotoSerifSC"*.ttf \
"$HOME/.local/share/fonts/NotoSerifSC"*.ttf \
"/usr/share/fonts/"*"/NotoSerifSC"*.ttf; do
if ls $CHECK_PATH &>/dev/null 2>&1; then
ok "Noto Serif SC ($CHECK_PATH)"
NOTO_SERIF_FOUND=1
break
fi
done
fi
# Method 3: Check env-setup bundled font
if [ $NOTO_SERIF_FOUND -eq 0 ]; then
if [ -f "$ENV_SETUP_FONT" ]; then
warn "Noto Serif SC not installed system-wide, but available in env-setup bundle"
info "Run this setup interactively to auto-install, or copy manually"
if [ -t 0 ]; then
read -p " Install Noto Serif SC to user fonts? [Y/n] " -n 1 -r REPLY
echo ""
REPLY=${REPLY:-Y}
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
USER_FONT_DIR="$HOME/Library/Fonts"
if [ "$OS" = "Linux" ]; then
USER_FONT_DIR="$HOME/.local/share/fonts"
fi
mkdir -p "$USER_FONT_DIR"
cp "$ENV_SETUP_FONT" "$USER_FONT_DIR/"
if command -v fc-cache &>/dev/null; then
fc-cache -f "$USER_FONT_DIR" 2>/dev/null
fi
ok "Noto Serif SC installed to $USER_FONT_DIR"
NOTO_SERIF_FOUND=1
fi
fi
else
fail "Noto Serif SC not found"
info "Download from: https://fonts.google.com/noto/specimen/Noto+Serif+SC"
case "$OS" in
Linux) info "Or install: sudo apt install fonts-noto-cjk" ;;
esac
fi
fi
# ── Summary ──
echo ""
echo "============================================"
echo " Setup complete."
echo " Run 'python3 pdf.py env.check' for detailed status."
echo " Run 'python3 pdf.py env.fix' to auto-install Python deps."
echo "============================================"