The Portrait CLI lets a linked Portrait user send Easel requests, manage sessions, and upload supported files from a terminal.
It is also built to be driven by an AI coding agent. Operational commands support machine-readable output when standard output is non-interactive, and the package ships an agent skill that documents the supported workflow and safety boundaries for compatible agents. Use the CLI command and output reference for schemas, completion events, pagination, and recovery.
Using Easel through the CLI—its ask and sessions commands—requires full Easel access on your Portrait account and separate provisioning for the Easel CLI bridge. Successful OAuth sign-in alone grants neither. Authentication, file operations, and direct Monitor reads use the connected account and their own access checks; install-skill is local. Contact the team if Easel commands report that the account has not been enabled.
Note
Remote commands use your Portrait identity and remain subject to the access, usage, and data entitlements of the operation they call. OAuth sign-in does not by itself enable Easel ask or session operations.
Install
The CLI is published on npm and needs Node.js 20.17 or later. Run node --version in the terminal where you will use Portrait.
npm install --global @portrait-research/portrait
After installation, run portrait --version and portrait --help. If the command is not found or installation fails, see If a command fails.
Sign in
portrait auth login
The command prints a verification URL and user code, and attempts to open the URL in your default browser. If it cannot, open the printed URL and enter the code. Sign in with your normal Portrait credentials; if verification fails, do not reuse an expired code.
The CLI stores its sign-in credential in the operating system keyring. The account running the CLI needs a working, accessible keyring; browser approval alone does not prove that the credential was saved. A container, remote shell, or unattended environment must meet this requirement too.
Use portrait auth status to check whether you are signed in and portrait auth logout to disconnect this CLI installation.
Ask a question
portrait ask "What did Microsoft management say about AI capex on the last three earnings calls?"
The answer comes back with a session ID. Pass it to a later question to continue the same conversation using its retained context:
portrait ask --session <session-id> "Turn that into a table by quarter."
An ask command without --session starts a new session when Portrait accepts the research turn. Use --session to continue the same durable conversation.
Two things worth knowing:
- Use the session ID returned by the most recent create or resume response. Resume keeps the durable session identity; use the returned value rather than reconstructing or guessing it.
- Available source links open either the Portrait item or the originating website. Display, linking, and access remain subject to the source and licence; restricted news can be attributed by title without a document link.
Place --stream before the quoted prompt to watch the answer arrive. All ask options must precede the prompt, which must be one shell argument:
portrait ask --stream 'Compare the latest earnings commentary for AAPL and MSFT.'
Manage sessions
portrait sessions list [--limit N] [--cursor <cursor>] # one page of sessions, with titles and previews
portrait sessions history <session-id> # the turns in one session
portrait sessions create # start an empty session
portrait sessions create --resume <id> # reopen a past session
Use list to find the session you want, history to see what was said in it, then ask --session to carry on. A list request returns one page without automatically loading more. Set --limit from 1 to 50, and request a next page by passing the returned opaque data.next_page_cursor unchanged to --cursor.
Ask for a monitor or a screen
Monitors and screens are ordinary requests — describe the one you want and Easel builds it, coming back to you for confirmation when it needs to.
For a monitor, give the name, the companies, the topics, the sources, how often you want to be notified, and the project it belongs in (or say it should stay unfiled):
portrait ask "Create a monitor named 'Semiconductor Capex Watch' for ASML, AMAT, and LRCX. Track capex guidance and order commentary from earnings calls, filings, and company news; notify in real time; keep it unfiled."
To inspect Monitors without changing their configuration or read state, use the direct read commands and omit --mark-read. These reads remain subject to the connected account's access and applicable request limits.
portrait monitors list --limit 50
portrait monitors get <monitor-id> --read unread --limit 50
Use the datapoint_monitor_id returned by list as <monitor-id>. Both commands return one page: list uses offset and total; get returns next_cursor for --cursor. Add --mark-read only when changing update read state is intended; it marks only the content in the returned page. See the pagination reference.
A screen stands on its own — it is not filed in a project. Give it a name, the filters, the universe of companies, and either a single run or a recurring cadence with a UTC run time (plus the weekday, for a weekly screen):
portrait ask "Create a one-shot screen named 'Large-Cap Compounders' across all active companies. Use these filters: market cap above USD 10 billion, positive revenue growth, and positive free cash flow."
A screen run takes several minutes. Ask for the outcome on a later turn in the same session rather than launching it again:
portrait ask --session <session-id> "Check 'Large-Cap Compounders' once. If it is complete, summarize the matching companies and filter results; if it is still running, report only its current status. Do not launch another screen."
If that session has gone idle, reopen it with portrait sessions create --resume <session-id> and use the ID that comes back.
Work with documents
Upload a file to My Library from the command line:
portrait files upload ./research-notes.pdf
portrait files upload ./model.xlsx --folder <folder-id>
portrait files upload ./memo.docx --project <project-id>
Upload takes one file at a time — one real file path, up to 25 MB. Directories, multiple paths (including shell-expanded wildcards), and symbolic links are refused; the CLI uploads only the one regular file path supplied.
A freshly uploaded document takes a moment to become readable through the CLI. Upload returns data.document_id; use that exact ID. Check data.llmReady in the status response before reading or using ask --document:
portrait files status <document-id>
portrait files read <document-id> --offset 0 --limit 65536
files read returns extracted LLM text, not the original file. If data.nextOffset is present, pass it unchanged as the next --offset; its absence means that read reached the end. A first chunk is not necessarily the full document. If llmReady is false, check again later; do not repeatedly retry ask or assume the file was included.
To ground a question in one specific document you already have in My Library:
portrait ask --document <document-id> "Summarize the key assumptions in this model."
Delete a document
portrait files delete <document-id> permanently removes that known My Library document. The CLI does not ask for confirmation: run it only when deletion of that exact document ID is intended. An AI agent must not infer the ID or use deletion as implicit cleanup.
Use it from an AI agent
The package includes a skill file that tells an AI coding agent how to drive the CLI — how to hold a session, when to ask for your approval, and where the boundaries are. Install it into your agent's skills directory:
portrait install-skill ~/.claude/skills
--force recursively replaces the existing <skills-directory>/portrait directory, including local files. Back up or version any local changes first; the CLI does not provide undo.
Two boundaries the bundled skill instructs a compatible agent to follow, and that are worth knowing yourself:
- Confirmations come back to you. Creating a monitor or a screen consumes usage and shows up in your account, so when Easel asks for confirmation or a missing detail the agent must relay the question to you and send your answer on — never answer on your behalf. It must not poll a running screen or launch a second one.
- Uploads need an exact path you confirmed. The agent must not scan a directory, expand a wildcard, or guess a file to send.
Output format
Operational commands return a JSON object when standard output is non-interactive. portrait ask --stream instead returns newline-delimited JSON records. --help and --version use plain text, and authentication commands use concise text in an interactive terminal. Sign-in instructions and interactive diagnostics use standard error.
For ask, use data.finalText as the authoritative answer. In streaming mode, retain answer.final and require operation.complete; do not reconstruct the final answer from deltas or treat an interrupted stream as success. The reference includes complete envelopes and error handling.
Verify the installation
- Run
portrait auth statusand confirm that this CLI installation is signed in. The command does not identify the user; if account identity matters, runportrait auth logout, sign in again, and verify the intended account in the browser flow. - Send a low-risk
portrait askrequest with standard output redirected to a file or pipe. - Confirm that stdout contains valid JSON and that the response includes a session ID.
- Open an available source link and compare it with the Portrait web app.
If a command fails
Start with the stage that failed. Reinstalling the package does not resolve missing account access, and repeating a research request can send another turn.
| Symptom | What to check | Next action |
|---|---|---|
portrait: command not found or command not recognized |
The active Node installation and npm global executable directory | Run npm prefix --global. Its bin directory on macOS/Linux, or the prefix itself on Windows, must be on your terminal's PATH. Reopen the terminal after correcting it |
npm installation reports EACCES |
Permission to write to npm's global directory | Follow npm's installation-permissions guidance, or ask your IT team to install it through its approved process |
| Unsupported Node version | node --version in this terminal |
Use Node.js 20.17 or later, then retry installation |
| Browser does not open | The verification URL and code printed by login | Open that URL manually and complete verification while the command is waiting |
DEVICE_CODE_EXPIRED |
The code's validity | Run portrait auth login again and use the new code |
AUTHORIZATION_DENIED |
Whether authorization was declined | Sign in again only if you intend to authorize this CLI |
AUTH_LOGIN_IN_PROGRESS or AUTH_MUTATION_IN_PROGRESS |
Another login/logout command | Complete or stop the other local authentication command before trying again |
KEYRING_* error after browser approval |
Availability of the OS credential store | Restore keyring access, then sign in again and check auth status; do not paste tokens into a file as a workaround |
Login succeeds but ask or sessions is refused |
Full Easel access and CLI bridge provisioning | Ask Portrait support to confirm both requirements |
LOGOUT_INCOMPLETE |
The message distinguishes remote revocation from local removal | Follow the authentication error reference; do not assume the credential was fully revoked |
The executable-location rules are documented in npm's folder reference.
- Run
portrait --helpto confirm the installed command and option names. - Run
portrait auth status; sign in again if the session is absent or expired. - Use the session ID returned by the most recent create or resume response when continuing a conversation.
- If a request may have reached Portrait, inspect
sessions historybefore repeating it. Anotheraskinvocation sends a new turn even with the same--session; resuming a conversation is not deduplication of the previous question. - Stopping the local CLI process does not confirm that remote work stopped. The CLI has no public cancel or end command; use the supported conversation controls in Portrait when you need to stop work.
- For an upload error, use one explicit regular-file path and remain within the documented size limit.
- Contact Portrait support with the CLI version, command name, exit status, and redacted error. Do not send tokens, credentials, or private file content.
Run portrait --help for the full command list.
For questions about access, contact the team.