CLI Reference#

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

Upgrading from a release before targets were introduced changes how a project directory names its destination. For what to change, see the Upgrading from an Earlier Version section on the Installation 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

init, lint, tree, deploy and export take the project directory as an optional 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. No other command takes a path. 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. That pair is an environment, and a target holds one or more of them. A destination can be named by target, by target and environment, or given field by field.

A target registered with a single destination has one environment, default. It is not shown, and you never pass --env for it. Adding a second environment makes both visible.

A destination resolves whole, from one source. The highest source that names one completely wins, and lower sources add nothing to it — not a field, not a fallback. --cluster and --project are one source together, never two:

Order

Source

Names a destination by

1

--target NAME

Name. A name registered on this machine for one cluster and one project together, for example squirro-demo. See genai target.

2

--cluster URL and --project ID

Value. Both, or neither — --cluster alone is an error, and so is either alongside --target.

3

GENAI_TARGET

Name.

4

GENAI_CLUSTER and GENAI_PROJECT

Value. Where only one is set the whole level is skipped, with a note; an exported variable from hours ago must not block a command.

5

the directory’s pointer, .genai/target

Name.

--env NAME selects among the resolved target’s environments. It is an argument to whatever the table produced, not a source of its own, so it composes with rows 1, 3 and 5 alike. A literal --cluster/--project pair has no target to reach into, so naming --env beside one is an error. An environment the target does not hold is an error listing the ones it does; it never falls back to the default.

genai login, genai logout and genai whoami act on a cluster, not a destination, and resolve by a shorter ladder: --cluster or --target, then GENAI_CLUSTER or GENAI_TARGET, then the pointer. GENAI_CLUSTER alone is enough for these, and they never take --project.

genai lint and genai tree run offline and accept neither. genai init selects a registered target rather than resolving one: it takes --target NAME, or on a terminal offers the registered targets, and reads GENAI_TARGET only as the default it offers. The genai target subcommands manage the registry and take a target name, except that genai target show without one reports what the table resolves. The genai skill subcommands work on local files and need no destination. Every other command, including every genai task subcommand, resolves a destination by the table above.

To stay readable, the command synopses further down omit the target flags. Add --target, or --cluster and --project together, to any command that accepts them.

Each source in the table, in the same order:

# 1. A registered target, named on the command line
genai deploy --target squirro-demo

# 2. The cluster and project, named on the command line
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

# 3 and 4. Environment variables, which suit continuous integration
export GENAI_TARGET=squirro-demo
genai deploy

export GENAI_CLUSTER=https://squirro-demo.example.com
export GENAI_PROJECT=Xy7QmN2pR9sT4vWz1AbC3D
genai deploy

# 5. The directory's own pointer. genai init writes it once,
# so later commands need no flags and no variables.
genai target add squirro-demo --cluster https://squirro-demo.example.com --project Xy7QmN2pR9sT4vWz1AbC3D
genai init --target squirro-demo
genai deploy

A command that cannot resolve a target exits with an error naming what to supply. Pointing a directory at one is genai init or genai target use.

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 to act with. It takes precedence over the cached credential for that run and is never written to the cache, so a rotated key takes effect immediately rather than losing to one cached earlier. For how to generate one, see the Authentication page.

GENAI_TARGET

Names a target registered on this machine.

GENAI_TARGETS_PATH

Overrides the registry location (default ~/.config/genai/targets.yaml).

GENAI_CREDENTIALS_PATH

Overrides the location of the credentials cache.

genai init#

genai init [ROOT] [--target NAME] [--api-key TOKEN | --api-key-stdin] [--no-skill]

Takes a checkout from a fresh clone to ready to deploy. It settles which deploy target this directory uses, points the directory at it, and authenticates you to the cluster.

--target NAME points the directory at a target already registered on this machine. A name it does not hold is registered first, by the same questions genai target add asks. To register one ahead of time, see genai target.

On a terminal with nothing supplied, genai init offers the registered targets, each row carrying that target’s cluster and project, with the one this directory points at marked. Typing part of a name narrows the list; typing a name it does not hold registers a new target. Registering one asks for the cluster, logs you in, then offers the projects on that cluster where you hold project administrator, by title and ID. Projects withheld for lack of that role are counted in a note.

A project ID the offer does not hold is refused, and the refusal says which of three things it is: you are not an administrator on that project, it is of a kind that is never a configuration target, or the account you are signed in as cannot see it at all. Each has a different answer, and the message names genai target add for the case where you know the ID and it is the offer that is wrong.

A choice made by number is read back before anything is written. A name or an ID typed in full is not.

