Update memory: Camie Tagger v2 upgrade results

This commit is contained in:
root
2026-03-30 12:27:20 +02:00
commit c3033db6a7
1082 changed files with 649864 additions and 0 deletions

View File

@@ -0,0 +1,103 @@
# curl + bash (Unix/macOS, no dependencies)
Run in a single shell call (avoid relying on exported variables persisting across tool calls).
Replace:
- `<API_KEY>` with the user's Evolink key
- `<OUTPUT_FILE>` with `evolink-<TIMESTAMP>.webp` (MUST be sanitized, see below)
- `<USER_PROMPT>`, `<SIZE>`, and `<true|false>`
**SECURITY: Sanitize `<OUTPUT_FILE>` before substitution:**
```bash
# Strip all shell metacharacters, keep only alphanumeric, dash, underscore, dot
SAFE_NAME=$(echo "<OUTPUT_FILE>" | tr -cd 'A-Za-z0-9._-')
```
```bash
API_KEY="<API_KEY>"
RAW_OUT="<OUTPUT_FILE>"
# CRITICAL: Sanitize filename to prevent shell injection
OUT_FILE=$(echo "$RAW_OUT" | tr -cd 'A-Za-z0-9._-')
# Ensure it has a valid extension
if [[ ! "$OUT_FILE" =~ \.(webp|png|jpg|jpeg)$ ]]; then
OUT_FILE="${OUT_FILE}.webp"
fi
# Ensure it's not empty
if [ -z "$OUT_FILE" ]; then
OUT_FILE="evolink-$(date +%s).webp"
fi
PROMPT="<USER_PROMPT>"
SIZE="<SIZE>"
NSFW_CHECK=<true|false>
json_escape() {
local s="$1"
s="${s//\\/\\\\}"
s="${s//\"/\\\"}"
s="${s//$'\n'/\\n}"
s="${s//$'\t'/\\t}"
s="${s//$'\r'/\\r}"
printf '%s' "$s"
}
PROMPT_ESC=$(json_escape "$PROMPT")
RESP=$(curl -s -X POST "https://api.evolink.ai/v1/images/generations" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d @- <<EVOLINK_END
{"model":"z-image-turbo","prompt":"$PROMPT_ESC","size":"$SIZE","nsfw_check":$NSFW_CHECK}
EVOLINK_END
)
TASK_ID=$(echo "$RESP" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
if [ -z "$TASK_ID" ]; then
echo "Error: Failed to submit task. Response: $RESP"
exit 1
fi
MAX_RETRIES=72
for i in $(seq 1 $MAX_RETRIES); do
sleep 10
TASK=$(curl -s "https://api.evolink.ai/v1/tasks/$TASK_ID" \
-H "Authorization: Bearer $API_KEY")
STATUS=$(echo "$TASK" | grep -o '"status":"[^"]*"' | head -1 | cut -d'"' -f4)
if [ "$STATUS" = "completed" ]; then
URL=$(echo "$TASK" | grep -o '"results":\["[^"]*"\]' | grep -o 'https://[^"]*')
curl -s -o "$OUT_FILE" "$URL"
# Safe path resolution (no command substitution on user input)
FULL_PATH="$(cd "$(dirname "$OUT_FILE")" && pwd)/$(basename "$OUT_FILE")"
echo "MEDIA:$FULL_PATH"
break
fi
if [ "$STATUS" = "failed" ]; then
echo "Generation failed: $TASK"
break
fi
done
if [ "$i" -eq "$MAX_RETRIES" ]; then
echo "Timed out after max retries."
fi
```
If you only have a URL and no file yet, download it immediately (URL expires in ~24 hours):
```bash
RAW_OUT="<OUTPUT_FILE>"
# CRITICAL: Sanitize filename
OUT_FILE=$(echo "$RAW_OUT" | tr -cd 'A-Za-z0-9._-')
if [[ ! "$OUT_FILE" =~ \.(webp|png|jpg|jpeg)$ ]]; then
OUT_FILE="${OUT_FILE}.webp"
fi
if [ -z "$OUT_FILE" ]; then
OUT_FILE="evolink-result.webp"
fi
curl -L -o "$OUT_FILE" "<URL>"
FULL_PATH="$(cd "$(dirname "$OUT_FILE")" && pwd)/$(basename "$OUT_FILE")"
echo "MEDIA:$FULL_PATH"
```

View File

