How to install community-publish
npx skills add https://github.com/starchild-ai-agent/official-skills --skill community-publishFull instructions (SKILL.md)
Source of truth, from starchild-ai-agent/official-skills.
name: community-publish version: 0.14.1 description: | Publish previews, list on dashboard, open-source projects to community GitHub.
Use when the user wants to share, publish, list, or open-source what they built (e.g. make this dashboard public, share my project, push to GitHub). delivery: script metadata: starchild: emoji: ๐ฆ skillKey: community-publish user-invocable: true disable-model-invocation: false
Three independent actions
This skill handles three completely different kinds of sharing. They are NOT stages of one flow and NOT mutually exclusive โ a project can be in any combination.
| Action | What "share" means here | Audience can | Applies to |
|---|---|---|---|
publish_preview(preview_id) | Allocate a public URL https://community.iamstarchild.com/{user_id}-{slug} | Open the URL if they know it โ point-to-point access | Any running service |
list_in_dashboard(slug) | Show the listing on the public Project Dashboard | Discover and browse to it from the gallery | A previously-published preview |
open_source(project_dir) | Push project source to the community GitHub repo | Fork the code and run their own copy | Any project (task, service, script) |
Critical: do NOT auto-list when publishing. publish_preview() only allocates the URL. Listing is a separate, deliberate user decision. If the user just says "publish my preview" / "ๅ
ฌๅผ" without mentioning the dashboard, only call publish_preview(). After it succeeds, you may mention that list_in_dashboard() exists if they want others to discover it.
Service lifecycle (start / stop / health check) lives in the
previewtool. This skill only handles the share side.
Visibility model โ read this before answering anything about who can see a project
A project's "publicness" is two orthogonal switches, not one:
| Switch | Off state | On state | Flipped by |
|---|---|---|---|
| URL access | Visiting the URL returns 404 / no service | URL works for anyone who has the link | publish_preview / unpublish_preview |
| Dashboard discoverability | Listing row has is_public=false (or doesn't exist) โ invisible in the public gallery | Listing row has is_public=true โ appears in /projects | list_in_dashboard / unlist_from_dashboard |
Public URL โ public discovery. A preview can be URL-reachable but undiscoverable (the default state right after publish_preview), or listed but URL-down (preview stopped after being listed), or any other combination. Never collapse these into "is it public yet".
Status questions are read-only operations. Whenever the user asks anything like:
- "is it visible / public / discoverable yet?"
- "can other people find it?"
- "ไธๆถไบๅ / ๅจ dashboard ไธๅ / ๅซไบบ่ฝ็ๅฐๅ"
- "is the listing live?"
The authoritative answer comes ONLY from a fresh get_listing_status(slug) call. Do NOT infer the answer from "I called publish_preview earlier so it must be visible" โ that's exactly the trap (publish_preview leaves is_public=false). Treat your own past actions as suggestive but never authoritative for a state question.
Project types โ three only
| type | What it is | Eligible for publish_preview()? |
|---|---|---|
task | Scheduled cron/interval job | No (no HTTP port) |
service | Long-running HTTP service (dashboard, API, page) | Yes โ any service can be exposed at a public URL |
script | One-shot script | No (no HTTP port) |
Note: there is no preview type. If you encounter older docs mentioning it, treat as service.
Routing โ match user intent to the right surface
User requests fall into two fundamentally different categories. Mixing them up is the #1 source of wrong answers in this skill.
A. Status intents โ user wants to know current state
The user is asking a question about how things stand right now. The answer is data, not an action. Always reach for a read endpoint first; never answer from memory of past actions.
| Sample phrasing | Action |
|---|---|
| "is it visible / public / discoverable / live for others?" | get_listing_status(slug) |
| "ไธๆถไบๅ / ๅจ dashboard ไธๅ / ๅซไบบ่ฝไธ่ฝ็ๅฐ / ๅซไบบ่ฝๆๅฐๅ" | get_listing_status(slug) |
| "what URLs do I have published?" / "ๆๅๅธไบๅชไบ" | list_published_previews() |
| "what's open-sourced?" / "้ฝๆๅชไบๅผๆบไปฃ็ " | list_open_source(...) |
B. Action intents โ user wants to change state
| Sample phrasing | Action | Notes |
|---|---|---|
| "publish" / "share" / "make public" / "ๅ ฌๅผ" / "ๅๅธ" (no qualifier) | publish_preview(preview_id) | Allocates the URL only. Listing is NOT auto-flipped. |
| "publish the URL" / "share the link" / "let people visit" / "ๅ ฌๅผ่ฎฟ้ฎ" | publish_preview(preview_id) | Same. |
| "list on the dashboard" / "ไธๆถ" / "show on community" / "make discoverable" / "let people find this" / "ๅๅฐๅนฟๅบ" | list_in_dashboard(slug) | Requires the preview to already exist. |
| "publish AND list" / "publish and put on dashboard" / "ๅๅธๅนถไธๆถ" | publish_preview() THEN list_in_dashboard() | Two separate calls in order. |
| "remove from dashboard" / "ไธๆถ" / "unlist" / "hide from gallery" | unlist_from_dashboard(slug) | Preview URL stays alive. |
| "open source" / "open-source the code" / "share the code" / "let others fork" / "ๅผๆบไปฃ็ " | open_source(project_dir) | Explicit code-sharing intent. |
| "unpublish the URL" / "take down the link" | unpublish_preview(slug) | Listing row stays. |
| "remove the open source" / "delete from GitHub" | remove_open_source(slug) | |
| "fork" / "install someone's project" | fork(source) | |
| Ambiguous after rereading | Ask one question, don't guess | "Do you want it (a) just shareable by URL, (b) also discoverable on the public dashboard, or (c) also have the code open-sourced on GitHub?" |
Cross-link via publisher: binding
When the same project has BOTH a public URL AND open-sourced code, you want them paired so the frontend renders "View Source" on the listing card and "Visit Live Demo" on the code card. This skill drives that pairing through one explicit binding in project.yaml โ no name guessing, no fuzzy matching, no follow-up dialogues.
How to declare the binding
Add a publisher: block to project.yaml:
name: my-app # GitHub catalog slug + default for both sides
type: service
version: 1.0.0
publisher:
code_slug: my-app # OPTIONAL โ defaults to manifest.name
public_slug: my-app-pub # OPTIONAL โ URL suffix; defaults to code_slug
Both fields are optional. If omitted, both default to manifest.name. Set them only when you want different slugs on each side (e.g. short URL dash for code my-detailed-dashboard).
Either side can be published first
The gateway holds a pending entry until the second side arrives. No ordering requirement, no manual link step.
| Order | What happens |
|---|---|
open_source first โ publish_preview second | open_source records pending entry; publish_preview consumes it and links |
publish_preview first โ open_source second | publish_preview records pending entry (needs publisher_code_slug arg); open_source consumes it and links |
| Both at once / rename later | Re-run either side with updated binding; gateway re-links |
Calling publish_preview with binding
When publishing the URL FIRST and the code will follow:
publish_preview(
preview_id="my-app-a3f1",
slug="my-app-pub",
publisher_code_slug="my-app", # so future open_source(my-app) auto-links here
)
If publisher_code_slug is omitted, no pending entry is recorded โ the code side will need to declare publisher.public_slug itself in project.yaml to wire the link.
Return value
Both functions return a publisher field showing what binding was sent and a hint describing the cross-link state:
{"ok": True, "url": "...", "publisher": {"code_slug": "my-app"},
"hint": "Cross-link binding declared. If that code is already open-sourced, it's now linked. If not, the link is pending..."}
You don't need to react to the hint โ it's informational. The gateway handles wiring automatically.
Manual repair (rare)
If a pairing was wired wrong (e.g. after a rename or a slug typo), use:
link_to_listing(listing_slug="2004-my-app-pub", code_slug="my-app")
This skips the binding flow and writes the link directly. Don't use this for normal publishing โ fix project.yaml's publisher: block instead so the binding survives future republishes.
Architecture
community.iamstarchild.com (single gateway domain)
โ
โโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโ
โ โ
โโโโโโโโโโผโโโโโโโโโโ โโโโโโโโโโผโโโโโโโโโโ
โ /api/register โ โ/api/code-projectsโ
โ /api/unregister โ โ /publish, /list, โ
โ /api/list โ โ /unpublish, ... โ
โโโโโโโโโโฌโโโโโโโโโโ โโโโโโโโโโฌโโโโโโโโโโ
โ โ
โโโโโโโโโโผโโโโโโโโโโ โโโโโโโโโโผโโโโโโโโโโ
โ DB: route table โ โ GitHub: โ
โ + project_ โ โ Starchild-ai- โ
โ listings โ โ agent/community- โ
โ + publisher_ โ โ projects โ
โ pending_links โ โ โ
โ โ โ Permanent. โ
โ Service stays up โ โ โ
โ as long as your โ โ โ
โ container runs. โ โ โ
โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ
publish_preview() open_source()
publisher_pending_links is the cross-link table. Either side writes a pending row, the other side consumes it on arrival.
publish_preview() โ public URL
publish_preview(preview_id, slug="", title="", publisher_code_slug="")
Map a running service to https://community.iamstarchild.com/{user_id}-{slug}.
preview_id: frompreview(action='serve'). Must bestatus=running.slug: URL suffix only (lowercase alphanumeric + hyphens, 3-50 chars). User_id prefix is added automatically โ pass'my-app', NOT'1463-my-app'.title: display name for the listing.publisher_code_slug: optional cross-link binding to a code project's slug. Sets up the pending entry so the eventualopen_source()call auto-links.
Returns {"ok": True, "url": "...", "publisher": {...}, "hint": "..."}.
Constraints:
- Max 20 published previews per user (gateway returns 429 over).
- Service must be running. Stops working when the container goes down (visitors see offline page).
- Slug stays bound to the port โ stop and re-serve, the URL stays valid.
- Only works inside the Starchild Fly container (needs
FLY_MACHINE_ID). - Listing visibility default is
is_public=false. A successfulpublish_previewallocates the URL but creates the listing row in PRIVATE state โ strangers cannot discover the project on the dashboard. Discovery requires a separatelist_in_dashboard()call. Do NOT tell the user "your project is now public" after only callingpublish_previewโ say "the URL is live" and offerlist_in_dashboardas the next step if they want it discoverable.
Companions:
unpublish_preview(slug)โ remove the public URL. Slug accepts full{user_id}-{suffix}or just suffix.list_published_previews()โ all currently published preview URLs for this user.
list_in_dashboard() โ show on Project Dashboard
list_in_dashboard(slug, name=None, description="", cover_url=None, tags=None)
Make a published preview discoverable in the public Project Dashboard at https://community.iamstarchild.com/projects. Without this, the preview URL works but is invisible to anyone who doesn't already know it.
slug: the full slug returned bypublish_preview()(i.e.{user_id}-{suffix}). The gateway's ownership check uses this exact value.name: dashboard card display name. Defaults toslug.description: โค500 chars.cover_url: must be onstorage.googleapis.com,image.thum.io, orapi.microlink.io. Other domains rejected with 400. If omitted, the gateway captures a screenshot asynchronously.tags: โค5 tags, โค20 chars each.
Returns {"ok": True, "listing": {...}, "url": "...", "dashboard_url": "..."}.
Constraints:
- Requires
publish_preview()to have run first for the same slug โ returns 404 with a clear error otherwise. - Idempotent: calling again with different name/tags updates the existing listing.
- Listings created via
publish_preview()start as private (not on dashboard) โlist_in_dashboard()is the ONLY way to make them discoverable.
Companions:
unlist_from_dashboard(slug)โ remove from dashboard, keep URL alive.get_listing_status(slug)โ read-only check: returns{ok, exists, is_public, listing}. Note: only public listings are observable through this โ if the gateway returns 404, the listing is either nonexistent OR private (no way to distinguish).
open_source() โ push code to GitHub
open_source(project_dir, version_bump="patch", message="", make_discoverable=True)
Push project source to community-projects/projects/{user_id}/{slug}/ on GitHub. Versioning is delegated entirely to git history โ there's no {version}/ snapshot directory and no per-type bucket above the slug. The type field inside project.yaml stays around as runtime metadata (so forks know whether to schedule, run-once, or expose a service) but no longer affects the storage path.
project_dir: e.g.output/projects/my-taskversion_bump:patch|minor|major|nonemessage: commit message body describing what this version changed. You (the agent) should always compose this based on the actual code changes you made in this session โ never leave it blank if you know what changed, never ask the user to write it. Aim for one to three short lines. Don't list every file; describe the user-visible change. If the user explicitly said "just publish", use a one-line summary like "Initial publish" or "Re-publish without changes".make_discoverable(defaultTrue): "open source" matches the user-intuition default of "make this fully public". After pushing the code, if a paired preview/listing row exists for the same slug (viapublisher.public_slug/publisher.code_slug/manifest.name), it is automatically flipped to public on the Project Dashboard. Pure libraries / pure scripts with no preview row are skipped silently โ no side effect. Passmake_discoverable=Falsewhen the listing should stay private.- Returns
{"ok": True, "github_url": ..., "version": ..., "publisher": {...}, "auto_list": {...}, "hint": "..."}auto_list.attempted: whether the auto-list path was tried (False whenmake_discoverable=False)auto_list.listed: True on success, False otherwiseauto_list.reason:"no_preview_for_slug"(pure code case โ expected) or"list_failed"witherrorfield
Commit message style โ write like a normal git commit body:
โ "Add WebSocket reconnect on dropped connections; refactor prompt builder for shared state." โ "Fix funding-rate sign convention; add unit test for negative-funding path." โ "Updated 3 files" (uninformative) โ "Modified src/index.html, src/main.py, project.yaml" (lists files instead of intent) โ "User asked to publish" (describes the request, not the change)
Companions:
fork(source, dest_dir=None)โ install someone else's open-sourced project locallysource:"user_id/slug"(always pulls current state โ older snapshots live in GitHub commit history)- For
tasktype: registers as paused, returnsnext_stepinstructions - For
servicetype: returns ready-to-serve info
list_open_source(type=None, tag=None, user=None, q=None)โ browse the GitHub catalogget_open_source(source)โ fetch one project's full metadataremove_open_source(slug)โ delete project directory from GitHub catalog (owner only). Git history of the deletion + previous commits is preserved in the repo's commit log.validate_open_source(project_dir)โ pre-flight check before publishing
Project structure
Every project under output/projects/{slug}/:
project.yaml # metadata (name, version, type, env_required, sc_proxy, publisher)
PROJECT.md # required sections: What / Required env / How to start / Outputs / Troubleshooting
.env.example # all env vars with placeholder values
.gitignore # secrets blacklist
src/
โโโ run.py # for type=task (must start: # -*- task-system: v3 -*-)
โโโ index.html # for type=service (or app.py + frontend)
โโโ main.py # for type=script
Don't conflate the two list functions
list_published_previews() returns live URLs (preview side). list_open_source() returns open-sourced code (GitHub side). Different datasets โ never quote one number to answer a question about the other.
Usage from a bash block
python3 - <<'EOF'
import sys
sys.path.insert(0, "/data/workspace/skills/community-publish")
from exports import (
# Public URL
publish_preview, unpublish_preview, list_published_previews,
# Open source code
open_source, remove_open_source, fork,
list_open_source, get_open_source, validate_open_source,
# Manual repair (rare)
link_to_listing,
)
# Cross-linked publish: declare publisher in project.yaml first, then both
# sides auto-pair regardless of order.
print(publish_preview(preview_id="my-app-a3f1", slug="my-app",
publisher_code_slug="my-app"))
print(open_source("output/projects/my-app", version_bump="patch"))
EOF
Behavioral rules
- Show the diff before
open_source(). Aftervalidate_open_source, summarize what's about to be pushed (file list, version, type, tags, env_required) and ask for confirmation. Exception: explicit "publish without confirmation" or re-publish of a known good project. - Never auto-run setup.sh on fork. Show the command, let the user confirm.
- Always collect env in one batch on fork. Read project's
env_required, diff againstworkspace/.env, callrequest_env_inputONCE with the missing keys. Don't ask one-by-one. - Slug rules: lowercase alphanumeric + hyphens, 3-50 chars, no leading/trailing hyphen. Skill auto-strips duplicate
{user_id}-prefix if you accidentally include it. - Version rules (
open_source): strict semver. Re-publishing same version is rejected. New version must be > current latest. - Type immutability (
open_source): once published astask, can't change toservicelater. Pick a different slug. - URL โ code: a public URL going down (container off) does NOT remove the open-source code, and vice versa. They're independent.
- Don't manually call
link_to_listing()in the normal flow. Thepublisher:binding handles cross-linking. Manual link is only for repair.
Common gotchas
| Symptom | Cause | Fix |
|---|---|---|
publish_preview: Preview not found | Wrong preview_id, or service was stopped | Check /data/previews.json, restart with preview(action='serve') |
publish_preview: 429 Too many published previews | Hit 20-per-user gateway cap | unpublish_preview() something old first |
publish_preview: FLY_MACHINE_ID not set | Running locally, not in Starchild container | URL publish only works in the production container |
open_source: 400 Validation failed: env names not in .env.example | Listed MY_KEY in env_required but forgot .env.example | Add the missing key to .env.example |
open_source: 400 Possible secret detected | Secret scanner found a real-looking API key | Move to env var; .env.example value should be your-key-here |
| open_source: 400 Type cannot change after publish | Trying to switch task โ service โ script | Pick a different slug |
| remove_open_source: 403 Permission denied | Trying to remove someone else's project | Only the owner can remove |
| Cross-link not appearing on frontend | Binding mismatch between sides | Check both sides' slugs match the publisher: block; or use link_to_listing to repair |
| Forked task doesn't run | Auto-registered as paused | Tell user: scheduled_task(action='activate', job_id={id}) |
References
lib/manifest.pyโ project.yaml parser/writer + semver helperslib/validate.pyโ local pre-publish validation (mirrors gateway-side checks)lib/install.pyโ type-specific install handlers (task/service/script)lib/gateway.pyโ HTTP client for/api/register(URL side) and/api/code-projects/*(code side)
Related skills
More from starchild-ai-agent/official-skills and the wider catalog.
coinglass
Crypto derivatives data: funding rates, open interest, liquidations, long/short ratios, and whale tracking.
hyperliquid
|
wallet
|
coingecko
|
|
skill-creator
|