
Quip To Gitlab Wiki
- 19 installs
- 41 repo stars
- Updated July 6, 2026
- aws-samples/sample-agent-skills-for-builders
quip-to-gitlab-wiki is a Claude skill that migrates Quip documents into a GitLab Wiki, converting HTML to Markdown and preserving embedded images and video.
About
This skill migrates Quip documents into a GitLab Wiki while preserving content and media. A developer uses it to convert Quip HTML to Markdown, download embedded images, compress videos, and push the results into a GitLab Wiki with organized navigation. It also configures a GitLab MCP for future edits.
- Migrates Quip documents to GitLab Wiki with content preserved
- Converts HTML to Markdown and preserves embedded images and video
- Compresses video (MOV to MP4 at 720p) and organizes wiki navigation
Quip To Gitlab Wiki by the numbers
- 19 all-time installs (skills.sh)
- Ranked #1,011 of 1,879 Documentation skills by installs in the Skillselion catalog
- Data as of Jul 30, 2026 (Skillselion catalog sync)
quip-to-gitlab-wiki capabilities & compatibility
- Capabilities
- gitlab docs publishing · documentation
- Works with
- gitlab
- Use cases
- documentation
- Pricing
- Free
What quip-to-gitlab-wiki says it does
Migrate Quip documents to GitLab Wiki with full content preservation.
Migrate Quip documents to GitLab Wiki with full content and media preservation.
Video compression (MOV→MP4 at 720p)
npx skills add https://github.com/aws-samples/sample-agent-skills-for-builders --skill quip-to-gitlab-wikiAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 19 |
|---|---|
| repo stars | ★ 41 |
| Last updated | July 6, 2026 |
| Repository | aws-samples/sample-agent-skills-for-builders ↗ |
What it does
Migrate Quip documents into a GitLab Wiki, converting HTML to Markdown and preserving embedded images and video.
Who is it for?
Teams migrating documentation from Quip into a GitLab Wiki.
When should I use this skill?
When converting Quip docs to a wiki, migrating documentation, or setting up a GitLab wiki from Quip sources.
What you get
Quip docs are converted to Markdown, pushed to GitLab Wiki with images and compressed video preserved and navigation organized.
- Markdown wiki pages in GitLab
- downloaded images and compressed videos
- organized wiki navigation
By the numbers
- 5-step migration flow
- video compressed MOV to MP4 at 720p
Files
Quip to GitLab Wiki Migration
Migrate Quip documents to GitLab Wiki with full content and media preservation.
When to Apply
Reference this skill when:
- Migrating Quip documents to GitLab
- Converting documentation to wiki format
- Preserving embedded images and videos
- Setting up GitLab wiki structure
How It Works
1. Gather Sources - Collect Quip URLs and tokens 2. Convert - Transform Quip HTML to Markdown 3. Push - Upload to GitLab Wiki 4. Configure - Set up GitLab MCP for future edits 5. Structure - Organize wiki navigation
Prerequisites
- Quip API token
- GitLab project with wiki enabled
- Python 3.8+ with html2text
- ffmpeg (for video compression)
Usage
# Convert Quip document (token from environment)
export QUIP_TOKEN="your_token_here"
python scripts/quip-to-markdown.py <thread_id> <output_dir>
# Push to GitLab wiki
./scripts/push-to-wiki.sh <wiki_git_url> <content_dir> <page_path> <quip_url>Conversion Features
- HTML to Markdown via html2text
- Embedded image download
- Video compression (MOV→MP4 at 720p)
- Asset organization in
uploads/
Wiki Structure
wiki/
├── Home.md
├── Section-Name/
│ ├── _index.md
│ └── Page-Name.md
└── uploads/
├── image1.png
└── video1.mp4Image Path Rules
| Page Location | Image Path |
|---|---|
| Root pages | uploads/image.png |
| Section pages | ../uploads/image.png |
| Nested pages | ../../uploads/image.png |
References
- GitLab MCP Configuration
Quip to GitLab Wiki Migration Skill
Effortlessly migrate Quip documents to GitLab Wiki while preserving all content, images, and videos.
Installation
npx skills add https://github.com/aws-samples/sample-agent-skills-for-builders --skill quip-to-gitlab-wikiQuick Start
1. Set Up Your API Token
export QUIP_TOKEN="your_quip_api_token_here"2. Convert a Quip Document
python scripts/quip-to-markdown.py <quip_thread_id> ./outputExample:
python scripts/quip-to-markdown.py ABC123XYZ ./migrationThe script outputs:
content.md- Your Quip document as Markdownuploads/- Downloaded images and compressed videos- JSON summary with conversion details
3. Push to GitLab Wiki
./scripts/push-to-wiki.sh <wiki_git_url> ./migration my-page "https://quip.com/threadid"Example:
./scripts/push-to-wiki.sh https://gitlab.com/mygroup/myproject.wiki.git ./migration setup-guide "https://quip.com/ABC123XYZ"Prerequisites
- Quip API Token - Generate from Quip Developer Settings
- GitLab Project - Must have wiki feature enabled
- Python 3.8+ - With
html2textlibrary - System Tools -
ffmpeg(for video compression),curl,git
Install Dependencies
# Python packages
pip install html2text
# System packages (macOS)
brew install ffmpeg
# System packages (Ubuntu/Debian)
sudo apt-get install ffmpegFile Structure
skills/quip-to-gitlab-wiki/
├── README.md # This file
├── SKILL.md # Skill metadata & reference
├── scripts/
│ ├── quip-to-markdown.py # Main conversion script
│ └── push-to-wiki.sh # GitLab wiki uploader
└── references/
└── gitlab-mcp-config.md # GitLab MCP integration guideWhat Gets Converted
| Quip Content | Conversion |
|---|---|
| Text & Formatting | Markdown syntax |
| Images | Downloaded to uploads/ |
| Videos (MOV, MP4) | Compressed to 720p MP4 |
| Links | Preserved with relative paths |
| Tables | Markdown table format |
| Code Blocks | Markdown code fences |
Output Example
After conversion, your wiki directory looks like:
migration/
├── content.md
└── uploads/
├── img_ABC123_0.png # Images from Quip
├── img_ABC123_1.mp4 # Compressed videos
└── img_ABC123_2.pngImages in your Markdown are automatically linked:
Advanced Usage
Batch Migration
for thread_id in ABC123 XYZ789 DEF456; do
python scripts/quip-to-markdown.py $thread_id ./batch/$thread_id
doneCustom Wiki Structure
After converting, organize pages:
# Create structured wiki layout
mkdir -p wiki/Team-Docs/Onboarding
mv ./migration/content.md wiki/Team-Docs/Onboarding/Getting-Started.md
mv ./migration/uploads/* wiki/uploads/Relative Image Paths
The skill automatically handles relative paths:
- Root pages:
uploads/image.png - Section pages:
../uploads/image.png - Nested pages:
../../uploads/image.png
Troubleshooting
QUIP_TOKEN not set
ERROR: QUIP_TOKEN environment variable is not set.
Export it: export QUIP_TOKEN='your_token_here'Make sure the token is exported before running the script.
Invalid thread ID
Thread IDs must be alphanumeric. Check your Quip URL: https://quip.com/THREADID
Video compression fails
Ensure ffmpeg is installed and in your PATH:
ffmpeg -versionGitLab wiki push issues
- Verify wiki is enabled in your GitLab project settings
- Check Git credentials:
git config credential.helper - Test access:
git clone <wiki_git_url>
Integration with Claude Code
Once pushed to GitLab, use the GitLab MCP to:
- Create and update wiki pages from Claude Code
- Link issues to wiki documentation
- Manage wiki structure programmatically
License
MIT - See project LICENSE file
References
- Quip API Documentation
- GitLab Wiki Documentation
- GitLab MCP Configuration
Claude Code GitLab MCP Server Setup
Overview
The GitLab MCP (Model Context Protocol) server enables AI assistants to interact with GitLab repositories, issues, merge requests, and other GitLab resources through a standardized interface. This guide works for both gitlab.com and self-hosted/enterprise GitLab instances — replace gitlab.example.com in the examples below with your own GitLab hostname.
Prerequisites
- Node.js 20+
- GitLab instance access (e.g., https://gitlab.example.com/ or https://gitlab.com/)
- GitLab Personal Access Token with appropriate permissions
- Auth cookie file (only if your enterprise GitLab enforces SSO/cookie auth in addition to the PAT)
Installation
1. GitLab Personal Access Token Setup
1. Go to GitLab → User Settings → Access Tokens: https://<your-gitlab-host>/-/user_settings/personal_access_tokens 2. Create new token with required scopes 3. Copy the token (you won't see it again)
Required scopes:
api— Full API accessread_user— Read user informationread_repository— Read repository contentwrite_repository— Write repository content (for file operations)
2. Auth Cookie (enterprise GitLab only)
Some enterprise GitLab deployments sit behind SSO and require a browser auth cookie in addition to the personal access token. If that is the case, export the cookie from your authenticated browser session (or use your org's SSO tooling) and save it to a local file — for example ~/.gitlab/cookie.
If your GitLab instance only requires a PAT, you can skip this step and omit GITLAB_AUTH_COOKIE_PATH from the config below.
3. MCP Client Configuration
Add to .mcp.json in the project root:
{
"mcpServers": {
"GitLab": {
"command": "npx",
"args": ["-y", "@zereight/mcp-gitlab"],
"env": {
"GITLAB_PERSONAL_ACCESS_TOKEN": "${GITLAB_PERSONAL_ACCESS_TOKEN}",
"GITLAB_API_URL": "https://gitlab.example.com/api/v4",
"GITLAB_READ_ONLY_MODE": "false",
"USE_GITLAB_WIKI": "false",
"USE_MILESTONE": "false",
"USE_PIPELINE": "false",
"GITLAB_AUTH_COOKIE_PATH": "~/.gitlab/cookie"
}
}
}
}Tips:
- Do not commit TOKEN information to the repository — use environment variables
- Save
GITLAB_PERSONAL_ACCESS_TOKENas an env var (e.g., in~/.bashrcor~/.zshrc) - Set
USE_GITLAB_WIKIto"true"if wiki operations are needed - Replace
gitlab.example.comwith your GitLab hostname (orgitlab.comfor the SaaS instance)
4. Verify
$ claude mcp list
Checking MCP server health...
GitLab: npx -y @zereight/mcp-gitlab - ✓ ConnectedAvailable GitLab MCP Tools
Provides operations for:
- Repository Management
- File Operations
- Branch Management
- Issues Management
- Merge Request Operations
- Code Review Operations
Full list: https://github.com/zereight/gitlab-mcp?tab=readme-ov-file#tools-%EF%B8%8F
SSH Config for Wiki Repo Push
Wiki push uses git over SSH (not MCP). Configure ~/.ssh/config with your GitLab SSH host and key:
Host ssh.gitlab.example.com
User git
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yesIf your enterprise GitLab uses short-lived SSH certificates, add CertificateFile as well and refresh the certificate with your org's tooling whenever it expires.
#!/bin/bash
# Push converted content to GitLab Wiki repo.
# Usage: push_to_wiki.sh <wiki_git_url> <page_path> <content_dir> <quip_url>
set -euo pipefail
WIKI_URL="$1"
PAGE_PATH="$2"
CONTENT_DIR="$3"
QUIP_URL="${4:-}"
# Input validation
if [[ -z "$WIKI_URL" || -z "$PAGE_PATH" || -z "$CONTENT_DIR" ]]; then
echo "Usage: $0 <wiki_git_url> <page_path> <content_dir> [quip_url]"
exit 1
fi
# Validate wiki URL format (git SSH or HTTPS only)
if ! [[ "$WIKI_URL" =~ ^(git@|https://) ]]; then
echo "ERROR: Invalid wiki URL format. Must start with git@ or https://"
exit 1
fi
# Validate page path (no .. traversal)
if [[ "$PAGE_PATH" == *".."* ]]; then
echo "ERROR: Page path must not contain '..'"
exit 1
fi
# Sanitize QUIP_URL for safe use in commit messages (strip anything suspicious)
SAFE_QUIP_URL=$(echo "$QUIP_URL" | sed 's/[^a-zA-Z0-9:/_?#.=-]//g')
WORK_DIR=$(mktemp -d)
if [[ -z "$WORK_DIR" || ! "$WORK_DIR" == /tmp/* ]]; then
echo "ERROR: Failed to create secure temp directory"
exit 1
fi
trap "rm -rf '$WORK_DIR'" EXIT
echo "Cloning wiki repo..."
git clone "$WIKI_URL" "$WORK_DIR/wiki" 2>&1 | tail -1
cd "$WORK_DIR/wiki"
# Determine image path prefix based on page depth
PAGE_DIR=$(dirname "$PAGE_PATH")
if [ "$PAGE_DIR" = "." ]; then
IMG_PREFIX="uploads"
else
DEPTH=$(echo "$PAGE_DIR" | tr '/' '\n' | wc -l)
IMG_PREFIX=""
for i in $(seq 1 "$DEPTH"); do IMG_PREFIX="../${IMG_PREFIX}"; done
IMG_PREFIX="${IMG_PREFIX}uploads"
fi
# Copy uploads
mkdir -p uploads
if [ -d "$CONTENT_DIR/uploads" ]; then
cp "$CONTENT_DIR/uploads/"* uploads/ 2>/dev/null || true
echo "Copied $(ls uploads/ | wc -l) assets"
fi
# Create page directory if needed
mkdir -p "$(dirname "${PAGE_PATH}.md")"
# Copy content with adjusted image paths
sed "s|uploads/|${IMG_PREFIX}/|g" "$CONTENT_DIR/content.md" > "${PAGE_PATH}.md"
# Append source attribution
if [ -n "$SAFE_QUIP_URL" ]; then
printf '\n---\n> **Source**: Migrated from [Quip](%s)\n' "$SAFE_QUIP_URL" >> "${PAGE_PATH}.md"
fi
echo "Created: ${PAGE_PATH}.md"
# Commit and push
git add -A
git commit -m "Add ${PAGE_PATH} from Quip" 2>&1 | head -3
git push origin main 2>&1 | tail -3
echo "Pushed to wiki successfully"
#!/usr/bin/env python3
"""
Convert a Quip document to Markdown with images/videos downloaded.
Usage:
QUIP_TOKEN="xxx" python3 quip_to_markdown.py <quip_thread_id> <output_dir>
Token is read from QUIP_TOKEN environment variable (not command line args).
"""
import json, os, re, subprocess, sys
def get_token():
"""Read token from environment variable only."""
token = os.environ.get("QUIP_TOKEN")
if not token:
print("ERROR: QUIP_TOKEN environment variable is not set.", file=sys.stderr)
print("Export it: export QUIP_TOKEN='your_token_here'", file=sys.stderr)
sys.exit(1)
return token
def sanitize_path(base_dir, filename):
"""Prevent path traversal by ensuring output stays within base_dir."""
abs_base = os.path.realpath(base_dir)
abs_target = os.path.realpath(os.path.join(base_dir, filename))
if not abs_target.startswith(abs_base + os.sep) and abs_target != abs_base:
raise ValueError(f"Path traversal detected: {filename}")
return abs_target
def fetch_thread(thread_id, token, base_url="https://platform.quip-amazon.com"):
"""Fetch Quip thread via API with SSL verification."""
import urllib.request, ssl
# Validate thread_id: alphanumeric only
if not re.match(r'^[A-Za-z0-9]+$', thread_id):
raise ValueError(f"Invalid thread ID: {thread_id}")
# Security: Only allow HTTPS URLs to prevent file:// scheme attacks
if not base_url.startswith("https://"):
raise ValueError(f"Only HTTPS URLs are allowed: {base_url}")
url = f"{base_url}/1/threads/{thread_id}"
ctx = ssl.create_default_context()
req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"})
with urllib.request.urlopen(req, timeout=30, context=ctx) as resp: # nosec B310 - URL scheme validated above
return json.loads(resp.read().decode())
def download_blob(blob_id, blob_name, token, output_path):
"""Download blob using curl with token via stdin to avoid ps leakage."""
# Validate blob_id and blob_name
if not re.match(r'^[A-Za-z0-9_-]+$', blob_id):
return False
clean_name = re.sub(r'[^A-Za-z0-9._-]', '', blob_name)
url = f"https://quip-amazon.com/blob/{blob_id}/{clean_name}"
# Use --config stdin to pass header, avoiding token in process list
config = f'header = "Authorization: Bearer {token}"'
proc = subprocess.run(
["curl", "-s", "-L", "--config", "-", url, "-o", output_path],
input=config, capture_output=True, text=True, timeout=60
)
return os.path.exists(output_path) and os.path.getsize(output_path) > 100
def detect_file_type(path):
return subprocess.run(["file", "-b", path], capture_output=True, text=True).stdout.strip()
def compress_video(input_path, output_path):
r = subprocess.run(
["ffmpeg", "-i", input_path, "-vf", "scale=1280:720",
"-c:v", "libx264", "-crf", "28", "-preset", "fast", "-an",
output_path, "-y"],
capture_output=True, timeout=300
)
return r.returncode == 0 and os.path.exists(output_path)
def html_to_markdown(html):
import html2text
html = re.sub(r'<control[^>]*>', '', html)
html = re.sub(r'</control>', '', html)
h = html2text.HTML2Text()
h.body_width = 0; h.unicode_snob = True; h.protect_links = True
md = h.handle(html)
md = re.sub(r'\{#temp:C:[^}]+\}', '', md)
md = re.sub(r'\n{4,}', '\n\n\n', md)
return md
def convert(thread_id, token, output_dir):
# Validate output_dir
output_dir = os.path.realpath(output_dir)
uploads_dir = os.path.join(output_dir, "uploads")
os.makedirs(uploads_dir, exist_ok=True)
data = fetch_thread(thread_id, token)
title = data["thread"]["title"]; html = data["html"]
print(f"Title: {title} ({len(html)} chars HTML)")
blobs = re.findall(r'/blob/([^/\s"\'<>]+)/([^/\s"\'<>]+)', html)
asset_map = {}
for i, (blob_id, blob_name) in enumerate(blobs):
clean_name = blob_name.rstrip("'\"")
fname = f"img_{thread_id[:6]}_{i}.png"
fpath = sanitize_path(uploads_dir, fname)
if download_blob(blob_id, clean_name, token, fpath):
ftype = detect_file_type(fpath)
if "QuickTime" in ftype or "ISO Media" in ftype:
mp4 = fname.replace(".png", ".mp4")
mp4_path = sanitize_path(uploads_dir, mp4)
if compress_video(fpath, mp4_path):
os.remove(fpath); fname = mp4
asset_map[f"/blob/{blob_id}/{clean_name}"] = f"uploads/{fname}"
fsize = os.path.getsize(os.path.join(uploads_dir, fname))
print(f" Video {i}: {fname} ({fsize//1024}KB)")
else:
asset_map[f"/blob/{blob_id}/{clean_name}"] = f"uploads/{fname}"
print(f" Image {i}: {fname} ({os.path.getsize(fpath)//1024}KB)")
md = html_to_markdown(html)
for old, new in asset_map.items(): md = md.replace(old, new)
md_path = os.path.join(output_dir, "content.md")
with open(md_path, "w") as f:
f.write(md)
print(f"Output: {md_path} ({len(md)} chars, {len(asset_map)} assets)")
return {"title": title, "thread_id": thread_id, "md_path": md_path, "assets": list(asset_map.values())}
if __name__ == "__main__":
if len(sys.argv) < 3:
print(f"Usage: QUIP_TOKEN=xxx {sys.argv[0]} <thread_id> <output_dir>")
sys.exit(1)
token = get_token()
print(json.dumps(convert(sys.argv[1], token, sys.argv[2]), indent=2))
Related skills
FAQ
Is embedded media preserved during migration?
Yes. Embedded images are downloaded and videos are compressed (MOV to MP4 at 720p) so content and media are preserved.
What is needed to run the migration?
A Quip API token, a GitLab project with wiki enabled, Python 3.8+ with html2text, and ffmpeg for video compression.