@@ -0,0 +1,131 @@
# PowerShell fallback (Windows, no Python needed)
> Requires PowerShell 5.1+ (pre-installed on all Windows). Save the script below as a `.ps1` file and run with `powershell -File generate.ps1 -Prompt "a cute cat"`.
```powershell
<#
.SYNOPSIS
Generate an image via Evolink Z-Image-Turbo (Windows PowerShell fallback).
.DESCRIPTION
Zero external dependencies. Uses Invoke-RestMethod (built into PowerShell 5.1+).
Outputs MEDIA:<path> for OpenClaw auto-attach.
#>
param(
[string]$ApiKey = $env:EVOLINK_API_KEY,
[Parameter(Mandatory)][string]$Prompt,
[string]$Size = "1:1",
[string]$NsfwCheck = "false",
[Nullable[int]]$Seed,
[string]$Out,
[int]$PollSeconds = 10,
[int]$MaxRetries = 72,
[switch]$Verbose_
)
$ErrorActionPreference = "Stop"
if (-not $ApiKey) {
Write-Error "EVOLINK_API_KEY not set and -ApiKey not provided."
exit 2
}
if ($Prompt.Length -gt 2000) {
Write-Error "Prompt exceeds 2000 characters."
exit 2
}
if ($Out) {
$Out = [System.IO.Path]::GetFullPath($Out)
}
$apiBase = "https://api.evolink.ai/v1"
$headers = @{
"Authorization" = "Bearer $ApiKey"
"User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
"Accept" = "application/json"
}
$body = @{
model = "z-image-turbo"
prompt = $Prompt
size = $Size
nsfw_check = ($NsfwCheck -eq "true")
}
if ($null -ne $Seed) {
$body["seed"] = $Seed
}
$jsonBody = $body | ConvertTo-Json -Compress -Depth 5
try {
$resp = Invoke-RestMethod -Uri "$apiBase/images/generations" -Method POST -Headers $headers -Body $jsonBody -ContentType "application/json" -TimeoutSec 60
} catch {
Write-Error "Failed to submit task: $_"
exit 1
}
$taskId = if ($resp.id) { $resp.id } elseif ($resp.task_id) { $resp.task_id } else { $null }
if (-not $taskId) {
Write-Error "Failed to submit task; response: $($resp | ConvertTo-Json -Compress)"
exit 1
}
if ($Verbose_) {
Write-Host "Task submitted: $taskId"
}
for ($i = 1; $i -le $MaxRetries; $i++) {
Start-Sleep -Seconds $PollSeconds
try {
$task = Invoke-RestMethod -Uri "$apiBase/tasks/$taskId" -Method GET -Headers $headers -TimeoutSec 60
} catch {
Write-Error "Failed to poll task: $_"
exit 1
}
$status = $task.status
if ($Verbose_) {
Write-Host "[$i] Status: $status"
}
if ($status -eq "completed") {
$results = $task.results
if (-not $results -or $results.Count -eq 0) {
Write-Error "Task completed but no results found: $($task | ConvertTo-Json -Compress)"
exit 1
}
$url = $results[0]
# Infer extension from URL
if (-not $Out) {
$urlPath = ([System.Uri]$url).AbsolutePath
$ext = [System.IO.Path]::GetExtension($urlPath).ToLower()
if ($ext -notin ".png", ".jpg", ".jpeg", ".webp") { $ext = ".webp" }
$ts = Get-Date -Format "yyyyMMdd-HHmmss-fff"
$Out = [System.IO.Path]::GetFullPath("evolink-$ts$ext")
}
try {
Invoke-WebRequest -Uri $url -OutFile $Out -TimeoutSec 120 -UserAgent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
} catch {
Write-Error "Failed to download image: $_"
exit 1
}
if ($Verbose_) {
Write-Host "Image URL: $url"
Write-Host "Downloaded to: $Out"
}
Write-Output "MEDIA:$Out"
exit 0
}
if ($status -eq "failed") {
Write-Error "Generation failed: $($task | ConvertTo-Json -Compress)"
exit 1
}
}
Write-Error "Timed out after $MaxRetries polls. Task ID: $taskId"
exit 1
```

View File

