@oppira/cli
v0.11.0
Published
Oppira CLI — terminal control for the adaptive marketing system (competitor intelligence, playbook, studio, ops).
Maintainers
Readme
Oppira CLI
Drive Oppira from your terminal — competitor intelligence, your marketing playbook, battlecards, the content studio, and the Oppira AI agent — with scriptable, JSON-friendly output.
Requirements
- Node.js 20+
- A Pro Oppira plan — the CLI is a Pro surface. On lower plans the AI agent, playbook and competitor data are still available in the web app and (read-only) over MCP.
Install
npm install -g @oppira/cli
oppira --helpQuick start
oppira config set-url https://api.oppira.com # your Oppira API endpoint
oppira auth login # log in (approve it in your browser)
oppira comp list # see your tracked competitors
oppira ask "how did my top competitor shift this month?"Config is stored in ~/.oppira/ (credentials are kept in a separate, locked-down
file). You can run against several accounts/environments with profiles
(oppira config use <name> or --profile <name>).
Authentication
Pick a method per profile:
Login (default):
oppira auth loginOpens your browser, where you approve this device with the session you already have — no password in the terminal. On a headless box add
--no-browserand approve the printed code from any other machine (a phone works).Password (CI, or no browser anywhere):
oppira auth login --email [email protected] --passwordAPI key (best for scripts / CI): create a key in the Oppira web app, then:
oppira auth key set ca_xxxxxxxxAPI-key access to the CLI requires a Pro plan. (On other plans the same key still works for read-only MCP/analytics.)
Logging in here does not log you out anywhere else. The terminal, the web app and any other machine each hold their own session:
oppira auth sessions # what is signed in, and where
oppira auth sessions revoke <sid> # end one
oppira auth sessions revoke --others # end everything except this terminaloppira auth whoami shows the current account; oppira auth logout ends this
session only.
Commands
| Group | What you can do |
|-------|-----------------|
| oppira ask "…" | Ask the Oppira AI agent — streams the answer, its tool steps and any result cards; --out <dir> saves the images it produced. Reads args or stdin. |
| oppira comp | Manage: list, show, add, track, toggle-tracking, insights, alerts, rm · read: posts, top-posts, summary, timeline, digests, digest, compare, tone |
| oppira insights | list, summary, generate |
| oppira alerts | list, run |
| oppira battlecard | show, history, regen |
| oppira playbook | show, bootstrap, refresh-section, refresh-cluster, edit-section, suggestions |
| oppira reports | list, show, create, update, rm, run, runs — scheduled email reports |
| oppira ads | Your own ad accounts: accounts, performance, campaigns, tree, list-ads, ad, analytics, comments · writes: create, boost, pause, resume, bulk-status, ad-pause, ad-resume, budget, ad-update, campaign-update, adset-status, adset-update, duplicate, rm-ad, rm-campaign, audiences, scope |
| oppira keywords / oppira kw | Google Search keywords (Pro, Google Ads connected): research, plans, plan, metrics, account, search-terms, search-terms-refresh, decide · plan shaping: plan-new, plan-regroup, plan-add · writes: launch, apply |
| oppira discover | profiles, suggestions, dismiss |
| oppira studio | Posts: posts, create, update, publish, rm, quick-post, retry, generate-image · assets: assets, asset-upload, asset-import, asset-rm · brand kit: measure, brand-system, formats, format-attach, format-sync, format-recapability, templates, design · plus calendars, calendar-generate, comments, channels, policy, settings |
| oppira artifact | create, get, export (reports / decks / diagrams) |
| oppira signals | Oppira Signals — the cross-industry market report (admin). One command; --md gives the material an article is written from |
| oppira config / oppira auth | profiles, API URL, login, API keys |
Run oppira <group> --help for a group's commands, or oppira <group> <command> --help for details.
The CLI itself requires a Pro plan; within it, every Oppira feature (AI agent, playbook, battlecards, reports, studio) is available. If something isn't available you'll get a clear message.
Examples
# Ask the agent, pipe the answer into your own tooling
oppira ask "summarize my playbook" --json | jq -r .text
# Competitors
oppira comp show Acme
oppira comp add "Globex" --website globex.com --instagram https://instagram.com/globex
# Refresh strategic docs on demand
oppira insights generate
oppira battlecard regen Acme
# Generate a report artifact, then export it
oppira artifact create --prompt "Q2 competitive landscape brief" --title "Q2 landscape"
oppira artifact formats <id> # what this one can become
oppira artifact export <id> --format pdf
oppira artifact export <id> --format md
# The whole playbook as one document
oppira playbook export --format pdf --out playbook.pdf
oppira playbook export --format md --include-empty
# Schedule a weekly emailed report (Mon 08:00 Budapest)
oppira reports create --prompt "Weekly competitor movements" \
--frequency WEEKLY --day 1 --hour 8 --timezone Europe/Budapest
# Tweak a playbook section directly
oppira playbook edit-section channel-strategy --text "Instagram and Facebook are core channels…"
# Oppira Signals: pull the market report, then write the article from it
oppira signals --from 2026-02 --to 2026-07
oppira signals --from 2026-02 --to 2026-07 --md --out signals-h1-2026.md
oppira signals --from 2026-02 --to 2026-07 --json --out signals-h1-2026.jsonSignals recomputes on every call — the digests behind it are rewritten as scraping
continues — so save the --json run beside whatever you publish from it. That
file, not a re-run, is the record of the figures you quoted.
Reading competitor data
oppira comp posts Acme --platform instagram --from 2026-06-01 --to 2026-06-30
oppira comp posts Acme --full # each post printed in full
oppira comp summary Acme # followers / posts / engagement per platform
oppira comp timeline Acme --months 6 # month by month
oppira comp digests Acme # which monthly digests exist
oppira comp digest Acme 2026-06 # one month incl. ad + review channels
oppira comp top-posts Acme --from 2026-01 --to 2026-06 --limit 10
oppira comp compare Acme Globex --months 6
oppira comp tone Acme # messaging / tone driftposts returns normalized posts — one shape across Facebook, Instagram, X and
LinkedIn, newest first, capped at 100. Everything else is served from the
pre-computed monthly digests, so those calls are cheap.
Your plan sets how far back you can read (free 1 month, basic 6, pro 12). When a request reaches past it, the CLI says so instead of silently returning less.
Setting up a new brand
The order matters — each step is a precondition for the next, and the API says so when you skip one.
# 1. Your own company, then the competitors it is measured against.
oppira comp add "My Co" --company --website myco.com --instagram https://instagram.com/myco
oppira discover suggestions --generate # grounded in your own company + market
oppira comp add "Rival" --website rival.com # add the ones worth tracking (>= 2)
# 2. The playbook. Needs your own company, 2+ competitors and one battlecard
# (battlecards generate themselves after the first scrape — give it a while).
oppira playbook bootstrap # streams; a few minutes
oppira studio formats # your post formats, imported from it
# 3. The Brand Kit. Measure the site, then give each format a design AND ATTACH IT.
oppira studio measure --approve # colours, type, logo from your own site
oppira studio design ./promo.html --css ./promo.css --format product_promo
oppira studio format-attach product_promo <templateId>
# 4. Connect a channel in the web app, then plan the week.
oppira studio channels # must list at least one
oppira studio calendar-generateA design is not in use until it is attached. oppira studio formats shows a
DESIGNS count per format; any format at 0 posts as plain text, and
calendar-generate refuses while no format has one.
Publishing a post
oppira studio channels # the TARGET values --channel accepts
oppira studio asset-upload ./launch.png --kind image
oppira studio create --body "Launch day 🚀" --media <assetId> --channel facebook:123
oppira studio publish <postId> --now # or --at 2026-08-01T09:00:00Z
# …or all of it in one run
oppira studio quick-post ./launch.png --body "Launch day 🚀" --channel facebook:123 --now
# THE USER'S FLOW, in a terminal: the agent composes, you watch the steps and
# get the images. This is what to reach for when the question is "does it design well?".
oppira ask "make me a facebook template for the blog" --out ./out
# THE ENGINE'S FLOW: your own HTML+CSS straight through the gate, no model in the
# loop. For tuning the compiler and reading its refusals verbatim (exit code 2).
oppira studio design ./card.html --css ./card.css --width 1080 --height 1350 --out ./card.pngasset-upload is for local files. asset-import only accepts URLs already hosted
by Oppira (a deliberate SSRF guard), so it is not a substitute.
Publishing runs a price fact-check first. If a price claim in the body no longer
matches your catalog the post is held back; edit it, or re-run with
--skip-fact-check.
Creating and editing ads
Ad writes touch real money, so every command that can start or raise spend
(create, boost, resume, ad-resume, budget, campaign-update,
bulk-status --status active) prints a preview and refuses to act without --yes.
The preview is rendered before any API call, so a forgotten --yes never reaches
the platform. With --json the blocked command emits
{"status":"needs_confirmation","preview":{…}} so scripts can branch on it.
oppira ads accounts # accountId per platform
oppira ads ad-accounts <accountId> # act_… / Google customer ids
oppira ads create --account <id> --ad-account act_123 \
--name "Summer traffic" --goal traffic --budget 25 \
--headline "Try it free" --link https://example.com --countries US,HU # preview only
oppira ads create … --yes # actually creates it
oppira ads pause <campaignId> # stops spend, no --yes needed
oppira ads budget <adId> --amount 30 --yes # ad-level budget: every platform
oppira ads list-ads --campaign <campaignId>Capabilities differ per platform — campaign-level (CBO) budgets and campaign
deletion are Meta-only, boost has no Google Ads path, and creative/targeting
edits are Meta + TikTok. The CLI does not guess: the API answers with a message
naming what is possible on that platform, and the CLI prints it as-is.
Researching keywords
Google Search only, Pro, and it needs a connected Google Ads account —
accountId is part of every Keyword Planner call, so there is no "research
first, connect later".
A research run measures a market and creates the draft plan out of its own
rows: the terms it priced, clustered into ad groups by intent, with a match
type per keyword. Everything after it reads what that run stored, so only two
commands ever call Google again — research (5/day) and
search-terms-refresh (10/day).
# 1. Measure. Seeds are 3-5 concrete things a customer would actually type.
oppira keywords research --seeds "domain registration,buy domain,cheap domain" \
--countries HU --url https://example.com/domains --name "Domains HU"
# 2. Read it back, any time, for free.
oppira keywords plans
oppira keywords plan <planId> --budget 25 # ad groups, match types, reach verdict
oppira keywords metrics --plan <planId> --json # every measured figure, as rows
oppira keywords metrics --terms "buy domain,domain price" --currency HUF
# 3. Re-shape it without touching Google.
oppira keywords plan-regroup <planId> --landing https://example.com/pricing
oppira keywords plan-add <planId> --group "Generic" --terms "domain sale"
oppira keywords plan-new --file ./chosen-terms.txt --name "Brand terms"
# 4. Launch (preview first — it is the server's, so it checks the ad copy).
oppira keywords launch <planId> --budget 25 --headline "Domains from $6" \
--body "Register in a minute." --link https://example.com/domains
oppira keywords launch <planId> --budget 25 … --yes # creates it PAUSED
# 5. The weekly loop: the real queries, and what they propose.
oppira keywords search-terms-refresh
oppira keywords search-terms
oppira keywords decide negated --terms "free domain hosting"Two things to know before the last step. launch creates the campaign
paused unless you pass --status active, and every keyword is stored broad
by the create call — the match types in your plan are installed by a follow-up
write, which the command reports per ad group. And keywords apply is
declarative: the list you send becomes the ad group's full set, so anything
you leave out is deleted. It therefore asks twice — --yes to write, and
--confirm-removals once you have read the removals in the diff it prints.
research and search-terms-refresh can come back with "accepted, nothing
happened" (the allowance is refunded). They say which of three it was —
Google's shared quota with the time it resets, seeds too abstract, or no data
for that market — and exit non-zero, so a script never reads a quota wall as an
empty market.
Scripting & automation
- Add
--jsonto any command for machine-readable output. - Commands exit non-zero on failure, so they behave well in CI.
oppira askreads a question from arguments or piped stdin.
echo "what should I post about this week?" | oppira ask --jsonDaily request limit
The CLI is capped at 100 API requests per user per day (resets at UTC midnight). The cap counts requests, not commands — so keep in mind:
- Passing a competitor name costs one extra request to resolve it. In a loop,
pass the id (
oppira comp list --jsononce, then reuse the ids). quick-postis three requests (upload, create, publish).- Over the cap every command exits non-zero with
error: "quota-exceeded",scope: "daily"and aretryAfterin seconds. - Below 10 remaining, the CLI prints a warning to stderr; every response also
carries
X-Oppira-Daily-Limit/X-Oppira-Daily-Remaining.
Generation-heavy operations (image generation, calendar generate, insight/alert regeneration) have their own, tighter per-day caps that apply to the web app too.
Support
Questions or issues? Reach the Oppira team at oppira.com.
