- The specs index no longer carries a table mapping the three original spec names to the renumbered ones, and spec headers no longer record a feature branch or "first written as" name. - A task that pointed at research under the old 001 directory now points at its current path, so every reference resolves to a spec that exists. - The constitution's 1.4.1 amendment still records that the specs were split and renumbered, without listing the retired names.
9.1 KiB
Contract: Command-Line Interface
Feature: 001-service-and-clients | Version: 1.0 | Date: 2026-09-20
The user-facing contract for the midi-harbor executable. Governed by FR-039 through FR-039h and
FR-042. Every command here is a thin projection of the IPC protocol; the CLI
holds no state of its own.
1. Invocation model
One executable selects its role from its arguments (FR-039). There is no separate daemon binary.
midi-harbor # full build: launches the GUI
# headless build: prints help and exits 0 (FR-039e)
midi-harbor gui # launches the GUI explicitly; exits 2 on a headless build
midi-harbor daemon # runs the engine in the foreground (FR-039a)
Running daemon registers nothing with the operating system, so it can be supervised externally
or attached to a debugger.
Global flags
| Flag | Effect |
|---|---|
--json |
Machine-readable output on stdout (FR-039d) |
--socket <path> |
Override the daemon socket path |
-v, -vv |
Raise log verbosity to debug, then trace |
--quiet |
Suppress non-error output |
--no-color |
Disable styling; also honours NO_COLOR |
--json emits a single JSON document per invocation on stdout. Diagnostics go to stderr, so
midi-harbor --json status | jq is always safe.
2. Service management (FR-039f, FR-039g, FR-039h)
midi-harbor service install [--start] [--name <label>]
midi-harbor service uninstall [--purge-config]
midi-harbor service start
midi-harbor service stop
midi-harbor service status
install writes a per-user service definition — a launchd agent on macOS, a systemd user unit on
Linux — enables it for login, and with --start starts it immediately. It never requires
elevation (FR-043) and never asks the user to author a plist or unit file by hand.
Re-running install when a registration already exists updates it in place and says so,
rather than duplicating it (edge case).
uninstall stops and deregisters the service and leaves configuration intact (FR-039h).
--purge-config additionally removes configuration, and prompts unless --yes is given.
status reports installed, running, version, uptime, and socket path. It explicitly detects a
stale registration — one pointing at a moved or deleted executable — and names the missing
path (edge case).
Where no supported service manager exists (a container, or Linux without systemd), install
fails with a clear message and tells the user to run midi-harbor daemon under their own
supervisor (edge case, R-007).
3. Endpoint commands
Virtual ports (FR-001..007)
midi-harbor port list [--kind <kind>]
midi-harbor port create <name> [--direction in|out|both]
midi-harbor port rename <name-or-id> <new-name> [--yes]
midi-harbor port delete <name-or-id> [--yes]
midi-harbor port enable <name-or-id>
midi-harbor port disable <name-or-id>
rename warns that connected applications may need to reselect the port and requires confirmation
(FR-001 scenario 4); --yes supplies it non-interactively. delete likewise requires --yes,
since every application using the port loses it, and exits 7 without it. It silences sounding
notes before removing the port (FR-006).
Endpoints are addressable by name or by id everywhere. An ambiguous name is an error listing the candidates with their ids — never a silent pick.
Physical devices (FR-015a..g)
midi-harbor device list [--all]
midi-harbor device forget <name-or-id>
midi-harbor device resolve <name-or-id>
--all includes remembered devices that are currently unplugged (present: false). resolve
disambiguates two identical devices whose fingerprints matched with Ambiguous confidence.
Network sessions (FR-008..015)
midi-harbor session list
midi-harbor session create <name> [--port <n>] [--policy prompt|known|all|reject]
midi-harbor session discover [--timeout <secs>]
midi-harbor session connect <session> <peer>
midi-harbor session disconnect <session>
midi-harbor session peer add <address>[:<port>] [--name <name>]
midi-harbor session peer remove <peer>
midi-harbor session policy <session> <prompt|known|all|reject>
discover browses for _apple-midi._udp peers and prints them; it never lists this machine's own
advertised session (edge case). connect returns as soon as the session enters Connecting —
progress is observable through status and monitor.
Bluetooth (FR-016..021)
midi-harbor bluetooth scan [--timeout <secs>]
midi-harbor bluetooth connect <address-or-name>
midi-harbor bluetooth disconnect <name-or-id>
midi-harbor bluetooth forget <name-or-id>
midi-harbor bluetooth advertise <on|off> [--name <name>]
When Bluetooth is unavailable — no adapter, adapter off, or permission not granted — these
commands exit 4 with the specific reason and guidance on granting permission (FR-021).
4. Routing (FR-030..035)
midi-harbor route list [--broken]
midi-harbor route create <source> <destination>
midi-harbor route delete <id>
midi-harbor route enable <id>
midi-harbor route disable <id>
create succeeds even when it forms a cycle, printing a warning naming the routes in the cycle
(FR-033). The repeater case is just a route whose source is a physical device and whose
destination is a network session (FR-030a) — no special command exists, deliberately.
5. Observation and diagnostics
midi-harbor status [--watch]
midi-harbor monitor <endpoint> [--raw]
midi-harbor dismiss-warning
midi-harbor send-note <endpoint> [--note <0-127>] [--channel <1-16>] [--velocity <1-127>] [--length <ms>]
midi-harbor events [--since <time>] [--endpoint <id>] [--follow]
midi-harbor diagnostics export [--output <path>] [--include-messages]
status prints every endpoint with its phase, time in phase, last error, next retry, and traffic
counters (FR-044), with when a message was last received and last sent. A network port's
automatic port has a row of its own beneath it, since the network port's counts say what the
network carried and the automatic port's say what reached the applications on this computer.
--watch redraws on events.
When the daemon replaced one that lost the platform's MIDI service, status warns above its
table, and --json carries the time the loss was found as midi_server_replaced_at: the daemon
recovered, but other applications may have lost their MIDI connection with the service and need
relaunching. The warning stands until dismiss-warning, or Dismiss in any window, clears it in the
daemon for every client.
monitor prints decoded MIDI in human-readable form (FR-047); --raw prints hex bytes. Both are
explicitly lossy under load and report the number of dropped updates rather than applying
backpressure to the data path.
send-note sends one note out of an endpoint for testing: the note-on at once, and the note-off
after --length milliseconds (500 by default, at most 10000), sent by the daemon so a command
stopped mid-note cannot leave it sounding. Numbers outside MIDI's ranges exit with a usage error;
an endpoint nothing can be sent out of, or one switched off, is refused as a failed precondition.
diagnostics export writes configuration, connection history, and counters to a file for bug
reports (FR-048).
6. Configuration (FR-049..052)
midi-harbor config path
midi-harbor config show
midi-harbor config export [--output <path>]
midi-harbor config import <path> [--mode merge|replace] [--yes]
midi-harbor config reload
import and reload diff against running state and disturb only the connections whose
configuration actually changed (FR-050).
7. Exit codes
Stable and scriptable. Derived from the IPC error.code.
| Code | Meaning |
|---|---|
0 |
Success |
1 |
Generic failure |
2 |
Usage error, or GUI requested on a headless build |
3 |
Daemon not running or not reachable |
4 |
Requested capability unavailable on this system |
5 |
Not found — no such endpoint, route, peer, or device |
6 |
Conflict — name already in use, duplicate route |
7 |
Confirmation required and not supplied |
8 |
Protocol version mismatch between client and daemon |
8. Behavioural requirements
- Every GUI action has a CLI equivalent (FR-039c). This is verified as a test: the set of IPC request kinds reachable from the GUI must be a subset of those reachable from the CLI.
- The daemon not running is a recognised, recoverable state (FR-042). Any command needing the
daemon exits
3with an offer:daemon is not running — run 'midi-harbor service install --start' to install and start it at login, or 'midi-harbor daemon' to run it in the foreground. - A headless build behaves identically for every non-GUI command (FR-039b scenario 4). The
only differences are bare invocation printing help, and
guiexiting2. - No command blocks indefinitely. Operations that take time return once the state machine has
accepted the request; progress is observed through
status,events, ormonitor. --jsonoutput is contract-stable. Field names match the IPC wire types. New fields may be added in a minor version; removal or re-typing is a major version.