Skip to content

Releases: anhyobin/mac-app-for-claude-code

v0.6.1 — Accurate workflow phase counts + live progress

Choose a tag to compare

@anhyobin anhyobin released this 02 Jun 19:30

Fixed

  • Workflow phase counts stuck at "1/3" or "0/2" even after the run finished. Phase completeness was derived from the agents' 60-second mtime heuristic (isActive), so a just-finished workflow whose last phase still had freshly-touched agent files read as incomplete — and the mtime cache then froze that wrong snapshot permanently. Phase completeness now uses the authoritative per-agent state field ("done"/"error" = terminal) from workflows/{id}.json, falling back to the mtime heuristic only for older data that lacks state. Verified against all 27 completed workflows on disk — every one now reports an accurate n/n.
  • Conditionally-skipped phases shown as forever-incomplete. A declared phase that dispatched zero agents (e.g. a "Fix" phase with nothing to fix) was counted as incomplete even on a finished workflow, producing counts like 2/3. An empty phase now mirrors the workflow's terminal status (completed → done, running → pending).
  • Mismatched token and agent totals. The loader summed tokens across every agent file on disk (including retries and nested sub-agents — e.g. 188 files) while agentCount came from the state JSON (e.g. 87), producing inconsistent figures. When the state JSON is present, its pre-computed totalTokens/totalToolCalls/agentCount (the logical-agent totals) are now treated as authoritative.

Added

  • Phase skeleton and agent-progress aggregate for running workflows. The agent-to-phase mapping is only written to disk at completion, so a running workflow now shows its phase skeleton (names and order, e.g. Research → Build → Verify) parsed from the script's meta.phases, plus an accurate "M/N agents done" aggregate computed from journal.jsonl started/result events. Per-phase agent attribution remains a completed-only view (mid-run attribution is not recoverable from disk).

Install: download ClaudeCodeMonitor.zip, unzip, move ClaudeCodeMonitor.app to /Applications, and open. (arm64, macOS 14+, ad-hoc signed — right-click → Open on first launch.)

v0.6.0 — Workflow visibility

Choose a tag to compare

@anhyobin anhyobin released this 30 May 06:47

Summary

Workflow visibility. When a dynamic workflow fans out into a fleet of subagents, the monitor no longer goes dark. A new "Workflows" section at the top of the session detail view surfaces each run as a per-phase agent tree, and the menu-bar count gently pulses purple while a workflow is running.

The problem

Dynamic workflows (e.g. ultracode orchestration) spawn agents under a directory the app never read. SubagentLoader scans the flat subagents/agent-*.jsonl, but workflow agents live one level deeper at subagents/workflows/{wf_id}/agent-*.jsonl. A workflow could spin up a dozen agents and burn hundreds of thousands of tokens with zero signal in the UI.

What you get

  • Workflows section at the top of an expanded session — above the flat agent list, so the phase structure stays prominent.
  • Phase tree per workflow: each phase shows its agents; a completed phase gets a check, an in-progress one a spinner. Running workflows are expanded by default; completed ones collapse to a one-line summary you can click to expand.
  • Per-agent detail — click any workflow agent to expand its recent messages, modified files, and tool breakdown, exactly like flat subagents (the nested transcript path is handled transparently).
  • Live progress — a progress bar (completed agents / total) and running token total, refreshed on the same 5s cadence as the rest of the app.
  • Menu-bar signal — the active-session count tints purple and pulses while any workflow is running. A /goal in progress still takes priority (blue), so an explicit goal is never masked.

How "running" is detected

The run-state file workflows/{wf_id}.json is written only at completion (startTime + durationMs == timestamp), so its status field can't tell you a workflow is currently running. Detection therefore reads two signals:

Signal Source Meaning
Unfinished agent journal.jsonl — a started event with no matching result Primary "still running" signal
Definitive done workflows/{wf_id}.json status: "completed" Overrides everything — the file only exists at completion
Activity fallback agent-dir mtime within 60s Bridges brief gaps between journal writes

This ordering closes a subtle bug where a just-finished workflow could otherwise stay pinned as a spinner forever (its directory mtime never changes again once complete).