The offers need a terminal. Without one, genai init enumerates nothing and asks nothing: pass --target NAME, naming a target the registry already holds. Register it first with genai target add NAME --cluster URL --project ID, which asks nothing either. There is no machine-readable project listing, so a caller without a terminal takes a project ID from the web client, from the project’s URL or its settings page, or from an entry genai target list already holds.

The offers and the questions around them are written to standard error. A setup run stopped before you choose registers nothing and writes no local configuration.

The command is idempotent. --no-skill skips installing the CLI’s own documentation as an agent skill in the directory.

genai target#

genai target add    [NAME] [--cluster URL --project ID] [--env ENV] [--principal IDENTITY] [--meta K=V]
genai target set    NAME [--env ENV] [--cluster URL --project ID] [--principal IDENTITY] [--default-env ENV] [--meta K=V]
genai target remove NAME [--env ENV]
genai target list   [--verify]
genai target use    [NAME]
genai target show   [NAME] [--env ENV]

Manages the deploy targets registered on this machine. A target is a name for a system; each of its environments names one cluster and one Squirro project together, so that a directory, a command, or an environment variable can name a destination once instead of repeating a URL and an ID.

add registers a destination — creating the target when the name is new and adding an environment when it is not; the output says which. Given --cluster and --project it contacts no cluster and asks nothing, which makes it the form to use from a script and the one a refused project ID points you at. Run bare on a terminal it asks for the cluster and then offers the projects you can deploy to on it. Registering the same address again is a no-op, so a provisioning script can run twice. A name is content that ends up in checkouts and in transcripts, so avoid customer names in it.

set changes what a destination already holds. Because the address decides which credential resolves, changing it reports the credential the destination now points at, or says that none is held.

remove drops an environment, or the whole target when --env is omitted; removing a target’s last environment removes the target. A removal that would leave a held credential reachable only by retyping its URL is refused, naming the genai logout that clears the way. Otherwise credentials are held per cluster, are not affected, and the command says so.

list prints two sections: every destination, and every credential this machine holds, including credentials for clusters no environment names. --verify contacts each cluster and adds a liveness column. use NAME points the working directory at a registered target. Run bare on a terminal, use offers the registered targets and points the directory at the one you pick; a name it does not hold leaves the offer on screen. Without a terminal the missing NAME is an error, so a script always passes one.

show answers what this directory will deploy to, and why, without contacting a cluster:

genai target show --env prod
target:      squirro-demo                  (from .genai/target, --env prod)
environment: prod
cluster:     https://squirro-demo.example.com
project:     Xy7QmN2pR9sT4vWz1AbC3D
credentials: cached for this cluster (alice@example.com)

A directory’s pointer applies to that directory; otherwise a target is named per command or per shell.

genai skill#

genai skill install   [--to claude|agents | --path DIR] [--user] [--copy] [--force]
genai skill list      [--to claude|agents | --path DIR] [--user]
genai skill uninstall [--to claude|agents | --path DIR] [--user]

Places the CLI’s own documentation where an AI coding agent reads it. The installed package carries that documentation as a skill, so these verbs copy or link it into a skills directory. Nothing else the CLI does depends on whether it is installed.

install writes into .claude/skills/ in the working directory by default, .agents/skills/ with --to agents, or any directory with --path. --user targets the personal directory under your home instead, which is explicit on purpose: a personal skill shadows the project one in every workspace on the machine, including workspaces pinned to another release.

The default is a relative symbolic link into the installed package, so upgrading the package upgrades what the agent reads and nothing needs re-running. Where a link cannot hold, the CLI copies instead and says so; --copy asks for a copy outright. A copy records the version it was made from and tells its reader to compare that against genai --version. Running install again is harmless: it leaves a current installation alone, repairs a link whose target moved, and upgrades a copy from an older release. It refuses a directory it did not write, unless --force.

list reports what is installed in each destination, at which version and in which mode. It covers both the project and personal scopes by default, because a personal skill takes precedence over a project one, and a report of the project scope alone could show a healthy installation that nothing actually reads. uninstall removes only what install wrote.

genai init runs the default install as part of setting up a directory; --no-skill skips it.

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. The command still succeeds, so a continuous integration step that gates on the exit code passes with warnings present. Treat them as review material rather than a broken build.

Run genai lint to see the advisories that apply to your own project.

Only genai lint runs these checks. genai deploy runs the deploy-blocking checks and stops there, so a deploy never surfaces an advisory.

