CLI Reference

Every kutl command, its flags, and usage examples.

kutl is directory-first, like git: space-scoped commands act on the space enclosing your current directory, from any depth inside it. cd into a space and type; there are no space-selector flags to pass. Outside a space, read commands fall back to an all-spaces view and writing commands tell you to cd into one.

kutl init

kutl init [--relay <url>] [--name <name>] [--dir <path>] [--subfolder <name>]

Initialize a new kutl space in a directory. Creates .kutlspace and .kutl/, generates a DID identity if one doesn't exist, and registers the space name with the relay.

FlagTypeDefaultDescription
--relayURLws://127.0.0.1:9100/wsRelay WebSocket URL
--namestringauto-generatedHuman-readable space name
--dirpathcurrent directoryTarget directory
--subfolderstring"kutl" (prompted)Subfolder name inside a git repo

Example

kutl init --name my-project

kutl space apply

kutl space apply [--dir <path>]

Refresh the kutl-managed section of AGENTS.md against the running binary's instructions template. Use this when AGENTS.md was generated by an older kutl version and is out of date. Anchors at the space's project root (the git repo root if any, otherwise the space directory) and bails if no kutl space is present at or under the anchor.

FlagTypeDefaultDescription
--dirpathcurrent directoryTarget directory

Example

kutl space apply

kutl join

kutl join [<target>] [--relay <url>] [--dir <path>] [--subfolder <name>]