Under the hood

  • New: WorkflowInfo / WorkflowPhase / WorkflowRunStatus models, WorkflowJournal (pure running-detection parser), WorkflowLoader (disk loader with per-wf_id mtime caching), and the WorkflowSection view.
  • SubagentScan was extracted from SubagentLoader so the flat and workflow loaders share one identical JSONL scanner (DRY) — flat-subagent behavior is unchanged.
  • SubagentInfo, AgentRow, and AgentDetailView are reused for workflow-phase agents; AgentRow/loadAgentDetail gained an optional workflowId to resolve the nested transcript path, with consistent cache keys across read, write, and the live-refresh loop.

Tests

WorkflowJournalTests (running-detection: complete / unfinished / malformed-line / empty / result-before-started / dedup) and WorkflowLoaderTests (workflow-name parsing, phase mapping) cover the pure logic.

Note: XCTest requires the full Xcode SDK; on Command Line Tools–only machines swift build and bash scripts/build-app.sh succeed, while swift test runs on a machine with Xcode. The disk-loading path was additionally verified against real on-disk workflow transcripts.

Version

Info.plist: 0.5.0 → 0.6.0, build 8 → 9.

Full Changelog: v0.5.0...v0.6.0

v0.5.0 — Opus 4.8 support

Choose a tag to compare

@anhyobin anhyobin released this 30 May 06:47

Summary

Adds support for Opus 4.8 (claude-opus-4-8), the flagship model introduced in Claude Code 2.1.154. Sessions running 4.8 now show the correct "Opus 4.8" model badge and — crucially — a context gauge scaled to the 1M-token window rather than 200K.

Why this matters

Opus 4.8 ships with a 1M-token context window as the API default — no beta header, no [1m] settings suffix required. But the JSONL message.model field only ever carries the bare id claude-opus-4-8 (verified across 3,185 turns on real transcripts; the [1m] suffix never appears there).

If 4.8 fell through to the generic opus branch, ModelContextLimits would report 200K, and every 4.8 session would over-report context-gauge fullness by 5× — the exact failure mode that bit Opus 4.7 in CC 2.1.117.

What changed

  • ModelContextLimits — claude-opus-4-8 joins the inherent-1M branch alongside 4.7, so the context gauge is always computed against 1M regardless of settings.json.
  • ModelNameFormatter — opus-4-8 → "Opus 4.8", listed newest-first so it's never shadowed by 4.7/4.6. Provider-prefixed ids (us.anthropic.claude-opus-4-8) resolve too.
  • The model-family color (orange/Opus), badge, and (1M) label all flow through automatically — the parser, settings reader, and badge views are model-agnostic and needed no change.

Tests

Regression guards added to ContextUsageRatioTests:

  • claude-opus-4-8 (and the provider-prefixed form) → 1_000_000 without any settings.json [1m] mapping — closes the 5×-over-report failure mode.
  • Display name resolves to "Opus 4.8" and is never shadowed by 4.7/4.6.

Note: this repo's test target uses XCTest, which requires the full Xcode SDK. On Command Line Tools–only machines swift build and bash scripts/build-app.sh succeed; run swift test on a machine with Xcode installed.

Version

Info.plist: 0.4.1 → 0.5.0, build 7 → 8.

Full Changelog: v0.4.1...v0.5.0

v0.4.1 — Fix space-bearing cwd JSONL lookup

Choose a tag to compare

@anhyobin anhyobin released this 21 May 06:28

Summary

A bug-fix release. Sessions whose cwd contained spaces (e.g. ~/Documents/Solutions Arhitect/Public Events/Claude Webinar) were showing only their task list — model badge, context gauge, thinking counter, skill counter, and /goal banner all came up empty.

Root cause

PathDecoder.encodedProjectPath only replaced / and . with -, leaving spaces intact. The Claude CLI, however, also collapses spaces to - when naming ~/.claude/projects/ subdirectories, so the encoder produced a path that never matched on disk:

