CLI Reference#

Warning

Project Neo is currently in Technical Preview. Features described in this section may change before general availability.

The genai command line interface authors, validates, and deploys agent configuration. It is a thin client over the HTTP API, so it never writes to a cluster directly. For the HTTP surface it wraps, see the API Reference page.

This page is the flag-by-flag reference for the commands. If you have not set the tool up yet, start elsewhere: for what to have in place first, such as a target Squirro project and a refresh token, see the Prerequisites page. To install the tool, see the Installation page. To author and deploy a first configuration end to end, see the Quick Start page.

Note

The genai CLI configures GenAI agents. It is a separate tool from the neo-ui CLI, which builds frontend dashboard extensions. For the neo-ui CLI, see the Developer Guide page.

Running It#

genai --help

Every command takes an optional directory as its first argument, written as ROOT in the synopses below. It is the directory holding your agent configuration project, the files you author, validate, and deploy, and it defaults to the current directory. Omit it when you run from inside the project, or pass a path to point a command somewhere else on disk:

genai lint                                  # the current directory
genai lint ./my-agent-configuration-project # a project elsewhere

The --help output of the tool calls this argument the workspace root, which is the same thing.

Commands that talk to a server also resolve a target cluster URL and a Squirro project ID, described in the next section.

Target Resolution#

Each agent configuration project deploys to exactly one cluster and one Squirro project. Both fields resolve independently for each command.

Field

Precedence

Meaning

cluster

--cluster flag, then GENAI_CLUSTER, then local target configuration

The upstream server URL, for example https://squirro-demo.example.com.

project

--project flag, then GENAI_PROJECT, then local target configuration

The Squirro project ID, for example Xy7QmN2pR9sT4vWz1AbC3D.

Not every command needs both fields. genai lint and genai tree run offline and accept neither. genai login, genai logout, and genai whoami act on a cluster and accept --cluster only. Every other command, including every genai task subcommand, accepts both.

To stay readable, the command synopses further down omit both flags. The exception is genai init, where supplying them is the point of the command. Add either flag to any command that accepts it.

The three ways to supply them, in precedence order:

# Flags win over everything else
genai deploy    --cluster https://squirro-demo.example.com --project Xy7QmN2pR9sT4vWz1AbC3D
genai status    --cluster https://squirro-demo.example.com --project Xy7QmN2pR9sT4vWz1AbC3D
genai task list --cluster https://squirro-demo.example.com --project Xy7QmN2pR9sT4vWz1AbC3D
genai whoami    --cluster https://squirro-demo.example.com

# Environment variables come next, which suits continuous integration
export GENAI_CLUSTER=https://squirro-demo.example.com
export GENAI_PROJECT=Xy7QmN2pR9sT4vWz1AbC3D
genai deploy

# The local target configuration comes last. genai init writes it once,
# so later commands need no flags and no variables.
genai init --cluster https://squirro-demo.example.com --project Xy7QmN2pR9sT4vWz1AbC3D
genai deploy

On a terminal, a command that cannot resolve the cluster or the project prompts for the missing field and saves the answer. Run non-interactively, it exits with an error naming the missing field.

Commands that talk to a server also need credentials. When none are cached for the cluster, they authenticate on demand: interactively on a terminal, or using GENAI_API_KEY when it is set.

The environment variables that control this behavior:

Variable

Purpose

GENAI_CLUSTER and GENAI_PROJECT

The target fields, following the precedence above.

GENAI_API_KEY

The refresh token, for non-interactive login. It takes the same refresh token as the --api-key flag on genai login. For how to generate one, see the Authentication page.

GENAI_NO_INPUT

Turns off all prompts, so a missing input becomes an error. Useful for continuous integration.

GENAI_CREDENTIALS_PATH

Overrides the location of the credentials cache.

genai init#

genai init [ROOT] [--cluster URL] [--project ID] [--api-key TOKEN]

Takes a checkout from a fresh clone to ready to deploy. It resolves the cluster and the project, prepares the local target configuration, and authenticates you to the cluster.

The command is idempotent and runs without prompting when every input is supplied through a flag or an environment variable, which is the continuous integration path.

genai lint#

genai lint [ROOT]

Runs every offline check against the agent configuration project, with no network and no credentials. On success it prints OK (N resources, M agents). On failure it prints one line per failure and exits with an error.

