Publishing
You don't host anything. Upload your folder, try it on your own robot, then submit it for review.
1. Build it in the simulator
The simulator runs your plugin with the robot stubbed out — revoke permissions, fake a missing dock, watch every call. Get it working there first; it is much faster than a device.
2. Upload it as a draft
Drop your folder on hairobo.com/developers/submit.
manifest.json plus every file it lists.
The checks run immediately, before anything is stored. Blocking errors come
back as checks.errors and nothing is recorded until they are fixed:
- manifest doesn't parse, or
idisn't reverse-DNS, orversionisn't semver entryisn't a relative.htmlpath inside your folder- a file in
fileswasn't in the upload - bundle over 5 MB, or over 60 files
- code calls
fetch,XMLHttpRequest,WebSocket,EventSource,sendBeaconorimportScripts— plugins have no network access (connect-src 'none'), so these cannot work on a device - a remote
src=— the sandbox blocks it and you get a blank screen. Bundle the file and list it infiles. - you call
hairobo.motor.*without"motor"inpermissions— every call would be refused at runtime
checks.warnings don't block. The most common one:
permission "camera" is requested but never used
Asking for a permission you don't use is the single most common reason a submission comes back. Remove it.
3. Try it on your own robot
Open Hai Robo signed in with the account you uploaded from, go to Settings → Plugins → Gallery. Your draft is there, marked DRAFT, above the public plugins. Tap Try it.
Nobody else can see it. It is not in the gallery for anyone but you, and it will not be until a reviewer approves it.
You get the real thing: the real sandbox, the real permission prompt, the real CSP, the real motors. The only difference from a published plugin is that nobody has reviewed it.
4. Iterate
Re-upload the same version as often as you like. A draft is mutable — fixing one line does not cost you a version number. Tap Reinstall in the gallery to pull the new bytes.
Freezing happens when you submit, not when you upload.
5. Submit for review
One button on the portal. From that moment the bytes are fixed: what a reviewer reads and what eventually ships are exactly what you submitted, and further uploads to that version are refused.
The checks run again here, against what is stored rather than what you last uploaded — so the report a reviewer sees describes the code a reviewer reads.
6. Review
A person reads the source, runs it, and checks the permissions match what it does. Not a code review: a check that the plugin is what it says it is and isn't hostile. Expect a few days.
If it comes back, review_note says why. Fix it, upload a new version, and
submit again — a rejected version stays rejected.
Approved plugins appear in the gallery for everyone.
Updating
Upload a new version and submit it. Each version is reviewed on its own, and
your live version keeps serving throughout — a rejected update never takes down
the version already approved.
Adding a permission means the user is asked again, and the new permission stays ungranted until they agree; the rest keep working meanwhile. Removing one needs nothing.
Version numbers
- A draft can be re-uploaded any number of times.
- Once submitted, that version is closed. To change anything, bump the version.
- A rejected version can never be resubmitted. Bump and upload again.
What gets removed
Set out fully in CONTENT-POLICY.md. In short: plugins that request permissions they do not use, misrepresent what they do, target or harass anyone, or try to get around the sandbox rather than work within it.
Users can report a plugin from the gallery. Reports are read by a person.
A plugin can be pulled two ways, and both are used together when something is actually wrong: suspending it hides it from the gallery, which stops new installs; the blocklist stops the copies already on people's devices.
Being useful, not just working
The plugins people keep are the ones that respect where they are — a device on a desk that is mostly idle. Quiet by default, obvious at a glance, and correct about whether a dock is even connected.
info.get() tells you what you actually have. Use it before offering a robot
feature you cannot deliver.