Input cwd App-encoded (before) Actual directory
/Users/anhyobin/Documents/Solutions Arhitect/Public Events/Claude Webinar -Users-anhyobin-Documents-Solutions Arhitect-Public Events-Claude Webinar ❌ -Users-anhyobin-Documents-Solutions-Arhitect-Public-Events-Claude-Webinar

Because the main session JSONL never loaded, every value derived from it (mainTokens, mainModel, mainLastTurnUsage, mainThinkingBlockCount, mainSkillCounts, activeGoal) was empty. Only TaskLoader survived because tasks live under ~/.claude/tasks/{sessionId}/ — keyed by sessionId, not by encoded cwd — so the symptom was "task list shows up, nothing else does."

Fix

One-line change in PathDecoder.swift to also collapse spaces to -. The encoder is shared by SubagentLoader and both JSONL load paths in ClaudeDataStore, so the fix flows through every call site automatically.

Tests

New PathDecoderTests covers:

  • Space-bearing path (the regression that motivated the fix)
  • No-space path (regression guard — encoder must remain a no-op for the previously-working case)
  • Dot handling

Version

Info.plist: 0.4.0 → 0.4.1, build 6 → 7.

Full Changelog: v0.4.0...v0.4.1

v0.4.0 — /goal monitoring

Choose a tag to compare

@anhyobin anhyobin released this 13 May 09:45

Highlights

Claude Code CLI's /goal <condition> installs a Stop hook that makes Claude keep taking turns until the condition is satisfied. Previously there was no signal outside the terminal that a session was running under this mode. v0.4.0 surfaces /goal state directly in the menu bar so you can see at a glance which sessions are grinding on a verifiable end state.

Added

Menu-bar session count tint

The active-session count next to the status dot shifts to the system accent color whenever at least one active session has an in-progress goal. A separate glyph was rejected because a third 8pt mark beside the icon and status dot was too easy to misread as a second status indicator — folding the signal into the count keeps the menu bar uncluttered. Intentionally separate from the status dot's priority stack (error/warning/active) so goal state reads as a parallel signal, not a replacement for session health.

Per-session Goal banner

Inside each session row, between the context gauge and the expanded detail block, a compact banner shows the goal condition and elapsed time since installation.

  • Collapsed: condition truncated to 2 lines.
  • Expanded (click to toggle): scrollable region (up to 180pt tall) so multi-paragraph acceptance specs stay fully readable.
  • The expand chevron and clickable surface hide for short single-line conditions so the collapsed row stays uncluttered and there is no dead tap target.
  • Chevron direction (right/down) matches SessionRow, AgentRow, and SessionDetailView so every expandable surface in the app reads identically.
  • Active goals use an accent tint. Achieved goals step down to a neutral secondary tint with a "· Done" suffix — intentionally NOT green, so the status dot's green-means-active semantic stays unambiguous.

Changed

JSONLParser tracks goal_status in a single pass

scanTokensAndThinking now parses type:"attachment" rows with attachment.type:"goal_status" alongside tokens/thinking/skills. A cheap "goal_status" substring prefilter keeps JSON decoding off the hot path for unrelated attachment rows.

Rules:

  • met:false, sentinel:true is the start marker — captures condition and the install timestamp.
  • A new start always resets the tracker so only the most recent goal is surfaced (stale achievements for prior goals are dropped).
  • met:true after a start marker is the achievement marker — freezes elapsed at that moment so the figure does not creep upward forever.
  • Orphan met:true with no prior start is ignored.

SessionExpandedData + ClaudeDataStore

SessionExpandedData gains activeGoal: GoalStatus?. The mtime cache path in ClaudeDataStore.loadSessionDetail includes it so goal state refreshes on the same cadence as other quick stats, with no extra parse passes. New hasActiveGoal computed property drives the menu-bar count tint.