genai lint is sufficient as a pre-merge check. The cluster-bound checks, such as credential handle resolvability and publish policy, run at deploy time. Among the offline checks is whether a constrains block admits any value at all, so a contradictory predicate is caught here. For the predicate syntax itself, see the Predicate Syntax for Constraints section.

Advisory Warnings#

Once the checks that can fail a deploy have passed, genai lint runs a second set of checks that only advise. These report a configuration that deploys and runs but is probably not what you meant. Each one prints a line naming its severity, the resource, and what it found, and then the command reports OK and exits successfully:

genai lint
warning: Project.prefers: 2 KnowledgeGraph resources authored (company_kg, product_kg) but no source.knowledge_graph.default is set; the kg tool will reject any call that omits kg_id.
warning: Agent.deep_research: entrypoint_order 0 is shared by deep_research, assistant; the (entrypoint_order, id) tiebreaker decides their picker order. Set distinct values to make the order explicit.
OK (4 resources, 2 agents)

Read them as suggestions rather than errors. The severity is warning for something worth changing and info for something worth knowing. Because the command still succeeds, a continuous integration step that gates on the exit code passes with warnings present, which is intended: treat them as review material rather than a broken build.

The current advisories cover a model flagged as supporting reasoning or image input where the platform knows that model does not, a project publishing several knowledge graphs without naming a default, two entrypoint agents sharing an entrypoint_order, an agent carrying entrypoint-only fields that a delegate call ignores, a context budget whose truncation target sits above the real model window, and legacy document-source tuning fields set under the split engine.

Only genai lint runs these checks. genai deploy runs the deploy-blocking checks and stops there, so a deploy never surfaces an advisory. That is another reason to lint while you author rather than relying on the deploy to tell you everything.

Lint rather than deploy while you author. Almost every authoring mistake is structural, and lint catches those locally: a duplicate identifier, an extends cycle, a pin that contradicts a constraint, a delegate with no description, a missing inference.model. Because it needs no cluster and no credentials, it runs on a laptop offline and fits a pull-request check that has no deploy secrets. A plain genai deploy is not a test but a replacement, since it switches the live configuration of the Squirro project. genai deploy --dry-run is the safe way to test against a cluster, and it is the right gate before shipping, but it still needs a reachable cluster, a resolved project, and a valid token, so it is a slower loop to author against. Deploy also runs the offline checks first and stops before contacting the cluster if any fail, so it finds the same structural problems as lint, only later.

genai deploy#

genai deploy [ROOT] [--dry-run] [--label KEY=VALUE]...

Publishes the agent configuration project as a full configuration snapshot. It first runs the offline checks, and if they fail it exits without contacting the cluster. On clean checks it posts the files to the cluster and prints the deploy receipt.

genai deploy --label git.hash=abc1234
deployed: sha256:4a3f...
at:       2026-05-14T11:32:00+00:00
labels:   git.hash=abc1234

--dry-run runs every check the server would run, including the cluster-bound checks, without writing the snapshot. It prints dry-run OK instead of a receipt, and the previous snapshot is untouched.

--label KEY=VALUE attaches an arbitrary string label to the deploy receipt. It is repeatable. Empty keys are rejected, and a duplicate key takes the last value.

When the content you deploy is identical to what is already live, genai deploy still contacts the cluster and prints the usual receipt, followed by a hint that nothing changed. Running the deploy above a second time, with no edits in between:

genai deploy --label git.hash=abc1234
deployed: sha256:4a3f...
at:       2026-05-14T11:41:07+00:00
labels:   git.hash=abc1234
Hint: no change — content is identical to the current deployment.

The receipt still records a fresh timestamp and your labels, because the deploy did happen. Only the content hash is unchanged, which is what the hint is comparing.

Roll Back a Deployment#

There is no rollback command, and the cluster does not let you redeploy a past snapshot: genai log returns each past deploy receipt, not its resources, and genai export only writes the current snapshot. Because every deploy is a full-snapshot replacement, you roll back by redeploying a previous version of your configuration files.

Keep your agent configuration project in version control so a previous configuration is always recoverable. To revert, check out the last known-good version and deploy it again:

git checkout <prior-commit>
genai deploy --dry-run
genai deploy --label reason=rollback --label git.hash=$(git rev-parse HEAD)

The --dry-run deploy is optional: it runs the full set of cluster checks against the version you are about to restore without writing it, so you can confirm the rollback passes before it replaces the live snapshot.

Attaching a git.hash label to every deploy lets you match a deploy receipt from genai log back to the commit that produced it, so the previous good version is easy to find.

