Create a `service-launcher.json` manifest at this project's root for a native macOS Service Launcher, following the v1 contract below. Inspect this project's actual source, startup scripts, configuration, and existing launchd setup first. Do not invent commands, endpoints, flags, executable locations, or registration behavior. Ask me about missing machine-specific paths or ambiguous launch modes. Do not start services or run registration commands just to generate the file. The launcher controls per-user launchd jobs in the logged-in GUI session; services survive closing the launcher, not logout. It supports two modes: 1. `managed`: the launcher generates a plist from `process`. Prefer this if the project has no existing launchd installation. 2. `existing`: point to an existing plist by `plistPath`, with matching `label`. Do not replace or regenerate that job; omit `process`, `startAtLogin`, and `restartPolicy` in this mode. JSON contract (no unknown keys or null values): ```text { "schemaVersion": 1, "id": required stable identifier, "name": required nonblank display name, "launchd": { "mode": "managed" | "existing", "label": required unique launchd label, "plistPath": existing mode only, required path, "startAtLogin": managed only, optional boolean, default false, "restartPolicy": managed only, "never" | "on-failure" | "always", default "never" }, "process": managed mode only, required COMMAND, "shutdown": optional COMMAND for a REAL supported graceful shutdown operation, "shutdownTimeoutSeconds": optional number 1..300, default 15, "healthCheck": optional { "url": real HTTP/HTTPS readiness endpoint, "timeoutSeconds": optional number 0.1..60, default 3 }, "links": optional { "open": HTTP/HTTPS browser URL }, "actions": optional [ { "id": required identifier unique among actions, "title": required nonempty menu title, "command": required COMMAND, "availableWhen": "always" | "running" | "stopped", default "always", "timeoutSeconds": optional number 1..300, default 30, "confirmation": optional boolean, default false } ] } COMMAND = { "executable": required absolute or variable-based executable path, "arguments": optional array of separate argument strings, default [], "workingDirectory": optional absolute or variable-based path, "environment": optional object of string values } ``` Ids and labels must match `^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$`. Labels cannot start `com.apple.`; prefer `local.services.`. `$schema` is optional string editor metadata; add it only if the referenced schema really exists. Use `${projectDir}` for the project folder selected at import and `${home}` for the importing user's home. These are the ONLY supported variables, substituted literally in command paths, arguments, environment values, and plistPath. Paths must begin `/`, `${projectDir}` or `${home}` and resolve to an absolute path. No `~`, relative executable paths, `${PORT}`, shell interpolation, or PATH lookup. Do not put variables in URLs, ids, names, or labels. Avoid personal absolute paths where a supported variable works. The process must remain in the foreground and be the actual long-lived server. No `&`, `nohup`, detached startup, daemonizing, or wrapper that spawns a server and exits. Prefer direct Node/Python/etc. execution; use the project's virtualenv Python when relevant. Command arguments are passed literally, WITHOUT a shell: do not include quotes around individual arguments or use shell operators as arguments. For a required wrapper script use an executable foreground wrapper with `exec`. The GUI/launchd environment is NOT an interactive shell. Verify the Node/Python/Homebrew binary location or ask me. Specify PATH in environment if scripts need Homebrew or `/usr/bin/env` interpreter lookup; do not rely on nvm/pyenv setup. Never embed secrets. If the server already reads a protected environment file, preserve that documented mechanism; there is no launcher `envFile` feature. Shutdown/actions inherit `process.workingDirectory` if omitted and merge `process.environment` with command-specific overrides; existing-mode actions must specify their own environment/directory. Executable and arguments never inherit. Prefer default SIGTERM shutdown when the server supports it; only add `shutdown` if a real app-specific graceful command exists. The launcher always performs launchd cleanup after the hook, even if it fails. Managed jobs use a 20-second launchd exit timeout. Reload, Register, Migrate, etc. are ordinary named actions invoked ONLY by user selection. Include them only if the project actually supports them. “Register” means this app's own client/integration registration, NOT installing the launcher's job. Recommend confirmation for registration, migration, and destructive operations. Commands must exit within their configured timeout, returning 0 for success; timeout terminates their process group. Do not create a fake reload command that merely starts another server. Health checks are optional: only include a real lightweight endpoint whose 2xx response means ready. No custom auth headers are supported. Check failure changes status only, never auto-restarts the server. An MCP server communicating only over stdio should usually be spawned by its client, not launchd; if this project has no independent HTTP/socket server, explain that and ask rather than generating an invalid daemon manifest. Import never runs commands. `startAtLogin: true` installs a plist for next-login startup; default false is safest. Explicit Stop persistently disables/unloads a job even if automatic restart is configured. Start re-enables it. `restartPolicy: on-failure` uses launchd SuccessfulExit=false semantics and also starts upon bootstrap. After creating the manifest, validate its JSON, validate against the supplied JSON Schema if available, and summarize: - the actual foreground server command and any required build/install steps; - machine-specific runtime paths I must verify; - health/browser URLs and included actions, with evidence they exist; - how to import it (select JSON, select project folder, review, then Start). Do not alter application behavior or introduce new server commands without asking. Use this published JSON Schema for validation and the manifest's $schema field: https://service-launcher.gorbunov.dev/schema/service.schema.json