Repository navigation
feat: add a menu bar app that shows and controls the stack - #187
Merged
Merged
Conversation
A native SwiftUI menu bar app in `apps/macos/`, built with the Command Line Tools alone (`apps/macos/build.sh`). It is a client of the stack CLI: it reads `./stack status --json` every minute and when the panel opens, and runs `./stack up|down|restart <id>` in the background. - one row per installed stacklet with its state, port and health issues - start, stop, restart and open in the browser from the row - first installs, logs and doctor open in Terminal, since they prompt or never end - restart for stacklets running code the checkout has moved past - disk and memory from the host figures in the status The app starts no services and keeps no state beyond the chosen checkout. It is a prototype: not built in CI and not distributed.
`stack doctor` found a stacklet's containers by the name pattern `stack-<id>-*`. A stacklet with one service is named `stack-<id>`, so doctor never saw the code stacklet's Forgejo container: an exited or drifted `stack-code` went unreported. Containers are now matched by their compose project, `stack-<id>`, which every container of a stacklet carries whatever its name.
`stack doctor --json` prints the running version, the one-line summary and every finding with its level, title, detail and fix command. The exit code is unchanged: 1 when any finding is an error.
`stack errors` reads the log of every stacklet container over the last 24 hours and lists each container that logged errors, how many, and the newest three lines. `--since 30m` or `--since 168h` changes the window; `--json` gives the newest five per container with their timestamps. A line counts when its log format marks it as an error: an `ERROR`, `CRITICAL`, `FATAL` or `PANIC` level, Forgejo's `[E]`, nginx's `[error]`, `level=error`, or the start of a Python traceback. Lines that only mention an error in their message are not counted. The README and the admin guide have listed this command for a while; it now exists.
`stack host` prints the machine's memory, disk and uptime, and each stacklet's memory and CPU summed over its containers, largest first. `--json` gives the same for scripts. The per-stacklet figures come from `docker stats`, which samples for about two seconds. A stacklet that runs on the host rather than in containers, such as the AI server, has none.
The panel now opens with a verdict and a list of what needs the admin: `stack doctor` findings, backups that are missing, failed or overdue, and a disk above 90%. Each item shows its fix command; `up`, `restart` and `down` run in the background, anything else opens in Terminal. Below it: memory per stacklet, the containers that logged errors in the last 24 hours with their newest line, the last backup, and disk, memory and uptime. These come from `stack doctor --json`, `stack errors`, `stack host` and `stack backup status`, read when the panel opens and every ten minutes. The icon warns only on errors, so a standing warning such as "no backup set up" does not keep it lit. Against an older checkout without those commands, their sections are left out.
`stack config` read `stack.toml` from the checkout even when `STACK_DIR` points the CLI at another instance, so it printed the wrong file or none. It now reads the file every other command uses.
`stack config --json` prints both files as written, with the paths to them, for scripts and the menu bar app. Any key named like a credential (ending in `key`, `password`, `secret` or `token`) is replaced by `(hidden)` wherever it appears, so an AI server's API key or a password in users.toml never leaves the file. An empty credential stays empty, so set and unset can still be told apart.
A collapsed Setup section lists the family from users.toml, port or domain mode, language and time zone, the AI server and model, the update time and the data dir, read from `stack config --json`. Two buttons open `stack.toml` and `users.toml` in the text editor; doctor then names what still runs with the old setting. A missing backup is no longer reported: the backup stacklet is in beta. A failed or overdue run is still reported once it is installed.
The panel had grown taller than the screen. Overview keeps what an admin checks often: what needs attention, the stacklets, recent errors, disk and memory. Setup holds what changes rarely: the configuration, the stacklets not installed yet, and the checkout with its version. Sections are cards with more room between rows, stacklet rows show memory with the port moved into their menu, and the panel scrolls once it reaches the height of the screen. `StackMenu --snapshot <prefix>` renders both tabs from the real data to PNG files, to check a layout without opening the menu.
The menu bar shows the "a" with its two dots in monochrome, so macOS tints it like every other menu bar icon. State stays monochrome: a badge dot when something needs attention, hollow dots while an action runs, a dimmed mark when the CLI does not answer. The app icon is the dark tile of the website's favicon, rendered at build time from the same drawing. The outline of the "a" is generated once from Inter Bold (SIL Open Font License) by `apps/macos/script/logo-outline.py`, so the app ships no font. `--snapshot` also renders the icon states, and neither tool mode puts a second icon in the menu bar any more.
`stack list --json` and `stack status --json` now give every stacklet
with a web port a `url`: `http://<LAN address>:<port>` in port mode,
`<scheme>://<id>.<domain>` in domain mode. It is the address the stack
already hands to its stacklets as `{url}`, so a client on another
machine can open a stacklet without rebuilding that rule.
A switch in Setup chooses between a checkout on this Mac and one on another Mac, and both are remembered. Remotely the app runs `ssh <host> 'zsh -lc "cd <checkout> && ./stack …"'` with the admin's SSH key and never prompts, so a server with no screen can be watched from a laptop. Logs, doctor and editing open Terminal with an SSH session to that Mac, and stacklets open at the address the stack names for them. Every CLI call now stops after a minute, lifecycle actions after fifteen, so an unreachable or hanging machine shows an error instead of a panel that never updates. Answers that arrive after a switch of machine are dropped.
A failed read or action now says what happened and what to do instead of showing raw output: - the CLI's own error reply, or its last lines, for a failed command - one message for an SSH failure (ssh exits 255) with ssh's own line and a button that tests the connection in Terminal - a missing checkout on the remote Mac, a checkout older than the app (argparse exits 2), a command that did not answer in time - a success confirms itself for a few seconds A command is done when its process exits, not when its pipes close, so a docker child holding them no longer hangs a read. Reads stop after a minute (SIGKILL if SIGTERM is ignored); an action past fifteen minutes keeps running and reports when it ends. One action runs at a time and shows its elapsed time. Reads asked for during another read run after it, the panel marks an old state as old when the machine stops answering, and it reads again after the Mac wakes. A report without JSON never shows its stdout: an older CLI that ignores `--json` can print the whole stack.toml there. The SSH host is checked before it becomes an argument, and cancelling the remote dialog leaves the switch on This Mac.
`stack doctor` derived the stacklet for `stack logs` from the container name. A container left from an older version of a stacklet keeps its old name, so `famstack-wiki-preview-de` in the memory project got `stack logs wiki`, a command for a stacklet that does not exist. The fix names the stacklet the container belongs to.
The panel said "up 0 days" for a Mac started today and "up 1 days" a day later. It gives hours for the first day, then "1 day" and "n days".
The bundle said 0.1.0 whatever the release. `build.sh` takes the version from the CLI, without a release candidate's label, which macOS does not accept there, and uses the commit count as the build number, so a release candidate and its release differ.
The admin guide gets a section on the menu bar app: what it shows, how to build and install it from the checkout, and how to connect it to the server Mac over SSH. The app's README calls it beta and names the copy step; the operator guide says to rebuild it after an update.
The menu bar app is signed for the Mac that builds it, so it ships as source. Installing it took the build script and a copy by hand, and an update meant knowing to do both again. `./stack app install` builds the app from the checkout, puts it in `/Applications` (`~/Applications` when that is not writable), replacing an older build as a whole, and opens it. Running it again updates the app, and `./stack update` says so when the app is installed.
arthware-dev
added a commit
that referenced
this pull request
Oct 7, 2026
A macOS menu bar app (beta, `apps/macos/`) shows what needs attention with the command that fixes it, each stacklet with start, stop, restart, open and logs, errors from the last 24 hours, the Mac's disk and memory, and a Setup tab. It is a client of the `stack` CLI only, on this Mac or on another one over SSH. `./stack app install` builds it from the checkout, puts it in `/Applications` and opens it; running it again after `./stack update` updates it. The CLI gains the commands the app reads: - `./stack errors`: recent error lines across every stacklet - `./stack host`: memory, disk, uptime and what each stacklet uses - `./stack doctor --json` and `./stack config --json`, credentials hidden - each stacklet's address in `./stack list` and `./stack status` `./stack doctor` checks stacklets that run a single container (Forgejo went unreported) and points an exited container's fix at its stacklet's logs. The admin guide describes installing and connecting the app.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
apps/macos/): what needs attention with the command that fixes it, each stacklet with start, stop, restart, open and logs, errors from the last 24 hours, disk and memory, and a Setup tab. It is a client of thestackCLI only, on this Mac or on another one over SSH../stack app install: builds the app from the checkout, puts it in/Applicationsand opens it; again after./stack update, which says so when the app is installed.stack errors,stack host,stack doctor --json,stack config --jsonwith credentials hidden, and each stacklet's address inlistandstatus. The admin guide describes installing and connecting the app.Commits stand alone: rebase merge.