The two hashes in a receipt are unrelated. The hash field is computed by the server over the deployed content, and identical content always produces the same value. A git.hash label is a string you supply, and the key name is only a convention used in these examples: the platform stores labels without interpreting them, so commit or build would work just as well. The commit is recoverable from a receipt only because you chose to record it there.

genai reseed#

genai reseed [ROOT] [--force]

Returns a Squirro project to the starter configuration the platform ships with it, rebuilding the snapshot from that starter and deploying it. Use it to discard experiments and start over from a known baseline, or to pick up an updated starter after a platform upgrade. It prints the same fields as genai status:

genai reseed
project:  Xy7QmN2pR9sT4vWz1AbC3D
cluster:  https://squirro-demo.example.com
deployed: 2026-05-14T14:08:52+00:00
by:       alice@example.com
hash:     sha256:7b02...

That starter configuration is called the bundled workspace. The platform ships it with the project, and it is separate from the agent configuration project you author. Not every project has one, and genai reseed works only for a project that does.

Whether a reseed is allowed depends on how the project got its current configuration:

Current state

What genai reseed does

Never deployed

Seeds the project from its bundled workspace. No override needed. The platform also does this on its own the first time the project is used, so a project works before anyone authors a configuration for it.

Deployed by an operator

Refuses, so a reseed cannot silently discard authored work. Pass --force to override.

The first genai deploy moves a project from the first state to the second, and the platform never reverts it to the bundled workspace on its own after that: your deployed configuration is authoritative.

Warning

Passing --force replaces the current operator-deployed configuration with the bundled workspace. The replaced configuration is not recoverable from the server. Restore it from version control if needed, as described in the Roll Back a Deployment section.

genai tree#

genai tree [ROOT]

Prints the expanded resource tree: every envelope grouped by kind, after symlinks are followed and multi-document files are split. The same files always produce the same output, so you can commit a copy of it and have continuous integration compare later runs against that copy, which surfaces a resource added or removed by accident.

genai tree
Project (1):
  project.yaml
Model (3):
  models/claude-sonnet.yaml
  models/claude-haiku.yaml
  models/gpt-5.yaml
Agent (2):
  agents/assistant.md
  agents/deep_research.md

genai status#

genai status [ROOT]

Prints the deploy metadata for the current snapshot of the configured project: when it deployed, who deployed it, the content hash, and any labels attached.

project:  Xy7QmN2pR9sT4vWz1AbC3D
cluster:  https://squirro-demo.example.com
deployed: 2026-05-14T11:32:00+00:00
by:       alice@example.com
hash:     sha256:4a3f...
labels:   git.hash=abc1234  ci.build=42

genai log#

genai log [ROOT] [--since ISO] [--until ISO] [--actor IDENTITY]

Prints the deploy history for the configured project, most recent first. The flags filter on the server side, and all are optional and combinable.

2026-05-14T11:32:00+00:00  alice@example.com  sha256:4a3f...  git.hash=abc1234
2026-05-13T09:15:44+00:00  bob@example.com    sha256:9c12...  git.hash=def5678

genai explain#

genai explain PATH --agent AGENT_ID [--user-prefs FILE] [--request-prefs FILE]

Prints how a setting resolves for an agent: the winning value, the source, and one entry for each source the resolver examined, along with its state. The states are selected, shadowed, and invalid.

Warning

Like the other server-bound commands, genai explain sends a request to the cluster it resolves, in this case to a resolve route. That route is a development-time facility that a deployed Squirro instance does not serve, so during Technical Preview the command works only against a local run of the configuration library’s development application. Pointed at a Squirro instance it fails with a 404. To reason about resolution in the meantime, read the prefers, constrains, and pins blocks of your Project resource and of the agent. For the route, see the API Reference page.

genai explain inference.model --agent deep_research --user-prefs prefs.yaml
path:     inference.model
value:    claude-sonnet
source:   agent.prefers
preferences:
  - slot: user             value: claude-haiku   state: invalid
  - slot: agent.prefers    value: claude-sonnet  state: selected
  - slot: project.prefers  value: claude-opus    state: shadowed

Read the entries as the resolver walked them, from the request down to the project: request, user, agent.prefers, project.prefers. Here the user asked for a model the agent does not allow, so that entry is invalid and the walk continued. The agent value was allowed, so it is selected. The project value never came into play, so it is shadowed.

