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
This commit is contained in:
1 parent
7acc0c5451
commit
e45b68bb8b
1089 files changed
+260614
No files matched your search
Executable
+49
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
# Lightweight environment check for PDF skill.
|
||||
# Exit 0 = all OK, exit 1 = missing dependencies.
|
||||
# Also resolves and exports PDF_SKILL_DIR and FONT_DIR.
|
||||
# Usage: source env_check.sh (preferred, exports vars to caller)
|
||||
# or: bash env_check.sh [--quiet]
|
||||
QUIET=false; [ "${1:-}" = "--quiet" ] && QUIET=true
|
||||
FAIL=0
|
||||
check() { local desc="$1"; shift; if ! "$@" &>/dev/null; then $QUIET || echo "MISSING: $desc"; FAIL=1; fi; }
|
||||
|
||||
# ── Resolve PDF_SKILL_DIR & FONT_DIR ──
|
||||
_ENV_CHECK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
|
||||
PDF_SKILL_DIR="$(cd "$_ENV_CHECK_DIR/.." && pwd)"
|
||||
export PDF_SKILL_DIR
|
||||
|
||||
if [ "$(uname -s)" = "Darwin" ]; then
|
||||
FONT_DIR="${HOME}/Library/Fonts"
|
||||
else
|
||||
FONT_DIR="/usr/share/fonts"
|
||||
fi
|
||||
export FONT_DIR
|
||||
|
||||
check "python3" command -v python3
|
||||
check "node" command -v node
|
||||
check "pikepdf" python3 -c "import pikepdf"
|
||||
check "pdfplumber" python3 -c "import pdfplumber"
|
||||
check "pypdf" python3 -c "import pypdf"
|
||||
check "reportlab" python3 -c "import reportlab"
|
||||
check "PyMuPDF" python3 -c "import fitz"
|
||||
check "playwright" node -e "require('playwright')"
|
||||
|
||||
# LaTeX engine (tectonic) — needed for Academic pipeline
|
||||
if [ -x "$PDF_SKILL_DIR/scripts/tectonic" ]; then
|
||||
: # bundled binary OK
|
||||
elif command -v tectonic &>/dev/null; then
|
||||
: # system tectonic OK
|
||||
else
|
||||
$QUIET || echo "MISSING: tectonic (needed for LaTeX/Academic PDFs)"
|
||||
FAIL=1
|
||||
fi
|
||||
|
||||
# Font check: verify CJK fonts are available
|
||||
if command -v fc-list &>/dev/null; then
|
||||
fc-list :lang=zh 2>/dev/null | grep -qi "noto\|simhei\|wenquanyi" || { $QUIET || echo "MISSING: CJK fonts"; FAIL=1; }
|
||||
fi
|
||||
|
||||
$QUIET || echo "PDF_SKILL_DIR=$PDF_SKILL_DIR"
|
||||
$QUIET || echo "FONT_DIR=$FONT_DIR"
|
||||
return $FAIL 2>/dev/null || exit $FAIL
|
||||
Executable
+82
@@ -0,0 +1,82 @@
|
||||
/usr/share/fonts/truetype/chinese/NotoSansSC[wght].ttf
|
||||
/usr/share/fonts/truetype/chinese/SarasaMonoSC-Regular.ttf
|
||||
/usr/share/fonts/truetype/chinese/SarasaMonoSC-Bold.ttf
|
||||
/usr/share/fonts/truetype/chinese/SarasaMonoSC-Italic.ttf
|
||||
/usr/share/fonts/truetype/chinese/SarasaMonoSC-BoldItalic.ttf
|
||||
/usr/share/fonts/truetype/chinese/SarasaMonoSC-Light.ttf
|
||||
/usr/share/fonts/truetype/chinese/SarasaMonoSC-LightItalic.ttf
|
||||
/usr/share/fonts/truetype/chinese/SarasaMonoSC-ExtraLight.ttf
|
||||
/usr/share/fonts/truetype/chinese/SarasaMonoSC-ExtraLightItalic.ttf
|
||||
/usr/share/fonts/truetype/chinese/SarasaMonoSC-SemiBold.ttf
|
||||
/usr/share/fonts/truetype/chinese/SarasaMonoSC-SemiBoldItalic.ttf
|
||||
/usr/share/fonts/truetype/chinese/NotoSansSC[wght].ttf
|
||||
/usr/share/fonts/truetype/chinese/NotoSansSC-Thin.ttf
|
||||
/usr/share/fonts/truetype/chinese/NotoSansSC-ExtraLight.ttf
|
||||
/usr/share/fonts/truetype/chinese/NotoSansSC-Light.ttf
|
||||
/usr/share/fonts/truetype/chinese/NotoSansSC-Regular.ttf
|
||||
/usr/share/fonts/truetype/chinese/NotoSansSC-Medium.ttf
|
||||
/usr/share/fonts/truetype/chinese/NotoSansSC-SemiBold.ttf
|
||||
/usr/share/fonts/truetype/chinese/NotoSansSC-Bold.ttf
|
||||
/usr/share/fonts/truetype/chinese/NotoSansSC-ExtraBold.ttf
|
||||
/usr/share/fonts/truetype/chinese/NotoSansSC-Black.ttf
|
||||
/usr/share/fonts/truetype/chinese/LiberationSans-Regular.ttf
|
||||
/usr/share/fonts/truetype/chinese/LiberationMono-Regular.ttf
|
||||
/usr/share/fonts/truetype/chinese/LiberationSerif-Regular.ttf
|
||||
/usr/share/fonts/truetype/noto-serif-sc/NotoSerifSC-VariableFont_wght.ttf
|
||||
/usr/share/fonts/truetype/noto-serif-sc/NotoSerifSC-VariableFont_wght.ttf
|
||||
/usr/share/fonts/truetype/noto-serif-sc/NotoSerifSC-ExtraLight.ttf
|
||||
/usr/share/fonts/truetype/noto-serif-sc/NotoSerifSC-Light.ttf
|
||||
/usr/share/fonts/truetype/noto-serif-sc/NotoSerifSC-Regular.ttf
|
||||
/usr/share/fonts/truetype/noto-serif-sc/NotoSerifSC-Medium.ttf
|
||||
/usr/share/fonts/truetype/noto-serif-sc/NotoSerifSC-SemiBold.ttf
|
||||
/usr/share/fonts/truetype/noto-serif-sc/NotoSerifSC-Bold.ttf
|
||||
/usr/share/fonts/truetype/noto-serif-sc/NotoSerifSC-ExtraBold.ttf
|
||||
/usr/share/fonts/truetype/noto-serif-sc/NotoSerifSC-Black.ttf
|
||||
/usr/share/fonts/truetype/lxgw-wenkai/LXGWWenKai-Regular.ttf
|
||||
/usr/share/fonts/truetype/lxgw-wenkai/LXGWWenKai-Medium.ttf
|
||||
/usr/share/fonts/truetype/lxgw-wenkai/LXGWWenKai-Light.ttf
|
||||
/usr/share/fonts/truetype/lxgw-wenkai/LXGWWenKaiMono-Regular.ttf
|
||||
/usr/share/fonts/truetype/lxgw-wenkai/LXGWWenKaiMono-Medium.ttf
|
||||
/usr/share/fonts/truetype/lxgw-wenkai/LXGWWenKaiMono-Light.ttf
|
||||
/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc
|
||||
/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf
|
||||
/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf
|
||||
/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf
|
||||
/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf
|
||||
/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Oblique.ttf
|
||||
/usr/share/fonts/truetype/dejavu/DejaVuSansMono-BoldOblique.ttf
|
||||
/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf
|
||||
/usr/share/fonts/truetype/dejavu/DejaVuSerif-Bold.ttf
|
||||
/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf
|
||||
/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf
|
||||
/usr/share/fonts/truetype/liberation/LiberationSans-Italic.ttf
|
||||
/usr/share/fonts/truetype/liberation/LiberationSans-BoldItalic.ttf
|
||||
/usr/share/fonts/truetype/liberation/LiberationSerif-Regular.ttf
|
||||
/usr/share/fonts/truetype/liberation/LiberationSerif-Bold.ttf
|
||||
/usr/share/fonts/truetype/liberation/LiberationSerif-Italic.ttf
|
||||
/usr/share/fonts/truetype/liberation/LiberationSerif-BoldItalic.ttf
|
||||
/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf
|
||||
/usr/share/fonts/truetype/liberation/LiberationMono-Bold.ttf
|
||||
/usr/share/fonts/truetype/liberation/LiberationMono-Italic.ttf
|
||||
/usr/share/fonts/truetype/liberation/LiberationMono-BoldItalic.ttf
|
||||
/usr/share/fonts/truetype/english/Tinos-Regular.ttf
|
||||
/usr/share/fonts/truetype/english/Tinos-Bold.ttf
|
||||
/usr/share/fonts/truetype/english/Tinos-Italic.ttf
|
||||
/usr/share/fonts/truetype/english/Tinos-BoldItalic.ttf
|
||||
/usr/share/fonts/truetype/english/Carlito-Regular.ttf
|
||||
/usr/share/fonts/truetype/english/Carlito-Bold.ttf
|
||||
/usr/share/fonts/truetype/english/Carlito-Italic.ttf
|
||||
/usr/share/fonts/truetype/english/Carlito-BoldItalic.ttf
|
||||
/usr/share/fonts/truetype/freefont/FreeSans.ttf
|
||||
/usr/share/fonts/truetype/freefont/FreeSansBold.ttf
|
||||
/usr/share/fonts/truetype/freefont/FreeSansOblique.ttf
|
||||
/usr/share/fonts/truetype/freefont/FreeSansBoldOblique.ttf
|
||||
/usr/share/fonts/truetype/freefont/FreeSerif.ttf
|
||||
/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf
|
||||
/usr/share/fonts/truetype/freefont/FreeSerifItalic.ttf
|
||||
/usr/share/fonts/truetype/freefont/FreeSerifBoldItalic.ttf
|
||||
/usr/share/fonts/truetype/freefont/FreeMono.ttf
|
||||
/usr/share/fonts/truetype/freefont/FreeMonoBold.ttf
|
||||
/usr/share/fonts/truetype/freefont/FreeMonoOblique.ttf
|
||||
/usr/share/fonts/truetype/freefont/FreeMonoBoldOblique.ttf
|
||||
/usr/share/fonts/truetype/libreoffice/opens___.ttf
|
||||
Executable
+212
@@ -0,0 +1,212 @@
|
||||
# PDF Skill — Environment Setup Guide
|
||||
|
||||
This document contains full platform-specific instructions for setting up the PDF skill environment.
|
||||
The model should read this file when first-time setup is needed.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Platform Detection
|
||||
|
||||
Detect the OS and set core variables:
|
||||
|
||||
### macOS / Linux (bash/zsh)
|
||||
|
||||
```bash
|
||||
OS="$(uname -s)" # Darwin = macOS, Linux = Linux
|
||||
ARCH="$(uname -m)" # x86_64 or arm64
|
||||
|
||||
PDF_SKILL_DIR="<skill_directory>"
|
||||
export PDF_SKILL_DIR
|
||||
```
|
||||
|
||||
### Windows (PowerShell, Win10/Win11)
|
||||
|
||||
```powershell
|
||||
$WinVer = [System.Environment]::OSVersion.Version
|
||||
$Arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture
|
||||
|
||||
$env:PDF_SKILL_DIR = "<skill_directory>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Dependency Check & Install
|
||||
|
||||
Run the platform-appropriate setup script:
|
||||
|
||||
| Platform | Command |
|
||||
|----------|---------|
|
||||
| macOS / Linux | `bash "$PDF_SKILL_DIR/env_setup/setup_mac_linux.sh"` |
|
||||
| Windows | `powershell -ExecutionPolicy Bypass -File "$env:PDF_SKILL_DIR\env_setup\setup_windows.ps1"` |
|
||||
|
||||
### Required Dependencies
|
||||
|
||||
| Category | Package | Purpose |
|
||||
|----------|---------|---------|
|
||||
| Runtime | Python 3 + pip | PDF generation and processing |
|
||||
| Runtime | Node.js + npm | Playwright for Creative pipeline |
|
||||
| Python pkg | pikepdf | PDF manipulation (merge/split/encrypt) |
|
||||
| Python pkg | pdfplumber | Text/table extraction |
|
||||
| Python pkg | pypdf | PDF reading/writing |
|
||||
| Python pkg | reportlab | Report pipeline PDF generation |
|
||||
| Python pkg | PyMuPDF (fitz) | Fast PDF rendering and extraction |
|
||||
| npm pkg | playwright | HTML-to-PDF for Creative pipeline |
|
||||
| Browser | Chromium (via Playwright) | Headless rendering |
|
||||
| Optional | tectonic | LaTeX/Academic PDF compilation |
|
||||
| Optional | LibreOffice | Office-to-PDF conversion |
|
||||
| Font | CJK fonts (from CDN) | Chinese text in PDFs |
|
||||
|
||||
### Manual Install by Platform
|
||||
|
||||
#### macOS
|
||||
|
||||
```bash
|
||||
brew install python3 node
|
||||
python3 -m pip install pikepdf pdfplumber pypdf reportlab PyMuPDF
|
||||
npm install -g playwright@1.50.0 && npx playwright install chromium
|
||||
# Pinned to 1.50.0 — npmmirror has Chromium rev 1155 for this version
|
||||
brew install tectonic # optional: LaTeX
|
||||
brew install --cask libreoffice # optional: Office-to-PDF
|
||||
```
|
||||
|
||||
#### Linux (Debian/Ubuntu)
|
||||
|
||||
```bash
|
||||
sudo apt install python3 python3-pip nodejs npm
|
||||
python3 -m pip install pikepdf pdfplumber pypdf reportlab PyMuPDF
|
||||
npm install -g playwright@1.50.0 && npx playwright install chromium && npx playwright install-deps
|
||||
# Pinned to 1.50.0 — npmmirror has Chromium rev 1155 for this version
|
||||
# optional:
|
||||
conda install -c conda-forge tectonic # use Tsinghua mirror: conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
|
||||
sudo apt install libreoffice-core # LibreOffice
|
||||
```
|
||||
|
||||
#### Windows (PowerShell)
|
||||
|
||||
```powershell
|
||||
winget install Python.Python.3.11
|
||||
winget install OpenJS.NodeJS.LTS
|
||||
python -m pip install pikepdf pdfplumber pypdf reportlab PyMuPDF
|
||||
npm install -g playwright@1.50.0
|
||||
# Pinned to 1.50.0 — npmmirror has Chromium rev 1155 for this version
|
||||
npx playwright install chromium
|
||||
# optional:
|
||||
scoop install tectonic # LaTeX
|
||||
winget install TheDocumentFoundation.LibreOffice # Office-to-PDF
|
||||
```
|
||||
|
||||
Alternative Windows package managers:
|
||||
- `choco install python3 nodejs-lts`
|
||||
- `scoop install python nodejs-lts`
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Font Installation
|
||||
|
||||
Fonts are downloaded individually from CDN on first setup.
|
||||
|
||||
- **CDN base**: `https://z-cdn.chatglm.cn/office-skill/fonts/`
|
||||
- **Font list**: `env_setup/font_list.txt` (78 fonts, one relative path per line)
|
||||
- **Marker file**: `.office-skill-fonts-installed` in the user font directory prevents re-download
|
||||
- Special characters in filenames (e.g., `[`, `]`) are URL-encoded automatically by the setup script
|
||||
|
||||
The setup scripts read `font_list.txt`, check which fonts are already installed, and download only the missing ones. Each font is saved flat (filename only) to the user font directory.
|
||||
|
||||
### CDN Directory Structure (78 fonts)
|
||||
|
||||
| Directory | Count | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `truetype/lxgw-wenkai/` | 6 | LXGW WenKai — Chinese handwriting style |
|
||||
| `truetype/noto-serif-sc/` | 9 | Noto Serif SC — Chinese serif (variable + 8 static weights) |
|
||||
| `chinese/` | 14 | Noto Sans SC, Sarasa Mono SC, Liberation fallbacks |
|
||||
| `dejavu/` | 8 | DejaVu Sans/Serif/Mono — Latin/symbol fallback |
|
||||
| `emoji/` | 1 | Noto Color Emoji |
|
||||
| `english/` | 12 | Tinos, Carlito, Calibri |
|
||||
| `freefont/` | 12 | FreeSans/FreeSerif/FreeMono — open-source fallback |
|
||||
| `liberation/` | 12 | Liberation Sans/Serif/Mono — MS-metric-compatible |
|
||||
| `libreoffice/` | 1 | OpenSymbol |
|
||||
| `noto/` | 1 | Noto Color Emoji (duplicate) |
|
||||
| `wqy/` | 1 | WenQuanYi Zen Hei — CJK fallback |
|
||||
| *(root)* | 1 | Japanese Gothic |
|
||||
|
||||
### Install Targets by Platform
|
||||
|
||||
| Platform | User Font Directory |
|
||||
|----------|-------------------|
|
||||
| macOS | `~/Library/Fonts/` |
|
||||
| Linux | `~/.local/share/fonts/` (then run `fc-cache -f`) |
|
||||
| Windows | `%LOCALAPPDATA%\Microsoft\Windows\Fonts` (per-user, no admin) |
|
||||
|
||||
### Manual Font Installation
|
||||
|
||||
If CDN is unreachable, download fonts manually. Example:
|
||||
|
||||
```bash
|
||||
# Download a single font (use static-weight files, NOT variable fonts)
|
||||
curl -fSLO "https://z-cdn.chatglm.cn/office-skill/fonts/chinese/NotoSansSC-Regular.ttf"
|
||||
# Copy to user font dir
|
||||
cp "NotoSansSC-Regular.ttf" ~/Library/Fonts/ # macOS
|
||||
```
|
||||
|
||||
Or download all fonts listed in `font_list.txt`:
|
||||
|
||||
```bash
|
||||
while read f; do
|
||||
encoded=$(echo "$f" | sed 's/\[/%5B/g; s/\]/%5D/g')
|
||||
curl -fSLO "https://z-cdn.chatglm.cn/office-skill/fonts/$encoded"
|
||||
done < env_setup/font_list.txt
|
||||
```
|
||||
|
||||
### Post-Install Variable
|
||||
|
||||
After font installation, `FONT_DIR` points to the user font directory:
|
||||
|
||||
| Platform | FONT_DIR |
|
||||
|----------|---------------|
|
||||
| macOS | `~/Library/Fonts` |
|
||||
| Linux | `~/.local/share/fonts` |
|
||||
| Windows | `%LOCALAPPDATA%\Microsoft\Windows\Fonts` |
|
||||
|
||||
---
|
||||
|
||||
## China Network Fallback
|
||||
|
||||
If default sources are unreachable, use China mirrors:
|
||||
|
||||
### pip (Tsinghua mirror)
|
||||
|
||||
```bash
|
||||
python3 -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple \
|
||||
--trusted-host pypi.tuna.tsinghua.edu.cn \
|
||||
pikepdf pdfplumber pypdf reportlab PyMuPDF
|
||||
```
|
||||
|
||||
### npm (npmmirror)
|
||||
|
||||
```bash
|
||||
npm install -g playwright@1.50.0 --registry https://registry.npmmirror.com
|
||||
# Pinned to 1.50.0 — npmmirror has Chromium rev 1155 for this version
|
||||
```
|
||||
|
||||
### Playwright browser download (npmmirror)
|
||||
|
||||
```bash
|
||||
PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright/ npx playwright install chromium
|
||||
```
|
||||
|
||||
### Windows (PowerShell) China mirrors
|
||||
|
||||
```powershell
|
||||
python -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn pikepdf pdfplumber pypdf reportlab PyMuPDF
|
||||
npm install -g playwright@1.50.0 --registry https://registry.npmmirror.com
|
||||
# Pinned to 1.50.0 — npmmirror has Chromium rev 1155 for this version
|
||||
$env:PLAYWRIGHT_DOWNLOAD_HOST = "https://npmmirror.com/mirrors/playwright/"
|
||||
npx playwright install chromium
|
||||
```
|
||||
|
||||
### Installer downloads (China)
|
||||
|
||||
| Software | China Mirror |
|
||||
|----------|-------------|
|
||||
| Node.js | https://npmmirror.com/mirrors/node/ |
|
||||
| Python | https://npmmirror.com/mirrors/python/ |
|
||||
Executable
+350
@@ -0,0 +1,350 @@
|
||||
#!/usr/bin/env bash
|
||||
# ---
|
||||
# name: pdf-env-setup (macOS / Linux)
|
||||
# description: Environment detection, dependency check & install for PDF skill on macOS and Linux.
|
||||
# ---
|
||||
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"; }
|
||||
|
||||
# ── Resolve PDF_SKILL_DIR ──
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PDF_SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
export PDF_SKILL_DIR
|
||||
|
||||
echo "============================================"
|
||||
echo " PDF Skill — Environment Setup"
|
||||
echo " (macOS / Linux)"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
|
||||
# ── Step 1: Platform Detection ──
|
||||
OS="$(uname -s)"
|
||||
ARCH="$(uname -m)"
|
||||
echo "Platform: $OS $ARCH"
|
||||
|
||||
if [ "$OS" = "Darwin" ]; then
|
||||
PLATFORM="mac"
|
||||
elif [ "$OS" = "Linux" ]; then
|
||||
PLATFORM="linux"
|
||||
else
|
||||
echo "Unsupported platform: $OS. For Windows use env_setup/setup_windows.ps1"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Detected: $PLATFORM"
|
||||
echo "PDF_SKILL_DIR=$PDF_SKILL_DIR"
|
||||
echo ""
|
||||
|
||||
# ── China mirror detection & config ──
|
||||
USE_CN_MIRROR=false
|
||||
PIP_MIRROR_ARGS=""
|
||||
NPM_MIRROR_ARGS=""
|
||||
PLAYWRIGHT_HOST=""
|
||||
|
||||
if [ "${USE_CN_MIRROR_FORCE:-}" = "true" ]; then
|
||||
USE_CN_MIRROR=true
|
||||
elif curl -s --connect-timeout 3 https://pypi.org > /dev/null 2>&1; then
|
||||
USE_CN_MIRROR=false
|
||||
else
|
||||
warn "pypi.org unreachable — enabling China mirrors"
|
||||
USE_CN_MIRROR=true
|
||||
fi
|
||||
|
||||
if [ "$USE_CN_MIRROR" = true ]; then
|
||||
PIP_MIRROR_ARGS="-i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn"
|
||||
NPM_MIRROR_ARGS="--registry https://registry.npmmirror.com"
|
||||
PLAYWRIGHT_HOST="https://npmmirror.com/mirrors/playwright/"
|
||||
info "China mirrors enabled (pip: tuna, npm: npmmirror, playwright: npmmirror)"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
ERRORS=0
|
||||
|
||||
# ── Step 2a: Homebrew (macOS only) ──
|
||||
if [ "$PLATFORM" = "mac" ]; then
|
||||
echo "--- [1/10] Homebrew (macOS package manager) ---"
|
||||
if command -v brew &>/dev/null; then
|
||||
ok "brew installed"
|
||||
else
|
||||
fail "brew not found"
|
||||
info "Install: /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ── Step 2b: Python 3 ──
|
||||
echo "--- [2/10] Python 3 ---"
|
||||
if command -v python3 &>/dev/null; then
|
||||
PY_VER=$(python3 --version 2>&1)
|
||||
ok "python3 ($PY_VER)"
|
||||
if [ "$PLATFORM" = "mac" ]; 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 "$PLATFORM" in
|
||||
mac) 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)" ;;
|
||||
esac
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ── Step 2c: pip ──
|
||||
echo "--- [3/10] pip ---"
|
||||
if python3 -m pip --version &>/dev/null 2>&1; then
|
||||
ok "pip installed"
|
||||
else
|
||||
fail "pip not found"
|
||||
case "$PLATFORM" in
|
||||
mac) info "Install: python3 -m ensurepip --upgrade" ;;
|
||||
linux) info "Install: sudo apt install python3-pip" ;;
|
||||
esac
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ── Step 2d: Python packages ──
|
||||
echo "--- [4/10] Python Packages (pikepdf, pdfplumber, pypdf, reportlab, PyMuPDF) ---"
|
||||
PY_PKGS=(
|
||||
"pikepdf:pikepdf"
|
||||
"pdfplumber:pdfplumber"
|
||||
"pypdf:pypdf"
|
||||
"reportlab:reportlab"
|
||||
"pymupdf:PyMuPDF"
|
||||
)
|
||||
|
||||
MISSING_PY=()
|
||||
for entry in "${PY_PKGS[@]}"; do
|
||||
mod="${entry%%:*}"
|
||||
pkg="${entry##*:}"
|
||||
# PyMuPDF imports as 'fitz'
|
||||
if [ "$mod" = "pymupdf" ]; then mod="fitz"; fi
|
||||
if python3 -c "import $mod" 2>/dev/null; then
|
||||
ver=$(python3 -c "import $mod; print(getattr($mod, '__version__', getattr($mod, 'version', 'ok')))" 2>/dev/null)
|
||||
ok "$pkg ($ver)"
|
||||
else
|
||||
fail "$pkg not installed"
|
||||
MISSING_PY+=("$pkg")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#MISSING_PY[@]} -gt 0 ]; then
|
||||
info "Installing missing Python packages: ${MISSING_PY[*]}"
|
||||
# shellcheck disable=SC2086
|
||||
python3 -m pip install -q $PIP_MIRROR_ARGS "${MISSING_PY[@]}" 2>/dev/null \
|
||||
|| python3 -m pip install -q --user $PIP_MIRROR_ARGS "${MISSING_PY[@]}" 2>/dev/null \
|
||||
|| python3 -m pip install -q --break-system-packages $PIP_MIRROR_ARGS "${MISSING_PY[@]}" 2>/dev/null \
|
||||
|| { fail "pip install failed. Try: pip install $PIP_MIRROR_ARGS ${MISSING_PY[*]}"; ERRORS=$((ERRORS + 1)); }
|
||||
ok "Installed: ${MISSING_PY[*]}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ── Step 2e: Node.js + npm ──
|
||||
echo "--- [5/10] Node.js + npm ---"
|
||||
if command -v node &>/dev/null; then
|
||||
ok "node ($(node --version))"
|
||||
else
|
||||
fail "node not found"
|
||||
case "$PLATFORM" in
|
||||
mac) 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"
|
||||
if [ "$USE_CN_MIRROR" = true ]; then
|
||||
info "China alt: https://npmmirror.com/mirrors/node/"
|
||||
fi ;;
|
||||
esac
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
|
||||
if command -v npm &>/dev/null; then
|
||||
ok "npm ($(npm --version))"
|
||||
else
|
||||
fail "npm not found"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ── Step 2f: Playwright + Chromium ──
|
||||
echo "--- [6/10] Playwright + Chromium ---"
|
||||
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"
|
||||
if [ "$USE_CN_MIRROR" = true ]; then
|
||||
info "Install: npm install -g playwright@1.50.0 $NPM_MIRROR_ARGS"
|
||||
else
|
||||
info "Install: npm install -g playwright@1.50.0"
|
||||
fi
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
|
||||
if [ "$PLATFORM" = "mac" ]; 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
|
||||
ok "chromium installed"
|
||||
else
|
||||
fail "chromium not installed"
|
||||
if [ "$USE_CN_MIRROR" = true ]; then
|
||||
info "Install: PLAYWRIGHT_DOWNLOAD_HOST=$PLAYWRIGHT_HOST npx playwright install chromium"
|
||||
else
|
||||
info "Install: npx playwright install chromium"
|
||||
fi
|
||||
if [ "$PLATFORM" = "linux" ]; then
|
||||
info " npx playwright install-deps (system libs, needs sudo)"
|
||||
fi
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ── Step 2g: Tectonic (optional, LaTeX engine) ──
|
||||
echo "--- [7/10] Tectonic (optional, LaTeX/Academic PDFs) ---"
|
||||
BUNDLED="$PDF_SKILL_DIR/scripts/tectonic"
|
||||
if [ -x "$BUNDLED" ] && [ "$OS" = "Darwin" ] && [ "$ARCH" = "arm64" ]; then
|
||||
ok "tectonic (bundled, macOS arm64)"
|
||||
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 "$PLATFORM" in
|
||||
mac) 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" ;;
|
||||
esac
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ── Step 2h: LibreOffice (optional) ──
|
||||
echo "--- [8/10] 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 "$PLATFORM" in
|
||||
mac) info "Install: brew install --cask libreoffice" ;;
|
||||
linux) info "Install: sudo apt install libreoffice-core" ;;
|
||||
esac
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ── Step 2i: Font Installation (from CDN) ──
|
||||
echo "--- [9/10] Font Installation ---"
|
||||
FONT_CDN_BASE="https://z-cdn.chatglm.cn/office-skill/fonts"
|
||||
FONT_LIST="$SCRIPT_DIR/font_list.txt"
|
||||
|
||||
if [ "$PLATFORM" = "mac" ]; then
|
||||
USER_FONT_DIR="$HOME/Library/Fonts"
|
||||
else
|
||||
USER_FONT_DIR="$HOME/.local/share/fonts"
|
||||
fi
|
||||
mkdir -p "$USER_FONT_DIR"
|
||||
|
||||
MARKER="$USER_FONT_DIR/.office-skill-fonts-installed"
|
||||
if [ -f "$MARKER" ]; then
|
||||
ok "Fonts already installed (marker found). To re-install, delete $MARKER"
|
||||
else
|
||||
if [ ! -f "$FONT_LIST" ]; then
|
||||
fail "Font list not found: $FONT_LIST"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
else
|
||||
TOTAL=$(wc -l < "$FONT_LIST" | tr -d ' ')
|
||||
INSTALLED=0
|
||||
SKIPPED=0
|
||||
FAILED=0
|
||||
info "Downloading $TOTAL fonts from CDN..."
|
||||
|
||||
while IFS= read -r rel_path || [ -n "$rel_path" ]; do
|
||||
[ -z "$rel_path" ] && continue
|
||||
fname="$(basename "$rel_path")"
|
||||
|
||||
if [ -f "$USER_FONT_DIR/$fname" ]; then
|
||||
SKIPPED=$((SKIPPED + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
# URL-encode special chars (brackets)
|
||||
encoded=$(echo "$rel_path" | sed 's/\[/%5B/g; s/\]/%5D/g; s/ /%20/g')
|
||||
url="$FONT_CDN_BASE/$encoded"
|
||||
|
||||
if curl -fSL --connect-timeout 15 --retry 2 -o "$USER_FONT_DIR/$fname" "$url" 2>/dev/null; then
|
||||
INSTALLED=$((INSTALLED + 1))
|
||||
else
|
||||
warn "Failed to download: $rel_path"
|
||||
FAILED=$((FAILED + 1))
|
||||
rm -f "$USER_FONT_DIR/$fname" 2>/dev/null
|
||||
fi
|
||||
done < "$FONT_LIST"
|
||||
|
||||
if [ "$PLATFORM" = "linux" ] && [ $INSTALLED -gt 0 ]; then
|
||||
fc-cache -f "$USER_FONT_DIR" 2>/dev/null
|
||||
fi
|
||||
|
||||
if [ $FAILED -eq 0 ]; then
|
||||
touch "$MARKER"
|
||||
ok "Fonts: $INSTALLED newly installed, $SKIPPED already present (target: $USER_FONT_DIR)"
|
||||
else
|
||||
warn "Fonts: $INSTALLED installed, $SKIPPED skipped, $FAILED failed (marker not written, will retry next run)"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Set FONT_DIR to user font directory (fonts are now installed there)
|
||||
FONT_DIR="$USER_FONT_DIR"
|
||||
export FONT_DIR
|
||||
echo ""
|
||||
|
||||
# ── Step 2j: CJK Font Verification ──
|
||||
echo "--- [10/10] CJK Font Verification ---"
|
||||
CJK_FOUND=false
|
||||
|
||||
if [ "$PLATFORM" = "mac" ]; then
|
||||
if [ -f "$HOME/Library/Fonts/NotoSansSC[wght].ttf" ] || fc-list :lang=zh 2>/dev/null | head -1 | grep -q .; then
|
||||
ok "CJK fonts available (Noto Sans SC or system)"
|
||||
CJK_FOUND=true
|
||||
fi
|
||||
if [ -f "$HOME/Library/Fonts/NotoSansSC[wght].ttf" ]; then
|
||||
ok "Noto Sans SC (user fonts)"
|
||||
CJK_FOUND=true
|
||||
fi
|
||||
elif [ "$PLATFORM" = "linux" ]; then
|
||||
if fc-list :lang=zh 2>/dev/null | head -1 | grep -q .; then
|
||||
ok "CJK fonts available (fc-list)"
|
||||
CJK_FOUND=true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$CJK_FOUND" = false ]; then
|
||||
warn "No CJK font detected — fonts were downloaded and copied; rebuild font cache if needed"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ── Summary ──
|
||||
echo "============================================"
|
||||
if [ $ERRORS -eq 0 ]; then
|
||||
echo " All dependencies OK."
|
||||
else
|
||||
echo " $ERRORS issue(s) found. Fix them above."
|
||||
fi
|
||||
echo " PDF_SKILL_DIR=$PDF_SKILL_DIR"
|
||||
echo " FONT_DIR=$FONT_DIR (user font directory)"
|
||||
echo "============================================"
|
||||
Executable
+326
@@ -0,0 +1,326 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
PDF Skill — Environment Setup for Windows (Win10/Win11)
|
||||
.DESCRIPTION
|
||||
Detects platform, checks and installs all dependencies for the PDF skill.
|
||||
Supports China mirror fallback for pip/npm/playwright.
|
||||
#>
|
||||
|
||||
param(
|
||||
[switch]$UseChinaMirror
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
function Write-Ok { param($msg) Write-Host " [OK] $msg" -ForegroundColor Green }
|
||||
function Write-Fail { param($msg) Write-Host " [FAIL] $msg" -ForegroundColor Red }
|
||||
function Write-Warn { param($msg) Write-Host " [WARN] $msg" -ForegroundColor Yellow }
|
||||
function Write-Info { param($msg) Write-Host " [->] $msg" -ForegroundColor Cyan }
|
||||
|
||||
# ── Resolve PDF_SKILL_DIR ──
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$PDF_SKILL_DIR = Split-Path -Parent $ScriptDir
|
||||
$env:PDF_SKILL_DIR = $PDF_SKILL_DIR
|
||||
|
||||
Write-Host "============================================"
|
||||
Write-Host " PDF Skill - Environment Setup"
|
||||
Write-Host " (Windows)"
|
||||
Write-Host "============================================"
|
||||
Write-Host ""
|
||||
|
||||
# ── Step 1: Platform Detection ──
|
||||
$WinVer = [System.Environment]::OSVersion.Version
|
||||
$WinName = if ($WinVer.Build -ge 22000) { "Windows 11" } elseif ($WinVer.Build -ge 10240) { "Windows 10" } else { "Windows (older)" }
|
||||
Write-Host "Platform: $WinName (Build $($WinVer.Build)), $([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)"
|
||||
Write-Host "PDF_SKILL_DIR=$PDF_SKILL_DIR"
|
||||
Write-Host ""
|
||||
|
||||
# ── China mirror detection ──
|
||||
$PipMirrorArgs = @()
|
||||
$NpmMirrorArgs = @()
|
||||
$PlaywrightHost = ""
|
||||
|
||||
if ($UseChinaMirror) {
|
||||
$global:UseCN = $true
|
||||
} else {
|
||||
try {
|
||||
$null = Invoke-WebRequest -Uri "https://pypi.org" -TimeoutSec 3 -UseBasicParsing -ErrorAction Stop
|
||||
$global:UseCN = $false
|
||||
} catch {
|
||||
Write-Warn "pypi.org unreachable - enabling China mirrors"
|
||||
$global:UseCN = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($global:UseCN) {
|
||||
$PipMirrorArgs = @("-i", "https://pypi.tuna.tsinghua.edu.cn/simple", "--trusted-host", "pypi.tuna.tsinghua.edu.cn")
|
||||
$NpmMirrorArgs = @("--registry", "https://registry.npmmirror.com")
|
||||
$PlaywrightHost = "https://npmmirror.com/mirrors/playwright/"
|
||||
Write-Info "China mirrors enabled (pip: tuna, npm: npmmirror, playwright: npmmirror)"
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
$Errors = 0
|
||||
|
||||
# ── Step 2a: Python 3 ──
|
||||
Write-Host "--- [1/9] Python 3 ---"
|
||||
$PyCmd = $null
|
||||
foreach ($cmd in @("python3", "python", "py")) {
|
||||
try {
|
||||
$ver = & $cmd --version 2>&1
|
||||
if ($ver -match "Python 3") {
|
||||
$PyCmd = $cmd
|
||||
Write-Ok "$cmd ($ver)"
|
||||
break
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
if (-not $PyCmd) {
|
||||
Write-Fail "Python 3 not found"
|
||||
Write-Info "Install option 1: winget install Python.Python.3.11"
|
||||
Write-Info "Install option 2: https://www.python.org/downloads/"
|
||||
Write-Info "Install option 3: choco install python3"
|
||||
if ($global:UseCN) {
|
||||
Write-Info "China alt: https://npmmirror.com/mirrors/python/"
|
||||
}
|
||||
$Errors++
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
# ── Step 2b: pip ──
|
||||
Write-Host "--- [2/9] pip ---"
|
||||
if ($PyCmd) {
|
||||
try {
|
||||
$pipVer = & $PyCmd -m pip --version 2>&1
|
||||
if ($pipVer -match "pip") {
|
||||
Write-Ok "pip ($pipVer)"
|
||||
} else { throw "no pip" }
|
||||
} catch {
|
||||
Write-Fail "pip not found"
|
||||
Write-Info "Install: $PyCmd -m ensurepip --upgrade"
|
||||
$Errors++
|
||||
}
|
||||
} else {
|
||||
Write-Fail "pip - skipped (Python not found)"
|
||||
$Errors++
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
# ── Step 2c: Python packages ──
|
||||
Write-Host "--- [3/9] Python Packages (pikepdf, pdfplumber, pypdf, reportlab, PyMuPDF) ---"
|
||||
$PyPkgs = @(
|
||||
@{ Module = "pikepdf"; Package = "pikepdf" },
|
||||
@{ Module = "pdfplumber"; Package = "pdfplumber" },
|
||||
@{ Module = "pypdf"; Package = "pypdf" },
|
||||
@{ Module = "reportlab"; Package = "reportlab" },
|
||||
@{ Module = "fitz"; Package = "PyMuPDF" }
|
||||
)
|
||||
|
||||
$MissingPy = @()
|
||||
if ($PyCmd) {
|
||||
foreach ($pkg in $PyPkgs) {
|
||||
try {
|
||||
$result = & $PyCmd -c "import $($pkg.Module); print(getattr($($pkg.Module), '__version__', getattr($($pkg.Module), 'version', 'ok')))" 2>&1
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Ok "$($pkg.Package) ($result)"
|
||||
} else { throw "not installed" }
|
||||
} catch {
|
||||
Write-Fail "$($pkg.Package) not installed"
|
||||
$MissingPy += $pkg.Package
|
||||
}
|
||||
}
|
||||
|
||||
if ($MissingPy.Count -gt 0) {
|
||||
Write-Info "Installing: $($MissingPy -join ', ')"
|
||||
$installArgs = @("-m", "pip", "install") + $PipMirrorArgs + $MissingPy
|
||||
try {
|
||||
& $PyCmd @installArgs 2>&1 | Out-Null
|
||||
Write-Ok "Installed: $($MissingPy -join ', ')"
|
||||
} catch {
|
||||
Write-Fail "pip install failed. Try: $PyCmd -m pip install $($PipMirrorArgs -join ' ') $($MissingPy -join ' ')"
|
||||
$Errors++
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Fail "Python packages - skipped (Python not found)"
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
# ── Step 2d: Node.js + npm ──
|
||||
Write-Host "--- [4/9] Node.js + npm ---"
|
||||
try {
|
||||
$nodeVer = & node --version 2>&1
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Ok "node ($nodeVer)"
|
||||
} else { throw "no node" }
|
||||
} catch {
|
||||
Write-Fail "node not found"
|
||||
Write-Info "Install option 1: winget install OpenJS.NodeJS.LTS"
|
||||
Write-Info "Install option 2: https://nodejs.org/"
|
||||
Write-Info "Install option 3: choco install nodejs-lts"
|
||||
if ($global:UseCN) {
|
||||
Write-Info "China alt: https://npmmirror.com/mirrors/node/"
|
||||
}
|
||||
$Errors++
|
||||
}
|
||||
|
||||
try {
|
||||
$npmVer = & npm --version 2>&1
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Ok "npm ($npmVer)"
|
||||
} else { throw "no npm" }
|
||||
} catch {
|
||||
Write-Fail "npm not found (installed with Node.js)"
|
||||
$Errors++
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
# ── Step 2e: Playwright + Chromium ──
|
||||
Write-Host "--- [5/9] Playwright + Chromium ---"
|
||||
try {
|
||||
$null = & node -e "require('playwright')" 2>&1
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
$pwVer = & node -e "console.log(require('playwright/package.json').version)" 2>&1
|
||||
Write-Ok "playwright ($pwVer)"
|
||||
} else { throw "no pw" }
|
||||
} catch {
|
||||
Write-Fail "playwright not installed"
|
||||
if ($global:UseCN) {
|
||||
Write-Info "Install: npm install -g playwright@1.50.0 $($NpmMirrorArgs -join ' ')"
|
||||
} else {
|
||||
Write-Info "Install: npm install -g playwright@1.50.0"
|
||||
}
|
||||
$Errors++
|
||||
}
|
||||
|
||||
$PwCache = Join-Path $env:LOCALAPPDATA "ms-playwright"
|
||||
if (Test-Path "$PwCache\chromium-*") {
|
||||
Write-Ok "chromium installed"
|
||||
} else {
|
||||
Write-Fail "chromium not installed"
|
||||
if ($global:UseCN) {
|
||||
Write-Info "Install: `$env:PLAYWRIGHT_DOWNLOAD_HOST='$PlaywrightHost'; npx playwright install chromium"
|
||||
} else {
|
||||
Write-Info "Install: npx playwright install chromium"
|
||||
}
|
||||
$Errors++
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
# ── Step 2f: Tectonic (optional) ──
|
||||
Write-Host "--- [6/9] Tectonic (optional, LaTeX/Academic PDFs) ---"
|
||||
try {
|
||||
$tecVer = & tectonic --version 2>&1
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Ok "tectonic ($tecVer)"
|
||||
} else { throw "no tectonic" }
|
||||
} catch {
|
||||
Write-Warn "tectonic not installed (needed only for LaTeX/academic PDFs)"
|
||||
Write-Info "Install option 1: scoop install tectonic"
|
||||
Write-Info "Install option 2: choco install tectonic"
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
# ── Step 2g: LibreOffice (optional) ──
|
||||
Write-Host "--- [7/9] LibreOffice (optional, Office->PDF conversion) ---"
|
||||
$SofficePath = "C:\Program Files\LibreOffice\program\soffice.exe"
|
||||
if (Test-Path $SofficePath) {
|
||||
Write-Ok "libreoffice found"
|
||||
} elseif (Get-Command soffice -ErrorAction SilentlyContinue) {
|
||||
Write-Ok "libreoffice (in PATH)"
|
||||
} else {
|
||||
Write-Warn "libreoffice not installed (needed only for .docx/.xlsx -> PDF conversion)"
|
||||
Write-Info "Install: winget install TheDocumentFoundation.LibreOffice"
|
||||
Write-Info " or: https://www.libreoffice.org/download/"
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
# ── Step 2h: Font Installation (from CDN) ──
|
||||
Write-Host "--- [8/9] Font Installation ---"
|
||||
$FontCdnBase = "https://z-cdn.chatglm.cn/office-skill/fonts"
|
||||
$FontList = Join-Path $ScriptDir "font_list.txt"
|
||||
|
||||
$UserFontDir = Join-Path $env:LOCALAPPDATA "Microsoft\Windows\Fonts"
|
||||
if (-not (Test-Path $UserFontDir)) { New-Item -ItemType Directory -Path $UserFontDir -Force | Out-Null }
|
||||
|
||||
$Marker = Join-Path $UserFontDir ".office-skill-fonts-installed"
|
||||
if (Test-Path $Marker) {
|
||||
Write-Ok "Fonts already installed (marker found). To re-install, delete $Marker"
|
||||
} else {
|
||||
if (-not (Test-Path $FontList)) {
|
||||
Write-Fail "Font list not found: $FontList"
|
||||
$Errors++
|
||||
} else {
|
||||
$lines = Get-Content $FontList | Where-Object { $_.Trim() -ne "" }
|
||||
$Total = $lines.Count
|
||||
$Installed = 0; $Skipped = 0; $Failed = 0
|
||||
Write-Info "Downloading $Total fonts from CDN..."
|
||||
|
||||
foreach ($relPath in $lines) {
|
||||
$fname = Split-Path $relPath -Leaf
|
||||
$dest = Join-Path $UserFontDir $fname
|
||||
|
||||
if (Test-Path $dest) {
|
||||
$Skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
$encoded = $relPath -replace '\[','%5B' -replace '\]','%5D' -replace ' ','%20'
|
||||
$url = "$FontCdnBase/$encoded"
|
||||
|
||||
try {
|
||||
Invoke-WebRequest -Uri $url -OutFile $dest -TimeoutSec 30 -ErrorAction Stop
|
||||
$regPath = "HKCU:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts"
|
||||
$null = New-ItemProperty -Path $regPath -Name $fname -Value $dest -PropertyType String -Force -ErrorAction SilentlyContinue
|
||||
$Installed++
|
||||
} catch {
|
||||
Write-Warn "Failed to download: $relPath"
|
||||
$Failed++
|
||||
Remove-Item $dest -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
if ($Failed -eq 0) {
|
||||
New-Item -ItemType File -Path $Marker -Force | Out-Null
|
||||
Write-Ok "Fonts: $Installed newly installed, $Skipped already present (target: $UserFontDir)"
|
||||
} else {
|
||||
Write-Warn "Fonts: $Installed installed, $Skipped skipped, $Failed failed (marker not written, will retry next run)"
|
||||
$Errors++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Set PDF_FONTS_DIR to user font directory (fonts are now installed there)
|
||||
$PDF_FONTS_DIR = $UserFontDir
|
||||
$env:PDF_FONTS_DIR = $PDF_FONTS_DIR
|
||||
Write-Host ""
|
||||
|
||||
# ── Step 2i: CJK Font Verification ──
|
||||
Write-Host "--- [9/9] CJK Font Verification ---"
|
||||
$CjkFound = $false
|
||||
$FontsDir = Join-Path $env:WINDIR "Fonts"
|
||||
$UserFontDir2 = Join-Path $env:LOCALAPPDATA "Microsoft\Windows\Fonts"
|
||||
$CjkFonts = @("NotoSansSC[wght].ttf", "NotoSansSC[wght].ttf", "NotoSansSC[wght].ttf", "NotoSerifSC-Regular.ttf")
|
||||
foreach ($f in $CjkFonts) {
|
||||
if ((Test-Path (Join-Path $FontsDir $f)) -or (Test-Path (Join-Path $UserFontDir2 $f))) {
|
||||
Write-Ok "CJK font found: $f"
|
||||
$CjkFound = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $CjkFound) {
|
||||
Write-Warn "No CJK font verified yet - fonts were just installed, restart may be needed"
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
# ── Summary ──
|
||||
Write-Host "============================================"
|
||||
if ($Errors -eq 0) {
|
||||
Write-Host " All dependencies OK."
|
||||
} else {
|
||||
Write-Host " $Errors issue(s) found. Fix them above."
|
||||
}
|
||||
Write-Host " PDF_SKILL_DIR=$PDF_SKILL_DIR"
|
||||
Write-Host "============================================"
|
||||
Reference in new issue
Block a user