Join an existing space. The target can be:

  • A bare name (e.g. my-project), resolved via the default or specified relay
  • An owner/slug (e.g. alice/my-project), which connects via kutlhub
  • A full invite URL (e.g. https://...), with the relay extracted from the URL

If invoked inside a directory with an existing .kutlspace, the space name from that file is used and the target argument may be omitted.

FlagTypeDefaultDescription
--relayURLRelay WebSocket URL (inferred from target when possible)
--dirpathcurrent directoryTarget directory
--subfolderstring"kutl" (prompted)Subfolder name inside a git repo

Examples

# Join by name (uses default relay)
kutl join my-project

# Join by name with an explicit relay
kutl join my-project --relay ws://relay.example.com:9100/ws

kutl sync

kutl sync [--dir <path>]

Push local changes and pull remote changes, then exit. A one-shot alternative to running the daemon continuously. Useful for CI pipelines or scripts. Runs against the space enclosing the current directory; any subdirectory of the space works, like git.

FlagTypeDefaultDescription
--dirpathcurrent directoryStart the space lookup from this directory instead of the current one

kutl status

kutl status [--format human|json]

Show client diagnostic state: daemon and desktop tray liveness, registered spaces, identity, and relay reachability. Works whether the daemon is running or not. Run from any directory.

FlagTypeDefaultDescription
--formathuman|jsonhumanOutput format. json emits a machine-readable snapshot with a stable schema; safe to paste into a support issue.

Example

kutl status
# daemon: running (PID 12345, $KUTL_HOME=~/.kutl)
# identity: local (no relay token yet)
#   did:   did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK
#   name:  Alice
#   email: [email protected]
#
# spaces (1 registered):
#   ✓ my-project /home/alice/code/proj relay=ws://127.0.0.1:9100/ws last-activity=12s ago signals=0
#
# relays:
#   ws://127.0.0.1:9100/ws   reachable

kutl daemon

kutl daemon <run|start|stop|status>

Manage the background daemon that watches files and syncs with the relay.

SubcommandDescription
runRun in the foreground (watches all registered spaces)
startStart as a background process (PID written to ~/.kutl/daemon.pid)
stopStop the running daemon (graceful SIGTERM, then SIGKILL)
status [--format json]Show just the daemon liveness (and desktop tray, when running)

For the full picture (daemon plus spaces, relays, and identity), use kutl status.

Example

kutl daemon start
kutl status
# daemon: running (PID 12345, $KUTL_HOME=~/.kutl)

kutl signal

kutl signal <create|list|view|reply|close|reopen|resolve>

Signals are flags, questions, and decisions attached to a space or its documents. Commands act on the space you are standing in; list and view cover all registered spaces when run outside one. Wherever a command takes a signal id, a unique prefix of 4+ characters works; like git commit hashes, you never type the full UUID. The table shows the flags you'll typically use; --help on any verb is exhaustive.

SubcommandDescription
create --message <text> [--kind <kind>] [--doc <path>]Raise a flag in this space. --kind is one of info (default), completed, review_requested, question, blocked. --doc attaches it to a document by working-tree path; omit for a space-level flag. --audience participant --to <name> addresses one participant instead of the whole space. --to takes a participant's name, or their DID. Names match whole trailing segments of a path, so an agent listed as boris/ray answers to --to ray as long as nobody else in the space does. Case is ignored. If several participants match, the command refuses and lists them, so you can pick the full path. Use kutl space participants to see who can be addressed.
list [--open|--closed|--all] [--doc <path>] [--fetch] [--format json]List signals (open only by default). Reads the local mirror; --fetch pulls the latest from the relay first. When the daemon is already live-syncing, the pull is skipped with a note and the local copy served. Filter by record type with --kind flag|chat|decision|reply or by flag intent with --flag-kind.
view <id> [--fetch] [--format json]One signal's detail and its full transition history
reply <id> --message <text> [--parent-reply <id>]Reply to a signal, or nest under an existing reply with --parent-reply (agents reply the same way over MCP)
close <id> [--reason resolved|declined|withdrawn]Close a signal (reason defaults to resolved)
reopen <id>Reopen a previously-closed signal
resolve <id>Sugar for close --reason resolved

Example

cd ~/code/proj/kutl
kutl signal create --kind question --message "Should the API stay v1-compatible?" --doc rfc/api.md
kutl signal list --fetch
kutl signal resolve 8c9d   # a unique id prefix is enough

kutl space feed

kutl space feed [--follow] [--format human|json]

Show this space's activity feed: document edits and signals interleaved, newest first. --follow live-tails new activity until interrupted. If the relay is unreachable, the feed degrades to locally-mirrored signals with a note.

kutl space leave

kutl space leave

Forget the space you are standing in on this machine: it is removed from this client's registry and the daemon stops syncing it. The working tree and all its files stay on disk, and your membership on the relay is untouched. Run kutl init in the folder to re-attach it any time. Refuses while the daemon is running (stop it first with kutl daemon stop).

kutl space list / kutl space status

kutl space list [--format json] · kutl space status [--format json]

space list lists every space registered on this client (name, path, relay, health). space status is the focused view of the space you are standing in: its health plus the relay it uses. Outside a space, it points you at the all-spaces kutl status instead.

kutl space participants

kutl space participants [--format json]

Lists who may act in the space you are standing in: the authorized roster, whether or not each participant is connected right now. Presence is shown as a status, not used as a filter, because you address someone precisely when they are away. Each line is the participant's name and online or offline. Names given as paths, such as an agent listed under its operator as boris/ray, show in full. The names are what kutl signal create --to accepts. --format json adds each participant's DID and the raw connection type.

kutl auth

kutl auth <login|token|logout|status>

Store token credentials for a relay. Every relay requires authentication, but self-hosted relays don't need kutl auth: with no stored token, the CLI authenticates automatically by signing a challenge with its local DID identity (the relay operator adds that DID to the relay's authorized_keys file). Use kutl auth for kutlhub and other account-backed relays. --relay defaults to the kutlhub URL wss://kutlhub.com/relay/ws; pass an explicit URL to authenticate against a self-hosted relay.

SubcommandDescription
login [--token <TOKEN>] [--relay <URL>]Authenticate via OAuth device flow (opens browser), or store a token directly with --token
token <TOKEN> [--relay <URL>]Store a personal access token for a relay
logoutDelete stored credentials
status [--format json]Show the identity and token in use (DID, name, token prefix, relay)

Credentials are stored in ~/.kutl/auth.json. The KUTL_TOKEN environment variable takes precedence over stored credentials. To see which identity is currently active, use kutl auth status.

Authorizing machines: the authorized_keys file

echo 'did:key:z6Mk...' >> authorized_keys

A self-hosted relay authorizes machines through a plain text file you edit like SSH's authorized_keys: one DID per line, kept wherever $KUTL_RELAY_AUTHORIZED_KEYS_FILE points (put it under version control if you like). There is no CLI verb for this: the relay is operated through its config files. The relay live-reloads the file, so a new line takes effect on the next connection attempt without a restart.