--user-prefs and --request-prefs accept a file containing a flat map of preference paths to values. Both are optional, and they let you try out a value a user or a single request would contribute without deploying anything. A file named .json is read as JSON, and any other name is read as YAML.

A prefs.yaml holding three user preferences:

inference.model: claude-haiku
trait.style: concise
source.documents.limit: 50

The same three as prefs.json:

{
  "inference.model": "claude-haiku",
  "trait.style": "concise",
  "source.documents.limit": 50
}

Passing both files adds a request and a user entry to the receipt. Here the request asks for a model the agent allows, so it wins outright, and every lower slot is either shadowed or, in the case of the user value the agent does not allow, invalid:

genai explain inference.model --agent deep_research \
    --user-prefs prefs.yaml --request-prefs request-prefs.json
path:     inference.model
value:    claude-sonnet
source:   request
preferences:
  - slot: request          value: claude-sonnet  state: selected
  - slot: user             value: claude-haiku   state: invalid
  - slot: agent.prefers    value: claude-sonnet  state: shadowed
  - slot: project.prefers  value: claude-opus    state: shadowed

Each key is a whole path written as one string, dots included, exactly as it appears on the Preference Reference page. Do not nest the file the way YAML would let you:

# Wrong: nothing here is contributed
inference:
  model: claude-haiku

That file has a single key named inference, whose value happens to be a map. The resolver looks for a key named inference.model and finds none, so the file contributes nothing. Nothing is reported either, because the file is still a valid mapping. The symptom is a result that looks normal with your value missing from it: no user or request entry appears in the receipt.

genai export#

genai export [ROOT] [-y / --yes]

Writes the currently deployed snapshot of the project to disk, one file per resource, in the layout the tool always uses: the three single-instance kinds as project.yaml, document-source-settings.yaml, and code-interpreter.yaml at the root, and the rest grouped into models/, agents/, mcp/, knowledge-graphs/, and trait-dimensions/.

The result is what the platform runs, not the files you wrote. Exporting a project you authored yourself gives you back your resources, but your original directory names, your ordering inside each file, and your comments are gone, because the deployed configuration does not carry them. Treat genai export as a way to read a deployed configuration, not as a round trip with genai deploy.

If the root already contains authored files, genai export prompts before overwriting. -y or --yes skips the prompt.

Note

genai export always exports the current snapshot. There is no command to export a past snapshot. Use genai log to inspect a past deploy’s receipt, which carries its content hash, actor, timestamp, and labels, but not its resources.

genai task#

Unlike the commands above, genai task is a group of subcommands, one per subsection below. A task is an instruction owned by the user who created it, plus a schedule that runs it against the configured project. Run genai task --help to see the whole group at once.

For the concepts behind tasks and the --hitl approval model, with worked examples, see the Scheduled Tasks and Approvals page.

genai task create#

genai task create --name NAME --instruction TEXT
                  --recurrence {daily|weekly|monthly|annually} --time HH:MM --timezone ZONE
                  [--days-of-week 0,1,...] [--day-of-month N] [--month-of-year N]
                  [--enabled/--disabled] [--auto-fire/--no-auto-fire]
                  [--email-notifications] [--hitl PATTERN=DISPOSITION]...

Defines a task. --name and --instruction say what it is and what it does, and the recurrence flags say when it runs. On success it prints the new task in the same shape as genai task get, so you get its identifier back straight away.

--time is a time of day, written as HH:MM or HH:MM:SS, and it is read in the --timezone, which takes an IANA name such as Europe/Zurich. Because the time is local, a task keeps firing at the same clock time across a daylight-saving change. On the day the clocks go back, when the time occurs twice, the task fires at the first of the two. On the day they go forward, when the time does not exist at all, it fires as soon as the clock passes it.

The recurrence decides which further flags you must supply:

--recurrence

Extra flags it needs

daily

None.

weekly

--days-of-week, comma-separated, where 0 is Sunday and 6 is Saturday.

monthly

--day-of-month, from 1 to 28.

annually

--day-of-month and --month-of-year.

The same four schedules as commands, with the instruction left out for brevity:

# Every day at 02:00
genai task create --name "Nightly digest" --instruction "..." \
    --recurrence daily --time 02:00 --timezone Europe/Zurich

# Every Monday and Thursday at 08:30
genai task create --name "Twice-weekly report" --instruction "..." \
    --recurrence weekly --days-of-week 1,4 --time 08:30 --timezone Europe/Zurich

