> For the complete user guide index, see [llms.txt](https://portraitresearch.com/documentation/llms.txt).

# Portrait CLI

[Canonical page](https://portraitresearch.com/documentation/integrations/portrait-cli/)

## Applicability

- **Task:** Install, authenticate, and verify the Portrait CLI
- **Access:** Portrait account and OAuth sign-in for remote operations; full Easel access and an account-enabled Easel CLI bridge for Easel ask and session commands; command-specific access, usage, and entitlements
- **Audience:** Portrait users and admins configuring a connected surface
- **Surfaces:** cli
- **Availability:** Production; account-gated
- **Content type:** how-to
- **Last reviewed:** 2026-09-08

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](https://portraitresearch.com/documentation/integrations/portrait-cli-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](https://portraitresearch.com/documentation/more/contact-us/) 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.

```sh
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](#if-a-command-fails).

## Sign in

```sh
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

```sh
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:

```sh
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:

```sh
portrait ask --stream 'Compare the latest earnings commentary for AAPL and MSFT.'
```

## Manage sessions

```sh
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](https://portraitresearch.com/documentation/monitors/monitors/), 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):

```sh
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.

```sh
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](https://portraitresearch.com/documentation/integrations/portrait-cli-reference/).

A [screen](https://portraitresearch.com/documentation/screening/introduction-to-screening/) 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):

```sh
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:

```sh
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](https://portraitresearch.com/documentation/documents/my-library/) from the command line:

```sh
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`:

```sh
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:

```sh
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:

```sh
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](https://portraitresearch.com/documentation/integrations/portrait-cli-reference/) includes complete envelopes and error handling.

## Verify the installation

1. Run `portrait auth status` and confirm that this CLI installation is signed in. The command does not identify the user; if account identity matters, run `portrait auth logout`, sign in again, and verify the intended account in the browser flow.
2. Send a low-risk `portrait ask` request with standard output redirected to a file or pipe.
3. Confirm that stdout contains valid JSON and that the response includes a session ID.
4. 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](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally/), 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](https://portraitresearch.com/documentation/integrations/portrait-cli-reference/#authentication-errors); do not assume the credential was fully revoked |

The executable-location rules are documented in [npm's folder reference](https://docs.npmjs.com/files/folders/).


- Run `portrait --help` to 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 history` before repeating it. Another `ask` invocation 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](https://portraitresearch.com/documentation/more/contact-us/).