Guides · 2 of 6
Set up the local CLI
setup init creates a private configuration and an Entity Runtime metadata store; setup check says whether this machine can hold credentials before anything asks for one, and an instance needs only a name.
Set up the local CLI
The grouped connectors commands (setup, instances, connections, operations, approvals)
and invoke work through a local owner process, a private TOML configuration and a metadata
store. This guide creates both in a scratch directory, checks the machine's prerequisites and
configures two instances. It needs no credential and contacts no provider. Run it from
target/try as in Getting started, after
cargo build --locked -p connectors -p connectors-catalog-provider. The words connector, instance
and connection are those of the README vocabulary.
Create the configuration and the store
--config and --state-dir select where they live. Without --config the CLI uses
$XDG_CONFIG_HOME/connectors/config.toml, or ~/.config/connectors/config.toml when
XDG_CONFIG_HOME is unset or empty; without --state-dir it uses $XDG_STATE_HOME/connectors,
or ~/.local/state/connectors when XDG_STATE_HOME is unset or empty. Both must be absolute
paths:
../debug/connectors --config cli/config.toml --state-dir cli/state --output json setup init
{"error":{"code":"failure","data":{"code":"invalid_configuration","configuration_rule":"config_path_unresolved","kind":"usage","next_action":"check_configuration","stage":"configuration"}},"ok":false}
With absolute paths it creates them. The answer also names both paths; jq keeps the rest:
../debug/connectors --config "$PWD/cli/config.toml" --state-dir "$PWD/cli/state" \
--output json setup init | jq '{ok, disposition: .result.disposition, os: .result.os}'
{
"ok": true,
"disposition": "created",
"os": "linux"
}
The configuration names its owner and no credential. The state directory holds the metadata
store: Entity Runtime records over an Eventlog SQLite database. setup init creates it with
durable open checkpoints, which shorten the provider's own open; every fresh open still verifies
the whole store, so an edit that bypasses SQLite is refused by the next command.
Check the prerequisites
../debug/connectors --config "$PWD/cli/config.toml" --state-dir "$PWD/cli/state" \
--output json setup check \
| jq '{ok, keyring: .result.keyring, custody_backend: .result.custody_backend, prerequisites: [.result.prerequisites[] | "\(.name): \(.state)"]}'
{
"ok": true,
"keyring": "available",
"custody_backend": "secret_service",
"prerequisites": [
"configuration: ready",
"metadata: ready",
"installed_provider: ready",
"keyring: ready",
"persistent_custody_qualification: ready"
]
}
setup check authenticates nowhere. keyring is the Linux Secret Service that will hold saved
credentials; a machine without one cannot save a connection, and the check says so before any
connect asks for a credential. custody_backend names the store custody uses, fixed when the
binary is built: secret_service on Linux, macos_keychain on macOS and
windows_credential_manager on Windows. installed_provider is the connectors-catalog-provider
in the directory of connectors, which derives every instance that pins nothing: it is ready
when it prints its version, and otherwise failed with installed_provider_failure naming why
(missing, not_executable, killed, exited, deadline or unreadable_output).
A headless host or a container has no Secret Service. There, a connectors-local/4 or
connectors-local/5 configuration can select the read-only mounted_secret binding with a
[custody] table: it
reads each credential from a read-only mounted directory, such as a Kubernetes Secret volume,
and writes nothing. It is never chosen on its own; without the table an unusable Secret Service
still refuses with custody_unavailable. Under it setup check reports
custody_backend: mounted_secret and a mounted_custody prerequisite.
Mounted-secret custody
describes the binding and its threat model, and
running in a container
the setup, with a CronJob example.
Configure instances
setup init writes a connectors-local/5 file with an empty legacy table for pinned entries.
Replace its content with a configuration that enables the
connectors you use and names an instance of each:
cat > cli/config.toml <<EOF
format = "connectors-local/5"
owner_uid = $(id -u)
default_instance = "gitlab/work"
[connectors.gmail]
enabled = true
[connectors.gmail.instances.private]
description = "personal mailbox"
[connectors.gitlab]
enabled = true
[connectors.gitlab.instances.work]
endpoint = "https://gitlab.example.com"
EOF
gmail/private uses Gmail's default endpoint; gitlab/work names a self-hosted GitLab host.
Neither pins anything else: connectors derives each instance from the
connectors-catalog-provider in its own directory and the connector declaration shipped with it,
which give the executable and its SHA-256, the configuration revision and every operation. A
connector that declares more than one auth profile (Zendesk) also takes profile on the
instance. default_instance selects an instance for a command that names none; invoke always
names its connector and does not read it.
../debug/connectors --config "$PWD/cli/config.toml" --state-dir "$PWD/cli/state" instances list \
| jq -c '.[] | arrays | .[]'
{"connector":"gitlab","instance":"work","instance_id":"gitlab.work","restart":"never","startup":"on-demand"}
{"connector":"gmail","instance":"private","instance_id":"gmail.private","restart":"never","startup":"on-demand"}
Each item names its instance by connector and instance only. A command that acts on one instance
names it with --connector and --instance; no connection exists yet:
../debug/connectors --config "$PWD/cli/config.toml" --state-dir "$PWD/cli/state" \
connections list --connector gitlab --instance work
{"connections":[],"observed_at_ms":1793314648161,"source":"authority","stale":false,"valid_until_ms":1793314948161}
Invoke an operation
invoke takes the connector and, after a /, the instance, then the operation. The instance may
be left out while the connector has only one, and the input is read from standard input when no
input flag is given. The connection, schema and revision default to the instance's own; without a
connection the call is refused before anything is started:
printf '{"id":"group/project","merge_request_iid":9}\n' \
| ../debug/connectors --config "$PWD/cli/config.toml" --state-dir "$PWD/cli/state" \
--output json invoke gitlab merge_request.get
{"error":{"code":"failure","data":{"code":"connection_unresolved","connections":[],"kind":"operational","next_action":"create_connection","stage":"admission"}},"ok":false}
connections lists each connection the instance holds, with its state, and is empty here. A
connector with two instances, named without one, is refused as instance_ambiguous, listing
each as <connector>/<instance>. --describe prints an operation's input schema, its effect
(read or write), schema and revision without calling the provider, once the instance holds a
connection.
setup init never overwrites:
../debug/connectors --config "$PWD/cli/config.toml" --state-dir "$PWD/cli/state" \
--output json setup init
{"error":{"code":"failure","data":{"code":"configuration_exists","kind":"operational","next_action":"check_configuration","stage":"configuration"}},"ok":false}
A configuration with legacy pinned entries keeps working, and setup migrate moves them to
instances; the README
shows it.
After an upgrade
An owner the previous binary started keeps running after an upgrade, and commands that need it
are refused as owner_build_mismatch; Upgrade the CLI
shows how setup owner-stop retires it, and what a state directory admits across versions.
Next
connections connect --connector <connector> --instance <instance> --profile <profile>
validates a credential through the named auth profile (gitlab.pat for GitLab; each connector's
page names its profiles) and saves it in custody; each instance holds one connection.
The local runtime explains what the owner, custody and the
metadata store guarantee, the
runtime binding
shows the commands implemented today, and the CLI reference lists every
command.
Before you upgrade the binary, read Upgrade the CLI.
Every connector has a page that takes it from the provider's prerequisites through an instance, a connection and a first read to a first approved write: start at Connectors.