@series-a/cli
v1.9.0
Published
AdvisorPro command-line interface — drive leads, campaigns, sequences and agents from your terminal.
Maintainers
Readme
@series-a/cli
A command-line interface to the AdvisorPro platform — drive leads, campaigns, sequences and agents from your terminal. Built on top of the /agent-api M2M router, so anything the CLI does is auditable and workspace-scoped.
Install
# Run without installing — always fetches latest
npx @series-a/cli@latest --help
# Pin a version (recommended for CI)
npx @series-a/[email protected] --help
# Or install globally
npm i -g @series-a/cli
advisoros --helpRequires Node.js ≥ 20.
Releases are tagged
cli-vX.Y.Zin the repo and published to npm with provenance. SeeCHANGELOG.md.
1. Get an API key
- Sign in to AdvisorPro.
- Open Settings → API Keys.
- Click New key → choose Workspace scope (recommended).
- Copy the key — you won't see it again.
The CLI quickstart card on that page shows a copy-paste npx command pre-filled with your workspace ID.
Admin keys (cross-workspace) also work but are intentionally restricted. Default to workspace keys.
2. Log in
npx @series-a/cli@latest login
# Prompts for API key + workspace ID (the 6-char slug, e.g. dmw9wj)Admin login
Admin keys (advos_admin_…) are detected automatically — no workspace prompt:
advisoros login --admin
# ✓ Logged in as admin profile "default" — N workspaces reachable
advisoros workspaces list # admin-only, no workspace needed
advisoros campaigns list --workspace dmw9wj # pass workspace per command
advisoros whoami # shows scope: admin (cross-workspace)Multiple workspaces?
advisoros login --profile acme # save as "acme"
advisoros leads list --profile acme # use the named profileConfig is stored at ~/.advisoros/config.json with mode 0600. You can also use env vars: ADVISOROS_API_KEY, ADVISOROS_WORKSPACE_ID, ADVISOROS_PROFILE, ADVISOROS_BASE_URL.
advisoros whoami
advisoros profiles
advisoros logout --profile acme3. Command reference
Global flags (work on every subcommand):
| Flag | Description |
|---|---|
| --json | Emit raw JSON instead of formatted tables. |
| -w, --workspace <id> | Override the workspace from your profile. |
| -p, --profile <name> | Pick a saved profile. |
| --api-key <key> | Override the saved API key. |
User-made knowledge categories (CLI 1.9.0)
These groups are separate from the automatic topic tag set by knowledge add --category.
| Command | Purpose |
|---|---|
| knowledge categories list [--limit 50] [--offset 0] | List workspace categories. |
| knowledge categories create --name "Accounting" [--description "CPA and accounting insights"] | Create a category. |
| knowledge categories show <categoryId> [--limit 50] [--offset 0] | Read the category and its knowledge items. |
| knowledge categories update <categoryId> [--name ...] [--description ...] | Rename or describe a category. |
| knowledge categories add-items <categoryId> <itemId...> | Bulk add up to 200 existing workspace items; repeat safely. |
| knowledge categories remove-items <categoryId> <itemId...> | Remove memberships, keeping the knowledge items. |
| knowledge categories delete <categoryId> | Preview deletion; --confirm --yes executes and keeps all knowledge items. |
| knowledge categories settings show | Read the workspace's manual-choice setting. |
| knowledge categories settings set auto\|manual | Automatic by default; manual requires an explicit category. |
| content generate --angle "..." --format linkedin_post --knowledge-category <categoryId-or-auto> | Generate a draft using that category or automatic best fit. |
One item may belong to several categories. A selected category uses only its knowledge; reference IDs outside it are rejected. Empty selected categories fail before a paid job starts. Automatic selection with no match, and workspaces with no categories, retain existing grounding. --knowledge-category defaults to auto; manual mode rejects that default until you supply an ID. Category management uses the existing knowledge:* API scope, stays workspace-scoped and does not spend AI credits. Generation uses content:generate and normal generation credits. Nothing is published automatically.
Example (use full IDs from JSON output):
advisoros knowledge categories list --workspace <workspace> --as-agent my-agent --json
advisoros knowledge categories create --name "Accounting" --description "CPA and accounting insights" --json
advisoros knowledge categories add-items <categoryId> <itemId1> <itemId2> --json
advisoros content generate --angle "Accounting trends" --format linkedin_post --knowledge-category <categoryId> --wait --jsonLeads
advisoros leads list [--status new] [--persona founder] [--search acme] [--limit 50]
advisoros leads get <id>
advisoros leads set-status <id> <new|engaged|won|lost>
advisoros leads import --file leads.csv [--persona founder]
advisoros leads import-from <source> [--query ...] [--post <url>] [--file <path>] [--audience <name>] [--campaign <id>] [--limit 50] [--no-enrich]
advisoros leads import-status <jobId>
advisoros leads update <id> [--name ...] [--title ...] [--company ...] [--email ...] [--linkedin ...] [--persona ...] [--score ...] [--status ...] [--notes ...]
advisoros leads delete <id>
advisoros leads verify <id>leads import accepts CSV (header row), JSON array, or JSONL. Upserts on (workspace_id, linkedin_url). Bulk requests are auto-chunked at 200/call (server cap: 500/call).
leads import-from runs any of the import sources in Get Leads → Import Leads:
| source | required params | what it does |
|---|---|---|
| linkedin_search | --query | keyword / title / company people search via the AdvisorOS engine |
| company_search | --query | LinkedIn company search |
| post_reactors | --post <url> | everyone who reacted to a LinkedIn post |
| paste_profiles | --file <path> | name + LinkedIn URL lines; rest is enriched |
| csv | --file <path> | same CSV parser the UI uses |
| heyreach | --file <path> --campaign <id> | HeyReach campaign CSV, statuses preserved |
| sales_nav_url | --url <sales-nav-search-url> | queues a background Sales Navigator import, drip-fed inside the LinkedIn read limits |
| sales_nav_keywords | --query | same background queue, from Sales Navigator keywords |
All sources accept --audience to attach leads to a segment (created if missing). LinkedIn-backed sources run at bulk read priority so live campaigns are not starved. Long-running imports return a job_id; poll with advisoros leads import-status <jobId>.
Sales Navigator sources do not run inline — they create a scheduled import that the cap-aware scheduler drip-feeds over hours or days:
advisoros leads import-from sales_nav_url --url "https://www.linkedin.com/sales/search/people?..." \
--limit 500 --audience "Q3 ICP" --priority high
advisoros leads import-schedules
advisoros leads import-schedule pause|resume|cancel <id>Cancelling keeps the leads already imported.
Campaigns
advisoros campaigns list [--status active]
advisoros campaigns show <id>
advisoros campaigns create --name "Founder Pitch Club" \
--sequence <sequenceId> \
--senders acc_a,acc_b \
--daily-limit 25
advisoros campaigns add-leads <campaignId> --lead-ids id1,id2,id3
advisoros campaigns add-leads <campaignId> --file lead-ids.txt
advisoros campaigns launch <id>
advisoros campaigns pause <id>
advisoros campaigns resume <id>
advisoros campaigns rename <id> "New name" # rename without touching other settingslaunch flips status to active — the enforce_campaign_char_limits trigger validates every step's copy against LinkedIn limits before allowing the transition.
Sequences / Inbox / Content / Agents / Meetings / Newsletter / Integrations
advisoros sequences list
advisoros sequences show <id>
advisoros inbox threads [--campaign <id>] [--lead <id>] [--needs-attention] [--limit 50]
advisoros content list [--status published] [--type post] [--search ...]
advisoros content show <id>
advisoros content publish <id>
advisoros agents list
advisoros agents run <name> --input '{"foo":"bar"}'
advisoros agents trigger <name> --input '{"foo":"bar"}'
advisoros agents history [--agent <name>] [--days 30] [--limit 50]
advisoros agents job <jobId>
advisoros meetings list [--limit 50]
advisoros meetings transcripts [--provider fireflies|roam] [--since 2026-01-01] [--limit 50]
advisoros meetings transcript <transcriptId> [--original | --mode anonymised|original]
advisoros newsletter list
advisoros newsletter create --subject "Weekly roundup" --body "<html>..." [--preview-text "..."]
advisoros newsletter edit <id> [--subject ...] [--body ...] [--preview-text ...] [--status draft|scheduled]
advisoros newsletter delete <id>
advisoros integrations status
advisoros integrations check <platform>Heads-up:
inbox send/replyare intentionally not in v1. Outbound goes throughcampaigns launch(sender resolution + char limits + per-sender daily budget). For one-off DMs, use the web inbox.
meetings transcript returns anonymised text by default, once anonymisation is ready; otherwise metadata is returned with an explicit reason. Add --original to get the original text and speaker identities — your key needs this permission (all unrestricted keys already have it) and every original read is audited.
integrations status is read-only and never returns credentials. integrations check <platform> runs a live health probe for the requested platform (e.g. linkedin_tool).
Obsidian vault mirror (kb) — one-way
advisoros kb link ./client-vault --workspace dmw9wj
advisoros kb status ./client-vault # dry-run preview
advisoros kb pull ./client-vault # mirror notes + graph entities
advisoros kb pull ./client-vault --preserve-localOne-way push, AdvisorPro → Obsidian. Markdown notes with frontmatter, [[wikilinks]] and an
## Entities block, plus Entities/<Type>/*.md for graph nodes. Local edits are overwritten unless
--preserve-local is used (remote copy lands as <slug>.remote.md). Nothing is pushed back.
Examples
Spin up a campaign from a fresh CSV:
advisoros leads import --file ./prospects.csv --persona founder
advisoros leads list --persona founder --limit 200 --json \
| jq -r '.leads[].id' > lead-ids.txt
advisoros campaigns create --name "Q3 Founder Outreach" \
--sequence 1a2b3c4d-... --senders acc_milan --daily-limit 25
# → ✓ Created campaign 8297f04d-...
advisoros campaigns add-leads 8297f04d-... --file lead-ids.txt
advisoros campaigns launch 8297f04d-...Tail a long-running agent job:
JOB=$(advisoros agents run lead-intelligence --input '{}' --json | jq -r '.run.job_id')
watch -n 5 "advisoros agents job $JOB --json | jq '{status,progress}'"Safety rails
Anything that stops sending, un-enrols leads or deletes data now asks first.
# Launch is a dry run until you confirm — preflight runs server-side.
advisoros campaigns preflight 9884b9ec
advisoros campaigns launch 9884b9ec # shows blockers, changes nothing
advisoros campaigns launch 9884b9ec --confirm # actually launches
# Destructive commands prompt; scripts must pass --yes.
advisoros campaigns delete 9884b9ec --dry-run
advisoros campaigns delete 9884b9ec --yes
advisoros sequences delete-step 6b60d6ad --yesA campaign with no leads, no sequence, no steps or no sender account is
refused, not silently activated. --force overrides once you know why.
New commands: campaigns preflight|archive|clone|delete|remove-leads,
sequences delete|update-step|delete-step.
Reading more than 200 rows
List commands accept --limit up to 1000 and every list response carries
pagination (total, limit, offset, next_cursor, has_more). If you ask
for more than the maximum, the response says it was clamped instead of quietly
truncating.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Generic failure (error in JSON response or thrown). Stderr contains the message. |
Programmatic use
import { call } from "@series-a/cli/src/api.mjs";
const { campaigns } = await call("campaigns", "list", { status: "active" }, {
apiKey: process.env.ADVISOROS_API_KEY,
workspaceId: "dmw9wj",
});Troubleshooting
- 401 Unauthorized — key wrong / revoked / wrong base URL. Re-run
advisoros login. - 403 not authorized for the requested workspace_id — workspace-scoped key +
--workspaceto a different workspace. Use the matching key. - CHAR_LIMIT_EXCEEDED on launch — a sequence step exceeds LinkedIn limits (200 invitations / 8000 DMs / 1250 comments). Fix in web app.
- Network error — check
ADVISOROS_BASE_URLif you're pointing at a non-default deployment.
Releasing (maintainers)
cd tools/cli
npx changeset # describe the change
npm run version-packages # bump package.json + CHANGELOG.md
git commit -am "chore(cli): release v$(node -p "require('./package.json').version")"
git tag "cli-v$(node -p "require('./package.json').version")"
git push --follow-tags
# → .github/workflows/cli-release.yml publishes to npm with provenanceNPM_TOKEN must be set in the repo's GitHub Actions secrets (automation token with publish access to the @series-a org).
Roadmap (v1.1)
inbox send/inbox replywith explicit--confirm+ per-sender budget enforcement.agents tail <jobId>(SSE).admin workspaces …(admin keys only).- Single-binary builds via
pkg.
Full API reference
See docs/agent-api.md for the underlying /agent-api envelope.
Admin keys
Admin keys (advos_admin_…) are authorised by the key itself — they do not need an
agent_workspace_bindings row. agent_name is an audit label only.
advisoros login --admin --profile ops # no workspace prompt; prints reachable workspace count
advisoros whoami --profile ops # scope: admin (cross-workspace) + reachable count
advisoros workspaces list --profile ops # every reachable workspace (paginated, up to 1000)
advisoros analytics overview --workspace dmw9wj --profile opsCaller identity is overridable: --as-agent <name> on any command, ADVISOROS_AGENT_NAME
in the environment, or the agentName stored on the profile at login (default advisoros-cli).
Workspace-scoped keys are unchanged: they can only reach their own workspace (403 otherwise)
and cannot use the workspaces resource. An agent without bindings now gets a typed 403
explaining to use an admin key, instead of a 500.