Lint while you author. It catches the structural mistakes locally: a duplicate identifier, an extends cycle, a pin that contradicts a constraint, a delegate with no description, a missing inference.model. It needs no cluster and no credentials, so it runs offline and fits a pull-request check that holds no deploy secrets. genai deploy replaces the live configuration of the Squirro project, so use genai deploy --dry-run to test against a cluster before shipping.

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 [--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

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 [--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.

Note

Like the other server-bound commands, genai explain sends a request to the cluster it resolves, in this case to a resolve route. The receipt carries the project’s deployed configuration as well as the value you asked about, so the command requires administration of the project rather than read access to it. 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 [--cluster URL | --target NAME [--env ENV]] [--api-key TOKEN | --api-key-stdin | --use-device-code]

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.

To supply a refresh token yourself, pass it with --api-key, preferably from a variable such as --api-key "$GENAI_KEY" so the token stays out of your shell history. --api-key-stdin reads it from standard input instead, for example genai login --api-key-stdin < key.txt, which also keeps it out of process listings. On a terminal, --api-key-stdin asks for the token without echoing it, which is the way to paste one by hand. Either way, the token is verified and then stored.

genai login does not read GENAI_API_KEY. Every other command authenticates from that variable directly, so a continuous integration job that sets it needs no login step.

Without a token, on a terminal, genai login starts a device login on a cluster that has it turned on. A server administrator turns it on as described in the Turning On Device Login section. The CLI prints a link to an approval page on the cluster and a short code, opens your browser there if it can, and waits while you approve:

genai login --cluster https://squirro-demo.example.com
Not logged in to https://squirro-demo.example.com — let's authenticate.
Approve this login in your browser:

  https://squirro-demo.example.com/device?user_code=PDWK-MWDK

  Check the page shows this code:  PDWK-MWDK

Waiting for approval (expires in 15 min; Ctrl-C to cancel) ...
logged in to https://squirro-demo.example.com as alice@example.com

Before you approve, check that the page shows the code the CLI printed. You can approve it in any browser already signed in to the cluster, on any machine, so on such a cluster the login works over SSH and inside containers with nothing to copy back. Denying the code, or letting it expire, ends the login.

On a cluster without device login, which is the default, genai login says so, opens the web interface, and asks for a refresh token to be pasted:

genai login --cluster https://squirro-demo.example.com
Not logged in to https://squirro-demo.example.com — let's authenticate.
This cluster doesn't support device login; falling back to manual token entry.
Mint a refresh token (API key) on the Squirro cluster:
  https://squirro-demo.example.com/app/
  (profile picture -> My Account -> API Access -> User Token)

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

The token is not echoed as you paste it.

Without a terminal, genai login exits with an error at once rather than waiting for an approval that nobody may give. The error names both ways forward. To run the device login anyway on a cluster that has it turned on, for example in a container started without -t or from an AI agent that passes the approval URL on to you, add --use-device-code. To log in with a token instead, use --api-key-stdin. --use-device-code cannot be combined with --api-key or --api-key-stdin.

login writes its progress to standard error; the line naming who you are logged in as goes to standard output.

The token is exchanged once to confirm it works and to read your identity from it, and only then cached. genai whoami and genai target show report the account’s address. For how to mint a token by hand, see the Authentication page.

genai logout#

genai logout [--cluster URL | --target NAME [--env ENV]] [--principal IDENTITY] [-y / --yes]

Forgets the cached credentials for one cluster, and says whose:

genai logout --cluster https://squirro-demo.example.com
logged out of https://squirro-demo.example.com (alice@example.com)

A cluster can hold more than one credential, one for each identity you logged in as. Without --principal, genai logout forgets all of them. --principal IDENTITY forgets only the one for that identity.

Credentials are shared by every project directory on this machine that uses the cluster. So when the cluster comes from the directory’s pointer rather than from the command line, genai logout asks for confirmation first. -y or --yes skips the question. Without a terminal there is no one to ask, so the command refuses unless --yes or --cluster is given.

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 [--cluster URL | --target NAME [--env ENV]] [--principal IDENTITY]

Prints the identity cached for the cluster. Where the cluster holds credentials for more than one identity, name the one to report with --principal IDENTITY; without it, the command exits with an error listing them.

genai whoami
alice@example.com

Without --cluster, the cluster resolves as it does everywhere else: --target, then GENAI_CLUSTER or GENAI_TARGET, then the directory’s pointer dereferenced through the registry. So running the command inside an agent configuration project tells you who you are on that project’s cluster, with no flag needed. genai whoami never prompts — where nothing names a cluster it exits with an error naming what to supply. (genai login may prompt, because obtaining a credential is a setup act.)

Exit codes:

  • 0: a login is held.

  • 1: nothing is held, nothing names a cluster, or GENAI_API_KEY is set and the cluster refused it.

  • 2: a usage error. It never says anything about credentials.

  • 3: something is held that needs an action first, such as a token minted but never verified.

genai whoami reads the cache and makes no request, so 0 does not promise the cluster would still accept the credential; a token revoked since still exits 0. With GENAI_API_KEY set, the key itself is exchanged and its identity reported.