@@ -0,0 +1,158 @@
# Python reference (all platforms, zero dependencies)
> Requires Python 3.6+. Uses only stdlib (`urllib`, `json`, `time`).
Save the script below as `generate.py` and run with `python3 generate.py --prompt "a cute cat" --size "1:1"`.
Options: `--size` (1:1, 2:3, 3:2, 3:4, 4:3, 9:16, 16:9, 1:2, 2:1), `--seed INT`, `--nsfw-check true`
```python
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import datetime as _dt
import json
import os
import posixpath
import sys
import time
import urllib.error
import urllib.request
from urllib.parse import urlparse
API_BASE = "https://api.evolink.ai/v1"
_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
def _json_request(url: str, api_key: str, method: str = "GET", payload=None, timeout_s: int = 60):
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"User-Agent": _UA,
"Accept": "application/json",
}
data = None
if payload is not None:
data = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(url, data=data, headers=headers, method=method)
try:
with urllib.request.urlopen(req, timeout=timeout_s) as resp:
body = resp.read()
except urllib.error.HTTPError as e:
err_body = e.read()
raise RuntimeError(f"HTTP {e.code} {e.reason}: {err_body.decode('utf-8', 'replace')}") from None
except urllib.error.URLError as e:
raise RuntimeError(f"Network error: {e.reason}") from None
try:
return json.loads(body.decode("utf-8"))
except Exception:
raise RuntimeError(f"Non-JSON response: {body[:200]!r}") from None
def _download(url: str, out_file: str, timeout_s: int = 120):
req = urllib.request.Request(url, method="GET", headers={"User-Agent": _UA})
try:
with urllib.request.urlopen(req, timeout=timeout_s) as resp:
content = resp.read()
except Exception as e:
raise RuntimeError(f"Failed to download result: {e}") from None
with open(out_file, "wb") as f:
f.write(content)
def _default_out_file(ext: str = ".webp"):
ts = _dt.datetime.now().strftime("%Y%m%d-%H%M%S-%f")
return f"evolink-{ts}{ext}"
def _ext_from_url(url: str) -> str:
path = urlparse(url).path
_, ext = posixpath.splitext(path)
return ext.lower() if ext in (".png", ".jpg", ".jpeg", ".webp") else ".webp"
def main(argv: list[str]) -> int:
parser = argparse.ArgumentParser(description="Generate an image via Evolink Z-Image-Turbo.")
parser.add_argument(
"--api-key",
default=os.getenv("EVOLINK_API_KEY", ""),
help="Evolink API key (or set EVOLINK_API_KEY).",
)
parser.add_argument("--prompt", required=True, help="Image prompt (max 2000 chars recommended).")
parser.add_argument("--size", default="1:1", help="Aspect ratio (e.g. 1:1, 9:16) or custom WxH.")
parser.add_argument("--nsfw-check", default="false", choices=["true", "false"], help="Stricter NSFW filtering.")
parser.add_argument("--seed", type=int, default=None, help="Optional seed for reproducibility.")
parser.add_argument("--out", default=None, help="Output filename (default: evolink-<timestamp>.<ext>).")
parser.add_argument("--poll-seconds", type=int, default=10, help="Seconds between polls.")
parser.add_argument("--max-retries", type=int, default=72, help="Max polling attempts.")
parser.add_argument("--verbose", action="store_true", help="Print task id and per-poll status (debug).")
args = parser.parse_args(argv)
api_key = (args.api_key or "").strip()
if not api_key:
print("Error: EVOLINK_API_KEY not set and --api-key not provided.", file=sys.stderr)
return 2
if len(args.prompt) > 2000:
print("Error: prompt exceeds 2000 characters.", file=sys.stderr)
return 2
out_file = os.path.abspath(args.out) if args.out else None
payload = {
"model": "z-image-turbo",
"prompt": args.prompt,
"size": args.size,
"nsfw_check": (args.nsfw_check == "true"),
}
if args.seed is not None:
payload["seed"] = args.seed
resp = _json_request(f"{API_BASE}/images/generations", api_key=api_key, method="POST", payload=payload)
task_id = resp.get("id") or resp.get("task_id")
if not task_id:
raise RuntimeError(f"Failed to submit task; response: {resp}")
if args.verbose:
print(f"Task submitted: {task_id}")
for i in range(1, args.max_retries + 1):
time.sleep(args.poll_seconds)
task = _json_request(f"{API_BASE}/tasks/{task_id}", api_key=api_key, method="GET")
status = task.get("status")
if args.verbose:
print(f"[{i}] Status: {status}")
if status == "completed":
results = task.get("results") or []
if not results:
raise RuntimeError(f"Task completed but no results field found: {task}")
url = results[0]
ext = _ext_from_url(url)
if not args.out:
out_file = os.path.abspath(_default_out_file(ext))
_download(url, out_file=out_file)
if args.verbose:
print(f"Image URL: {url}")
print(f"Downloaded to: {out_file}")
print(f"MEDIA:{out_file}")
return 0
if status == "failed":
raise RuntimeError(f"Generation failed: {task}")
raise RuntimeError(f"Timed out after {args.max_retries} polls. Task ID: {task_id}")
if __name__ == "__main__":
try:
raise SystemExit(main(sys.argv[1:]))
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
raise SystemExit(1)
```