Sections

User Guide

config.json Reference

Every field of Runyard's config.json file, with examples.

Runyard reads a single JSON file that describes your tools. This page documents every field.

The config file lives at ~/Library/Application Support/Runyard/config.json by default. You can move it to a synced folder — see Syncing Across Macs.

Top-level structure

{
  "tools": [ /* ... */ ],
  "paths": ["/opt/homebrew/bin"],
  "advanced": { /* optional timing settings */ }
}
Field Type Default Description
tools array [] List of tool definitions (see below).
paths string[] [] Directories prepended to PATH when spawning any command. Listed in priority order. ~ is expanded.
advanced object Timing settings (see Advanced settings).

Launch at login is not stored in config.json. Toggle it in Settings → General → Launch Runyard at login; macOS persists the choice via the system Login Items list.

Tool definition

Each entry in tools is one tool.

Field Type Default Description
name string required Display name shown in the menu.
type string "service" "service", "shortcut", or "group" (see Tool types).
directory string required* Project root (~ expanded). Commands run from here unless workingDir is set.
startCommands array required* Processes to spawn (see Start commands).
stopCommands array Custom shutdown commands (see Stop commands).
actions array Extra menu items (see Actions).
tools array Nested tools inside a group. Services and shortcuts only — no nested groups.
autoStart boolean false Start this tool automatically when Runyard launches (service only).
installCommand object Command to run if the marker path is missing (see Install command).
keepMenuOpen boolean false Keep the menu open when clicking Start/Stop (service only).
paths string[] Additional PATH entries for this tool.
pathsOverride boolean false If true, paths replaces global paths entirely instead of merging.

* directory and startCommands are required for service tools, optional for shortcut/group.

Tool types

Type Renders as Required fields
"service" Bold header + Start/Stop + state dot + logs + actions. directory, startCommands
"shortcut" Bold header + list of actions. No process management. actions (at least one)
"group" Submenu with nested tools and/or actions. actions OR tools (at least one)

Nested services inside a group behave exactly like top-level services. Nested groups are not allowed.

Start commands

Each entry in startCommands is one process to spawn.

Field Type Default Description
label string required Unique name for this process.
command string required Executable to run (e.g., npm, docker-compose).
args string[] [] Arguments passed to the command.
workingDir string tool directory Working directory, relative to directory.
startupCheck string HTTP URL to poll until it returns a 2xx or 3xx during service startup.
startupFallbackPort number Fallback port if auto-detection fails. Also a hint when multiple ports are detected.
startupRequestTimeout number global Per-request HTTP timeout (seconds) for startup polling. Overrides advanced.startupRequestTimeout for this process only.
waitFor string Label of another process that must be healthy before this one starts.

Port-agnostic startup checks