Design decisions worth calling out

  • Turn count removed from the banner. An earlier draft showed "N turns" next to the elapsed time, but the underlying figure (assistant-message count since the goal started) conflates tool_use roundtrips with user-perceived turns — a single user prompt that triggers several tool calls still produces several assistant messages, so "100 turns" was more noise than signal. The field is kept in GoalStatus for future reuse under a clearer label, but not surfaced in v0.4.0.
  • Sub-minute elapsed renders as "just now", not "1m". The shared RelativeTimeFormatter rounds sub-minute intervals up to "1m", which misreads a freshly-installed goal as if a minute had already passed. A goal-local formatElapsed helper handles this bucket (<60s → "just now", <60m → "Nm", ≥1h → "Hh Mm").

Tests

GoalStatusParsingTests covers no-goal / active goal elapsed tracking / achieved-goal freeze / most-recent-goal override / multiline Korean condition preservation / truncated file fallback, plus an opt-in fixture check against a real JSONL containing a goal event.

Version

Info.plist: 0.3.2 → 0.4.0, build 5 → 6.

Full Changelog: v0.3.2...v0.4.0

v0.3.2 — Mid-session model swaps + 4.7 (1M) label

Choose a tag to compare

@anhyobin anhyobin released this 11 May 14:37

Bug Fixes

Mid-session /model swaps now reflected in the UI

Switching models mid-session with /model (e.g. Opus → Sonnet) left every UI surface stuck on the original model — session row badge, context gauge, warning threshold, and menu-bar dot all kept reporting the first model the session saw.

Root cause: Both JSONL parsers (scanSessionSummary, scanTokensAndThinking) captured the model field only from the first assistant message in a session, guarded by an if model == nil check. Subsequent assistant turns with a different model were ignored.

Fix: The guard is removed. Both parsers now overwrite model on every assistant turn, so mainModel reflects whichever model served the most recent turn — matching the existing last-turn rule used by mainLastTurnUsage. Context-window ratio and warning limit are now evaluated against the model that actually produced the last turn.

(1M) label rule extended to the 4.7 generation

ModelNameFormatter.displayName previously suppressed the (1M) suffix for 4.7 models, on the (incorrect) assumption that 1M was implicit for that generation.

Fix: The 4-7 special-case is removed. Opus/Sonnet/Haiku 4.7 now follow the same ~/.claude/settings.json rule as 4.6 — if an ANTHROPIC_*MODEL env var maps the model with a [1m] suffix, the badge shows "Opus 4.7 (1M)"; otherwise just "Opus 4.7". One rule, no generation-specific exceptions.

Additional

  • Test coverage expanded to assert the (1M) rule across all three 4.7 families (opus/sonnet/haiku), and the ordering-regression guard now tolerates the settings-dependent suffix.

Full Changelog: v0.3.1...v0.3.2

v0.3.1

Choose a tag to compare

@anhyobin anhyobin released this 07 May 05:07

Bug Fix

Context Gauge 1M Detection

Models configured as 1M-context variants (e.g., us.anthropic.claude-opus-4-6-v1[1m]) were incorrectly shown with a 200K context limit in the gauge.

Root cause: The JSONL API response only contains short model IDs (claude-opus-4-6) without the [1m] suffix, so the previous keyword-based detection could never match.

Fix: The app now reads ~/.claude/settings.json at launch to cross-reference ANTHROPIC_MODEL and related env vars. If the configured model contains both the JSONL model's family-version key and [1m], the context gauge correctly uses 1M as the denominator.

Additional

  • Model badge now shows "(1M)" suffix for non-4.7 models running with 1M context (e.g., "Opus 4.6 (1M)")
  • New ClaudeSettingsReader utility — reads settings once per app launch, cached for performance

Full Changelog: v0.3.0...v0.3.1

v0.3.0 — Skill Tool Usage Visualization

Choose a tag to compare

@anhyobin anhyobin released this 22 Apr 14:14

