Follow the rail in order. Each step builds on the session definition before it, so the commands stay useful as your project grows.
One command
01 / 08
Install the app, CLI, and local service.
Homebrew installs the signed app and gives your shell the launch command. The local catalog lives in Application Support, so it stays separate from any one project.
From your first launcher to a multi-service workflow. Syntax, defaults, constraints and practical examples, all in one place.
15 chapters50 topicsmacOS · local development
Chapter 01
Start here
Install once, then give each project a repeatable way to start. Launch Station manages local development on macOS; it does not deploy your application.
Requires macOS 13 or newer. Homebrew installs the signed, notarized app, the launch command, and its local service. Your project still needs its own tools and dependencies: Node for npm/Expo, Python for Python commands, or Xcode and a downloaded Simulator runtime for iOS.
Install Launch Station
brew install --cask JakeMawson/tap/launchstation
launch --help
launch doctor
Open Launch Station from Applications. launch doctor checks service compatibility and reports the installed service version. This reference covers the current CLI surface, including named endpoints; if a flag is unknown, check your installed version and update.
An existing directory registered in the local catalog. It groups launchers and has a display name.
Launcher
A named, saved definition: description, tags, one or more actions, one primary action, and optional named endpoints.
Action
One command or target, with a runner, working directory, environment, startup order, port policy, and stop/readiness rules.
Primary action
The action that receives runtime arguments and the run-time open request. It is not necessarily the first action to start.
Session
One recorded execution of a launcher. It snapshots the definition/revision, exact process ownership, endpoints, output, and state.
Primary / additional
One reusable primary session slot per launcher; --new creates a separate additional session.
Catalog / mirror
SQLite is authoritative. launch_details.md is a generated read-only project summary, not a configuration file to edit.
Saving a launcher does not run it. Editing a definition does not change an already-running session. Relaunch creates a fresh session using the updated definition.
In Terminal, move into an existing project folder. This self-contained example serves that folder using Python 3; confirm python3 is installed first. It only exposes a loopback address.
Register, run, inspect, close
cd ~/Projects/my-project
launch init . --project-name "My project"
launch create "My project docs" "Serve this project's files locally" \
--type process --port auto \
--url 'http://${HOST}:${PORT}/' \
--health 'http://${HOST}:${PORT}/' \
-- python3 -m http.server '${PORT}' --bind '${HOST}'
launch "My project docs" --open
launch status "My project docs"
launch logs "My project docs"
launch close "My project docs"
Choose a globally unique launcher name. Replace the example directory and project name with your own. Serving a directory makes its files available to local browser clients, so choose the directory intentionally.
Chapter 02
Command syntax & inputs
Understand the command boundary once and every example becomes easier to adapt.
A string. Quote names containing spaces, for example "Shop web". ACTION is an action name within that launcher.
[OPTION]
Optional in a syntax synopsis. Do not type the square brackets.
VALUE...
One or more values. The ellipsis is explanatory, not literal.
UUID / SESSION_UUID
A real UUID copied from details/history/status. Never use a PID or port in its place.
N / INDEX / SECONDS
A decimal integer. Argument indices start at zero. Accepted ranges are documented per flag.
TEXT / PATH / TEMPLATE
One shell argument, quoted when needed. ~/… is supported for configured directories; prefer an absolute path or a project-relative action directory.
TOKEN / OPTION_ID
An opaque value returned by the relevant command. Copy it exactly; do not construct one.
--json
A valueless switch on supported commands. Prints structured JSON instead of the usual readable result; it does not mean dry-run.
Flags use separate tokens: --port auto, not --port=auto. Boolean switches have no following true or false. Put positional names before options where the synopsis shows that order. launch --help, launch -h, and launch help show the top-level help; there is no universal per-command --help or dry-run switch.
launch create "Shop web" "Run the frontend" --type process --port auto \
-- npm run dev -- --host '${HOST}' --port '${PORT}' --strictPort
The first -- ends Launch Station's definition options. npm is the executable; everything after it is a stored argument. The second -- belongs to npm and forwards subsequent flags to the script. --host, --port, and --strictPort at that position belong to Vite, not Launch Station.
Run: append arguments for this session only
launch "Shop web" -- --mode demo
On run or relaunch, arguments after -- are appended to the primary action's stored arguments for this session. They are not saved back to the definition. On create/action add, a nonempty executable tail replaces any --executable and --arg values, clears the shell command, and converts a shell runner to process. Do not combine the two command forms.
Single quotes preserve ${HOST}, ${PORT}, and linked-action placeholders until Launch Station starts the action. Double quotes around a path such as "$PWD" expand it in your current shell at registration time. Direct actions do not interpret pipes, &&, redirects, glob patterns, or $NAME as shell syntax. Use --command when shell syntax is needed.
Shell command form
launch create "Shop shell" "Start via zsh" \
--port auto --url 'http://${HOST}:${PORT}/' \
--command 'npm run dev -- --host "$HOST" --port "$PORT" --strictPort'
Alias
Equivalent command
--init, --create, --list
init, create, list
--details, --retrieve, retrieve
details
--update, --delete, --status
update, delete, status
--close, --relaunch, --sync, --doctor
close, relaunch, sync, doctor
launch NAME
launch run NAME
These are top-level command aliases, not additional flags for every command. --dir aliases --directory only on commands that accept that directory option.
Chapter 03
Projects & discovery
Register the project root, find its launchers, and inspect the saved definition before changing it.
Initialize an existing folder. Paths are canonicalized and symlinks resolved. Re-initialization is idempotent.
--project-name NAME
Optional on init; folder name when omitted
Human-readable project label.
--name NAME
Required on project update
Changes the project display name, not its directory.
--directory / --dir PATH
Optional on project update; current directory
Resolve the registered project to rename.
--if-revision N
Optional on project update; fetched project revision
Reject an edit based on an outdated project revision.
--json
Optional; readable text
Return the project object.
Launcher creation resolves the exact project or nearest registered ancestor of the supplied directory. Initialize the actual repository/application root deliberately. A conflicting unmanaged launch_details.md is not overwritten; inspect it before deciding how to resolve the conflict. There is no CLI project-move or project-delete command.
launch list [QUERY] [--tag TAG] [--state STATE] [--directory PATH] [--json]
Input
Default
Behavior
QUERY
All launchers
One search string matching launcher name, description, or tags.
--tag TAG
No tag filter
One normalized tag filter. Unlike create's repeatable --tag, repeating this filter leaves the last value.
--state STATE
No state filter
Matches an active session or the launcher's latest session. idle is a display label, not an accepted state filter.
--directory / --dir PATH
All projects
Resolve and filter to one registered project.
--json
Readable list
Return complete launcher detail objects.
Combine filters
launch list "web" --tag local --directory ~/Projects/shop
launch list --state running --json
Launcher names are unique across all projects. Matching ignores case, repeated/surrounding whitespace, accents, and character width. Names allow at most 80 characters and 240 UTF-8 bytes, cannot begin with a hyphen, and cannot contain slashes or control characters. CLI command words such as run, status, project, and help are reserved launcher names.
launch details NAME [--json]
launch retrieve NAME [--json]
launch status [NAME] [--json]
launch doctor [DIRECTORY] [--json]
Command
Result
details / retrieve NAME
The saved launcher and project, revision, actions, tags, notes, named endpoints, active sessions and last session.
status NAME
The same detailed launcher view, useful before acting on a specific session.
status without NAME
Only currently active managed sessions across the catalog.
doctor [DIRECTORY]
Service version/schema/compatibility, endpoint, CLI path, and whether that directory resolves to a project. Defaults to the current directory.
These commands inspect the catalog and service; they do not launch your project. JSON is useful for copying exact session IDs and revisions. External listeners are a separate observation surface.
Chapter 04
Create & configure actions
A launcher starts with one action. The tables below cover every definition option and its initial default.
launch create NAME DESCRIPTION [RUN_DETAILS] [OPTIONS] -- EXECUTABLE ARG...
launch create NAME DESCRIPTION [RUN_DETAILS] --command COMMAND [OPTIONS]
NAME and DESCRIPTION are required nonempty strings. The project must already be initialized. Supply a runnable command or target: a direct executable tail, --command, or an explicit runner with --executable. Creation saves a definition only. RUN_DETAILS is optional descriptive prose and is never executed.
Flag / input
Default / requirement
Meaning
NAME
Required
Globally unique launcher name.
DESCRIPTION
Required
Non-whitespace description of what the launcher does.
RUN_DETAILS or --run-details TEXT
Optional; absent
Longer notes. Supply the positional form or the flag, not both.
--directory PATH / --dir PATH
Current directory
Registered project scope, not the action's working directory.
--tag TAG
No tags; repeatable
Add a searchable tag.
--tags A,B
No tags; repeatable
Add comma-separated tags. Empty entries are ignored; normalized duplicates collapse.
Names this action. Action names are unique inside a launcher; command words are allowed.
--action-description TEXT
String; Primary launch action on create
Human-readable purpose; cannot be blank. On action add, its positional DESCRIPTION supplies this value.
--cwd PATH
Existing directory; .
Relative to the registered project, or absolute/tilde-expanded. Does not create a directory.
--order N
Integer; 0 on create
Ascending startup order. Values must be unique inside the launcher. Negative integers are accepted. Added actions default to current maximum + 1.
--type TYPE
process, shell, app, url, ios
Default model is shell; an executable tail selects process unless app/url/ios was selected. Prefer explicit --type.
--command COMMAND
Nonempty zsh command
Selects shell and clears executable. Shell actions run through /bin/zsh -lc.
--executable VALUE
Executable path/name or target
For process/ios: an executable. For app: application path/identifier. For url: an absolute URL or existing absolute file.
--arg VALUE
String; empty array; repeatable
Append one stored argument. Even --arg --verbose stores one literal flag. Not allowed for URL actions.
--app-bundle-id ID
String; absent
Optional native app identifier. Use with an app action; not a command to install the app.
--required / --optional
Required by default
Required failure rolls back earlier owned actions. Optional failure may leave a partial session.
--allow-runtime-args / --deny-runtime-args
Allowed by default, except URL
Whether session-specific arguments may reach this action. Only the primary receives them.
Changing --type to shell clears executable; changing to a non-shell runner clears the shell command. Choosing url also clears arguments and disables runtime arguments. Put the type before its command fields, and finish with a valid combination. A bare --executable does not itself switch the default shell runner.
Split at the first =. Stores a non-secret string. Repeated keys overwrite earlier values; empty values are allowed.
--inherit-env NAME
Repeatable; none
Copy that named value from the daemon's environment at launch—not your current Terminal's environment.
Environment name
Starts with a letter or underscore; then letters/numbers/underscores
Use portable ASCII names such as APP_MODE. Invalid names fail validation.
Reserved names
CODEX_PORT, CODEX_HOST, CODEX_SERVICE_ID
Cannot be configured or inherited; managed lifecycle owns them.
Reserved prefix
LAUNCH_STATION_ACTION_
Cannot be assigned/inherited manually; earlier compound actions supply these values.
Do not put secrets in any saved field. Names, commands, arguments, environment values, descriptions, tags, and notes are visible in the app, JSON, database, and generated project summary. Have the launched program read credentials from an appropriate secret store.
When a managed port is enabled, its configured port and host aliases cannot also appear in --env or --inherit-env. Baseline PATH includes ~/bin, Apple system paths, /opt/homebrew/bin, and /usr/local/bin. A daemon is not your interactive shell: use a known executable or absolute virtual-environment path rather than relying on aliases, nvm activation, or .zshrc.
Chapter 05
Runner recipes
These are templates for existing projects with dependencies installed. Adapt directories, script names and targets before saving. Flags after the command boundary belong to that tool.
launch create "Shop Vite" "Run the Vite frontend" \
--directory ~/Projects/shop --tag web \
--type process --port auto \
--url 'http://${HOST}:${PORT}/' --health 'http://${HOST}:${PORT}/' \
-- npm run dev -- --host '${HOST}' --port '${PORT}' --strictPort
Vite's --strictPort stops it from silently moving to another port. Launch Station must know the actual listener it owns.
Next.js project with a Next dev script
launch create "Shop Next" "Run the Next.js frontend" \
--directory ~/Projects/shop-next --type process --port auto \
--url 'http://${HOST}:${PORT}/' --health 'http://${HOST}:${PORT}/' \
--ready-timeout 90 \
-- npm run dev -- --hostname '${HOST}' --port '${PORT}'
Next.js uses --hostname, not Vite's --host. Do not add Vite's --strictPort to a different CLI. If your dev script invokes another framework, use that framework's actual host/port syntax.
launch create "Research files" "Serve the local public directory" \
--directory ~/Projects/research --cwd public \
--type process --port auto \
--health 'http://${HOST}:${PORT}/' --url 'http://${HOST}:${PORT}/' \
-- python3 -m http.server '${PORT}' --bind '${HOST}'
A project's installed Uvicorn
launch create "Research API" "Run the ASGI API" \
--directory ~/Projects/research --type process --port auto \
--health 'http://${HOST}:${PORT}/health' --url 'http://${HOST}:${PORT}/docs' \
-- ./.venv/bin/uvicorn app:app --host '${HOST}' --port '${PORT}'
This assumes .venv contains Uvicorn, app:app exists, and the API implements /health. Replace those values to match your code. Slash-containing relative executables resolve from the action's working directory.
The last recipe assumes your server supports those two flags. --port-env changes the process variable alias, not the ${PORT} spelling used in endpoint templates.
Use process for exact argument boundaries; use shell when the shell must interpret a pipeline or redirect. Required executables/scripts must already exist. A finite command transitions to exited when it finishes; Launch Station does not keep it running or invent a server endpoint.
Shell stored arguments and runtime arguments are quoted and appended once. Direct executable, argument, and environment strings support ${NAME} substitution from the action environment. Undefined values cannot stand in for required configuration; inspect your command and output.
Application arguments are passed through LaunchServices as app arguments. They are not shell commands or guaranteed document-opening instructions. Launch Station only closes an exact newly owned instance; if LaunchServices reuses an existing app, it leaves that instance alone.
A URL runner accepts one absolute URL or existing absolute file path, stores no arguments, and completes after opening it. It does not own or close the receiving browser/editor. Use this form for a workspace/document when you only want it opened.
Chapter 06
Expo & iOS Simulator
Expo has specific port and Simulator behavior. Keep the Metro service, launch target, and device selection distinct.
launch create "Mobile Expo" "Start Expo in iOS Simulator" \
--directory ~/Projects/mobile --tag expo --tag ios \
--action-name metro --action-description "Expo Metro bundler" \
--type ios --port auto --open simulator \
--ready-timeout 120 \
-- npx expo start --ios --go
Requires a working Expo project and Xcode with an available iOS Simulator runtime. Launch Station recognizes expo, npx expo, and supported shell forms. It adds a missing localhost host mode and managed port, and adds --ios when simulator opening is configured. Explicit host modes, ports, --ios, and -i are not duplicated.
Expo's --go selects Expo Go. Replace it with --dev-client when you already have a compatible development build installed. Without either override, Expo chooses according to the project's development-client setup. These are Expo flags, not Launch Station definition options.
launch create "Mobile Metro" "Start Metro without opening a device" \
--directory ~/Projects/mobile --type process \
--port auto --open none \
-- npx expo start --localhost --port '${PORT}'
Use process, --open none, no --ios/-i, and no Simulator-specific environment when you want no device interaction. An ios runner can still select/boot a Simulator as part of preparation. The managed process has no attached interactive Terminal UI; use saved flags and session open choices rather than expecting to press Expo's interactive keys.
Expo web differs across bundlers/SDKs. Check npx expo start --help for the project's installed version before storing --web; confirm that the web server actually consumes the allocated port. Do not assume Metro and a separate web listener share a port. Give each persistent listener its own managed action. For exported static web files, use the Python static-server recipe.
The device name must exist on your Mac. Use xcrun simctl list devices available to see available names/UDIDs. Exact device IDs are less ambiguous when names repeat.
Replace the bundle identifier with your app's installed identifier. A built app can be prepared using the APP_PATH/BUNDLE_ID/APP_ARGUMENTS environment fields above; it must be a Simulator-compatible build.
Chapter 07
Ports, environment & endpoints
A port is a resource, not proof of ownership. Configure the actual listener and readiness path together.
--port auto asks codex-port for a verified loopback allocation. --port N requests a fixed port and refuses an unrelated listener. --port none uses the process-group runner without a managed TCP port. A URL field alone does not turn an unmanaged listener into a managed one.
The launched command must consume the allocated host/port, through environment variables or arguments. A framework that silently switches ports can invalidate the expected endpoint, so use its strict binding option when available. Managed host defaults to 127.0.0.1; detected Expo uses localhost.
HTTP health readiness succeeds on status 200–399 within the ready timeout. Without an HTTP health URL, successful process start is not proof that your application is usable. A required failure closes earlier owned actions; an optional failure may produce a partial session.
The port lease defaults to eight hours. The daemon renews active managed ownership before expiry while it remains healthy. A fresh lifecycle may reuse the same numeric port after the old listener has fully stopped; fresh means a new verified owner, not necessarily a different number.
${HOST}, ${PORT}, {host}, {port}, {{host}}, {{port}}. Result must be an absolute URL; health requires HTTP(S).
Direct executable / arguments / --env values
${NAME} references from the action's runtime environment.
Shell command
Normal zsh environment expansion, such as $PORT or ${PORT}.
Action --cwd
An existing directory path, not a shell expression to execute.
Cross-action references
${LAUNCH_STATION_ACTION_API_URL} and related HOST/PORT values in commands, arguments, or environment—not URL/health templates.
A port placeholder in a URL or health template requires auto/fixed mode. For a no-port static endpoint, HOST aliases resolve to loopback. Browser opening uses the action URL, then health URL; a managed port can otherwise supply a default HTTP endpoint.
Launcher update only; repeat once per desired destination. Supplying endpoints replaces the entire list, not just one row.
--clear-endpoints
Launcher update only; remove all configured named endpoints.
Endpoint name
Same normalized-name limits as an action; no colon. Names and paths must be unique; at most 20 destinations.
Endpoint path
Start with one /; no origin, //, whitespace, query, fragment, backslash, encoded slash/backslash, or ./.. segments. At most 2,048 characters / 8,192 UTF-8 bytes.
These are navigation shortcuts under the primary action's exact running HTTP(S) origin. They do not create server routes, add services, or perform health checks. The app offers them only when the selected session has a usable browser origin. Configure them after creating the launcher and relaunch to give a new session the updated endpoint snapshot.
Chapter 08
Compose a full stack
Each long-lived service gets one action. Explicit order and linked values connect the services without hard-coded ports.
Action add requires the launcher name, action name, and nonempty action description. It supports the action-definition flags in section 04, but not launcher-level --directory, --run-details, --tag, or --tags. It fetches and binds the current launcher revision internally; action add does not accept --if-revision.
Action update accepts --name / --description as aliases for action name/description, plus the mutation flags in section 10. It edits that action without changing which action is primary. Action delete follows the confirmation rules in section 10. The sole remaining action cannot be deleted; deleting the primary in a multi-action launcher selects the first remaining ordered action.
Assumes a project with server/server.mjs accepting host/port arguments and a frontend directory containing a Vite dev script. Install both projects' dependencies first.
Create the provider before its consumer
launch init ~/Projects/shop --project-name "Shop"
launch create "Shop stack" "Start API and frontend together" \
--directory ~/Projects/shop --tag compound \
--action-name api --action-description "Local API server" \
--cwd server --order 10 --type process --port auto --port-name api \
--url 'http://${HOST}:${PORT}/' --health 'http://${HOST}:${PORT}/health' \
-- node server.mjs --host '${HOST}' --port '${PORT}'
launch action add "Shop stack" frontend "Vite frontend" \
--cwd frontend --order 20 --type process --port auto --port-name frontend \
--url 'http://${HOST}:${PORT}/' --health 'http://${HOST}:${PORT}/' \
--env 'VITE_API_URL=${LAUNCH_STATION_ACTION_API_URL}' \
-- npm run dev -- --host '${HOST}' --port '${PORT}' --strictPort
launch update "Shop stack" --primary-action frontend
launch details "Shop stack"
launch "Shop stack" --open
launch close "Shop stack"
API starts and becomes ready first, then frontend. The frontend receives the actual API URL. Closing reverses the order. Runtime arguments and bare --open apply to frontend because it is primary.
For a database or worker, add another action with a unique earlier order and the project's real start command. If it opens a TCP listener, configure that listener's own managed port and explicit binding. A placeholder script does not automatically make database readiness or port management work.
Startup is ascending; shutdown is reverse. Action add defaults to maximum + 1, so gaps are safe.
Unique normalized names and linked tokens
api-worker and api worker both produce API_WORKER; a token collision is rejected.
Provider must be earlier and required
A consumer cannot depend on an optional or not-yet-started provider.
Use only exposed HOST/PORT/URL values
Managed providers expose all three. A no-port action with a static endpoint exposes URL only. URL one-shot actions do not expose provider values.
Link only process, shell or ios consumers
Native app and URL runners cannot consume linked-action references.
No linked variables in URL/health templates
Put them in arguments or --env instead; each action's readiness template describes its own endpoint.
Token generation uppercases the normalized action name and replaces punctuation with underscores. Prefer simple ASCII action names such as api and frontend. Names without an ASCII alphanumeric token use an ID-derived fallback; inspect the returned definition instead of guessing it.
Chapter 09
Run, open, inspect & close
Use the saved launcher name to start work; use a returned session UUID when choosing an additional instance.
launch NAME [--new] [--open] [--json] [-- RUNTIME_ARG...]
launch run NAME [--new] [--open] [--json] [-- RUNTIME_ARG...]
launch relaunch NAME [--session UUID] [--open] [--json] [-- RUNTIME_ARG...]
Input
Default
Behavior
NAME
Required
Saved launcher name.
--new
Off; run only
Create an additional independently owned session. Does not adopt outside processes.
--open
Off; no value
Request the primary action's opening behavior for a new run.
--session UUID
Primary; relaunch only
Replace this exact active session belonging to the named launcher.
-- RUNTIME_ARG...
Empty
Append to the primary action's saved arguments for this session, if allowed.
--json
Off
Return session data; relaunch returns previousSession and session.
Ordinary run returns an existing active primary unchanged. New runtime arguments and --open do not modify or re-open that reused instance. Use relaunch to apply new arguments, or launch open to select an existing target.
Relaunch is one daemon operation: reserve the exact selection, close it, then start a distinct replacement. It preserves primary/additional role and leaves other sessions alone. From idle it starts a new primary. It refuses an overlapping replacement if close loses ownership or the definition/selection changes during the operation.
launch open NAME [--session UUID] [--json]
launch open NAME --option OPTION_ID [--session UUID] [--probe] [--json]
Flag
Meaning
--session UUID
Optional; active primary by default. Use the actual additional session UUID to target that instance.
--option OPTION_ID
Optional, once. Without it: list choices only. With it: open the exact daemon-derived choice.
--probe
Requires --option; supported for existing Expo options. Validate without opening. It is not a general HTTP health checker.
--json
Structured list/result instead of text.
Option IDs are opaque and session-specific. Browser URLs, native app focus targets, named endpoints, or Simulator choices come from the daemon; do not substitute a raw URL, PID, or invented option ID. An expired or unavailable target must be refreshed from the current session.
Returns a page with sessions and optional nextCursor.
History is newest first and records managed runs, including their original revisions and action definitions. It is not a list of every process on your Mac. External listener observations never become managed history automatically.
Without --session, close targets the active primary only; if none exists, it reports a conflict instead of picking an additional instance. With a UUID, it verifies membership and that the session is active. Closing a multi-action session reverses startup order.
State
Meaning
starting
Actions are being started or checked for readiness.
running
The managed session is active.
partial
Some action work succeeded, but not every action is running successfully.
stopping
Exact owned work is closing.
exited
The session ended; inspect exit code and logs for context.
failed
Startup, readiness, or execution failed; inspect the recorded error and logs.
orphaned
Exact ownership cannot be proven. It is not permission to kill a similarly named process.
idle
A catalog/UI label when no active session exists; not a stored session state.
Process-group close escalates SIGINT → SIGTERM → SIGKILL within the recorded budget only while exact identity holds. Managed-port close delegates to the recorded manager ID. App close excludes reused instances; URL close does not close a receiving app; iOS close never shuts down Simulator.
Chapter 10
Edit & delete definitions
Edits are revision-bound and apply to future runs. Inspect the current definition first, then change only the fields you intend.
launch update NAME [LAUNCHER FIELDS] [ACTION MUTATIONS] [--if-revision N] [--json]
Flag
Input / default
Behavior
--name NEW_NAME
String; unchanged
Rename the launcher, subject to global uniqueness.
--description TEXT
Nonblank string; unchanged
Replace the launcher description.
--run-details TEXT / --clear-run-details
String or valueless clear; unchanged
Set or remove notes. Use one form intentionally.
--tags A,B / --clear-tags
List or clear; unchanged
Replace or empty the tag list.
--add-tag TAG / --remove-tag TAG
Repeatable strings
Add/remove normalized tag values.
--primary-action ACTION
Existing action name; unchanged
Select primary. Must precede any action mutation flags if used together.
--endpoint 'Name: /path' / --clear-endpoints
Replacement list or clear; unchanged
Set the complete named endpoint configuration.
--if-revision N
Integer; freshly fetched revision
Reject stale edits instead of overwriting a changed definition.
--json
Off
Return updated complete detail.
--tag is a creation flag; use --add-tag or --tags when updating. Project directory is not changed through launcher update. Action edits in this command target the current primary unless --primary-action selects another first.
Change a description and choose primary
launch update "Shop stack" --description "API and frontend for local work" --primary-action frontend
All action fields from section 04 can be changed using launch update or launch action update. In addition to those setters, the following flags alter or clear existing values. The command is validated as a complete action before saving.
Mutation
Input / behavior
--action-name NAME / --action-description TEXT
Rename/describe the action. action update also accepts --name and --description.
--clear-command
Clear the shell command. Pair with a valid runner/executable change; a shell action cannot remain empty.
--clear-executable
Clear executable/target. Pair with a valid shell command/runner change.
--arg VALUE / --append-arg VALUE
Append one stored string argument; repeatable.
--clear-args
Replace the argument array with an empty array.
--remove-arg VALUE
Remove every argument exactly equal to VALUE, including duplicates.
--set-arg INDEX VALUE
Replace an existing zero-based argument index with one string.
--args-json '["a","b"]'
Replace the complete argument array. Must be a JSON array of strings, excluding the executable.
--env KEY=VALUE / --remove-env KEY / --clear-env
Set one value, remove one key, or clear the stored environment.
--inherit-env NAME / --remove-inherit-env NAME / --clear-inherit-env
Add, remove, or clear inherited daemon variable names.
Mutation flags are applied in order. For example, --clear-args --arg a --arg b produces ["a","b"]. Prefer an atomic --args-json replacement when changing an entire command. Switching to URL clears arguments and denies runtime arguments.
launch action update "Shop stack" api \
--ready-timeout 90 --env LOG_LEVEL=debug
launch action update "Shop stack" api --remove-env LOG_LEVEL
An explicit --if-revision refers to the launcher revision, even for action mutations. On conflict, retrieve fresh details and review the concurrent change before retrying. Do not blindly repeat a mutation after a lost response; it may already have succeeded.
launch delete NAME
launch action delete LAUNCHER ACTION
Explicit noninteractive form
launch delete NAME --yes --if-revision N [--json]
launch action delete LAUNCHER ACTION --yes --if-revision N [--json]
Interactive deletion requires a Terminal/TTY and the exact displayed launcher/action name. --yes skips the typed prompt only when accompanied by --if-revision N; it is not an unrestricted force option. Supply the revision you just inspected.
Deleting a launcher is refused while managed sessions are active, when confirmation expires, or when its revision changes. The shortcut is removed from the catalog; project source files and durable history are not deleted. An action cannot be removed if it would leave the launcher empty. Do not delete and recreate a launcher merely to change its command.
Chapter 11
Separately started processes
Observe local listeners without confusing discovery with ownership.
launch external list [--refresh] [--json]
launch external draft OBSERVATION_UUID [--json]
List returns the daemon's cached listener inventory; --refresh requests a current scan. Each observation has an ephemeral UUID, PID identity, endpoints, ownership classification, and available command information. Bare launch external defaults to list.
Draft refreshes the observation and returns a proposal only. It neither saves a launcher nor adopts the process. Review directory, runner, command, arguments, and port policy, then create a launcher through the normal flow if appropriate. Use the real command rather than a redacted placeholder.
Requires a TTY and the exact freshly displayed confirmation text. It accepts no --json, --yes, arbitrary PID, or port argument. The daemon rechecks identity and closability before signaling.
Stale, already-managed, unverified, or unsafe observations are refused. Observation IDs and close intents are transient and do not survive daemon restart. After saving a reviewed launcher, future managed sessions have their own identities; the previously outside process does not become one of them.
Chapter 12
Use the macOS app
The desktop app and CLI share the same catalog, actions, revisions and managed sessions. You do not need agent configuration to use either.
Open Launch Station from Applications. Search by launcher name, project, directory or tags. Select a launcher to inspect its working directory, run details, saved actions, endpoint configuration and active sessions.
Control
What it does
LAUNCH
Starts an idle primary and requests its primary target to open.
LAUNCH NEW
Creates an additional exact managed session.
Runtime arguments
Arguments for the next launch/relaunch only; quotes and backslashes group values. Not a shell command field.
OPEN
Choose a daemon-derived destination for that session, including available named endpoints.
CLOSE / RELAUNCH
Review the exact service list and confirm the selected session operation.
Logs / activity / history
Inspect current/last output, lifecycle diagnostics, or durable managed sessions.
Copy / reveal
Copy a displayed command or reveal its working directory in Finder.
To create a launcher without Terminal, click Add Launcher below the catalog. Enter a name, description, and existing project directory (Browse can select it). Choose Process for a direct executable and arguments, or Shell for an intentional shell command. Review the working directory, port mode and opening behavior; the form marks uncertain fields as Review required. Confirm that you have reviewed the command, directory, and port settings, then save. Saving defines the launcher; it does not start it.
For a managed web server, choose an automatic port and pass its HOST/PORT placeholders to your server command. Enable the HTTP open target only when the action actually serves HTTP. Command arguments are separate values, not arbitrary shell source. The form's validation must pass before Save is available.
Edit Launcher exposes saved metadata, actions, runner/command, ports, opening behavior, named endpoints, and advanced action controls. Save commits a revised definition; running sessions retain the definition they started with. Use the CLI's full flag surface when a specialized property is not exposed in the editor.
Entries marked STARTED SEPARATELY are observed listeners, not managed sessions. Add Launcher opens a reviewable proposal. Saving that proposal does not take over the already-running process. External Close has its own exact confirmation.
The history view can inspect one launcher or all managed launchers. Session rows distinguish primary/additional role, start/end times, state and logs. The app supports light/dark appearance and reduced motion.
Settings → Agent integration can install/reinstall a managed skill, inspect its state, or export the standalone SKILL.md. These integrations are optional and are not required for using the app or CLI.
Application updates shows current availability and a Check for Launch Station updates control. Automatically prepare updates can download/prepare an update but does not restart the app or interrupt a launcher by itself. Apply a prepared update through the app's restart flow once safe.
Closing the app window is not the same as closing managed sessions. Use the session's Close control to stop its owned work. The local service is responsible for session lifecycle and recovery.
Chapter 13
Skills, sync & maintenance
Optional integrations and catalog maintenance have distinct scopes and confirmation rules.
Bare launch skill defaults to status. status reports product detection and managed file/version state. source prints the canonical standalone SKILL.md; with JSON it returns the source object. install installs or refreshes only the chosen supported product's managed files.
Product
Shared location / behavior
codex
~/.agents/skills/launchstation; shared by Desktop/CLI and the IDE's skill reader.
claude-code
~/.claude/skills/launchstation; shared by local Desktop/CLI.
uninstall
Interactive TTY and exact confirmation required; no --json or --yes. Removes only receipt-proven managed files, preserving unrelated files.
Installation refuses unsafe destinations and unavailable supported hosts. Reinstalling preserves sibling skills. Product detection is not the same as file verification: a product may be available while its skill is missing, outdated, or blocked. None of these integrations is a prerequisite for this manual.
DIRECTORY defaults to the current directory. Check is the default and compares generated content and read-only permissions. Drift returns exit code 4. Repair regenerates the daemon-owned launch_details.md; it does not import handwritten changes into the catalog. If both switches are supplied, the last one wins—prefer one.
The mirror is generated from SQLite after catalog edits. Never hand-edit or chmod it as a configuration workflow. If the project is offline or unwritable, the catalog can report pending/drifted/failed synchronization until the directory becomes available.
These commands exist for the signed app upgrader, not ordinary project startup. Preparation requires a completely idle daemon and places a temporary mutation reservation. It rejects mutations until cancelled, expired after 120 seconds, or the daemon restarts.
JSON preparation returns a short-lived cancellation capability. Keep it private in installer memory and cancel the exact reservation if installation fails. The non-JSON preparation message does not expose the token. A reservation does not itself install an update or authorize changing app files.
Chapter 14
Local HTTP API
The CLI is the easiest client. This section describes the authenticated JSON interface for building your own local integration.
The daemon listens on a dynamic loopback port. launch api endpoint prints the current base URL; with JSON it returns an endpoint field. Do not hard-code the port. Every route, including health, requires the current bearer token held in private service metadata.
A purpose-built client may load ~/Library/Application Support/Launch Station/service.json into private process memory, use its endpoint/token for the request, and avoid printing or logging credentials. Reload metadata after restart/401. Never expose this API to the network: it can run commands as your macOS user.
Requests/responses use JSON; logs return a SessionLogResponse with text and structured diagnoses. Responses use Cache-Control: no-store, timestamps use ISO 8601, and IDs are UUID strings. HTTP/1.1 accepts GET/POST/PATCH/DELETE, with a 16 KiB header and 1 MiB body limit. Mutations carrying expectedRevision must also send the same decimal If-Match header.
HTTP status
Interpretation
401
Missing/invalid bearer token; reload current private metadata.
404
Object/route not found; inspect names/IDs.
409
Duplicate/active-session/reservation conflict.
412
Stale revision or mismatched If-Match.
422
Invalid definition or unavailable installation host.
5xx
Service failure; preserve evidence and inspect before retrying.
Retrieve expiring confirmation intent and current definition.
DELETE /v1/launchers/{id}
expectedRevision, intentToken
Confirmed shortcut deletion.
LauncherPatchRequest contains required expectedRevision plus optional name, description, runDetails, replaceTags, replaceEndpoints, primaryAction and primaryActionID; boolean clearRunDetails and arrays addTags/removeTags express explicit mutation behavior. Omitted optional replacement fields preserve their current values. Endpoints are objects with id, name and path. Use complete model shapes from the public source when implementing a raw client; constructor defaults are not automatically JSON decoding defaults.
Optional action fields include shellCommand, executable, healthCheckURL and appBundleIdentifier; optional port fields include fixedPort and URLTemplate. Supply the required fields shown above when using direct Codable clients. For shell, replace executable with shellCommand; for url, use a target, empty arguments and allowsRuntimeArguments false. The same validation rules as the CLI apply.
Start mode is reuse-primary or new-instance. Relaunch uses expectedSessionID to bind the confirmed active instance, or requireIdle:true to bind an idle observation; they are mutually exclusive. expectedLauncherRevision binds the displayed definition and is rechecked after close. Relaunch returns previousSession (when one existed) and the new session.
Session JSON contains id, launcherID/name/revision, launchRole, projectSnapshot, primaryActionID, actionSnapshots, endpointSnapshots, runtimeArguments, state, actionRuns, startedAt and optional endedAt/lastError/exitCode. Action runs expose their own ID/action ID/name, state, manager, exact manager/process identity, log path, endpoint/host/port when available, and timestamps/errors. Treat manager and open-option identities as opaque. Read actual launch details --json or history output rather than constructing a runtime record yourself.
Use exact returned binding/confirmation; do not fabricate.
POST /v1/maintenance/upgrade/prepare
None
Idle upgrade reservation.
POST /v1/maintenance/upgrade/cancel
reservationToken
Cancel exact reservation.
SessionOpenRequest uses the returned optionID. External and uninstall intent responses include expiry and exact confirmation material; implement the review/confirmation step rather than treating the API as a bypass. A raw request that times out may have succeeded—read the resulting state before repeating it.
Chapter 15
Troubleshooting & limits
Use the recorded state and exact error before changing a command or stopping a process.
Inspect session state as well; a returned record can describe a failed action.
1
Unexpected local error
Read the error and logs.
2
Usage error
Check command-specific syntax and accepted flag values.
3
Not found
Recheck name, directory, observation or session ID.
4
Conflict / stale revision / mirror drift
Fetch current state; reconcile or repair the mirror intentionally.
5
Validation failed
Correct invalid fields, paths, dependencies or port configuration.
6
Service/API/transport failure
Run doctor; after an ambiguous mutation inspect before retrying.
7
Confirmation required or cancelled
Use the interactive Terminal flow or documented revision-bound deletion form.
No --version flag is provided by this CLI; use launch doctor to inspect the service version and the app's About/Settings UI for the app version. No generic --force, --dry-run, automatic process restart policy, remote deployment, or session adoption command is provided.
Verify the installation and PATH. Open a new shell after installing; do not fabricate a service metadata file.
Service unavailable
Open Launch Station and run doctor. Confirm the app/CLI match and the user service can recover; do not delete its database or token files.
Project not initialized
Run init for the existing project root. If a mirror conflict is reported, inspect the pre-existing file.
Command works in Terminal only
Use the action's actual cwd, an absolute executable/virtualenv path, and explicit non-secret environment. Interactive shell aliases and activation are not inherited.
Ready timeout
Read logs; ensure host/port placeholders are consumed and /health really exists. Increase timeout only for genuinely slow startup.
Fixed port conflict
Close the actual owning session or select a fresh managed port. Never kill whatever uses the number.
Wrong browser page
Check primary action, URL/health template, named endpoint path, and the exact session's open-options.
Simulator not found
Install an iOS runtime in Xcode and select an available exact device name/UDID.
Stale revision
Retrieve details again and review the concurrent change before updating.
Orphaned session
Ownership is unproven; inspect exact evidence. Do not use a name/port kill or relaunch an overlapping replacement.
Draft cannot be saved
Replace unavailable/redacted command text and ensure the proposed listener consumes its managed port.
Only run trusted commands under your user account. A local bearer token grants command-execution capability; protect it as a credential. Do not save secrets in launcher fields or expose the loopback service. Deleting a shortcut does not erase project files, outside processes, or managed history.
Launch Station coordinates local development; it does not install your project dependencies, create its source code, make missing server routes, replace your secret store, or deploy production. Its ownership guarantees depend on exact recorded identities, not guesses about command names.