Publisher Skill
Publish HTML frontend projects to the publishing platform and optionally package them as Android APKs.
Platform URL:
https://pushwebly.com
Use "project" in user-facing messages. The canonical API paths are
/api/projects and canonical tool names are project_*. Deprecated game_*
aliases and gameId response fields may still appear; treat gameId as the
project id.
Codex Execution Rules
- Prefer mounted
project_*MCP tools if they exist in the current environment. - If remote MCP tools are not mounted, use the HTTP APIs with
curl. - Do not require Python. Optional scripts, if present, are for CLI/CI users only.
- Ask only for genuinely missing information: credentials, zip path, project name, public/private visibility, or private access password preference.
- Never write credentials into a project zip or repository file. Store them only
in the user's local
~/.publisher/config.jsonafter consent. - On Windows/PowerShell, use a temp JSON file for request bodies instead of shell-escaping inline JSON.
Intent Mapping
| User intent | Action | Missing information to ask for |
|---|---|---|
| Publish, ship, upload, put online | Publish project | zip path, project name |
| Make an installable phone app, APK, Android package | Publish and build APK | zip path, project name |
| Build APK for an already published project | Build APK | project id; use project list if needed |
| Make an app private/public, add/remove access password | Change visibility | app URL, target visibility (optional password when private) |
| Show my projects, list published projects | List projects | none if token is available |
| Log in, register, account setup | Login/register | username and password |
If the intent is ambiguous, ask one short question: "Just publish it, or publish it and build an Android APK too?"
Publishing Conversation Process
When users express intentions such as publishing, deploying, launching, sharing applications, generating access links, etc., they must enter this process.
General Principles of Finite-State Machine
- On entering the flow, first run
S-1(version check, non-blocking), then proceed in the order ofS0 -> S1 -> S2 -> S3 -> S4. - Skipping steps is not allowed (except
S-1). - Multiple fields cannot be queried in a single request. Information that the user has clearly provided can be recorded, and there is no need to ask repeatedly. When the user's response is ambiguous, only follow up once for the current field.
- Only after the current step is completed can we proceed to the next step.
- If waiting for user input is required, do not append explanations, suggestions, or previews of the next step at the end of the response.
S-1: Version Check (first node of the publish flow, non-blocking)
When entering the publish flow, run a version self-check before S0. This step is NOT part of the <HARD-GATE> blocking rules: whatever the result — even if the endpoint is unreachable — always continue. Never interrupt publishing because of a version issue.
Steps:
-
Read the local skill version: the
versionfield in this file's frontmatter. -
Query the latest platform version (no login required):
curl -s https://ai-pub.pushwebly.com/api/skill/latest-versionThe response
datacontains:version(latest),downloadUrl(cross-platform doc package, unzip-and-use),downloadUrlWindows(Windows installer .exe), anddownloadUrlMac(macOS installer .zip). -
Compare local
versionwith the returnedversion.Comparison rule: a version is
major.minor.patch. Compare segment by segment as integers, left to right — major first, then minor, then patch; pad missing segments with 0 (1.1means1.1.0). Compare as integers, NOT as strings:1.10.0is greater than1.9.0(second segment10 > 9); a string compare would get this wrong. Ascending example:1.0.0 < 1.0.9 < 1.1.0 < 1.2.0 < 1.10.0 < 2.0.0.-
Local is older (local's integer-segment sequence is less than the returned one): you MUST output an update notice to the user before continuing, and the notice MUST include the actual download URL (this does not block publishing, but the notice must not be omitted). Pick the download URL by the user's OS:
- Windows → use
downloadUrlWindows - macOS → use
downloadUrlMac - OS unknown, or the matching field is empty → fall back to the generic
downloadUrl
Notice text (output it in the SAME language the user is conversing in — Chinese if they write Chinese, English if English; do not hard-code one language; the example below is English only as a template; replace the URL with the real selected one; write the download URL as a clickable Markdown link
[URL](URL)— do NOT wrap it in backticks as inline code, or the user cannot click it; never leave a placeholder, never say "a new version exists" without giving the link):Note: a newer Publisher skill version
<version>is available (you are on<local version>). Download: https://ai-pub.pushwebly.com/downloads/AI_Publisher_skill.exe (updating does not affect the current publish). - Windows → use
-
Local equals or is newer: stay silent and continue.
-
Request fails/times out/cannot parse: silently skip, go straight to
S0, do not surface an error.
-
-
In all cases, proceed to
S0next. Because this step only notifies (no input, no blocking), the "at most one user-facing question per reply" rule does not apply to it — the version note may share the same reply as the firstS0question.
S0: Check Account Status
First, check if there is already an available token/local credential.
- If you already have account credentials: go to
S2. - If there are no account credentials: Enter
S1_USERNAME. - If unable to check the account status: ask the user if they already have a publishing account.
Ask the user a question:
I need to confirm the publishing account first: Do you already have an available account or token?
User answer:
- Yes: Require the user to provide or confirm the token/login credentials, then proceed to
S2. - No: Enter
S1_USERNAME.
S1: Registration and Login Process
S1_USERNAME: Collect registered username
If there is no username, only ask:
Please provide the username to be registered.
After receiving the username, record it as username and enter S1_PASSWORD.
S1_PASSWORD: Collection registration password
If there is no password, only ask:
Please provide the registration password.
After receiving the password, record it as password and enter S1_VALIDATE.
S1_VALIDATE: Joint Validation
Only after having both username and password can the backend interface be called for joint verification.
Verification passed:
- Create Account
- Complete Login
- Prompt:
Registration and login successful - Enter
S2
Verification failed:
- Display the specific error reason returned by the backend
- Only inquire about the item that needs to be corrected
- Do not terminate the registration process
S2: Inquire about application visibility
If the public or private status has not been clearly defined, only ask:
Is this application public? Public means anyone can access it via a link; private requires a 6-digit access password.
User answer:
- Public / Yes / public: Record
visibility=public, enterS4 - Private / No / private: Record
visibility=private, enterS3 - Unclear: Continue to ask only about this item
S3: Private Access Password
If visibility=private, it is necessary to ask whether to customize the password, asking only:
Do you want to customize a 6-digit access password? You can also reply "system-generated".
User answer:
- Provide password: verification must be 6 digits or letters; after passing, record
accessPassword=<user password>and enterS4 - Reply system-generated / Not required / Random: Do not pass
accessPassword, let the backend generate it, and enterS4 - Password format error: Only prompt for the format requirements and re-ask for the password
S4: Execute Release
Calling the publishing interface is only allowed if one of the following conditions is met:
visibility=publicvisibility=privateand the user has selected a custom password or a system-generated password
Release Parameters:
Public Application:
{
"visibility": "public"
}
Private application, custom password:
{
Default to `public` if the user does not specify visibility. A private access
password must match `^[A-Za-z0-9]{6}$`; otherwise ask for a valid password or let
the backend generate one.
## Token Handling
A Bearer token is required for all publishing APIs.
Use this order:
1. If the user provided a token in the conversation, use it directly.
2. If `~/.publisher/config.json` exists, read the stored username/password and
log in again to refresh the token.
3. Otherwise ask for username and password. The login endpoint automatically
registers new accounts.
Login/register endpoint:
```bash
curl -s -X POST https://ai-pub.pushwebly.com/api/auth/login-or-register \
-H "Content-Type: application/json" \
-d @login.json
Write login.json as:
{"username":"alice","password":"plain-text-password"}
Use data.token from the response as:
Authorization: Bearer <TOKEN>
Delete the temp login file after use.
Optional local credential file:
{
"baseUrl": "https://ai-pub.pushwebly.com",
"username": "alice",
"password": "plain-text-password",
"authorization": "Bearer <TOKEN>",
"userId": 1
}
On Linux/macOS, set mode 600 after writing. On 401, refresh the token once by
logging in again, then retry the failed operation once.
Verify token:
curl -s https://ai-pub.pushwebly.com/api/auth/verify-token \
-H "Authorization: Bearer <TOKEN>"
Pre-Publish Checks
Before publishing:
- Confirm the file exists and has a
.zipextension. - Confirm the zip contains
index.htmlorindex.htm. - If the zip has nested directories, confirm the entry file can be located.
- Confirm the project name is non-empty.
- Confirm a valid token is available.
HTTP Operations
Publish Project
Public:
curl -s -X POST https://ai-pub.pushwebly.com/api/projects/publish \
-H "Authorization: Bearer <TOKEN>" \
-F "project_name=demo" \
-F "[email protected]"
Private with optional custom password:
curl -s -X POST https://ai-pub.pushwebly.com/api/projects/publish \
-H "Authorization: Bearer <TOKEN>" \
-F "project_name=demo" \
-F "[email protected]" \
-F "visibility=private" \
-F "access_password=a1b2c3"
For private apps, omit access_password to let the backend generate a password.
Always relay the final accessPassword from the response to the user.
List Projects
curl -s https://ai-pub.pushwebly.com/api/projects/my \
-H "Authorization: Bearer <TOKEN>"
Change Visibility (public <-> private)
Toggle an already-published app between public and private. The input is the app URL; the backend parses the app identifier from it and verifies the app belongs to the current user — you can only change your own apps, not others'.
curl -s -X PATCH https://ai-pub.pushwebly.com/api/projects/visibility \
-H "Authorization: Bearer <TOKEN>" \
-H "Content-Type: application/json" \
-d @visibility.json
Write visibility.json (to private):
{"url":"https://ai-pub.pushwebly.com/play/aB3xYz/","visibility":"private","accessPassword":"a1b2c3"}
Or to public (no password needed; the old password is cleared):
{"url":"https://ai-pub.pushwebly.com/play/aB3xYz/","visibility":"public"}
accessPassword is optional and only used when switching to private (6 digits or
letters; auto-generated if omitted). The response data includes the final
visibility, url, and — when private — the accessPassword. Relay a private
app's URL and password separately, never with ?pwd=. If the app is not found,
already deleted, or belongs to another user, state the concrete reason and that
only your own apps can be changed.
Build APK for Existing Project
curl -s -X POST https://ai-pub.pushwebly.com/api/apk/build \
-H "Authorization: Bearer <TOKEN>" \
-H "Content-Type: application/json" \
-d @build-apk.json
Write build-apk.json as:
{"projectId":12}
Use data.apkUrl from the response as the APK download URL.
Publish and Build APK
This is the recommended operation when the user wants an installable Android app.
curl -s -X POST https://ai-pub.pushwebly.com/api/apk/publish-and-build \
-H "Authorization: Bearer <TOKEN>" \
-F "project_name=airplane-war" \
-F "[email protected]"
For a private app, add:
-F "visibility=private" -F "access_password=a1b2c3"
Omit access_password to auto-generate it.
Expected response fields may include:
{
"gameId": 12,
"projectName": "airplane-war",
"playUrl": "https://ai-pub.pushwebly.com/play/aB3xYz/",
"status": "enabled",
"apkUrl": "https://ai-pub.pushwebly.com/api/apk/download/airplane-war_aB3xYz.apk",
"apkFileName": "airplane-war_aB3xYz.apk",
"apkFileSize": 2300000
}
Read gameId as the project id.
MCP Tool Parameters
If project_* tools are mounted, use these canonical parameter shapes.
project_publish:
{
"authorization": "Bearer <TOKEN>",
"projectName": "project-name",
"zipFilePath": "server-side local zip path",
"visibility": "public",
"accessPassword": ""
}
project_list:
{"authorization": "Bearer <TOKEN>"}
project_update_visibility:
{
"authorization": "Bearer <TOKEN>",
"url": "https://ai-pub.pushwebly.com/play/aB3xYz/",
"visibility": "private",
"accessPassword": "a1b2c3"
}
project_build_apk:
{
"authorization": "Bearer <TOKEN>",
"projectId": 12
}
project_publish_and_build_apk:
{
"authorization": "Bearer <TOKEN>",
"projectName": "project-name",
"zipFilePath": "server-side local zip path",
"visibility": "public",
"accessPassword": ""
}
User Reply Formats
Publish only, public:
Project published successfully.
Project name: <projectName>
Access URL: <url>
Visibility: public
Status: enabled
Publish only, private:
Private project published successfully.
Project name: <projectName>
Access URL: <url>
Access password: <accessPassword>
Visibility: private
Share the URL and the access password separately. The recipient opens the URL and enters the password on the page. Do not append the password to the URL.
Publish and build APK:
Project published successfully.
Project name: <projectName>
Access URL: <playUrl>
APK download: <apkUrl>
Build APK only:
APK build complete.
Project name: <projectName>
APK download: <apkUrl>
On failure, state the concrete reason and the next retry action. Common reasons:
missing index.html/index.htm, invalid or expired token, missing zip file,
empty project name, invalid private password, APK timeout, or server missing
Android SDK configuration.
Notes
- Private apps are opened by entering the password on the access page; share the
clean URL and the password separately, never appended together. (The backend
still accepts
?pwd=<password>, but do not promote that suffixed-link form.) After successful entry, the browser stores a cookie. - APK builds usually take about 1-3 minutes.
- Duplicate APK build requests during an active build are deduplicated.
- Each APK has a unique package name:
com.pushwebly.g{playToken}. - Use
project_publish_and_build_apkfor one-step publish plus APK generation.