Added

  • Skill tool usage visualization — extracts per-skill invocation counts
    from both the main session JSONL and every subagent JSONL, keyed by the
    full skill name (plugin namespaces like oh-my-claudecode:hud preserved).
    Surfaced in two places:

    • Session detail (expanded view): a one-line 🧩 Skills: summary
      below the thinking-block row, sorted by count DESC / name ASC. Shows
      top 4 entries joined by ·; overflow is +N more. Bound to
      totalSkillCounts (main + all subagents, active and completed) so
      skill calls remain visible after subagents drop off the active list.
    • Agent detail (expanded agent row): a purple-tinted chip section
      using FlowLayout, placed between the existing Tools and Files
      sections. Count shown only when > 1.
  • Skill counter badge (🧩 N) on collapsed active-session and
    recent-session rows, matching the existing thinking-counter (🧠 N)
    pattern. Hidden when zero. Active rows use the session-wide total
    (main + subagents); recent rows use main-session counts only (subagent
    data is not loaded for recent sessions).

Fixed

  • App footer version was hardcoded at "v0.2.0" — now reads from
    Info.plist dynamically so it stays in sync on every version bump.

Full Changelog

v0.2.0...v0.3.0

v0.2.0: Opus 4.7 + Apple-native status dot

Choose a tag to compare

@anhyobin anhyobin released this 19 Apr 15:19

Highlights

  • Opus 4.7 recognition with family accent colors (Opus=orange, Sonnet=blue, Haiku=green). Sonnet/Haiku 4.7 patterns are pre-registered for future releases.
  • Model badge on active session rows (previously missing) and recent session rows
  • Context-window gauge — 2pt hairline bar with orange at 80%, red at 95%. Expanded detail shows 312K / 1M (31%)
  • Extended-thinking counter (🧠 N) on every session row, with full "Thinking: N blocks" label in the expanded view
  • Menu-bar status dot — 8pt overlay with a priority stack (error > warning > active > inactive), following Apple's convention of reserving pulse for in-transition states

Added

  • Model badge on active and recent session rows with family accent color (Opus=orange, Sonnet=blue, Haiku=green). Active sessions previously showed no model at all.
  • Opus 4.7 recognition in ModelNameFormatter. Sonnet and Haiku 4.7 patterns are pre-registered for when those models ship; today only Opus 4.7 is released. The 4.7 generation is matched before 4.6 to avoid prefix shadowing. Also filled in the missing haiku-4-6 entry.
  • Extended-thinking block counter (🧠 N) on every session row, parsed from content[].type == "thinking" blocks. Hidden when zero. Expanded detail shows the full label "Thinking: N blocks" under the Context line.
  • Context-window gauge — 2pt hairline bar showing lastTurn usage / model max. Secondary below 80%, orange at 80–94%, red at 95%+. Expanded detail adds a tabular-num readout. Backed by a new ModelContextLimits table (1M for 4.7-generation models, 200K for earlier generations) and a contextUsageRatio that uses only the last assistant turn's usage snapshot to avoid per-turn cache_read over-counting.
  • Menu-bar status dot — 8pt overlay driven by a priority stack: error (red) > warning (orange, context ≥ 95%) > processing (blue, pulsing — reserved for v0.3) > active (green) > inactive (gray) > hidden. Green is static; pulse is reserved for in-transition states only.

Changed

  • JSONLParser.scanTokensOnly removed; callers use the new scanTokensAndThinking which returns tokens, thinking count, model, and the last-turn usage snapshot in a single pass.
  • Info.plist bundle version bumped to 0.2.0 (build 2).

Notes

  • Opus 4.7 uses a new tokenizer — token counts may read 1.0~1.35× higher than Opus 4.6 for the same work. Mixed-model comparisons may look inflated for Opus 4.7 sessions.

Full Changelog: v0.1.1...v0.2.0

v0.1.1

Choose a tag to compare

@anhyobin anhyobin released this 17 Apr 16:19

Fixed

  • Menu bar dropdown collapsed to ~70pt, hiding the session list and recent
    sessions between the header and the footer. MenuBarExtra(style: .window)
    sizes its window from the content's ideal size, and the inner ScrollView
    reports an ideal height of 0. Pinning a fixed height (.frame(height:))
    instead of a maximum (.frame(maxHeight:)) now allows the scroll view to
    fill the window. Observed on macOS 26.4; unclear which older versions are
    affected.

Changed

  • Extracted the menu bar dropdown width and height into a private enum Layout
    in MenuBarContentView.swift so the values live in one place.

Full Changelog: v0.1.0...v0.1.1