If startupCheck is a localhost URL without an explicit port (e.g., http://localhost/api/health), Runyard injects the detected port at startup. This means your config works even when the dev server picks a different port each run.

A URL with an explicit port (e.g., http://localhost:3001/health) is used as-is.

Process dependencies with waitFor

"startCommands": [
  { "label": "Backend",  "command": "npm", "args": ["run", "dev"], "startupFallbackPort": 3001 },
  { "label": "Frontend", "command": "npm", "args": ["run", "dev"], "workingDir": "frontend",
    "startupFallbackPort": 5173, "waitFor": "Backend" }
]

Frontend won't start until Backend's startup check passes.

Stop commands

Optional. When stopCommands is set, Runyard runs each command sequentially instead of sending SIGTERM. After they finish, any lingering processes are force-killed as a safety net.

Uses the same shape as startCommands. Fields like startupCheck, startupFallbackPort, and waitFor are ignored.

"stopCommands": [
  { "label": "Compose Down", "command": "docker-compose", "args": ["down"] }
]

When stopCommands is absent, the default shutdown is SIGTERM → grace period → SIGKILL.

Actions

Actions add custom items to a tool's menu. Every action has exactly one type: url, command, reveal, applescript, or applescriptFile.

Field Type Default Description
label string required Menu item text.
url string URL to open in the default browser.
command string Shell command to run.
args string[] [] Arguments for the shell command.
workingDir string tool directory Working directory for the command.
reveal string Path to open in Finder (relative to tool directory, or absolute/~).
applescript string Inline AppleScript source.
applescriptFile string Path to a .applescript file.
showWhen string "running" "always", "running", or "stopped" (service only).
keepMenuOpen boolean false Keep the menu open when clicking this action.

Action examples

Open a URL:

{ "label": "Open Frontend", "url": "http://localhost:5173" }

Run a shell command:

{ "label": "Seed Database", "command": "npm", "args": ["run", "db:seed"] }

Open a file in a specific app:

{ "label": "Edit README", "command": "open", "args": ["-a", "TextEdit", "./README.md"], "showWhen": "always" }

Open a project in Cursor:

{ "label": "Open in Cursor", "command": "cursor", "args": ["."], "showWhen": "always" }

Reveal a folder:

{ "label": "Open Project", "reveal": ".", "showWhen": "always" }
{ "label": "Open Backend Logs", "reveal": "backend/logs", "showWhen": "always" }

Inline AppleScript:

{ "label": "Activate Safari", "applescript": "tell application \"Safari\" to activate", "showWhen": "always" }

Multiline AppleScript (use \n):

{
  "label": "Focus Safari",
  "applescript": "try\ntell application \"System Events\" to tell process \"Safari\"\nset frontmost to true\nend tell\nend try",
  "showWhen": "always"
}

AppleScript from a file:

{ "label": "Deploy", "applescriptFile": "scripts/deploy.applescript", "showWhen": "always" }

Port placeholders

Action URLs support two placeholders:

{ "label": "Open API Docs", "url": "http://localhost:{{port:Backend}}/api/docs" }

If the port isn't known yet (tool not running), the placeholder is left in place and the URL won't open.

showWhen behaviour

Value When shown
"running" (default) Tool is running.
"stopped" Tool is stopped or errored.
"always" Always shown.

showWhen only applies to service tools. On shortcut and group tools, actions are always visible.

Install command

Runs once before the first Start, if a marker path doesn't exist. Useful for npm install and friends.

"installCommand": {
  "command": "npm",
  "args": ["install"],
  "markerPath": "node_modules"
}
Field Type Default Description
command string required Executable.
args string[] [] Arguments.
markerPath string "node_modules" Path (relative to tool directory) checked before running. If it exists, install is skipped.

Advanced settings

All fields are optional. Defaults shown.

"advanced": {
  "startupTimeout": 30.0,
  "startupPollInterval": 1.0,
  "startupRequestTimeout": 5.0,
  "sigTermGracePeriod": 3.0,
  "installTimeout": 300.0,
  "stopCommandTimeout": 30.0
}
Field Default Description
startupTimeout 30.0 Total seconds to wait for a process to pass its startup check before marking it errored.
startupPollInterval 1.0 Seconds between startup check polls.
startupRequestTimeout 5.0 Seconds to wait for a single startup check HTTP request. Individual processes can override this with startupRequestTimeout on the start command.
sigTermGracePeriod 3.0 Seconds between SIGTERM and SIGKILL when stopping a process without stopCommands.
installTimeout 300.0 Seconds to wait for the install command before aborting.
stopCommandTimeout 30.0 Seconds to wait for each stopCommands entry.

Paths and PATH resolution

Commands run through /usr/bin/env with a custom-built PATH. The rules:

A typical setup:

{
  "paths": ["~/.nvm/versions/node/v22.0.0/bin", "/opt/homebrew/bin"]
}

For asdf users, add ~/.asdf/shims (and /opt/homebrew/bin if asdf was installed via Homebrew):

{
  "name": "My Phoenix App",
  "directory": "~/Code/my-app",
  "paths": ["~/.asdf/shims", "/opt/homebrew/bin"],
  "startCommands": [
    { "label": "Server", "command": "mix", "args": ["phx.server"],
      "startupCheck": "http://localhost:4000/health", "startupFallbackPort": 4000 }
  ]
}

Health Checks

A probe is a tool type that polls an arbitrary endpoint on its own interval. Probes are independent of any service Runyard manages — you can probe a remote API, a local database, or anything that speaks HTTP or accepts a TCP connection.

Probes appear as rows in the menu with a live status dot:

When any probe is failing, a small red warning triangle appears next to the Runyard menu bar icon, and any group containing the failing probe gets the same triangle next to its name in the dropdown.

Probe schema

{
  "name": "Production API",
  "type": "probe",
  "autoStart": true,            // optional, default true
  "interval": 30,               // optional, seconds; default 30, minimum 5
  "failureThreshold": 2,        // optional, consecutive failed polls before marking failing; default 2, minimum 1

  // HTTP probe (mutually exclusive with tcp)
  "http": {
    "url": "https://api.example.com/health",
    "expectStatus": 200,                    // optional; int or [int]; default 200
    "expectBodyContains": "ok",             // optional; case-sensitive substring
    "requestTimeout": 5                     // optional; seconds; default 5
  },

  // TCP probe (mutually exclusive with http)
  "tcp": {
    "host": "db.internal",
    "port": 5432,
    "connectTimeout": 3                     // optional; seconds; default 3
  }
}

A probe must specify exactly one of http or tcp.

Failure threshold

A probe flips to failing after failureThreshold consecutive failed polls (default 2), and back to healthy after 1 successful poll. The threshold avoids flapping on transient network blips while still surfacing real outages within one polling interval. Set failureThreshold: 1 for an aggressive probe that flips on the first failure, or a higher value for more tolerance.

Pause / resume from the menu

Click the pause/play icon on the right edge of a probe row to suspend or resume polling. The new state is written to your config.json (the autoStart field). The menu does not close.

Per-probe submenu

Each probe row has a submenu containing:

Probes inside groups

A group tool's tools array can contain probe entries alongside services and shortcuts. Failing probes inside a group show as a red triangle on the group's parent menu item.

One-shot healthCheck action

Any tool's actions array can include an action whose type is healthCheck. Clicking it runs an ad-hoc probe and updates the action's label inline for ~3 seconds with the result.

"actions": [
  {
    "label": "Verify Staging",
    "healthCheck": {
      "http": {
        "url": "https://staging.example.com/health",
        "expectStatus": 200
      }
    }
  }
]

The healthCheck action shape mirrors a probe's http or tcp block, minus runtime fields (interval, autoStart).

Full example

{
  "paths": ["~/.nvm/versions/node/v22.0.0/bin", "/opt/homebrew/bin"],
  "tools": [
    {
      "name": "MyApp",
      "directory": "~/Code/my-app",
      "autoStart": true,
      "installCommand": { "command": "npm", "args": ["install"] },
      "startCommands": [
        { "label": "Backend",  "command": "npm", "args": ["run", "dev"],
          "startupCheck": "http://localhost/api/health", "startupFallbackPort": 3001 },
        { "label": "Frontend", "command": "npm", "args": ["run", "dev"],
          "workingDir": "frontend", "startupFallbackPort": 5173, "waitFor": "Backend" }
      ],
      "actions": [
        { "label": "Open Frontend",  "url": "http://localhost:{{port}}/" },
        { "label": "API Docs",       "url": "http://localhost:{{port:Backend}}/api/docs" },
        { "label": "Seed Database",  "command": "npm", "args": ["run", "db:seed"] },
        { "label": "Open in Cursor", "command": "cursor", "args": ["."], "showWhen": "always" },
        { "label": "Open Project",   "reveal": ".", "showWhen": "always" }
      ]
    },
    {
      "name": "Quick Links",
      "type": "shortcut",
      "actions": [
        { "label": "Grafana", "url": "https://grafana.example.com" },
        { "label": "Jira",    "url": "https://jira.example.com" }
      ]
    }
  ],
  "advanced": {
    "startupTimeout": 45.0,
    "startupPollInterval": 1.0
  }
}