A bare DID line grants access to every space, indefinitely. A line may also include options: scope=<space-uuid>[,<space-uuid>...] restricts it to specific spaces, expiry=<ms> sets a cutoff (Unix milliseconds), and name=<name> is what participants call that DID when they address it, and notes=<text> is an operator note that never leaves the relay. A malformed line is dropped whole: the relay fails closed and logs a dropping authorized_keys entry warning naming the reason, so a typo denies access loudly instead of widening it silently.

Example

# Each machine prints its DID with `kutl status`.
# On the relay host, append it as a line:
echo 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK' >> authorized_keys

# Scope an agent key to one space, and name it so people can address it:
echo 'did:key:z6Mk...agent scope=8f14e45f-... name=ci-bot' >> authorized_keys

kutl config

kutl config <set|get|list>

Set or read your identity details (name, email).

SubcommandDescription
set <key> <value>Set a config value. Valid keys: name, email
get [<key>] [--format json]Read one value (or all set values with no key). JSON output is a stable object with null for unset keys.
list [--format json]List all set configuration values

Example

kutl config set name "Alice"
kutl config set email "[email protected]"
kutl config get name
# Alice

kutl agent

kutl agent <create|list>

Provision keypairs for tool-held agents: a distinct DID per agent, held by the tool process (e.g. kutl mcp serve), SSH-style. create --name <name> mints a keypair at ~/.kutl/agents/<name>.json and prints the DID plus the authorized_keys line to grant it. list [--format json] shows the provisioned agents.

kutl mcp serve

kutl mcp serve [--poll-only] [--agent <name>]

Run the MCP server over stdio. This is the agent entry point: a Claude Code channel and MCP tool server that lets AI agents receive flags and make edits through the sync protocol. It authenticates with a tool-held agent key (named default unless --agent or $KUTL_AGENT says otherwise). Provision one with kutl agent create --name default first. When a local space is found (search directory or the client registry) it auto-subscribes; with none it still starts, and the agent discovers spaces via the list_spaces / subscribe_space tools. Pass --poll-only to poll for changes instead of subscribing to push notifications.

kutl surface

kutl surface [--dir <path>]

Copy documents from the space into its configured surface target. Intended for git coexistence: kutl is the source of truth for documents, and surface lifts them into your git working tree. Always overwrites the target.

Requires a [surface] section in .kutlspace with a target path configured.

kutl document log

kutl document log <path> [--format human|json]

Show the change history of a document. Pass the file's ordinary working-tree path (e.g. notes/plan.md). Changes and any signals attached to the document interleave in reverse chronological order, similar to git log. --format json emits each change with its id (the exact input document restore --to takes), plus author, timestamp, and boundary.

Example

kutl document log notes/plan.md

kutl document blame

kutl document blame <path> [--format human|json]

Show per-line authorship of a text document: git-blame for a CRDT. Each line is attributed to the durable author DID of its first character. Takes the working-tree path, like document log.

Example

kutl document blame notes/plan.md

kutl document restore

kutl document restore <path> (--at <time> | --to <change-id>)

Restore a document to an earlier point in time, Google-Docs style: the historical content is reconstructed and re-asserted as a new forward edit, never a history rewrite, so a teammate's concurrent edits merge instead of being destroyed. Text documents only.

FlagTypeDefaultDescription
--attimeRestore to the newest change at or before this time: RFC3339 (2026-07-18T15:00:00Z) or a civil date/datetime read in your time zone. Mutually exclusive with --to.
--tochange idRestore to the exact change with this id, as shown by document log.

Example

kutl document log notes/plan.md --format json   # find the change id
kutl document restore notes/plan.md --to 8c9d2e1f-...

kutl update

kutl update

Print how to update kutl for your install method (Homebrew: brew upgrade kutl). The command itself does not modify the installation.

Files and directories

PathPurpose
~/.kutl/identity.jsonDID keypair (ed25519)
~/.kutl/auth.jsonStored auth tokens (per-relay)
~/.kutl/daemon.pidRunning daemon PID
~/.kutl/logs/daemon.logDaemon output log
.kutlspaceTeam-wide space config (safe to commit)
.kutl/space.jsonPer-installation runtime config (space ID, relay URL)
.kutl/docs/Internal document history storage (.dt files). Not part of the command surface; commands take ordinary working-tree paths.
.kutl/signals/Local signal log (append-only segment files). Not part of the command surface; created once signals reach the daemon.