App CLI Reference
The binary produced by wick build registers its own command tree. Run ./bin/<app> --help for the live list — this page documents every subcommand.
For the wick host CLI (wick init, wick build, wick doctor, etc.) see CLI Reference.
Default behavior — no args
./bin/<app> with no arguments routes by environment:
GUI desktop (Windows / macOS / Linux with
$DISPLAYor$WAYLAND_DISPLAY) → launches the system tray. Tray internally starts the HTTP server and the background worker as goroutines and exposes MCP install / uninstall, preferences, and self-update from the menu.Headless (Termux, SSH session, Linux server without
$DISPLAY) → prints a hint and exits:Headless environment detected — tray needs a desktop session. Use `<app> start` for background daemon mode, or `<app> all` to run in foreground.
Headless builds (wick build --headless) strip the tray symbols entirely; this command exits non-zero with the same hint regardless of $DISPLAY.
Run (foreground)
These commands block the calling shell — Ctrl+C exits cleanly. Use them for dev runs, Docker CMD, or supervisors like systemd / pm2.
<app> server
Start the HTTP server only. Useful for splitting server / worker across containers, or running behind an external process supervisor.
./bin/myapp server
./bin/myapp server --port 9000 # override the resolved port
./bin/myapp server --host 127.0.0.1 # bind specific interface
./bin/myapp server --host 192.168.1.42 # bind one NIC only (multi-network host)
./bin/myapp server --localhost # shortcut for --host 127.0.0.1
WICK_HOST=127.0.0.1 ./bin/myapp server # same, via env--host <addr> / --localhost / WICK_HOST — pin the listen interface. Default (all empty / unset) binds 0.0.0.0 so Docker, systemd, and remote-VPS deploys keep working without any flag. Setting 127.0.0.1 makes the kernel drop SYN packets from any non-loopback source — required on Termux phones where unrooted Android has no firewall to keep :9425 private. Pair with ssh -L 9425:localhost:9425 -p 8022 user@phone-ip for remote access.
Precedence when multiple are set: --host wins over --localhost; both env-overridable via WICK_HOST (env only takes effect when no flag is passed).
<app> worker
Start the background job worker only. Pair with server in a separate process / container when you want to scale them independently.
<app> tray
Explicit tray subcommand — same as <app> with no args on a GUI host. In headless builds this prints tray not available in headless build and exits non-zero.
<app> all
Run the HTTP server and the cron scheduler in the same process, sharing one manager.Service. Use this for single-node deployments where server and worker can't share a volume (single-container Docker, simple VPS, etc.) — without a shared filesystem a separate worker pod can't see provider-storage files restored on the API pod, so jobs that touch those files silently no-op.
./bin/myapp all
./bin/myapp all --port 9000
./bin/myapp all --host 127.0.0.1 # bind specific interface
./bin/myapp all --localhost # shortcut for --host 127.0.0.1Trade-offs vs. running server + worker as separate processes:
| Aspect | all (single-node) | server + worker (multi-pod) |
|---|---|---|
| Filesystem | One pod, no volume sharing needed | Needs shared volume (or per-pod restore) |
| Scaling | Vertical only (one process) | Horizontal (scale worker independently) |
| Cron double-fire | Impossible (one manager.Service) | Possible if you accidentally run two workers |
| Failure isolation | HTTP crash kills cron too | Independent |
The scheduler goroutine auto-respawns with exponential backoff (2s → 30s cap) if it exits unexpectedly. Clean shutdown via SIGTERM / Ctrl+C stops both.
Daemon (background)
The daemon commands spawn the binary detached from the calling shell so the parent terminal can exit without killing it. A PID file under ~/.<app>/run.pid tracks the running instance; output goes to a dated log under ~/.<app>/logs/daemon-YYYY-MM-DD.log — a fresh file per day, swept by the same retention as the other runtime logs (see Logs).
<app> start
Spawn the binary in the background. Mode is chosen at runtime:
- GUI host → spawns
<app> traydetached (the interactive icon, with its own autostart / server / worker toggles) - Headless host → spawns
<app> alldetached (server + worker, no UI)
./bin/myapp start
./bin/myapp start --host 127.0.0.1 # bind specific interface — child inherits WICK_HOST
./bin/myapp start --localhost # shortcut for --host 127.0.0.1
# started myapp as `tray` (pid 12345)
# log: ~/.myapp/logs/daemon-2026-07-13.log
# pid: ~/.myapp/run.pid--host / --localhost (and WICK_HOST=...) propagate to the spawned child via process env, so both tray (GUI) and all (headless) modes honor the chosen bind interface. See server --host for the full security rationale.
Re-running start while a live daemon is recorded is a no-op — prints already running (pid N) and exits 0.
<app> stop
Send SIGTERM to the daemon, wait up to --timeout (default 5s) for graceful exit, then SIGKILL if still alive. Removes the PID file even after a force-kill.
./bin/myapp stop
./bin/myapp stop --timeout 15sStale PID files (process no longer alive) are silently cleaned up so the next start has a fresh slot.
<app> restart
stop + start in one command. Uses the same mode selection as start.
./bin/myapp restart
./bin/myapp restart --timeout 15s
./bin/myapp restart --host 127.0.0.1 # rebind specific interface on new daemon
./bin/myapp restart --localhost # shortcut for --host 127.0.0.1<app> reload
Replace the binary without downtime. The running daemon starts a successor, hands it the listening socket, and only then drains its own work and exits — the port is never closed, so nothing in front of wick (nginx, a load balancer, a browser) sees a refused connection, and no in-flight workflow run or cron job is cut off.
This is the difference that matters against restart: restart stops first, so the port is closed for the successor's whole boot — tens of seconds once a registry restore, a database and connectors are involved — and everything in flight is killed.
# install the new binary FIRST, then hand over
cp ./bin/myapp /usr/local/bin/myapp.new && chmod +x /usr/local/bin/myapp.new
mv -f /usr/local/bin/myapp.new /usr/local/bin/myapp # atomic rename
./bin/myapp reloadThe rename is not a detail: copying onto a running binary fails with ETXTBSY ("Text file busy"), while a rename swaps the directory entry and lets the running process keep its old inode until it exits.
Requires the daemon to run with WICK_GRACEFUL_UPGRADE=1; without it the signal is logged and ignored, and you should use restart. Supported on Linux, macOS and BSD, as a plain daemon or under a service manager. On Windows — and when wick runs inside the tray's in-process supervisor — a socket cannot be handed to another process, so reload reports that and you fall back to restart.
reload routes itself: an installed systemd unit gets systemctl --user reload, a unit without ExecReload is signalled by its MainPID, and a PID-file daemon is signalled directly. kill -HUP <pid> does the same thing.
What the drain waits for, because interrupting them costs different things:
| Work | Deadline | Why |
|---|---|---|
| Workflow runs, cron jobs, connector calls, plugin requests, scheduled deliveries | WICK_DRAIN_TIMEOUT (default 20m) | Interrupting them loses the work |
| Agent turns | WICK_DRAIN_AGENT_GRACE (default 45s) | The session is on disk and resumes on the next message |
The short grace for agent turns is deliberate: an interactive session stays "in flight" for as long as somebody keeps talking to it, so waiting for it would keep two processes alive for hours — and only one handover can be in flight at a time. Raise it (WICK_DRAIN_AGENT_GRACE=20m) when you would rather let a long run finish, and accept the longer overlap; 0 hands over immediately. Both are read at process start, so changing them needs a restart rather than a reload — a successor inherits its parent's environment.
Every 15 seconds a drain logs what is still outstanding, by name, so a long wait reads as "finishing a run" instead of a hang.
Self-update takes the same path. With graceful upgrade armed, applying an update from the admin UI swaps the binary and then hands over exactly like reload — no separate step, and no window where the port is closed. Without it, the update re-execs in place as before.
Proving it
Claiming zero downtime from the server's own logs proves nothing. Probe from outside, through whatever sits in front of wick, and count the failures:
while true; do
printf '%s %s\n' "$(date -Is)" "$(curl -s -o /dev/null -m 5 -w '%{http_code}' https://wick.example.com/health)"
sleep 0.5
done | tee probe.log
grep -v ' 200$' probe.log # anything here is real downtimeThen check that MainPID changed while the unit never left active.
<app> status
Report whether the daemon is alive, its PID, approximate uptime (PID file mtime), and the log / PID file paths. --log N tails the last N bytes of the daemon log — the newest daemon-YYYY-MM-DD.log in logs/, so a daemon that's been running since a previous day still tails correctly instead of showing an empty today-file.
./bin/myapp status
# myapp: running
# pid: 12345
# started: 2026-05-30T13:02:28+07:00 (37s ago)
# log: ~/.myapp/logs/daemon-2026-07-13.log
# pidfile: ~/.myapp/run.pid
./bin/myapp status --log 1000
# ... last 1000 bytes of the daemon log appendedAuto-start service
<app> service registers (or removes) the binary as an OS-level auto-start. Routing depends on the runtime environment:
| Host | Backend | Path |
|---|---|---|
| Windows desktop | HKCU Run registry | HKCU\Software\Microsoft\Windows\CurrentVersion\Run\<app> |
| macOS desktop | LaunchAgent plist | ~/Library/LaunchAgents/<app>.plist |
| Desktop Linux (DISPLAY set) | XDG autostart | ~/.config/autostart/<app>.desktop |
| Headless Linux server / Raspberry Pi | systemd-user unit | ~/.config/systemd/user/<app>.service |
| Termux on Android | Termux:Boot script | ~/.termux/boot/<app>-start |
GUI hosts share the same registration the tray's Auto-start app at login toggle uses, and service install / service uninstall also flip userconfig.AutoStartApp so the tray checkbox stays in sync. Headless hosts get a daemon-style unit that runs <app> all directly.
All backends install into user scope — no sudo / admin required.
<app> service install
./bin/myapp service install
# installed myapp service
# backend: autostart-gui
# path: HKCU\Software\Microsoft\Windows\CurrentVersion\Run\myappOn headless Linux (systemd-user backend), service install also attempts to auto-enable systemd lingering for the current user so the daemon survives logout and starts at boot with no one logged in. If the host's polkit allows self-linger (most modern distros do), this happens silently. If it fails, service status reports the exact command to run manually (see below).
Re-running over an existing install rewrites the unit / entry — handy after the binary moves.
<app> service uninstall
./bin/myapp service uninstall
# uninstalled myapp serviceReturns service not installed if nothing was registered.
<app> service status
./bin/myapp service status
# myapp service: installed
# backend: systemd-user
# path: ~/.config/systemd/user/myapp.service
# active: true
# note: linger enabled — service survives logout and starts at bootWhen lingering is off (e.g. the host denied the auto-enable during install), the note reads:
note: linger DISABLED — service stops at logout; run: sudo loginctl enable-linger <user>The username shown is the real login name detected at runtime — never a hardcoded value.
On GUI hosts, status also flags drift between the OS entry and userconfig.AutoStartApp so you can spot a tray checkbox that's out of sync with reality and re-run install / uninstall to re-align.
Headless vs. GUI on the same machine
On Termux or a Linux box without $DISPLAY, service install writes a <app> all unit / Termux:Boot script. The moment you SSH into a Mac, the same command refuses with a hint pointing back to the tray's autostart toggle — daemon-style auto-start only makes sense without a usable GUI session.
Config
Manage the app-level runtime configs that drive the manager (app URL, allowed origins, etc.). The same rows are editable from the admin UI; the CLI is here for headless setup and scripting.
<app> config list
Dump every config row (secrets masked). Env overrides are flagged.
./bin/myapp config list
# allowed_origins = http://localhost:9425
# app_url = http://localhost:9425
# default_admin_password = ******** (overridden by ADMIN_PASSWORD)<app> config get <key>
Print one value — secrets are not masked.
./bin/myapp config get app_url<app> config set <key> <value>
Update one value. Rejects locked rows and rows currently overridden by an env var.
./bin/myapp config set app_url https://my-deployment.example.com<app> config profile <full|agent|lite>
Set the connector build profile this instance registers at boot. The value is stored as a profile config row in the database; a restart is required for the change to take effect.
./bin/myapp config profile full # all builtin connectors (default)
./bin/myapp config profile agent # curated subset: HTTP REST, Slack
./bin/myapp config profile lite # no builtin connectors registered
# profile = agent (restart to apply)| Profile | Builtin connectors registered at boot |
|---|---|
full (default) | All 4: HTTP REST, Slack, Loki, Phoenix |
agent | HTTP REST, Slack |
lite | None |
GitHub, Bitbucket, and Google Workspace are no longer builtin connectors — they ship as downloadable plugins and are not profile-eligible. Install them separately with
<app> plugin install github/bitbucket/google_workspace.
The four runtime connectors (Wick Manager, Workflow, Notifications, Custom Connector) are never profile-gated and always register regardless of this setting.
The active profile can also be read and written via the admin Advanced page (/admin/variables, key profile).
<app> config allowed-origins
A second-level group for managing the host allowlist (URLs that may reach the manager beyond app_url).
./bin/myapp config allowed-origins list
./bin/myapp config allowed-origins add http://192.168.1.42:9425
./bin/myapp config allowed-origins remove http://192.168.1.42:9425
./bin/myapp config allowed-origins autodetect # interactive LAN whitelist pickerautodetect scans the host's private LAN IPv4 addresses, prints each as a candidate URL, and prompts which ones to whitelist:
LAN access — detected 2 IPv4 address(es) on this device:
[1] http://192.168.1.42:9425
[2] http://10.0.0.5:9425
Whitelist for browser access from other devices?
a = all n = none (default) 1,2,3 = pick by number
Choice [n]:Plugin
Manage external connector plugins — binaries distributed separately from the main app and installed without a rebuild. A hot-reload poller running inside the app detects changes and wires plugins into the connector registry automatically — no restart needed for install, enable, disable, or remove (changes take effect within a few seconds).
<app> plugin search [query]
List connector plugins available in the marketplace catalog. Optionally filter by name or description keyword.
./bin/myapp plugin search
./bin/myapp plugin search slackThe catalog is a single plugins.json file fetched raw from the wick repo's default branch (plugins/plugins.json, not the GitHub API — so no rate limit and no token needed). Override the catalog URL with WICK_PLUGIN_CATALOG=<url>.
<app> plugin install <name|url|path>
Install a connector plugin.
./bin/myapp plugin install slack # resolve from the marketplace registry
./bin/myapp plugin install ./slack-plugin/ # local directory {binary, plugin.json}
./bin/myapp plugin install https://…/slack-0.1.0-linux-arm64.zip # direct URLThe three install sources:
| Argument | Behavior |
|---|---|
<name> | Looks up the latest matching release in the registry, downloads the arch-matching zip, verifies the manifest, and installs |
<path> | Installs from a local directory containing the binary and plugin.json |
<url> or <file> | Downloads / extracts a .zip or .tar.gz archive, verifies, and installs |
<app> plugin list
List installed connector plugins with their version, arch compatibility, signature status, and enabled/disabled state.
./bin/myapp plugin list
# slack 0.1.0 arch:yes signed:valid enabled
# googleworkspace 0.2.1 arch:yes signed:valid enabled<app> plugin enable <key>
Enable a previously disabled plugin. The hot-reload poller picks it up without a restart.
./bin/myapp plugin enable slack<app> plugin disable <key>
Disable a plugin without removing it. The change takes effect on the next reload cycle.
./bin/myapp plugin disable slack<app> plugin remove <key>
Uninstall a plugin by deleting its directory under the plugins folder.
./bin/myapp plugin remove slackPlugins are stored under ~/.<app>/plugins/ by default.
MCP
The binary ships an MCP server over stdio so Claude Desktop / Cursor / Gemini / Codex / Claude Code can call wick tools as a model context provider.
<app> mcp serve
Run the MCP server over stdio. Spawned by the AI client based on the entry written by mcp install. Rarely invoked by hand.
<app> mcp install
Write the binary's MCP entry into the chosen client's config file. Resolves os.Executable() so the entry points at the actual built binary, not at wick.
./bin/myapp mcp install # all detected clients
./bin/myapp mcp install --client claude # Claude Desktop only
./bin/myapp mcp install --client claude-code # writes ~/.claude.json
./bin/myapp mcp install --name custom-server # override server name--client accepts: claude, cursor, gemini, codex, claude-code, all. The default server name is the basename of the current directory.
<app> mcp uninstall
Remove the entry written by install. Same flags as install.
Version
Three equivalent forms print the embedded release string in one line and exit. The format is stable — scripts/install.sh's probe greps it against the resolved release tag to decide whether a re-install should be skipped.
./bin/myapp version
./bin/myapp --version
./bin/myapp -v
# myapp version v0.14.13 (wick v0.14.13)The gate sidecar (<app>-gate) accepts the same three forms and prints its own version line:
./bin/myapp-gate version
./bin/myapp-gate --version
./bin/myapp-gate -v
# myapp-gate version v0.14.13The version handler on <app>-gate returns before the binary reads stdin, so probing it from a script does not interfere with its normal PreToolUse hook path.
Uninstall
<app> uninstall
Clean up per-user OS integrations before removing the binary. By default removes the autostart entry and every MCP client entry the app installed.
./bin/myapp uninstall # autostart + MCP entries
./bin/myapp uninstall --mcp=false # autostart only, leave MCP entriesRun this before deleting the binary so login items, autostart entries, and orphan MCP config rows don't pile up on the user's machine.