Repository navigation
Conversation
agentplay companion snapshot --json prints one versioned document (agentplay.companion/v1) built from the local store: recent runs with their last recorded events, process-backed activity evidence, context and plan data as typed DataPoints with source and freshness, repeated failure candidates, and one cross-session Learn card. No prompt text, titles, transcript paths, arguments, remotes or PR URLs. No listener: the companion runs it as a child process. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AgentPlayCore decodes agentplay.companion/v1 tolerantly (unknown values degrade to unavailable), builds viewer deep links with strict encoding, runs the Python core as an isolated child process with a minimal environment, and decides notifications: off by default, first-install watermark, dedupe, pattern cooldown, hourly budget, quiet hours in the current time zone, snooze, mutes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SwiftUI MenuBarExtra with Now, Limits and Learn, the three prototype directions behind a dev switch, five-step onboarding, Settings, local notifications, FSEvents-driven refresh and an app-owned viewer. scripts/companion-build.sh assembles AgentPlay Companion.app with a pruned python-build-standalone runtime and the agentplay package inside (44 MB), precompiled so nothing writes into the signed bundle, and signs it inside out. The core now reports how far a map lags its transcript, and a call with no result counts as running only on a live, current map. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Launch testing the real bundle found three problems, fixed here: - every file event restarted the debounce, so an agent writing every second starved the refresh; a queued refresh is now never pushed back - the gap between refreshes scales with the measured cost (4x the last refresh, 3x more in Low Power Mode): a 34 MB live transcript takes about 10 s to rebuild - SIGTERM skipped cleanup; the snapshot child and the viewer are now stopped synchronously, and a viewer left by a crash is reaped on the next launch, only if it is this app's own Python Also: keep cards on screen during a rescan, and explain a scan stalled on a macOS Files and Folders prompt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- docs/companion/screens: every state and prototype, light and dark, rendered from the real views over synthetic fixtures - docs/companion/media: 10 to 14 s keyframe clips per direction, a v1 tour, and the proof clip ending on the real viewer at the deep link - companion-dmg.sh: always a release build; signs, notarizes and staples only with credentials, otherwise prints BLOCKED and exits 2 - alerts carry firstAt, so "Since" means the first failure - PrefsStore uses .standard inside the bundle (macOS refuses the app's own id as a suite name) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nstall, release Also: a macOS CI job (fixtures must regenerate identically, Swift tests), and an uninstall message that only claims what it removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Claude limits Replace the tabbed popover with one native menu column on the system material: system colours and the accent, hover and arrow-key highlight, Return, Cmd-R/O/,/Q and right-click actions. The status item draws two template bars (5-hour and weekly) with a dot when a session needs you. - Claude and Codex 5-hour and weekly meters with reset countdowns, tinted by pace, with an "At this pace, runs out in" line. - Usage estimated from local maps (python/src/agentplay/usage.py): current 5-hour block, today, 7 and 30 days, by model and project, 14-day chart. Unpriced models are counted as unpriced, never $0. - Opt-in live Claude limits through the status line bridge, with the exact settings.json change shown first, the previous status line kept, and a clean undo. - Conflicting Codex plans are a structured field and say so in the menu. - Sessions sit above usage so expanding usage never hides them; the menu is as tall as the screen allows. - Fix two sessions sharing one "context near full" alert key. - Renderer falls back to the bundled fixtures; screens regenerated. - Test script reuses one build dir and passes the macro plugin path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…k with it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The menu was a SwiftUI window imitating a menu, with hand-built hover, keys and expanding rows. It is now a real NSMenu from an NSStatusItem, like CodexBar, Tailscale and Apple's own extras: hover, arrow keys, submenus, dismissal and key equivalents are the system's. Every clickable row is a native item; the provider cards, usage summary and usage chart are display-only hosted views. - Order follows attention: Needs You first, then plan limits, sessions, usage, actions. Sessions are titled by project with the agent and activity as the subtitle. - Limit cards: name, plan, freshness and a weekly pace verdict, then one line and a thin bar per window. Colour only when ahead of pace. - Usage is two lines; the chart and breakdowns moved into a Usage Details submenu, recent errors into a Recent Errors submenu. - Clicking a session brings its terminal app forward (walks up from the agent's pid); Option-click opens the replay. The snapshot now carries the live session pid. - Settings uses toolbar tabs (NSTabViewController) with grouped forms. - Renders capture the real NSMenu and the real Settings window. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… activation, remember the Settings pane Also keep hidden alternates from widening the menu and give Usage Details an icon to match Recent Errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tion - The app reads Claude Code's live-process records (~/.claude/sessions) itself and watches that folder with a 0.3 s latency, so a session that starts waiting shows in the menu and icon without waiting for a scan. Same privacy rule as the core: folder name of cwd only. - New "Session needs you" alert, offered in onboarding: skipped when the session's terminal is already frontmost, cleared once it moves on, click brings the terminal forward, Mute This Session on the banner. Names the project only when repo names in notifications are allowed. - The session tooltip names the app a click will bring forward; the self-test lists what each live session resolves to. - Settings General trimmed to real settings; plural fixes; renders capture onboarding as a real window in both appearances. - Removed code only the old popover used (pulse, pin, copy actions). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hat can't pass for yours Usage was wrong: today read about a quarter of what the transcripts held. - Subagent sessions were skipped, and they were most of today's work. - The biggest session had passed the build's 512 MB limit, so its map froze and stopped counting. - The first-run "full scan" passed --refresh with no window, so the core indexed two days, never 30. There is now --refresh-days (0: all). - Resumed sessions repeat earlier requests across files. Claude usage now comes from Claude Code's raw transcripts, read incrementally: one request per message id and request id, the finished line's tokens and the first line's time; other agents from maps, with map repeats of a counted request dropped. Checked against an independent count: within 0.6% for today, 7 and 30 days. Sample data: the menu opens with "Sample Data, Not Yours" and Show My Data, cards say "Sample data" instead of an update time, and the sample's live sessions are rebased like everything else (it said "Waiting 19h"). Codex on a plan without windows says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… writes where the app reads - Claude Code started from an editor's agent panel (entrypoint sdk-ts, as Zed's claude-acp does) has no statusline, so the bridge can never fire. The card now says so and names the editor, instead of waiting forever. - When Claude Code is refused for a plan limit it records which window and when it resets, from any entrypoint: that now shows as a full window with its reset time. - The bridge script used $AGENTPLAY_HOME, which Claude Code doesn't have: the folder is fixed at install, and installed scripts are refreshed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ent panels Claude Code run from an editor's agent panel (Zed's claude-acp and other Agent SDK hosts) has no statusline, so it never reports plan usage on the Mac. Settings > Live Limits now has an opt-in switch that reads the login Claude Code keeps in the Keychain through /usr/bin/security (macOS asks once) and asks api.anthropic.com/api/oauth/usage for the 5-hour and weekly windows, as CodexBar and the other usage menus do. Every 5 minutes and on menu open, backing off on 429; the token is never stored or renewed. Account rows replace the other Claude rows while fresh, and failures say what to do. The menu offers it when every Claude session comes from an editor. Privacy and provenance docs say so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stale one too Claude Code can leave an old "Claude Code-credentials" item (account "unknown") next to the live one under the user's name, and keeps "Claude Code-credentials-<hash>" items for other config folders. security -s returned the first match, the stale one, so the app said no login was found. Items are now listed by attributes (no prompt), the user's own account and then the newest are tried in turn, each read with an explicit account. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…version lookup can't deadlock Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…agents' state - Pip fills with the 5-hour session and stands on the week; its pose is what matters now: waving (needs you), hourglass (window spent), X eyes (failing command), arms up (finished), sweat (ahead of pace), typing (working, frame steps on real agent writes), asleep. Pixel-grid template art, legible at 18 pt, light and dark. - When something happens a short line shows beside Pip for 8 s, capped near 110 pt for notched menu bars. Clicking it acts: go to the session's terminal, open the replay at the failure, or the usage page. Otherwise clicks open the menu; right-click always does; Option-click opens AgentPlay. The status item now opens its menu itself to see clicks. - The menu opens with Pip large and one sentence about now. - Needs-you notifications get Go to Terminal and Snooze 15 Minutes. - No timer while Pip is still; nothing moves under Reduce Motion or Low Power Mode. Pip and the line each have a switch in Settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…draw red, live renders use real settings Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ssions keep updating Subagent transcripts count toward a session's size, and a busy session passed 512 MB: its map froze and silently stopped updating. The reader streams, so a session just past 512 MB builds in about 10 s with a small memory peak. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he run went Fires when a live session goes from working to idle after at least two minutes of work: how long, tool calls, errors and the last step, from the map. Go to Terminal and Open Replay on the banner. Same snooze, quiet hours, presenting and terminal-in-front rules as Session needs you; the repo name only when allowed. Allowance-low copy now names Claude sources. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After onboarding on another store, or with ~/.agentplay cleared, launch ran the incremental refresh and indexed two days. Now it scans everything. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Email and organization from oauthAccount in ~/.claude.json, local only, on the Claude card. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p way Save the signed-in account, then switch with one click: the outgoing login is saved first (Claude Code rotates tokens), the chosen one is written to Claude Code's Keychain item keeping this Mac's MCP logins, and only oauthAccount in ~/.claude.json changes, after a backup. Through /usr/bin/security with the login on stdin, as Claude Code itself writes it, so access lists and prompts don't change. Refused mid-step or on an expired login. The saved copy follows token rotations; each account shows its last-seen usage; remembering new sign-ins is opt-in. End-to-end tested on throwaway Keychain items. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… bar's wallpaper tint Opening the menu ourselves from the status button (for Pip's clicks) made it take the menu bar's appearance, which on macOS 26 follows the wallpaper: a light wallpaper gave a light menu in Dark Mode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add Another Account saves the account you're on, opens claude auth login in Terminal (a .command file, so no automation permission), and saves the new account the moment Claude Code is signed in as someone else. No dialog, no /logout, nothing to come back for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The placeholder SwiftUI Settings scene opened as an empty window when the app was launched again. Every window is AppKit already, so the SwiftUI app shell is gone: an AppKit entry point, a hidden main menu for Command-comma, Command-W, Command-Q and the Edit commands, and relaunching shows nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # python/src/agentplay/cli.py
…ew to nine Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ld raise it, and errors in full Uses the Run health report merged from main (#19): the snapshot carries the last 30 days' score against the 30 before, its five parts, and up to six fixes that name the command, tool, hook or server behind them, cached 15 minutes. The menu shows Run Health with its weakest parts, up to three fixes and three errors, each with its details one level down and actions: preview and apply in AgentPlay, open an example session, open the replay at the failing step for every run a pattern failed in, copy, mute or hide. Pattern evidence now carries the project. Providers with nothing to show are left out so the menu fits a laptop screen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scripts/companion-install.sh takes a DMG file or URL, installs the app, clears the quarantine flag that blocks an un-notarized app, and opens it. INSTALL.md says how one person builds and shares the DMG. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…notifications, quieter Insights Session above Weekly whatever resets first; every meter bar the same height; the forecast as one caption-size line. The Insights rows have their hover cards again (run health as bars, the fix with Apply, the steering numbers) with Open in Insights at the bottom. Notifications carry Pip in the matching pose instead of the app icon. The Insights window's sections are rows, not posters: fixes with one Apply, applied changes as before → since lines, steering as a four-line table, usage as three totals and two short lists. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Installed copies check the latest GitHub release's appcast once a day and offer the update; nothing installs without a click, every update is verified against the EdDSA key built into the app, and the check sends no system profile. Off in Settings, Check for Updates on right-click. Pushing a vX.Y.Z tag now builds, signs, notarizes and staples the DMG, writes the appcast and attaches both to the release; the job skips with a notice until the Apple and Sparkle secrets are set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a Pip header without the gap Plan bars are a fixed width beside a short reset label, so session and week compare at a glance; the forecast is one readable line. Pointing at a live session shows what it's doing, what it has cost, its tokens and context, files touched, commits, your messages, errors and where its effort goes, then Go to <terminal>; Open Replay is gone. The Pip header is two lines sized to fit: what's going on and the one thing that matters now; clicking Pip makes it say something cheeky about your numbers. The core adds each session's cost, tokens, files, commits, PRs, turns and subagents from its map. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lists the skills, subagents, slash commands, MCP servers and hooks Claude Code loads on this Mac, with 30-day use counts read from the transcripts. Only names that match something installed are kept; no prompt text, arguments or paths. Shows as a Your Setup row in the menu with a hover card and as a section in Insights, with unused items grouped since their descriptions load into every session. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lls help; suggestions from Claude - Menu: a tab strip under Pip switches in place, the menu stays open. Measured with the film harness: top edge fixed, no scroll, one width for every tab. - Improve: every fix, Claude's suggestions, what changed, run health, how you steer. - Setup: what you use with its count and how sessions went with it, what sits unused by kind with a way to its folder, your hooks. - Whether a skill, subagent or MCP server helps: sessions with it against sessions without, on corrections and failed tool calls. A verdict only with 5 sessions a side and a 3-point gap; otherwise "too few" or "no clear difference". - Suggestions from Claude, off by default: once a day, Haiku through the user's own Claude Code with tools, settings, MCP and session saving off. Counts and skill names only. Checked before kept. Documented in PRIVACY.md and tested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts an hour; tab width measured once per snapshot Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ecast The menu's Liquid Glass let about 60/255 of whatever was behind it through, so text sat on whatever page was under the menu. The menu and submenu glass is now tinted to the window background, with a solid backing in Dark: measured 0.1/255 over a white page against a black one, Light and Dark (--contrast-check, real screen captures). The weekly bar was red at 31% from a straight-line pace while the forecast under it said something else. The bar follows the forecast: orange when it runs out before the reset, red only from 90%. Numbers stay in plain text; the forecast line gets one orange mark. Pip no longer repeats the forecast the card shows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ery session map The Setup tab judged figma on 3 sessions from transcript text and showed one MCP server. The maps AgentPlay builds at scan time record every step's tool, failure, output size and time, for all 1,800 sessions. impact.py adds them up per item: calls, failed calls, tokens its results put into context, time waited, the sessions it ran in and your latest ones by id (click to copy claude --resume). - Your sessions (2+ messages) apart from one-prompt runs (scripts, bots, claude -p): here 29 yours against 1,729 automated. - claude.ai connectors and built-in subagents are listed with what they did. - One next step per item from checkable rules: failing (3+ failed, 20%+), heavy (40k+ tokens a session), helps or watch (your sessions with it vs without, 5 a side). - Hosted cards for the split and hooks, so they aren't greyed out; tabs in the accent colour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s headers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…exBar draw them Each window is the name, a bar across the card, then "54% used" on the left and "Resets in 1h 7m" on the right. Every bar spans the card, so session and week always compare at a glance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ign-in After a sign-in that worked, a detached helper asks Terminal to close the window on the script's own terminal device. A failed sign-in keeps the window open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ures
Improve listed only failing hooks and commands, in Claude Code's own terms. Now:
- Every fix and tip is a title, what happened, what to do, and the technical names
last, small. Hook events read as moments ("when a session starts", "when you send a
message"); a hook fix is named for its script, with its real count.
- New tips from the session maps: a tool that keeps failing, a tool filling your
context, sessions that run out of room, unused setup (only when it costs 2k+
tokens a session), and a tool worth using more (your sessions with vs without).
Each row's subtitle is the number behind it.
- A check is something that tests, lints, type-checks or builds; mkdir, echo or a
bare npx or python is never offered as one.
- Pip, the Improve badge and the list say the same count.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ng what you already use - A skill a hook runs (validator-nudge.sh runs the validator) or whose subagents ran (judge) counts as in use. Tools lists your own first, with what runs it. - Claude gets what's in use and what's already on screen, must quote a number for each suggestion, and may return none. Suggestions to use what you already use, fixes, and restatements of a line on screen are dropped before they're kept. - Hooks read as moments: "When you send a message · validator-nudge.sh". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tool card was drawn 340 pt wide in a 300 pt row, so both edges were clipped, and it grew with the menu, so its text was measured on one line and drawn on two, clipping top and bottom. It now has a 340 pt row and a fixed 340 pt width; the other cards size to their full height. Live renders now pop the first submenus of Tools and Fixes as real menu windows and print each row's size against what it needs. Also: "Its one call worked", and no "Into context" line under 1k tokens. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es for design files --export-vectors <dir> writes each SF Symbol the app shows as a 4x mask PNG and each Pip pose as a 12x PNG, used to rebuild every state in Figma as editable layers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…elligence sage accent - Tool cards answer four things: what it is (its own description, or what built-ins and connectors are), what a session with it costs, how it fails, and how many of your sessions with it changed no file, made no commit and opened no PR. A skill or subagent whose sessions mostly change nothing is flagged as idle. - Built-in subagents are Claude's own; no more "use general-purpose more". - Hooks are rows that say when they run, what script, and whether they've failed. - Unused subagents and skills are listed with their descriptions and what they are. - How You Steer is now How Your Prompts Land: plain rates plus findings from your own sessions (opening detail vs follow-ups, spend on retries and re-reads, blind edits), each with a next step. - Sessions split reads "You, typing" and "Scripts and bots". - Accent is Entelligence sage (#B8D395 dark, #5E773C light) with dark text on it in dark mode, distinct from the system green that means good. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d on from Tools Claude Code's own switches where it has them: skillOverrides "off" for a skill, a deniedMcpServers entry for an MCP server or claude.ai connector (by its display name), both in ~/.claude/settings.json. Subagents and single hooks have no switch, so AgentPlay sets them aside in ~/.agentplay/set-aside and puts them back exactly. Each change is confirmed first with what it writes, takes the write lock, and backs up settings.json. Turned-off things stay listed under Turned Off, with Turn On. Plugin skills are left to /plugin. Hooks your parallel-session and validator workflow depend on get a stronger warning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Pip while agents work Notifications never showed because "Send Sample" posted without ever asking macOS for permission, and macOS drops those silently (checked with --notify-check: once asked, permission is granted and posts go through). Alerts now default to AgentPlay's own banner: Pip at 2.5x, animated in the alert's pose, top-right where notifications sit, clickable to go to the session. It needs no permission. Settings → Notifications picks Pip on screen, macOS notifications, or both, and every sample asks permission first when macOS is part of it. An optional strip under the menu bar shows Pip walking while agents work. --banner-check captures the banner and strip on the real screen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EntelligenceAI PR SummaryAdds AgentPlay Companion, a native macOS menu-bar app that uses Pip to show agent activity, plan usage, failures, and setup insights from local AgentPlay data. It adds live Claude statusline and optional account-usage integrations, account switching, notifications, and links to sessions or fixes; changes are previewed before applying and can be undone. Fixtures and Swift core tests are added, along with macOS CI and a release workflow for building, signing, notarizing, and publishing updates when the required secrets are configured. flowchart TD
classDef newBehavior fill:#dcfce7,stroke:#16a34a,color:#14532d;
Src["Local agent maps and transcripts"] --> Core["AgentPlay snapshot CLI"]
Live["Claude live session records"] --> Model["Companion model"]
Account["Optional Claude account usage"] --> Model
Core --> Model
Model --> Menu["Menu bar and Pip"]
Model --> Alerts["Alerts and notifications"]
Menu --> Actions["Session replay, terminal, and fix actions"]
class Src,Core,Live,Account,Model,Menu,Alerts,Actions newBehavior;
Review Scorecard
Issues found:
Fix before merge; code quality rated Poor (2/5): 3 significant findings in 3 files, the most severe in Need to merge before these are addressed? Anyone with write access can comment Evaluated against
Files requiring special attention
|
WalkthroughIntroduces AgentPlay Companion, a macOS menu-bar app for monitoring local agent sessions, usage limits, insights, alerts, and Claude account workflows. Adds the Swift core and UI, fixture-driven development and test tooling, privacy-conscious local integrations, and CI/release automation for signed and notarized distribution. ChangesFiles
Adds companion development, build, packaging, and test targets; configures macOS CI to verify generated fixtures and run Swift tests; and adds a secrets-gated release workflow for signing, notarization, Sparkle appcast generation, and release uploads. Ignores generated build and capture artifacts and defines the macOS 14 Swift package with Sparkle pinned. Files
Adds versioned sample snapshots for multi-agent activity, idle and no-data states, partial source failures, stale quotas, conflicting plans, and concurrent sessions. These fixtures support UI previews, tests, and fixture-sync checks without changing runtime behavior. Files
Defines the menu-bar app bundle, update settings, hardened-runtime entitlements, and bundled privacy notice. Adds a Claude status-line bridge that stores local usage input by session, prunes old records, and forwards input to any previously configured status-line command. Files
Implements the menu-bar app lifecycle and observable model, data-driven Usage/Fixes/Tools menus, Pip status icons and banners, adaptive theme colors, filesystem and notification integrations, and packaged-build Sparkle updates. The model coordinates snapshots, live-session monitoring, refreshes, preferences, notifications, viewer navigation, and companion actions. Files
Adds the insights dashboard, five-step first-run onboarding, and pane-based settings window. These views surface run health, fixes, learning and usage data, account and notification controls, privacy options, and actions such as applying or undoing changes. Files
Adds development-only rendering, filming, and self-test workflows for UI states, launch-film assets, menus, app interactions, runtime health, viewer behavior, and setup controls. Fixture loading supports deterministic rendering by rebasing snapshot times; generated captures are written to the selected output directory. Files
Adds the pure Swift companion core for snapshot decoding, session and process records, Pip state, provider pace and forecasts, redacted formatting and viewer links, persisted preferences, and alert selection/throttling. It also provides the Python runtime and viewer bridge, Claude usage retrieval, opt-in status-line integration, and guarded Claude account storage and switching. Files
Adds Swift Testing coverage for alert and preference behavior, Claude credentials and account switching, forecasts, live-record handling and redaction, Pip decisions, snapshot decoding, viewer safety, and process-environment restrictions. |
| permissions: | ||
| contents: write # attach the DMG and appcast to the release | ||
| env: | ||
| HAS_SECRETS: ${{ secrets.DEVELOPER_ID_P12 != '' && secrets.SPARKLE_PRIVATE_KEY != '' }} |
There was a problem hiding this comment.
Check every credential before enabling the job
If only DEVELOPER_ID_P12 and SPARKLE_PRIVATE_KEY are set, this enables the job even when the P12 password or notary credentials are missing; key import or notary setup then fails instead of cleanly skipping the companion release.
Prompt to fix with AI
Copy this prompt into your AI coding assistant to fix this issue.
In .github/workflows/release.yml:71, make HAS_SECRETS require all six credentials listed in the Secrets present notice at line 79 and docs/companion/RELEASE_CHECKLIST.md:66-71. If any are absent, the job should take the existing skip path rather than entering signing and notarization.
| startAccountTimer() | ||
| refreshAccount(force: true) |
There was a problem hiding this comment.
Wait for consent before fetching Claude account usage
Startup fetches Claude usage before onboarding finishes. CompanionPrefs.usesClaudeAccount defaults to true, so even a user who turns the onboarding toggle off may first get a Keychain prompt and an API request before that choice takes effect.
Prompt to fix with AI
Copy this prompt into your AI coding assistant to fix this issue.
Defer the initial account fetch and timer until the user explicitly enables Claude account usage. Ensure a first launch with the onboarding toggle off never reads the Keychain or sends the usage request.
| } | ||
|
|
||
| public static func processIsAlive(_ pid: Int32) -> Bool { | ||
| kill(pid, 0) == 0 || errno == EPERM |
There was a problem hiding this comment.
Verify the recorded process identity before treating its PID as live
A stale <pid>.json becomes a false live session when its PID is reused: this check accepts any process with that PID, so refresh replaces the snapshot list with stale status and can send false alerts. Compare the record's procStart with the current process start before accepting it.
Prompt to fix with AI
Copy this prompt into your AI coding assistant to fix this issue.
In apps/macos/AgentPlayCompanion/Sources/AgentPlayCore/LiveRecords.swift, update the liveness validation around line 59 so a successful kill(pid, 0) is not sufficient: compare the record's procStart with the current process start time, matching the existing validation in python/src/agentplay/companion.py. Reject mismatches before returning the session.
|
|
||
| public enum ActivityState: String, TolerantEnum, Sendable { | ||
| case active, recent, idle | ||
| static var fallback: ActivityState { .idle } |
There was a problem hiding this comment.
Preserve unknown activity states instead of decoding them as idle
Unknown activity and live statuses become .idle, so a newer core's active or needs-you state is silently shown as idle; active runs can be omitted and needs-you UI can be missed. Preserve an unknown or unavailable state instead.
Prompt to fix with AI
Copy this prompt into your AI coding assistant to fix this issue.
In apps/macos/AgentPlayCompanion/Sources/AgentPlayCore/Snapshot.swift, lines 152–155 and 248–254, change unknown ActivityState and LiveStatus decoding so unknown values are preserved as unknown/unavailable rather than presented as idle. Update state consumers such as RunActivity.state and activeRuns to avoid silently hiding unknown activity.
| mkdir -p "$dir" 2>/dev/null || exit 0 | ||
| tmp="$dir/.in.$$" | ||
| cat > "$tmp" 2>/dev/null || exit 0 |
There was a problem hiding this comment.
Run the previous statusline when snapshot capture fails
If creating the directory or writing the snapshot fails, these exits skip the saved statusline at line 18, so users who enabled the bridge lose their existing status output on that invocation. Treat capture as best-effort and still run the previous command with the original input.
Prompt to fix with AI
Copy this prompt into your AI coding assistant to fix this issue.
In apps/macos/AgentPlayCompanion/Resources/claude-statusline-bridge.sh, lines 9-11, change the mkdir and stdin-capture failure paths so they do not exit before the saved statusline runs. Preserve and pass through the original stdin when snapshot storage fails.
| /// `used` 0...100, `windowMinutes` length, `resetsAt` end of the window. | ||
| public static func of(used: Double, windowMinutes: Int?, resetsAt: Date?, now: Date) -> Pace? { | ||
| guard let minutes = windowMinutes, minutes > 0, let reset = resetsAt, reset > now else { return nil } | ||
| let length = TimeInterval(minutes * 60) |
There was a problem hiding this comment.
Bound minutes before multiplying by 60
An oversized but Int-decodable windowMinutes from a Codex rate-limit record passes the positive-value guard, then minutes * 60 overflows and traps when the usage row calls Pace.of, crashing the menu. Reject values above Int.max / 60 before multiplying.
Prompt to fix with AI
Copy this prompt into your AI coding assistant to fix this issue.
In apps/macos/AgentPlayCompanion/Sources/AgentPlayCore/Pace.swift:15-16, prevent an oversized positive windowMinutes from overflowing during minutes * 60. Validate against the representable bound before multiplication, and add a test for an Int-decodable value above that bound.
| guard w.used.isAvailable, w.used.freshness != .stale, w.resetsAt.map({ $0 > now }) ?? true else { return false } | ||
| let used = w.used.value?.usedPercent ?? 0 | ||
| let p = Pace.of(used: used, windowMinutes: w.windowMinutes, resetsAt: w.resetsAt, now: now) |
There was a problem hiding this comment.
Apply the age-based stale check to Pip's windows
A reading older than six hours but still tagged fresh (or unknown) passes this guard and can keep Pip in .sweat; the same check in spentWindow can keep it in .limit. Use the age-based stale check in both functions.
Prompt to fix with AI
Copy this prompt into your AI coding assistant to fix this issue.
In apps/macos/AgentPlayCompanion/Sources/AgentPlayCore/Pip.swift, update the freshness guards in hotWindow (line 98) and spentWindow (line 57) to reject readings when `used.isStale(now: now, maxAge: ProviderSummary.maxReadingAge)` is true, matching ProviderSummary.isCurrent. Preserve the existing reset checks.
| func refreshLive() { | ||
| guard var s = snapshot, !isSample else { return } | ||
| let merged = LiveRecords.merge(snapshot: s.live, records: LiveRecords.read(dir: claudeSessionsDir)) | ||
| let news = LiveRecords.newlyWaiting(before: lastLive, after: merged) |
There was a problem hiding this comment.
Preserve live sessions from custom Claude homes
The Python snapshot honors added Claude homes, but this fast refresh reads only ~/.claude/sessions; LiveRecords.merge returns only those records and drops unmatched snapshot entries. A session found in an added Claude home can therefore disappear from the model and its alerts after a live refresh.
Prompt to fix with AI
Copy this prompt into your AI coding assistant to fix this issue.
Read live records from every configured Claude home, or merge fresh records with snapshot sessions from homes not covered by the fast path. Add coverage for a custom-home session surviving `refreshLive()`.
| guard isUsable(saved.login, now: now) else { return .failure(.expired) } | ||
| // 1. The outgoing account's newest login first, so its rotated token isn't lost. | ||
| let outgoing = ClaudeAccount.readIdentity(home: home)?.accountUuid | ||
| if outgoing != nil, outgoing != target.accountUuid, case .failure(let f) = saveCurrentLocked(env, now: now) { return .failure(f) } |
There was a problem hiding this comment.
Preserve the live login when the target is already active
If Claude Code changes accounts after the menu is rendered, a click on the now-current target can reach this branch. It skips saving that account’s rotated live login, then still installs the older saved login, which can leave Claude Code signed out.
Prompt to fix with AI
Copy this prompt into your AI coding assistant to fix this issue.
In apps/macos/AgentPlayCompanion/Sources/AgentPlayCore/ClaudeSwitch.swift:251-252, handle outgoing == target.accountUuid without later overwriting the live login with the saved copy. Return a no-op or refresh the saved target from the live login before proceeding; preserve the latest rotated token.
|
|
||
| func switchAccount(to target: ClaudeSwitch.Saved) { | ||
| guard !switching else { return } | ||
| let busy = (snapshot?.live ?? []).contains { $0.status == .busy } |
There was a problem hiding this comment.
Recheck live status immediately before switching accounts
This cached busy value can be false when a Claude session starts after the snapshot or while the detached switch runs, so the switch proceeds mid-step and may disrupt that session. Recheck live records as part of the switch operation.
Prompt to fix with AI
Copy this prompt into your AI coding assistant to fix this issue.
In apps/macos/AgentPlayCompanion/Sources/AgentPlayCompanion/Model.swift at line 452, the switch guard uses a cached snapshot that can become stale before ClaudeSwitch writes the account. Make the busy check use current live records as close as possible to the write, and ensure the operation refuses a switch if a session starts mid-step.
… its own ending The confirmation ran modally from inside the menu item's action, while the menu was still closing, and a menu-bar app never shows it there: the click did nothing. It now runs once the menu has closed, as Apply does. Built-in subagents no longer offer a switch. Writes keep settings.json's own final newline (or lack of one). --switch-check <name> <confirm|cancel> drives the real path: the Tools menu over this Mac's data, the item's Turn Off… through the menu, the confirmation found on screen and answered, the result read back from ~/.claude/settings.json, and with confirm, turned back on and compared byte for byte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lls and suggestions name the right thing Validated end to end with new dev checks driving the real menu over real data: --switch-check now covers skill, MCP, subagent and hook (off via the menu, listed under Turned Off, Turn On from there, settings.json and agents byte-identical after), and --menu-check walks every tab and submenu, fires copy, Insights, Pip banner, tab switching in an open menu, Apply This Fix… preview and Cancel, and Ask Claude, and checks each tab fits the screen. Fixed on the way: - Turning a hook back on put its event at the end of hooks; it now returns to its exact place (event, group, position), byte for byte. - Hooks with a tool matcher (Read|Edit|Write) were missing from Tools. - Skills go by their folder, as Claude Code calls them (17-review-the-work), so a used skill no longer also shows as unused. - A hook event's failures sit on the script that fails, not every script on that event; failing hooks list first and the list shrinks a row at a time on short screens. - Plugin subagents get no switch (the core refuses too); a "remove" suggestion offers that item's own Turn Off; a subagent suggestion copies @agent-name, not /name. - Hook confirmations name the script; the guard warning says what it guards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| let hooks = live.filter { $0.kind == "hook" }.map { h -> SetupReport.Item in | ||
| var h = h | ||
| let event = h.name.components(separatedBy: ":")[0] | ||
| if let script = failing.first(where: { ($0.subject ?? "").hasPrefix(event) })?.hook?.script, script != h.detail { h.failed = 0 } |
There was a problem hiding this comment.
Match every failing hook fix for the event
When two matcher groups under the same event have separate broken_hook fixes, this keeps only the first fix's script and zeroes failed on the other group's script, so a genuinely failing hook appears healthy in Tools.
Prompt to fix with AI
Copy this prompt into your AI coding assistant to fix this issue.
In `apps/macos/AgentPlayCompanion/Sources/AgentPlayCompanion/MenuItems.swift:290`, avoid attributing failures to only the first `broken_hook` fix whose subject starts with the event. Match all relevant fixes so scripts from separate matcher groups retain their failure indicators, and ensure unrelated scripts are still cleared.
| exit(code) | ||
| } | ||
| if LaunchOptions.isDev, let name = LaunchOptions.value("--switch-check") { | ||
| let confirm = LaunchOptions.args.contains("confirm") |
There was a problem hiding this comment.
Parse the mode after the item name
For an item named confirm, --switch-check confirm cancel sets this to true because the item name is included in args; switchCheck then takes the “Turn Off” path instead of “Cancel.”
Prompt to fix with AI
Copy this prompt into your AI coding assistant to fix this issue.
In apps/macos/AgentPlayCompanion/Sources/AgentPlayCompanion/App.swift at line 200, determine the switch-check mode from the argument following the item name, not from whether any argument equals `confirm`; this prevents an item literally named `confirm` from turning an explicit cancel into the confirm path.
| let a = (try? FileManager.default.contentsOfDirectory(atPath: home.appendingPathComponent(".claude/agents").path).sorted()) ?? [] | ||
| let x = (try? FileManager.default.contentsOfDirectory(atPath: home.appendingPathComponent(".agentplay/set-aside/agents").path).sorted()) ?? [] |
There was a problem hiding this comment.
Compare the agents' file contents, not just their names
These checks record only directory entry names. If turning an agent off and back on corrupts or changes a file without changing its name, state() still matches and the check prints restored PASS byte-identical.
Prompt to fix with AI
Copy this prompt into your AI coding assistant to fix this issue.
In apps/macos/AgentPlayCompanion/Sources/AgentPlayCompanion/SelfTest.swift:495-499, make state() recursively capture relative paths and file bytes for both .claude/agents and .agentplay/set-aside/agents. Keep the before/after comparison so content changes with unchanged filenames are detected.
| /// Apply This Fix…: the preview must show; Cancel must leave every file as it was. | ||
| @MainActor static func fixCheck(_ m: CompanionModel, _ all: [(String, NSMenuItem)]) async { | ||
| guard let (p, r) = all.first(where: { $0.1.title == "Apply This Fix…" }) else { print("menu-check: no fix to apply"); return } | ||
| let settings = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".claude/settings.json") |
There was a problem hiding this comment.
Check the file named by the fix preview
This snapshots only ~/.claude/settings.json, but fixes can target repo-level files such as .claude/settings.json or CLAUDE.md. If Cancel mistakenly writes the previewed target, same remains true and the check prints PASS; compare the actual target file instead.
Prompt to fix with AI
Copy this prompt into your AI coding assistant to fix this issue.
In apps/macos/AgentPlayCompanion/Sources/AgentPlayCompanion/SelfTest.swift:762-784, make fixCheck capture and compare the actual file identified by the selected fix preview (and any other files that can be changed), rather than only ~/.claude/settings.json, so a write to a repo-level target cannot produce a false PASS.
|
hey @hsrambo07 can you check the review comments once? |
AgentPlay Companion: a native macOS menu-bar app for your coding agents
Pip lives in the menu bar and tells you what your agents are doing, how much of your Claude plan is left, what's going wrong and how to fix it, and what your own Claude Code setup is actually doing for you. Everything is read on your Mac; nothing is uploaded.
What's in the app
skillOverrides,deniedMcpServers); subagents and single hooks set aside in~/.agentplayand restored exactly. Confirmed first, write-locked,settings.jsonbacked upCore (Python) additions
companion.pycoachandsetupcommandsimpact.pysetup_inventory.pysetup_control.pyprompting.pycoach.pyusage.py,health.pyTesting
./scripts/companion-test.sh: Python 1,447 + companion 90, Swift 96, fixtures regenerate identically, repo guards, bundle self-test. All pass.--contrast-check(menu, every Tools and Fixes submenu, Light and Dark),--banner-check(Pip banner and walker),--render-live,--notify-check.--switch-check <item> confirm|cancelturns a skill, MCP server, subagent or hook off through the menu, finds it under Turned Off, turns it on from there and checkssettings.jsonand the agents folder are byte-identical;--menu-check fire [coach]walks every row and submenu, fires copy, Insights, the Pip banner, tab switching in an open menu, Apply This Fix… (preview, Cancel) and Ask Claude, and checks every tab fits the screen.docs/companion/PRIVACY.md.Before release
scripts/companion-install.shis the team preview path).docs/companion/RELEASE_CHECKLIST.md.🤖 Generated with Claude Code