Campfire hosts one self-contained HTML page and gives back links. Links are unlisted, not private: anyone with the public link can view the page or share it further. Tell the user this before they publish anything sensitive.
Use it when the user asks you to publish, host or share an HTML page, app or game.
CANNOT RUN COMMANDS? (for example in an AI chat app such as Claude or ChatGPT)
The steps below need a terminal. Instead, tell the user to add Campfire as a connector in their AI app's settings, once:
1. Open the app's connector settings. In Claude: https://claude.ai/customize/connectors?modal=add-custom-connector (Customize → Connectors → Add custom connector).
2. Name: Campfire. URL: https://campfile.io/mcp
3. Click Add or Connect, and sign in with the code Campfire emails them. Then ask again: Campfire's tools will be there.
SIGN IN (once per computer)
1. If ~/.config/campfire/credentials exists, the user is signed in. Skip to the next section.
2. Otherwise ask the user for their email address. Do not guess it from git or other settings. Then send:
curl -sS -X POST https://campfile.io/api/signin -H "Content-Type: application/json" -d '{"email": "USER_EMAIL"}'
3. Tell the user: "I asked Campfire to email you a 6-digit code. What is it? If it's not in your inbox, check your spam folder." The code works for 10 minutes. A new email address gets a new account; there is no separate sign-up.
4. Send the code. This one command saves the key to the file without printing it:
(umask 077 && curl -sSf --create-dirs -o ~/.config/campfire/credentials -X POST https://campfile.io/api/signin/verify -H "Accept: text/plain" -H "Content-Type: application/json" -d '{"email": "USER_EMAIL", "code": "123456"}') && echo "Signed in"
If curl reports error 400, the code was wrong, expired or used up: ask the user to check it, or go back to step 2 for a new one. 403 means the account is disabled.
Never show the key to the user and never put it inside a page. The commands below read it from the file.
The page must be ONE self-contained HTML file, at most 5 MB.
- Put all CSS and JavaScript inline. Embed images, fonts and audio as data: URLs.
- Nothing is loaded from the internet: CDN scripts, web fonts, external images and fetch()/XMLHttpRequest calls are all blocked.
- The page runs in a sandbox. localStorage, sessionStorage and cookies throw errors, so keep state in JavaScript variables. Forms, alert()/confirm()/prompt() and file downloads do not work either.
- Links that open in a new tab work.
A new page starts as a PRIVATE DRAFT. Its url shows the page only to the user, after they log in with their email on the page; everyone else sees "This page isn't published yet" until it is published.
- After creating a page, give the user the url and say: "Open it to check it. Only you can see it until it's published: log in with your email on the page. When you're happy, click Publish on the page, or tell me to publish it." The user can publish on the page, where they choose who can open it, or ask you to.
- The url is the page's only link: the same link works as the public link once the page is published. A page open to anyone with the link is unlisted, not private: anyone who has its url can view it and share it further.
CREATE A PAGE
1. Save the HTML in a file, for example page.html.
2. Turn it into a JSON request body (this does the escaping for you):
jq -n --arg title "Page title" --rawfile html page.html '{title: $title, html: $html}' > request.json
No jq? Use Python instead:
python3 -c 'import json; print(json.dumps({"title": "Page title", "html": open("page.html", encoding="utf-8").read()}))' > request.json
3. Send it:
curl -sS -X POST https://campfile.io/api/pages -H "Authorization: Bearer $(cat ~/.config/campfire/credentials)" -H "Content-Type: application/json" --data-binary @request.json
The answer is {"id": "...", "url": "...", "published": false}. Give the user the url, as described above.
UPDATE A PAGE (same links, new content; "title" is optional in the JSON, and the old title stays if you leave it out)
curl -sS -X PUT https://campfile.io/api/pages/PAGE_ID -H "Authorization: Bearer $(cat ~/.config/campfire/credentials)" -H "Content-Type: application/json" --data-binary @request.json
The answer has the same id and url as before. A draft stays a draft. A published page changes for everyone who has its url.
DELETE A PAGE (gone for good)
curl -sS -X DELETE https://campfile.io/api/pages/PAGE_ID -H "Authorization: Bearer $(cat ~/.config/campfire/credentials)"
LIST THE USER'S PAGES
curl -sS https://campfile.io/api/pages -H "Authorization: Bearer $(cat ~/.config/campfire/credentials)"
The answer is {"pages": [{"id": "...", "title": "...", "url": "...", "published": true, "access": "link", "updated_at": "..."}]}, newest first.
PUBLISH A PAGE
Publish a page only when the user explicitly tells you to publish it (for example "publish it" or "make it public"), never on your own. Unpublish only when they ask.
- Use access "link" (anyone with the link can open it) unless the user wants to limit who can open it; then use "approval" (visitors ask with their email, the user approves them) and pass the emails the user named.
- The answer says whether the public link opened for a visitor. If public_link_works is true, give the user the url and say who can open it. If it is false, tell the user the public link did not open yet (public_link_status, if given), and suggest trying again in a minute.
curl -sS -X POST https://campfile.io/api/pages/PAGE_ID/publish -H "Authorization: Bearer $(cat ~/.config/campfire/credentials)" -H "Content-Type: application/json" -d '{"access": "link"}'
Only approved people: -d '{"access": "approval", "emails": "ingrid@example.com, omar@example.com"}' ("emails" is optional; they can open it without asking).
The answer is {"id": "...", "url": "...", "published": true, "access": "link", "public_link_works": true}. Publishing again with the other access switches it.
UNPUBLISH A PAGE (it becomes a draft again: only the user, logged in, can open its url)
curl -sS -X POST https://campfile.io/api/pages/PAGE_ID/unpublish -H "Authorization: Bearer $(cat ~/.config/campfire/credentials)"
WHO CAN OPEN AN APPROVAL PAGE (only for a page published with "approval")
See who asked, who is let in and who was told no:
curl -sS https://campfile.io/api/pages/PAGE_ID/people -H "Authorization: Bearer $(cat ~/.config/campfire/credentials)"
The answer is {"id": "...", "published": true, "access": "approval", "people": [{"email": "...", "status": "waiting"}]}; status is "waiting", "approved" or "denied". The user also gets an email when someone asks.
Let people in (they get a code by email when they open the page):
curl -sS -X POST https://campfile.io/api/pages/PAGE_ID/people -H "Authorization: Bearer $(cat ~/.config/campfire/credentials)" -H "Content-Type: application/json" -d '{"emails": "ingrid@example.com"}'
Approve, say no to, or remove one person, as the user decides:
curl -sS -X POST https://campfile.io/api/pages/PAGE_ID/people/decide -H "Authorization: Bearer $(cat ~/.config/campfire/credentials)" -H "Content-Type: application/json" -d '{"email": "ingrid@example.com", "decision": "approve"}'
"decision" is "approve", "deny" or "remove". Removing someone lets them ask again.
If a request fails, the answer is JSON like {"error": "..."}. Tell the user what it says, in plain words.