Skip to main content

pstack-pulse

Show every Claude Code and Codex session on the iPhone lock screen through ActivitySmith and in the Mac menubar, with each plan page's pipeline, and buzz when a session needs you, ships or fails.

Source facts

Repository
udecode/dotai
Last source activity
October 11, 2026 at 11:48
Detected SKILL.md language
English
Stars
1,157
Forks
82

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
15 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
pstack-pulse
description
Show every Claude Code and Codex session on the iPhone lock screen through ActivitySmith and in the Mac menubar, with each plan page's pipeline, and buzz when a session needs you, ships or fails.
argument-hint
install [--auto-update] | update | doctor | status | menubar | uninstall
disable-model-invocation
true
# pstack pulse A daemon on this Mac turns hook events from every Claude Code and Codex session into ActivitySmith Live Activities. It shows only pstack sessions: those whose title starts with a pstack stage or state emoji, as the Session title rule sets it, such as "🚧 scrub (2/5)". A lone session gets its own card. With two or more, a summary card comes first. It is titled with the project's name when every session shares one project. When they span several, its title counts their states, such as "1 needs you · 3 working · 1 to resume". It updates in place as sessions spread. The phone's other four cards go to sessions waiting on you: those that need you, through a question or a permission prompt, then failed ones, then runs waiting for you to resume them or whose title shows Waiting, Blocked or Paused. Working sessions fill the cards left over. A shown card keeps its place until a session that ranks higher needs it, because every new card spends one of the remote starts iOS allows. A session card shows the stage and the (n/total) count from the session's title as a stage bar, its stage name in the subtitle, an Open button and a Plan button while the session's plan is still open. A question turns the session's card orange and puts the question in its subtitle, without ending the card. A project is every clone and worktree of one git origin, so two clones of plate share a card. A repository without an origin is its own project, and sessions outside any repository share one called other. The summary card lists each session that needs you, failed or is working. Across projects, the project with the fewest sessions comes first, so one ellie session leads five plate ones. Each one shows the session's name with its stage emoji in large type, as on its session card, and under it, in small type, the stage from its title, or Needs you or Failed, in its state color. Sessions that need you come first, then failed and working ones. After them come, in yellow, sessions whose turn ended while their title still shows a stage. Such a run is not finished and waits for you to resume it, and each one shows its stage too. A run whose title is back to its bare name is finished and leaves the card. A card with more than eight sessions shows seven and a more value. While a session needs you, the card's subtitle shows its question and the card's one button opens that session. Otherwise the subtitle counts the failed, working and to-resume sessions, or across projects names each project with its session count, and the button opens the session list. A card never changes type, so a question starts no new card. It pushes a notification when a session needs you or fails, and sets the app badge to the number of sessions that need you. A git push buzzes as shipped only once its turn ends, once for the newest push of the turn, and not at all when that turn hands back a page, which buzzes as replied. While you have used the Mac in the last three minutes, a push waits one minute and goes out only if it still stands: a prompt you answer, a failure you follow with a new turn, or a reply you answer by continuing the session sends nothing. Away from the Mac, a push goes at once. ActivitySmith cannot take back a push it has sent. You answer in the vendor's own app. Tapping a push opens the session: a Claude session's Remote Control link opens it in the Claude app, and a Codex push opens the ChatGPT app. A push's Plan button opens the session's plan page while that plan is open. A Claude session with Remote Control off has no link the phone can open, so tapping its push opens its plan page instead, and a card that asks for it opens the session list. ActivitySmith takes links only for a card's button, not for a tap on the card itself. That button takes only web and Shortcuts links, so a card that asks for a Codex session opens the session list too. `<skill>` below is this skill's base directory, and every command is `node <skill>/scripts/pulse.mjs <command>`. ## What it reports - Claude sessions buzz for: - a question or plan approval, at once; - a permission prompt left unanswered for about six seconds. Claude reports it through its `permission_prompt` notification, which desktop sessions send from Claude Code 2.1.233 on; in a terminal, typing restarts the six seconds. A call that a rule allows, or that another hook allows within six seconds, stays silent. The push shows Claude's own notification text, which may not name the tool. Your next prompt or the end of the turn clears the card. No hook says when a prompt is answered, so the card stays on needs you for the rest of the turn after you answer. While one prompt or question waits, a second prompt does not buzz. After you deny a prompt, Claude stops and waits, so the card stays on needs you until your next prompt; - a push the remote branch has; - a required check still failing when the turn stops; - an API error; - a session that died while working. - Codex sessions from the app or the CLI buzz for questions and permission prompts, and show their plan. Codex pushes, failed checks and lost processes are not observed, because Codex hooks carry no git result or tool failure. - A required check counts only when the Bash command is the check itself, optionally with flags, such as `bun check --bail`. Any other form, such as `bun check | tail`, a chain or a second line, records nothing, because its exit status is not the check's. A configured check that is itself a chain never counts. - A session is working while a turn runs. It stays working after the turn while a background shell, subagent or monitor it started still runs, because that work wakes it with a new turn. An open artifact page watch does not count, because it never wakes the session. A dev server left running in the background keeps the session working until the server stops. - Panel seats, smoke runs, headless `claude -p` runs, subagents and Codex exec and review threads never show. A session started with `PSTACK_PULSE_OFF=1` in its environment never shows either. - A turn that hands back a plan page buzzes once as "<session> replied", with the first line of its last message. It does not count as needing you, so the card and the badge stay as they are. A hand-back counts only when the turn published a page from the plans folder's artifacts while the session is bound to a plan. - A buzz goes out at least once while its event still needs you. A refused push is retried until ActivitySmith accepts it; a question you answer first is never sent. If the daemon crashes, or the network drops, after ActivitySmith accepts a push but before the daemon saves that, the push goes out again, because the push API has no idempotency key. ## Install On another Mac, install the skill and its companion first with `npx --yes skills add udecode/dotai --skill pstack-pulse --skill rca --agent claude-code --global -y`. The phone needs its own ActivitySmith account and app, and cards show only sessions in repositories synced by sync-pstack, whose Session title rule gives each title its stage. 1. Run `install`, adding `--auto-update` when the owner wants the daily update in [Updates](#updates). It is done when it prints the hook files, `daemon: started`, `menubar: started` and the session list URL. The daemon picks up the key within 30 seconds of it being stored. 2. Hand the owner the three steps it prints, which only the owner can do: - Store the ActivitySmith key in the Keychain with the `security add-generic-password` line. - Publish the read-only session list with the `tailscale funnel` line. The list lives at a secret path and accepts no input. - In Codex, open `/hooks` once and trust the pstack-pulse hooks. 3. Ask the owner to turn off the Claude app's own "Push when actions required", so each question buzzes once. 4. Run `doctor`. It is done when every line reads `ok` or `warn`. A `warn` line names an optional part and what is lost without it. ## Updates The daemon checks its skill folder every 30 seconds. Once a change reads the same twice, it rewrites its hooks, rebuilds the menubar when its Swift source changed, and exits, and launchd starts it again on the new code. So an update is only `npx --yes skills update pstack-pulse rca --global --yes`, which `update` runs. With `install --auto-update`, the daemon runs `update` once a day, so every push to dotai's main branch reaches the Mac within a day; `install --no-auto-update` turns that off. `update` logs its result to the daemon log. `install` points the hooks and the daemon at the fnm default node, or else Homebrew's, when that node is version 22 or later, so upgrading node does not break them. ## Status and repair - `status` lists every live session with its state, then the cards on the phone and the badge. - `doctor` checks the key, the daemon, the menubar, both hook files and the node they run, the Claude session registry and the list secret. It warns when it has seen no pstack-titled session, when there is no session list URL and when auto-update is off. It also warns when cc-same or cswap is missing from the daemon's PATH, which only costs the account names. - The daemon logs to `~/Library/Logs/pstack-pulse.log` and keeps its state in `~/.pstack-pulse/`. An inbox file it cannot read as an event moves to `~/.pstack-pulse/inbox/bad/`. - A card's pipeline comes from `node .agents/pstack/plan-page.mjs <plan> --rail` in the session's own repository. A repository synced before that mode existed shows "No plan" until its next sync-pstack run. - `install` owns only the hook entries whose command ends in `--owner=pstack-pulse`. It keeps every other hook in the same group, saves a first backup of each settings file beside it, and writes through a temporary file, so a crash never leaves half a file; a change another program makes during that write is lost. Claude's `PreToolUse` hook runs only for `AskUserQuestion` and `ExitPlanMode`. - Only one daemon runs at a time: it holds a socket under /tmp named for its state folder, and a second daemon that finds the socket answering exits. - `uninstall` stops the daemon and the menubar, deletes their LaunchAgents and removes only its own hook entries. It ends its cards and clears the badge, says what is still on the phone if that fails, prints the line that stops the Funnel, and keeps the state folder. ## Menubar The menubar puts the phone's sessions on the Mac, one row each. Its number counts the sessions that need you, in orange. With none, it counts failed sessions in red, then working ones. A moon means every session is idle, and a struck-through bolt means the daemon does not answer. Its menu shows the fleet totals, which the phone no longer shows, then a row for every live session: those that need you first, then failed, working and idle. Each row shows the session's title, state, project, stage and stage bar. Clicking a row opens the session in the Claude desktop app, by the desktop id that Claude's session registry records, or on claude.ai when the session has none. A row under it opens the session's plan page while that plan is open. The phone shows at most five cards, but the menu has no limit. Its Connect all sessions to Remote Control item opens a new Code session in the Claude app, in `~/.pstack-pulse`, with a request typed in to turn Remote Control on for every non-archived session; press Enter to run it. It goes through the app because a headless `claude -p` has no session tools. New sessions connect on their own while the app's Connect new sessions to Remote Control setting is on, so the item is for sessions that lost their link, such as after an account switch. `install` compiles `menubar/PulseMenu.swift` with Xcode's `swiftc` into `~/.pstack-pulse/bin/pstack-pulse-menu` and runs it as the `dev.pstack.pulse.menu` LaunchAgent. Without `swiftc` it prints that the menubar was not built and installs everything else. `menubar` rebuilds and restarts only the menubar. Quit stops it until the next login. Any other exit restarts it. It reads the daemon's `board.json` on the session list port every two seconds. About three seconds after it starts, it writes its menubar position, and whether macOS draws it, to `~/Library/Logs/pstack-pulse-menu.log`. There, `shown=false` means macOS is not drawing it right then. `--dump` prints the menu as text and quits, which checks it without screen recording access. ## Pet The pet is paused while the menubar covers the Mac, so `install` never starts it. `node <skill>/scripts/pulse.mjs pet` opens a floating pet in the corner of the screen, above every window and on every Space, using the Codex desktop pet's sprite format and animations. It runs while any session works, waits with an orange badge counting the sessions that need you, plays its failed animation while one has failed, and jumps for ten seconds after a push ships. Click it for a tray that lists what needs you, with buttons that open the session or its plan page; drag it anywhere, and it keeps that spot. The first start fetches Electron through `npx` and the Codex sprite from OpenAI's pet CDN into `~/.pstack-pulse/pets/`. The pet reads the daemon's `state.json` on the session list port every two seconds. `--snapshot` saves `pet-snapshot.png` and `pet-snapshot-tray.png` in `~/.pstack-pulse/` and quits, which checks the render without screen recording access. ## Tests `node --test <skill>/scripts/pulse.test.mjs`
View on GitHub