# The first of every month at 06:00
genai task create --name "Monthly rollup" --instruction "..." \
    --recurrence monthly --day-of-month 1 --time 06:00 --timezone Europe/Zurich

# Every 31 January at 09:00
genai task create --name "Year-end review" --instruction "..." \
    --recurrence annually --day-of-month 31 --month-of-year 1 --time 09:00 --timezone Europe/Zurich

A task has two switches, and it fires on its schedule only when both are on. --enabled and --disabled are the master switch, so --disabled creates a task paused. --auto-fire and --no-auto-fire control the schedule alone.

--email-notifications emails the owner when a run completes. It is off unless you ask for it.

--hitl PATTERN=DISPOSITION is a repeatable rule matching tool names against a pattern. The last match wins, and a tool that needs approval but matches no rule is refused. The dispositions are approve, reject, interactive, defer, and hide.

genai task list#

genai task list [--limit N] [--offset N] [--sort FIELD] [--order asc|desc]

Lists the tasks you own in the configured project. Tasks belong to whoever created them, so this never shows another person’s tasks. Each row carries the task identifier, whether the task is enabled, its schedule, and its name:

genai task list
9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab  on   daily at 02:00 Europe/Zurich                Nightly digest
4b81d05c-93af-4e11-8c7a-2f6be0d1a934  on   weekly at 08:30 Europe/Zurich days=[1, 4]   Twice-weekly report  (auto-fire off)
c07e2f18-6a54-4d90-ba33-91f7c4e5b206  off  monthly at 06:00 Europe/Zurich dom=1        Monthly rollup
(3 total)

The identifier in the first column is the TASK_ID every other subcommand takes. A row ends with (auto-fire off) when the task itself is enabled but its schedule is not, so it runs only when you fire it with genai task run. When you own no tasks in the project, the command prints (no tasks).

--limit defaults to 50 and the server accepts at most 100. --offset skips that many rows, so --limit 50 --offset 50 gives you the second page.

--order defaults to desc, and --sort defaults to created_at. --sort accepts created_at, updated_at, name, next_execution_at, or last_execution_at, and any other field is rejected.

genai task get#

genai task get TASK_ID

Shows one task in full:

genai task get 9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab
id:            9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab
name:          Nightly digest
instruction:   Summarize the documents added today and email the summary to the team.
trigger:       daily at 02:00 Europe/Zurich
enabled:       True
auto-fire:     True
hitl:
  - delete_* → reject
  - send_email → defer
next run:      2026-05-15T02:00:00Z
last run:      2026-05-14T02:00:04Z succeeded
conversations: 12
created:       2026-05-01T09:14:22Z by alice@example.com

enabled and auto-fire are the two switches, so a task fires on its schedule only when both read True. The hitl block lists the approval rules in the order they are matched, and it is omitted entirely for a task that has none. next run reads (none) when nothing is scheduled, which is the case for a task with either switch off, and last run reads (none) until the task has run once.

genai task update#

genai task update TASK_ID [--name NAME] [--instruction TEXT]
                  [--recurrence {daily|weekly|monthly|annually}] [--time HH:MM] [--timezone ZONE]
                  [--days-of-week 0,1,...] [--day-of-month N] [--month-of-year N]
                  [--enabled/--disabled] [--auto-fire/--no-auto-fire]
                  [--email-notifications/--no-email-notifications] [--hitl PATTERN=DISPOSITION]...

Changes a task, as a partial patch: send only the flags you want to change. Two things are replaced as a whole rather than patched, the schedule and the approval rules, so passing any --hitl rule discards the rules the task had.

That distinction decides how you make two common changes:

  • Pause or resume

    Send --enabled or --disabled on its own. The schedule is untouched.

  • Toggle auto-firing

    Send --auto-fire or --no-auto-fire together with --recurrence, --time, and --timezone, because those flags rebuild the schedule the switch belongs to.

It prints the updated task, in the same shape as genai task get. Pausing the nightly digest, for example:

genai task update 9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab --disabled
id:            9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab
name:          Nightly digest
instruction:   Summarize the documents added today and email the summary to the team.
trigger:       daily at 02:00 Europe/Zurich
enabled:       False
auto-fire:     True
hitl:
  - delete_* → reject
  - send_email → defer
next run:      (none)
last run:      2026-05-14T02:00:04Z succeeded
conversations: 12
created:       2026-05-01T09:14:22Z by alice@example.com

