#Packaging & Distributing
Once your pack works locally, you can hand it to other people. A bundle is just your pack folder, zipped.
#Building a bundle
Zip the contents of your pack folder so that info.json and phrases.json
sit at the root of the archive — not nested inside an extra directory.
my-pack.zip
info.json
phrases.json
setVolume.js
hello.js
package.json (if you have dependencies)Distribute it as a .zip. Don't include node_modules/ — dependencies are
installed on the user's machine at install time.
#What gets validated
Whichever way a pack is installed, Personal Goober checks it before committing. A bundle is rejected if:
info.jsonorphrases.jsonis missing, or isn't valid JSON;info.jsonis missingid(a string) orname;- any action listed in
phrases.jsonhas no matching.jsfile; - a
package.jsonis present but isn't valid JSON.
If a pack with the same id is already installed, the install stops with a
conflict rather than overwriting the existing one. Bump behavior aside, this
means your id is effectively your pack's identity — choose it once and keep it.
#How users install
There are two paths, both of which end in a confirmation prompt:
- From a file. The user opens the install page and uploads your
.zip. Everything is validated and installed locally. This is the simplest thing to point people at — see the user guide on action installation. - From a website. A site can offer a one-click "Install" button that beams a bundle straight into a running Personal Goober. That's the API below.
However it arrives, the app installs the pack, and if it has a package.json,
runs npm install --ignore-scripts inside the pack folder to fetch
dependencies.
#The website install API
Personal Goober runs a local WebSocket server that trusted websites can use to install bundles. This is what powers the "Install" buttons on the official site, and you can build the same flow on your own.
- Endpoint:
ws://127.0.0.1:5577 - Origins: the connection is only accepted from an allowlisted origin. The
official site's origins are allowlisted;
http://localhost:3000is allowed for local development.
Every message is JSON and carries a requestId you generate, which is echoed
back on the matching reply.
1. Ask for access. Before anything else, request permission. The user sees a system dialog and can choose to remember the decision for your origin.
{ "type": "request-access", "appName": "My Action Site", "requestId": "..." }Reply: { "type": "access-result", "granted": true|false, "requestId": "..." }.
Until access is granted, other requests are rejected.
2. Send the bundle. Base64-encode the .zip and send it with its filename.
{ "type": "install", "filename": "my-pack.zip", "data": "<base64>", "requestId": "..." }If the bundle is valid you get back an install-confirm describing what's about
to be installed (so you can show your own confirmation UI):
{
"type": "install-confirm",
"name": "My Pack",
"id": "my-pack",
"description": "...",
"dependencies": ["loudness"],
"requestId": "..."
}3. Confirm or cancel.
{ "type": "confirm-install", "requestId": "..." }Reply: { "type": "install-result", "status": "ok" | "error", "requestId": "..." }
(with an error string when something went wrong). Send cancel-install instead
to drop the staged install.
There's also a ping / pong pair you can use to check whether Personal Goober
is running before you try any of this.
#The trust model, and your responsibility
Installing a pack is a big deal, and the app treats it that way:
- The user must explicitly approve every install, and the prompt lists your pack's name, description, and dependencies.
- Website access is gated per-origin behind a separate permission prompt.
- Dependencies install with
--ignore-scripts, so a dependency can't run code during installation.
None of that changes the core fact from the overview: once installed, your action code runs with full access to the user's computer. Distribute packs you'd trust yourself, be clear about what they do, and don't ask for dependencies you don't need.
That's the whole model. Go build something.