finalize doc-translator skill

This commit is contained in:
m3tm3re
2026-02-11 19:58:06 +01:00
parent b9d535b926
commit 94b89da533
6 changed files with 142 additions and 142 deletions

View File

@@ -1,5 +1,15 @@
# Opencode Skills Repository # Opencode Skills Repository
## MANDATORY: Use td for Task Management
Run td usage --new-session at conversation start (or after /clear). This tells you what to work on next.
Sessions are automatic (based on terminal/agent context). Optional:
- td session "name" to label the current session
- td session --new to force a new session in the same context
Use td usage -q after first read.
Configuration repository for Opencode Agent Skills, context files, and agent configurations. Deployed via Nix home-manager to `~/.config/opencode/`. Configuration repository for Opencode Agent Skills, context files, and agent configurations. Deployed via Nix home-manager to `~/.config/opencode/`.
## Quick Commands ## Quick Commands

View File

@@ -8,7 +8,7 @@
# ./scripts/test-skill.sh --run # Launch interactive opencode session # ./scripts/test-skill.sh --run # Launch interactive opencode session
# #
# This script creates a temporary XDG_CONFIG_HOME with symlinks to this # This script creates a temporary XDG_CONFIG_HOME with symlinks to this
# repository's skill/, context/, command/, and prompts/ directories, # repository's skills/, context/, command/, and prompts/ directories,
# allowing you to test skill changes before deploying via home-manager. # allowing you to test skill changes before deploying via home-manager.
set -euo pipefail set -euo pipefail
@@ -72,17 +72,17 @@ list_skills() {
validate_skill() { validate_skill() {
local skill_name="$1" local skill_name="$1"
local skill_path="$REPO_ROOT/skill/$skill_name" local skill_path="$REPO_ROOT/skills/$skill_name"
if [[ ! -d "$skill_path" ]]; then if [[ ! -d "$skill_path" ]]; then
echo -e "${RED}❌ Skill not found: $skill_name${NC}" echo -e "${RED}❌ Skill not found: $skill_name${NC}"
echo "Available skills:" echo "Available skills:"
ls -1 "$REPO_ROOT/skill/" ls -1 "$REPO_ROOT/skills/"
exit 1 exit 1
fi fi
echo -e "${YELLOW}Validating skill: $skill_name${NC}" echo -e "${YELLOW}Validating skill: $skill_name${NC}"
if python3 "$REPO_ROOT/skill/skill-creator/scripts/quick_validate.py" "$skill_path"; then if python3 "$REPO_ROOT/skills/skill-creator/scripts/quick_validate.py" "$skill_path"; then
echo -e "${GREEN}✅ Skill '$skill_name' is valid${NC}" echo -e "${GREEN}✅ Skill '$skill_name' is valid${NC}"
else else
echo -e "${RED}❌ Skill '$skill_name' has validation errors${NC}" echo -e "${RED}❌ Skill '$skill_name' has validation errors${NC}"
@@ -95,14 +95,14 @@ validate_all() {
echo "" echo ""
local failed=0 local failed=0
for skill_dir in "$REPO_ROOT/skill/"*/; do for skill_dir in "$REPO_ROOT/skills/"*/; do
local skill_name=$(basename "$skill_dir") local skill_name=$(basename "$skill_dir")
echo -n " $skill_name: " echo -n " $skill_name: "
if python3 "$REPO_ROOT/skill/skill-creator/scripts/quick_validate.py" "$skill_dir" > /dev/null 2>&1; then if python3 "$REPO_ROOT/skills/skill-creator/scripts/quick_validate.py" "$skill_dir" > /dev/null 2>&1; then
echo -e "${GREEN}${NC}" echo -e "${GREEN}${NC}"
else else
echo -e "${RED}${NC}" echo -e "${RED}${NC}"
python3 "$REPO_ROOT/skill/skill-creator/scripts/quick_validate.py" "$skill_dir" 2>&1 | sed 's/^/ /' python3 "$REPO_ROOT/skills/skill-creator/scripts/quick_validate.py" "$skill_dir" 2>&1 | sed 's/^/ /'
((failed++)) || true ((failed++)) || true
fi fi
done done

View File

@@ -72,76 +72,72 @@ If an image download fails, log it and continue. Use a placeholder in the final
### 4. Upload Images to Outline ### 4. Upload Images to Outline
**IMPORTANT:** Always use Outline MCP tools for all Outline operations. If Outline tools throw errors: MCP-outline does not support attachment creation. Use the bundled script for image uploads:
1. Load the outline skill first: `skill name=outline`
2. Retry with `skill_mcp` tool for outline operations
3. Only fallback to direct API calls via `bash` after exhausting MCP options
mcp-outline cannot create attachments. Use direct API calls via `bash` for image uploads only.
**Required env:** `OUTLINE_API_KEY` (read from /run/agenix/outline-key)
For each downloaded image:
```bash ```bash
#!/usr/bin/env bash # Upload with optional document association
set -euo pipefail bash scripts/upload_image_to_outline.sh "/tmp/doc-images/screenshot.png" "$DOCUMENT_ID"
IMAGE_PATH="/tmp/doc-images/screenshot.png" # Upload without document (attach later)
IMAGE_NAME="$(basename "$IMAGE_PATH")" bash scripts/upload_image_to_outline.sh "/tmp/doc-images/screenshot.png"
CONTENT_TYPE="image/png" # Detect from extension: png->image/png, jpg/jpeg->image/jpeg, gif->image/gif, svg->image/svg+xml, webp->image/webp
# 1. Get file size (cross-platform)
FILESIZE=$(stat -f%z "$IMAGE_PATH" 2>/dev/null || stat -c%s "$IMAGE_PATH")
# 2. Create attachment record
RESPONSE=$(curl -s -X POST "https://wiki.az-gruppe.com/api/attachments.create" \
-H "Authorization: Bearer $OUTLINE_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"$IMAGE_NAME\",
\"contentType\": \"$CONTENT_TYPE\",
\"size\": $FILESIZE
}")
# 3. Extract URLs from response
UPLOAD_URL=$(echo "$RESPONSE" | jq -r '.data.uploadUrl')
ATTACHMENT_URL=$(echo "$RESPONSE" | jq -r '.data.attachment.url')
# 4. Check for errors
if [ "$UPLOAD_URL" = "null" ] || [ -z "$UPLOAD_URL" ]; then
echo "ERROR: Failed to create attachment. Response: $RESPONSE" >&2
exit 1
fi
# 5. Upload binary to signed URL
curl -s -X PUT "$UPLOAD_URL" \
-H "Content-Type: $CONTENT_TYPE" \
--data-binary "@$IMAGE_PATH"
# 6. Output the attachment URL for use in markdown
echo "$ATTACHMENT_URL"
``` ```
Replace image references in the translated markdown: The script handles API key loading from `/run/agenix/outline-key`, content-type detection, the two-step presigned POST flow, and retries. Output is JSON: `{"success": true, "attachment_url": "https://..."}`.
Replace image references in the translated markdown with the returned `attachment_url`:
```markdown ```markdown
![description](ATTACHMENT_URL) ![description](ATTACHMENT_URL)
``` ```
**Content-Type detection by extension:** For all other Outline operations (documents, collections, search), use MCP tools (`Outline_*`).
| Extension | Content-Type |
|-----------|-------------|
| `.png` | `image/png` |
| `.jpg`, `.jpeg` | `image/jpeg` |
| `.gif` | `image/gif` |
| `.svg` | `image/svg+xml` |
| `.webp` | `image/webp` |
### 5. Translate with TEEM Format ### 5. Translate with TEEM Format
Translate the entire document into each target language. Apply TEEM format to UI elements. Translate the entire document into each target language. Apply TEEM format to UI elements.
#### Address Form (CRITICAL)
**Always use the informal "you" form** in ALL target languages:
- **German**: Use **"Du"** (informal), NEVER "Sie" (formal)
- **Czech**: Use **"ty"** (informal), NEVER "vy" (formal)
- This applies to all translations — documentation should feel approachable and direct
#### Infobox / Callout Formatting
Source documentation often uses admonitions, callouts, or info boxes (e.g., GitHub-style `> [!NOTE]`, Docusaurus `:::note`, or custom HTML boxes). **Convert ALL such elements** to Outline's callout syntax:
```markdown
:::tip
Tip or best practice content here.
:::
:::info
Informational content here.
:::
:::warning
Warning or caution content here.
:::
:::success
Success message or positive outcome here.
:::
```
**Mapping rules** (source → Outline):
| Source pattern | Outline syntax |
|---|---|
| Note, Info, Information | `:::info` |
| Tip, Hint, Best Practice | `:::tip` |
| Warning, Caution, Danger, Important | `:::warning` |
| Success, Done, Check | `:::success` |
**CRITICAL formatting**: The closing `:::` MUST be on its own line with an empty line before it. Content goes directly after the opening line.
#### TEEM Rules #### TEEM Rules
**Format:** `**English UI Term** (Translation)` **Format:** `**English UI Term** (Translation)`
@@ -217,7 +213,7 @@ Use mcp-outline tools to publish:
|-------|--------| |-------|--------|
| URL fetch fails | Use `question` to ask for alternative URL or manual paste | | URL fetch fails | Use `question` to ask for alternative URL or manual paste |
| Image download fails | Continue with placeholder, note in completion report | | Image download fails | Continue with placeholder, note in completion report |
| Outline API error (attachments) | Save markdown to `/tmp/doc-translator-backup-TIMESTAMP.md`, report error | | Outline API error (attachments) | Script retries 3x with backoff; on final failure save markdown to `/tmp/doc-translator-backup-TIMESTAMP.md`, report error |
| Outline API error (document) | Save markdown to `/tmp/doc-translator-backup-TIMESTAMP.md`, report error | | Outline API error (document) | Save markdown to `/tmp/doc-translator-backup-TIMESTAMP.md`, report error |
| Ambiguous UI term | Use `question` to ask user for correct translation | | Ambiguous UI term | Use `question` to ask user for correct translation |
| Large document (>5000 words) | Ask user if splitting into multiple docs is preferred | | Large document (>5000 words) | Ask user if splitting into multiple docs is preferred |
@@ -254,9 +250,9 @@ Items Needing Review:
## Environment Variables ## Environment Variables
| Variable | Purpose | | Variable | Purpose | Source |
|----------|---------| |----------|---------|--------|
| `OUTLINE_API_KEY` | Bearer token for wiki.az-gruppe.com API | | `OUTLINE_API_KEY` | Bearer token for wiki.az-gruppe.com API | Auto-loaded from `/run/agenix/outline-key` by upload script |
## Integration with Other Skills ## Integration with Other Skills

View File

@@ -1,32 +0,0 @@
#!/usr/bin/env bash
# List all collections available in Outline
#
# Usage:
# list_outline_collections.sh
#
# Environment:
# OUTLINE_API_KEY - Bearer token for wiki.az-gruppe.com API
#
# Output (JSON):
# [{"id": "...", "name": "...", "url": "..."}, ...]
set -euo pipefail
# List collections
RESPONSE=$(curl -s -X POST "https://wiki.az-gruppe.com/api/collections.list" \
-H "Authorization: Bearer $OUTLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{}')
# Check for errors
if echo "$RESPONSE" | jq -e '.error' > /dev/null 2>&1; then
ERROR_MSG=$(echo "$RESPONSE" | jq -r '.message // "Failed to list collections"')
echo "{\"success\": false, \"error\": \"$ERROR_MSG\", \"response\": \"$RESPONSE\"}" >&2
exit 1
fi
# Extract collection list
COLLECTIONS=$(echo "$RESPONSE" | jq -r '.data[] | {id: .id, name: .name, url: .url}')
# Output as JSON array
echo "$COLLECTIONS" | jq -s '.'

View File

@@ -1,26 +1,40 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Upload an image to an Outline document via signed URL # Upload an image to Outline via presigned POST (two-step flow)
# #
# Usage: # Usage:
# upload_image_to_outline.sh <image_path> <document_id> # upload_image_to_outline.sh <image_path> [document_id]
# #
# Environment: # Environment:
# OUTLINE_API_KEY - Bearer token for wiki.az-gruppe.com API # OUTLINE_API_KEY - Bearer token for wiki.az-gruppe.com API
# Auto-loaded from /run/agenix/outline-key if not set
# #
# Output (JSON): # Output (JSON to stdout):
# {"success": true, "attachment_url": "https://...", "document_id": "..."} # {"success": true, "attachment_url": "https://..."}
# OR # Error (JSON to stderr):
# {"success": false, "error": "error message"} # {"success": false, "error": "error message"}
set -euo pipefail set -euo pipefail
if [ $# -ne 2 ]; then MAX_RETRIES=3
echo '{"success": false, "error": "Usage: upload_image_to_outline.sh <image_path> <document_id>"}' >&2 RETRY_DELAY=2
if [ $# -lt 1 ] || [ $# -gt 2 ]; then
echo '{"success": false, "error": "Usage: upload_image_to_outline.sh <image_path> [document_id]"}' >&2
exit 1 exit 1
fi fi
IMAGE_PATH="$1" IMAGE_PATH="$1"
DOCUMENT_ID="$2" DOCUMENT_ID="${2:-}"
if [ -z "${OUTLINE_API_KEY:-}" ]; then
if [ -f /run/agenix/outline-key ]; then
OUTLINE_API_KEY=$(cat /run/agenix/outline-key)
export OUTLINE_API_KEY
else
echo '{"success": false, "error": "OUTLINE_API_KEY not set and /run/agenix/outline-key not found"}' >&2
exit 1
fi
fi
# Check if file exists # Check if file exists
if [ ! -f "$IMAGE_PATH" ]; then if [ ! -f "$IMAGE_PATH" ]; then
@@ -31,60 +45,72 @@ fi
# Extract image name and extension # Extract image name and extension
IMAGE_NAME="$(basename "$IMAGE_PATH")" IMAGE_NAME="$(basename "$IMAGE_PATH")"
EXTENSION="${IMAGE_NAME##*.}" EXTENSION="${IMAGE_NAME##*.}"
IMAGE_NAME_BASE="${IMAGE_NAME%.*}"
# Detect content type by extension # Detect content type by extension
case "$EXTENSION" in case "${EXTENSION,,}" in
png) CONTENT_TYPE="image/png" ;; png) CONTENT_TYPE="image/png" ;;
jpg|jpeg) CONTENT_TYPE="image/jpeg" ;; jpg|jpeg) CONTENT_TYPE="image/jpeg" ;;
gif) CONTENT_TYPE="image/gif" ;; gif) CONTENT_TYPE="image/gif" ;;
svg) CONTENT_TYPE="image/svg+xml" ;; svg) CONTENT_TYPE="image/svg+xml" ;;
webp) CONTENT_TYPE="image/webp" ;; webp) CONTENT_TYPE="image/webp" ;;
*) CONTENT_TYPE="application/octet-stream" ;; *) CONTENT_TYPE="application/octet-stream" ;;
esac esac
# Get file size (cross-platform: macOS uses stat -f%z, Linux uses stat -c%s) FILESIZE=$(stat -c%s "$IMAGE_PATH" 2>/dev/null || stat -f%z "$IMAGE_PATH" 2>/dev/null)
FILESIZE=$(stat -f%z "$IMAGE_PATH" 2>/dev/null || stat -c%s "$IMAGE_PATH" 2>/dev/null)
if [ -z "$FILESIZE" ]; then if [ -z "$FILESIZE" ]; then
echo "{\"success\": false, \"error\": \"Failed to get file size for: $IMAGE_PATH\"}" >&2 echo "{\"success\": false, \"error\": \"Failed to get file size for: $IMAGE_PATH\"}" >&2
exit 1 exit 1
fi fi
# Create attachment record REQUEST_BODY=$(jq -n \
--arg name "$IMAGE_NAME" \
--arg contentType "$CONTENT_TYPE" \
--argjson size "$FILESIZE" \
--arg documentId "$DOCUMENT_ID" \
'if $documentId == "" then
{name: $name, contentType: $contentType, size: $size}
else
{name: $name, contentType: $contentType, size: $size, documentId: $documentId}
end')
# Step 1: Create attachment record
RESPONSE=$(curl -s -X POST "https://wiki.az-gruppe.com/api/attachments.create" \ RESPONSE=$(curl -s -X POST "https://wiki.az-gruppe.com/api/attachments.create" \
-H "Authorization: Bearer $OUTLINE_API_KEY" \ -H "Authorization: Bearer $OUTLINE_API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "{ -d "$REQUEST_BODY")
\"name\": \"$IMAGE_NAME\",
\"contentType\": \"$CONTENT_TYPE\",
\"size\": $FILESIZE,
\"documentId\": \"$DOCUMENT_ID\"
}")
# Extract URLs from response
UPLOAD_URL=$(echo "$RESPONSE" | jq -r '.data.uploadUrl // empty') UPLOAD_URL=$(echo "$RESPONSE" | jq -r '.data.uploadUrl // empty')
ATTACHMENT_URL=$(echo "$RESPONSE" | jq -r '.data.attachment.url // empty') ATTACHMENT_URL=$(echo "$RESPONSE" | jq -r '.data.attachment.url // empty')
# Check for errors if [ -z "$UPLOAD_URL" ]; then
if [ -z "$UPLOAD_URL" ] || [ "$UPLOAD_URL" = "null" ]; then
ERROR_MSG=$(echo "$RESPONSE" | jq -r '.message // "Failed to create attachment"') ERROR_MSG=$(echo "$RESPONSE" | jq -r '.message // "Failed to create attachment"')
echo "{\"success\": false, \"error\": \"$ERROR_MSG\", \"response\": \"$RESPONSE\"}" >&2 echo "{\"success\": false, \"error\": \"$ERROR_MSG\", \"response\": $(echo "$RESPONSE" | jq -c .)}" >&2
exit 1 exit 1
fi fi
# Upload binary to signed URL FORM_ARGS=()
UPLOAD_RESPONSE=$(curl -s -w "%{http_code}" -X POST "$UPLOAD_URL" \ while IFS= read -r line; do
-H "Content-Type: $CONTENT_TYPE" \ key=$(echo "$line" | jq -r '.key')
--data-binary "@$IMAGE_PATH") value=$(echo "$line" | jq -r '.value')
FORM_ARGS+=(-F "$key=$value")
done < <(echo "$RESPONSE" | jq -c '.data.form | to_entries[]')
# Extract HTTP status code (last 3 characters) # Step 2: Upload binary to presigned URL with retry
HTTP_CODE="${UPLOAD_RESPONSE: -3}" for attempt in $(seq 1 "$MAX_RETRIES"); do
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$UPLOAD_URL" \
"${FORM_ARGS[@]}" \
-F "file=@$IMAGE_PATH")
if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "201" ]; then if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "204" ]; then
echo "{\"success\": false, \"error\": \"Upload failed with HTTP $HTTP_CODE\", \"upload_url\": \"$UPLOAD_URL\"}" >&2 echo "{\"success\": true, \"attachment_url\": \"$ATTACHMENT_URL\"}"
exit 1 exit 0
fi fi
# Output success result if [ "$attempt" -lt "$MAX_RETRIES" ]; then
echo "{\"success\": true, \"attachment_url\": \"$ATTACHMENT_URL\", \"document_id\": \"$DOCUMENT_ID\"}" sleep "$((RETRY_DELAY * attempt))"
fi
done
echo "{\"success\": false, \"error\": \"Upload failed after $MAX_RETRIES attempts (last HTTP $HTTP_CODE)\"}" >&2
exit 1

View File

@@ -6,8 +6,8 @@ Usage:
init_skill.py <skill-name> --path <path> init_skill.py <skill-name> --path <path>
Examples: Examples:
init_skill.py my-new-skill --path ~/.config/opencode/skill init_skill.py my-new-skill --path ~/.config/opencode/skills
init_skill.py my-api-helper --path .opencode/skill init_skill.py my-api-helper --path .opencode/skills
init_skill.py custom-skill --path /custom/location init_skill.py custom-skill --path /custom/location
""" """