enabled has flipped to False and next run has become (none), because a paused task has nothing scheduled. Everything else is untouched, which is what a partial patch means. Passing no field at all is an error rather than a silent no-op.

genai task delete#

genai task delete TASK_ID

Removes the task definition, so it never fires again, and confirms with a single line:

genai task delete 9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab
deleted task 9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab

The task itself is gone, but its run history and the conversations its runs produced survive, so the work a task did outlives the task.

genai task run#

genai task run TASK_ID

Fires the task immediately, as a manual run. This works whatever the two switches are set to, which is how you test a task before letting its schedule start it.

It prints the run it started:

genai task run 9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab
run:           35
task:          9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab
status:        running
trigger:       manual
conversation:  conv_44b9d1
started:       2026-05-14T16:22:09Z

The completed and error lines appear once the run has finished, and conversation reads (none) if the run failed before one existed.

genai task runs#

genai task runs TASK_ID [--limit N] [--offset N]

Lists a task’s runs, most recent first. Each row carries the run number, its status, what triggered it, when it started, and the conversation it produced:

genai task runs 9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab
34  succeeded         scheduled   2026-05-14T02:00:04Z  conv_8f21ac
33  failed            scheduled   2026-05-13T02:00:03Z  conv_5d09b7
32  running           manual      2026-05-12T16:41:55Z  conv_1a77e2
31  succeeded         scheduled   2026-05-12T02:00:06Z  conv_c3be40

The trigger is scheduled when the schedule fired the run and manual when you did, with genai task run or genai task retry. A run still in progress shows as running, and a run whose conversation ended without finishing shows as incomplete. The last column is empty for a run that failed before its conversation started.

The listing ends with a (N total) line, and a task that has never run prints (no runs). The number in the first column is the RUN_ID that genai task retry takes.

--limit defaults to 50 and the server accepts at most 100. --offset skips that many runs, so --limit 50 --offset 50 gives you the second page of the history.

genai task retry#

genai task retry TASK_ID RUN_ID [--conversation-id CID]

Re-runs a past run as a new run. RUN_ID is the number in the first column of genai task runs, not a task identifier. Only a run that failed or ended without finishing can be retried, and any other run is rejected.

Retrying run 33:

genai task retry 9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab 33
run:           36
task:          9f3c1a7e-4d2b-4c58-b1e0-6a7d5f2c88ab
status:        running
trigger:       manual
conversation:  conv_9e02fa
started:       2026-05-14T16:35:41Z

The retry is a new run with its own number, and run 33 stays in the history as it was, so genai task runs shows both. Its trigger is manual even though the run it replaces was scheduled, because you started it. By default it works in a fresh conversation, as here; pass --conversation-id to continue an existing one instead.

genai login#

genai login [ROOT] [--cluster URL] [--api-key TOKEN]

Authenticates you to a cluster and caches the credential, so later commands do not ask again. genai init does this as part of setup, which leaves genai login for re-authenticating after a token expires, or for adding a second cluster.

With --api-key, or with GENAI_API_KEY set, it uses the refresh token you supply. Without either, it points you at the settings page of the cluster, tries to open it in your browser, and waits for you to paste a token:

genai login --cluster https://squirro-demo.example.com
Not logged in to https://squirro-demo.example.com — let's authenticate.
Mint a refresh token (API key) on the Squirro cluster:
  https://squirro-demo.example.com/app/#settings

Paste refresh token:
logged in to https://squirro-demo.example.com as alice@example.com

The token is exchanged once to confirm it works and to read your identity from it, and only then cached. For how to mint one, see the Authentication page.

genai logout#

genai logout [ROOT] [--cluster URL]

Forgets the cached credential for one cluster, and says so:

genai logout --cluster https://squirro-demo.example.com
logged out of https://squirro-demo.example.com

Only the local cache is cleared. The refresh token itself stays valid on the cluster until you revoke it there, so logging out is not a way to withdraw access.

genai whoami#

genai whoami [ROOT] [--cluster URL]

Prints the identity cached for the cluster:

genai whoami
alice@example.com

Without --cluster, the cluster resolves as it does everywhere else: GENAI_CLUSTER if it is set, otherwise the local target configuration that genai init wrote. So running the command inside an agent configuration project tells you who you are on that project’s cluster, with no flag needed. If none of the three yields a cluster, the command asks for one on a terminal, and otherwise exits with an error naming what to supply.

With no cached credential for that cluster it prints not logged in and exits with an error, which makes it usable as a check in a script before a command that needs credentials.