@island-pitch/ip-bot-cli
v0.3.0
Published
IP Bot CLI (ipbot) — accessible-by-default social publishing for AI agents and humans: alt text on every image, human-in-the-loop approval, agent-grade automation
Maintainers
Readme
@island-pitch/ip-bot-cli (ipbot)
#AutomateThis — ship social content the way an agent should: draft → human review → schedule → publish, from one CLI.
Your AI writes the post. A human approves it. IP Bot publishes it — with the alt text, venue tag, collaborator invites, and compliance gating most tools never send. Do Cool Things the Right Way!®
# Draft it (your agent, your voice, full a11y)
ipbot content create --type social_post --platform instagram \
--excerpt "Doors at 8. The crew is ready. 🌊" \
--alt-text "Two dancers mid-spin under purple stage lights" \
--location-id 213385402 --collaborators dj_kaya \
--dest-config '{"igAccountId":"...","imageUrl":"https://media.islandpitch.bot/…"}'
# A human approves it (or an external reviewer via a tokenized link)
ipbot content approve <draftId>
# Schedule it in plain English
ipbot content schedule <draftId> --at "fri 7pm" --tz ET
# → publishes automatically; check performance later with `ipbot analytics post`Why this exists
- Human-in-the-loop by design. Nothing publishes without an explicit approval — reviewers see the full assembled post (caption, image, alt text, destination, timing), and outside clients can approve via secure tokenized links with no account.
- Accessible by default. Alt text is a first-class flag on every image —
published as Instagram
alt_textand Facebookalt_text_custom. A bridge is only finished when it's wide enough for everyone to cross. - Built for agents, not adapted for them. Stable exit codes
(
0/1/2/3/124),--output jsoneverywhere, idempotency keys so retries never double-post, natural-language scheduling, andschedule debugthat explains publisher failures with a suggested fix. - The Meta features everyone else skips. Collaborator co-author invites (your post on both profiles), venue location tags, people tags, Reels covers, IG Stories, Facebook CTA buttons, 21+ audience gating for alcohol/nightlife compliance, timeline backdating.
- Pairs with your agent runtime. The same surface ships as an MCP server:
@island-pitch/ip-bot-mcp— one command to give Claude a content crew.
🏝️ IP Bot is in private beta — request access at islandpitch.bot, then grab your
ipbot_live_…key from Settings → API Keys.
Accessibility, for people who automate
If you script your posting, your automation decides whether a blind follower gets a description or silence. IP Bot makes the accessible path the easy path:
--alt-texton every image create;mediaAltTextsfor every carousel item — published as Instagramalt_text/ Facebookalt_text_custom, helping your posts meet WCAG 1.1.1 (non-text content).- The review page works like an a11y linter for your feed: reviewers see the alt text with the assembled post, and a missing description is flagged in amber — "missing — screen readers get no description" — before anything ships.
- Content tickets carry a per-cell
**Alt:**field, so bulk ticket-driven pipelines (draft from-ticket) stay accessible at scale. - Honest platform limits, documented: Meta exposes no alt-text API for
video/Reels/Stories — we say so in the tools instead of pretending.
Facebook video captions (
.srt) are next on the roadmap.
Got a publishing chore your tools make inaccessible or manual? #AutomateThis.
Everything below is the full reference. Every command supports --output json
for machine consumption.
Install
Run without installing (recommended for one-offs and CI):
npx @island-pitch/ip-bot-cli <command>
# e.g.
npx @island-pitch/ip-bot-cli system statusOr install globally to get the ipbot binary on your PATH:
npm i -g @island-pitch/ip-bot-cli
ipbot --helpRequires Node.js >= 18.
Prerequisite: IP Bot is in private beta. You need an
ipbot_live_…key (request access, then copy it from Settings → API Keys) inIPBOT_API_KEY. Without a valid key, commands fail withUNAUTHENTICATED.
Configuration
The CLI talks to an AppSync GraphQL endpoint. It resolves connection settings in this order (first match wins):
- CLI flags:
--endpoint <url>,--api-key <key>,--token <jwt> - Environment variables (below)
- Saved config at
~/.ipbot/config.json(written byipbot auth ...) - Auto-discovered
amplify_outputs.json(walks up from the current directory — useful inside the monorepo, not when installed vianpx)
Environment variables
| Variable | Purpose |
| --- | --- |
| IPBOT_APPSYNC_ENDPOINT | AppSync GraphQL URL |
| IPBOT_API_KEY | Canonical key var. A per-user ipbot_live_… key is auto-routed to the Authorization header; a shared da2-… key goes to x-api-key. |
| IPBOT_USER_API_KEY | Deprecated/advanced explicit per-user key override. Prefer IPBOT_API_KEY=ipbot_live_…. |
| IPBOT_USER_ID | Default user id (Cognito sub) for user-scoped commands (content/calendar/org). |
| IPBOT_ID_TOKEN | Cognito ID token (human auth) |
| IPBOT_REGION / AWS_REGION | AWS region (default us-west-2) |
| IPBOT_COGNITO_DOMAIN / IPBOT_COGNITO_CLIENT_ID | Cognito Hosted UI settings for auth login |
| ATLASSIAN_SITE / ATLASSIAN_EMAIL / ATLASSIAN_API_TOKEN | Optional Jira integration (draft from-ticket, Jira notify) |
When installed standalone (via npx/global), set at least
IPBOT_APPSYNC_ENDPOINT and either IPBOT_API_KEY (agent) or run
ipbot auth login (human).
Authentication modes
Humans — Cognito Authorization Code + PKCE:
ipbot auth login # opens the Hosted UI, completes PKCE, stores tokens
ipbot auth whoami
ipbot auth refresh # refresh the access token
ipbot auth logout
ipbot auth login --token <jwt> # or paste a Cognito ID token directlyAgents / CI — AppSync API key + explicit user:
export IPBOT_APPSYNC_ENDPOINT="https://api.islandpitch.bot/graphql"
export IPBOT_API_KEY="da2-..." # or: ipbot auth key set <apiKey>
# pass --user-id on commands that act on behalf of a user:
ipbot content list --user-id <cognito-sub> --output jsonipbot auth key set <apiKey> / ipbot auth key clear persist the key in
~/.ipbot/config.json.
Command catalog
Run ipbot <group> --help for the full options of any group.
auth — authentication
login, refresh, logout, whoami, key set <apiKey>, key clear
content — content pipeline
create, find, get <draftId>, list, submit <draftId>,
approve <draftId>, reject <draftId>, schedule <draftId>,
reschedule <scheduleId>, publish <draftId>, cancel <scheduleId>
schedule/rescheduleaccept ISO 8601 or natural language for--at("in 10 minutes","fri 7pm ET") with--tzas the default zone.reschedulerequires an identity-bound caller (per-user key or login).createtakes publishing option flags that merge into the dest-config — see Publishing options below.
media — native media hosting
upload <filePath> — upload a local image (JPEG/PNG/WebP ≤8 MB) or video
(MP4/MOV ≤200 MB) to the platform media bucket and print a publicly
fetchable URL for **Image:** ticket fields or --dest-config imageUrl.
Requires an identity-bound caller (the shared key is rejected).
review — tokenized external review & comments
share <draftId>, shares <draftId>, revoke <shareId>,
comment <draftId>, comments <draftId>
calendar — publication calendar
show [--month YYYY-MM], get <scheduleId>, wait <scheduleId>
org — organization ("Your Own Crew") management
create, list, context <orgId>, invite <orgId>, accept <inviteToken>,
remove <orgId>, role <orgId>, update <orgId>, drafts <orgId>,
audit <orgId>, share <orgId>, unshare <sharedAccountId>
accounts — connected social accounts
list --user <sub|email> (shows page-token freshness: cached / stale / missing)
schedule — schedule inspection & debugging
debug <scheduleId> (classifies publisher errors with a suggested fix)
draft — multi-cell draft helpers
from-ticket <jiraKey> (build a draft from a Jira ticket)
system — infra health
status (endpoint, auth mode, token freshness across users)
analytics — published-post analytics
post <platformPostId> (fetch insights for a published post)
campaigns — AutoReply campaigns (Instagram comment/DM automation)
create, list [--status <active|paused|archived>], get <campaignId>,
update <campaignId>, activate|pause|archive <campaignId>,
set-steps <campaignId> --steps <json-file|->,
set-triggers <campaignId> [--keywords <csv>] [--all-comments|--no-all-comments] [--media-ids <csv>] [--clear-media-ids]
--media-ids <csv>restricts triggering to specific IG media IDs; omit to trigger on all media. Inbound DMs are never media-filtered.- Meta rules the engine enforces (details in
ipbot campaigns --help): one private reply per comment — ever (private_reply_already_sent), private replies only within 7 days of the comment (private_reply_window_expired), and free-form DMs require the user to have messaged within 24 hours.
powers — discover & run Powers
list (catalogue + Winds cost) · run <powerId> [--input '<json>'] [--input-file <path>] [--team-id <id>] [--account-ref <provider:id>]
winds — Winds balance & top-ups
balance (current balance + plan) · topup <packId> (small | medium | large; returns a Stripe Checkout URL)
powersandwindsare user-scoped — they require a per-user API key (ipbot_live_…) or interactive login, and debit the key owner's Winds. The shared AppSync key is rejected for these.
Publishing options (accessibility + Meta differentiators)
content create flags that merge into the draft's dest-config. The publisher
gates each per media type and omits anything unsupported.
| Flag | Publishes as |
| --- | --- |
| --alt-text <text> | IG alt_text / FB alt_text_custom — a first-class accessibility field: provide for every image; omitting it drops alt text silently. Images only (not Reels/Stories). |
| --media-alt-texts <json-array\|csv> | Per-carousel-child alt text, paired by index with --media-urls / mediaUrls. Same a11y rule — omit and alt text is dropped silently. Note: Instagram crops all carousel children to the FIRST child's aspect ratio. |
| --location-id <id> | IG location_id / venue tag (Facebook place ID) |
| --collaborators <list> | IG co-author invites (reels/carousels) — the featured accounts the post is about; appears on both profiles |
| --user-tags <json> | IG user_tags — [{"username","x","y"}] (x/y on single images only) |
| --cover-url <url> / --thumb-offset <ms> / --audio-name <name> / --share-to-feed | IG Reels presentation |
| --story | Publish as an IG Story (media_type=STORIES; no caption/alt) |
| --place-id <id> | FB place (check-in) |
| --cta <json> | FB call_to_action — {"type":"GET_TICKETS","value":{"link":"…"}} |
| --targeting <json> / --feed-targeting <json> | FB audience restriction / feed preference — e.g. {"age_min":21} for 21+ compliance content |
| --backdated-time <iso> | FB backdated_time (timeline import) |
Content tickets support a per-cell **Alt:** line that draft from-ticket
carries automatically.
Typical agent flow
export IPBOT_APPSYNC_ENDPOINT=... IPBOT_API_KEY=...
ID=$(npx @island-pitch/ip-bot-cli content create --type blog --platform wordpress \
--title "Launch" --body "..." --user-id $SUB --output json | jq -r .draftId)
npx @island-pitch/ip-bot-cli content submit $ID --user-id $SUB
npx @island-pitch/ip-bot-cli content approve $ID --user-id $SUB --output json
npx @island-pitch/ip-bot-cli content schedule $ID --at "in 10 minutes" --output json
npx @island-pitch/ip-bot-cli calendar wait <scheduleId> --output jsonExit codes
| Exit | Meaning |
| --- | --- |
| 0 | Success |
| 1 | Unexpected error (transport failure, crash, bad usage) |
| 2 | Operation failed or not found (e.g. unhealthy system status, draft not found, failed schedule) |
| 3 | Structured handler failure — content commands returning {success:false} (wrong state, not found, permission denied) and Power failures (INSUFFICIENT_WINDS, PAID_ONLY, …) — distinct from a crash |
| 124 | Timeout (e.g. calendar wait exceeded its deadline) |
On failure with --output json, the payload includes a typed error.code. See the
docs Troubleshooting page for the full error-code catalog.
License
UNLICENSED — internal Island Pitch tooling.
