Install
openclaw skills install @durenzidu/powpow-simple-enPublish posts/travelogues to PowPow (global.powpow.online), and create digital humans pinned to the public map. Triggers when the user wants to publish travel content (photos / travelogue / trip stories) to PowPow, e.g. "publish a PowPow post", "post to PowPow feed", "turn my travel photos into a tr
openclaw skills install @durenzidu/powpow-simple-enTurn your photos or raw material into a travelogue and publish it to PowPow on your behalf; or turn a person/character into a chat-capable digital human pinned to the public map.
Version 1.0.0 · 2026-09-20 (history: references/changelog.md; security architecture & API list: references/security.md; file structure: references/file-structure.md; product screenshots: references/screenshots.md)
Language policy (hard rule): Always converse in the user's language. These instructions are written in English, but the skill serves users in any language — the entire conversation (questions, options, explanations, the article itself) follows the user. Chinese trigger phrases are recognized too.
This is an instruction-style skill: SKILL.md describes the flow, and scripts/*.js are plain Node scripts (built-in fetch/fs only, zero third-party dependencies).
fetch). Verify first: node -vscripts/ in this skill's directory. Before running, change to the skill root, e.g.:
cd <this-skill-dir> && node scripts/doctor.jsPOWPOW_STATE_DIR (default ~/.powpow/session.json). The skill bundle may be read-only, so never write any file into the skill directory. Users can customize the location via that environment variable.--image; relative paths fail to resolve.Most users have never seen what PowPow looks like. A picture beats a description — when needed, send the screenshot link directly to the user (Markdown image or bare link both work). Full list, descriptions, and when to use each: references/screenshots.md. Quick mapping:
Rules:
durenzidu/durenzidu under screenshots/ (powpow-0N.jpg). Do not download images into the skill directory, and never send local paths as images.raw.githubusercontent.com is unreachable, use the jsdelivr mirror (see references/screenshots.md).references/screenshots.md.This skill triggers when the user wants to publish travel content (travelogue/photos/trip) to PowPow, or wants to create a digital human pinned to the map. Typical phrasings:
Co-occurrence rule (prevent false triggers): mentioning travel/photos/travelogue alone is not enough — the user must also express publish intent (post/publish/upload/发/发布) or name the platform (PowPow/泡泡/powpow). "This photo is beautiful" does not trigger; "post this photo to PowPow" does.
Does not trigger for generic social media or other platforms.
Many users have never published anything through a chat assistant. Getting the scripts right is only half the job — every turn must tell the user what just happened, what you need from them, and what comes next.
This is a product for ordinary users. You are serving the user, not instructing them. Keep the tone gentle and respectful, consistently.
| Don't say (lecturing / talking down) | Say instead (serving / side-by-side / inviting) |
|---|---|
| First, let me be clear about one thing: … | There's one thing I'd like to explain first: … |
| A reminder: … / I have to remind you that … | There's one thing I'd like you to double-check with me |
| There are two things I can't guess — you have to tell me | I can't see when or where a photo was taken, so I'll need you to tell me those two things |
| I won't decide this for you / you need to know this | I'd rather not decide this one for you — it goes into the article, and the wrong pick would feel off |
| Do you need to worry about coordinates? No. | Leave the coordinates to me — you just pick the place name |
Always:
Explicitly forbidden: no lecturing; no announcing "I need to remind you"; no judging the user's choices; never "I have to…"; when the user hasn't asked, don't explain internal mechanics, limits, or security design.
Provider-neutral naming (hard rule): never mention the map provider's name to the user. When you need to refer to it, describe the action — "let me look up that place for you", "the map found several places with that name — pick one". Don't use jargon like "map database" either. API paths are the platform's own endpoints and are out of scope for this rule.
On first trigger (doctor reports no session, or the user asks what you can do), open with a short paragraph (~120 words, don't pile up features), branch by user intent, then ask the first question:
Template (use as a starting point, adapt to the user's language — don't parrot it into template-speak):
It's a pleasure to serve you — welcome to the world of PowPow. Here, you can turn the photos you took today into a travelogue, and I'll write it, pin it to the map, and publish it to PowPow for you — when someone taps it on the map, they'll see the path you walked that day. [Part 2: what the result looks like — capsules, images, the map-bubble mechanic (use the standard line above)] [Part 3: what's needed — one login; include the register link + intro video if they may not have an account] [Part 4: the 3-beat flow, login first] Ready? Hand me what you'd like to publish today, and we'll start with step one.
In long messages users lose the point — not because the content is wrong, but because the action gets buried. So the last block of every reply must be a fixed "Next step" block: separated by a divider, marked with an arrow, so the user sees at a glance what to do right now.
Fixed format:
────────────────────
👉 Your move: reply 1 / 2 / 3 to pick a location
(Once you pick, I'll draft the post — next turn you'll see the preview with capsules)
Three hard rules:
Publish results follow the same rule (see Step 8): link and location go at the very end of the message, closest to where the eye lands.
fetch).node scripts/doctor.js
printf '%s' '<password>' | node scripts/login.js <username>
printf): you can run
node scripts/login.js <username>
POWPOW_STATE_DIR, default ~/.powpow/session.json), not inside the skill directory.pending_payment — the account exists but platform activation isn't complete. Stop, don't retry, say: "Your account isn't fully activated yet, so publishing isn't possible right now. Please open PowPow, log in, and follow the on-page instructions to finish activation; tell me when it's done and we'll continue." This is neither a transient error nor a login problem — don't make the user re-enter credentials, and don't keep collecting material.config.json (ships defaults, no secrets; read-only):
{
"platformUrl": "https://global.powpow.online",
"unsplashAccessKey": "",
"sessionMaxAgeDays": 6
}
unsplashAccessKey is optional, user-supplied; without it, skip image search (image-less posts are fully supported) or use direct URLs from the user.
Overriding config under OpenClaw: don't modify the bundled config.json (may be read-only). Put a config.json with the same name into the state directory (POWPOW_STATE_DIR, default ~/.powpow/config.json) — its values override the bundled defaults. Or set the UNSPLASH_ACCESS_KEY environment variable.
Execution convention (OpenClaw): all
node scripts/...commands below assume the current directory is this skill's root. If unsure, run with absolute paths:node <skill-dir>/scripts/xxx.js. Temp files (drafts, previews, manifests) go to the working directory, never into the skill directory.
The flagship flow: the user sends photos, you write a first-person travelogue around them.
Other inputs are fine too: Mode A user provides finished text — skip generation; Mode C partial input — you fill in. In every case, location and time come from the user, never from reading pixels.
List/suggest healthy digital humans:
node scripts/match-digital-human.js "<topic>" --limit 3 --json # ranked suggestions
node scripts/list-digital-humans.js "<name>" --json # search by name
Health filtering is built in: disabled, test-named, garbled, and placeholder-avatar digital humans are excluded automatically. Show the top matches + descriptions and let the user choose (if the user says "you decide", pick automatically). If the digital human the user wants doesn't exist, give the creation page: https://global.powpow.online Note: the platform's digital-human library is currently mostly Chinese-language; matching works best when the topic is given in Chinese.
Location is strongly recommended, but optional. It decides whether the post appears on the public map (the core gameplay).
node scripts/geocode.js "地坛公园" --limit 5 # add --city 北京 to narrow
--loc <name> --lng <x> --lat <y>.--city <city> (district, landmark, road name).--limit (e.g. 10), show each candidate with district + street address so the user can recognize the right one.isLocationExposed: false). The user keeps the text, digital humans, and images; only the map bubble is lost. State this as the outcome, not as a failure.
Making an ordinary user supply latitude/longitude is never a legitimate fallback: they have no way to obtain it, and what they find is usually GCJ-02, which pins off by hundreds of meters — a visible factual error in a first-person post. Coordinates are an expert channel: use them only if the user offers.isLocationExposed: false). Never pressure, never block publishing — it's their choice.locationName, lng, lat).A provided location always goes on the public map (isLocationExposed: true) — this is an internal rule; do not offer the user a "hide from map" option. Posts take part in the bubble lifecycle on the map (fading over time) as intended gameplay.
Location component format:
<span data-type="location" data-lng="116.316" data-lat="39.979" name="中关村">
<span class="location-name">中关村</span>
</span>
Three ways to attach images:
--image @<local-path>. Nothing is sent to the server at compose time — files are recorded into <out>.manifest.json and previewed locally via file://. Upload happens only at publish time in publish.js (POST /api/upload/post-image, JPEG/PNG/WebP/GIF/BMP/HEIC, no SVG, ≤10MB each, server-side compression). Editing drafts never touches the server and never creates orphan files.unsplash.com/photos/..., commonly copied from a browser) — automatically resolved to images.unsplash.com direct links. File-ID links (unsplash.com/photos/1507513319174-...) resolve locally without a key; slug/short-ID links require an Unsplash API key (the site's bot protection blocks keyless scraping). Without a key, skip the slug form and say so — ask the user for a direct link; never embed a page link as an image (it renders broken).--image search:<english keywords> (or node scripts/search-image.js "<keywords>")No fixed image-count cap. The platform's boundary is 50,000 characters (each image tag ≈100 chars); remind the user when approaching 200 images. Display: the feed grid shows the first 9 + "+N"; the full-screen viewer shows all.
Interactive components (digital human / location) are rendered by the web frontend from rich HTML inside content. Writing just <span data-type="digital-human">Marie Curie</span> renders as plain text — only a span carrying the tiptap editor's exact structure (Tailwind classes, inner avatar <img>, pin icon, .location-name) displays as a tappable capsule.
Mandatory: always use scripts/compose.js to assemble post HTML. Never hand-write component spans, never write glue code to bypass html-formatter.js.
# 1. Save the post text to a file. Separate paragraphs with blank lines.
# Placeholders (all optional; any misuse is a hard error, never silently altered):
# {{dh}} or {{数字人}} -> digital-human capsule (requires --dh)
# {{loc}} or {{位置}} -> location capsule (requires --loc)
# {{img}} or {{图}} -> image paragraph; must be its own paragraph, consumes --image in order
# (each one needs a spare --image, otherwise compose fails)
# Note: the Chinese aliases above are literal tokens recognized by the script —
# use either form, but never invent new tokens.
# Unknown/extra/inline placeholders make compose.js exit 1 and write nothing.
# Fix the text file and rerun — never delete text to force it through.
# 2. Assemble in one command (fully local, no network):
node scripts/compose.js --text-file post.txt \
--dh name:岳飞 \ # or --dh <id>, or --dh auto --topic "..."
--loc dh \ # or --loc <name> --lng 116.3 --lat 39.9
--image @./photos/west-lake.jpg \ # local file (uploaded at publish); or <direct-url>, or search:<keywords>
--out post-draft.html
compose.js enforced rules:
--dh/--loc is given but the text has no placeholder, the capsule is auto-prepended to the first paragraph / appended to the last one.powpow-local://N placeholders + post-draft.html.manifest.json; publish.js uploads them at publish time.post-draft.html.preview.html — a Chinese-language preview page the user opens in a browser. Its banner states whether the post will appear on the public map and, if so, where it's pinned — so the map consequence is visible on the page the user actually reads (the local map renders via file://). Tell the user in their own language what the banner says.publish.js re-runs validateEditorFormat() and rejects anything else.contentItems in the publish payload — the frontend prefers structured rendering when contentItems is present, and an incomplete list drops post text. publish.js handles this.<img> as the card thumbnail.First show the four-item breakdown, then end the turn with a "Next step" block whose only action is the publish decision (publish / revise more / hold off):
<out>.preview.html) — Chinese-language preview; its banner also states whether the post goes on the public map. One line noting it's an approximation, not a pixel-perfect copy.The decision must be the last block — never buried after the breakdown. (Why so strict: in testing, users read exactly this content and replied "and then?" — the information was all there, but the action didn't land where the eye stops.)
Revision loop: if the user wants changes, make them and rerun compose.js — fully local, any number of rounds with zero server contact and zero orphan uploads. No iteration cap; only an explicit 发布/publish/confirm from the user ends it. Until then, never publish.
node scripts/publish.js <html-file-path>
If the draft contains local images, this is the moment they upload — one by one, before the post goes out. Progress is persisted after each success (HTML + manifest rewritten with real URLs), so a failed rerun doesn't re-upload what already succeeded.
The post is created as the logged-in user — identity comes from the JWT; there is no way to post as someone else. The server enforces content moderation and rate limits. On 429 the script honors Retry-After automatically.
node scripts/verify.js <post-id>
Checks post accessibility, author, digital-human/location components, image count, word count. Report in this order — breakdown first, result last:
────────────────────
✅ Post published
📍 Pinned at: Yanjiao Xinggong Ruins (116.816667, 39.95)
👉 Please open the link and check: do both capsules tap, is the image order right?
(The command line can't verify that — your eyes are the final gate)
🔗 https://global.powpow.online/posts/<post-id>
If there's no location, say so in the same block: "📍 No location — this post won't appear on the public map".
Interactive rendering (capsule taps, image lightbox) can't be verified from the CLI — the user's eyes are the final gate.
Test-post cleanup:
node scripts/delete-post.js <post-id>
A separate flow from posting: turn a person (historical figure, character, the user's own avatar…) into a chat-capable digital human pinned to the public map. Triggers: "create a digital human", "turn someone into a digital human on the map", "publish a digital human".
Cost — confirm before running, just like the publish decision:
Collect from the user:
--avatar-ref). The platform generates the avatar from persona + reference (8~30 s). A local reference photo works too — upload it to /api/upload/post-image first, then feed the public URL to --avatar-ref.--avatar @<path>): use the user's own image directly.--avatar <url>): a direct image URL.node scripts/create-digital-human.js \
--name 史铁生 \
--desc-file persona.txt \ # or --desc "..."
--avatar-ref https://...jpg \ # or --avatar @./photo.jpg, or --avatar <url>
--lng 116.408195 --lat 39.952372 # from the resolution result the user picked
After creation, report the digital-human ID, location, expiry date (30 days), and badge balance, and link the map: https://global.powpow.online/map . The user can immediately @ it in a post (compose.js --dh <id>), and it becomes a related candidate for match-digital-human.js.
To look up existing ones: node scripts/list-digital-humans.js --search "<name>".
Hard rule: never throw raw script output, node commands, or error codes (e.g. DEVICE_MISMATCH) at the user; translate every error into one human sentence + the next action.
login.js, retry once. Everything done so far is kept: drafts, material, chosen digital human and location stay local — resume from where you were, never rerun earlier steps.printf '%s' '<password>' | node scripts/login.js <username>. Never the positional form (login.js <user> <password>): it lingers in shell history and process lists. No echoing, no disk writes, no shell variables. login.js still accepts the positional form for compatibility — that is not permission to use it. Asking the user for credentials should be one plain message, without security lecturing (see Conversation UX → Tone).--text-file/--image, or files you created in the current working directory. Never scan the machine, never guess paths — a wrong path publishes private files.