Commit graph

380 commits

Author SHA1 Message Date
iamtoruk
c2524308bf fix(menubar): drop the copilot-cli Keychain rung from Copilot discovery (#1198)
The Copilot CLI's Keychain item is written by a Node keyring library, so
/usr/bin/security is not in its partition list. Unlike the Claude Code item,
every read would raise the macOS "allow access?" dialog, and with the probe
cache a user who clicks Deny would be asked again every five minutes.

`gh auth token` already reaches the same users, because the Copilot CLI itself
falls back to gh, so the rung was risk without benefit.

The chain is now legacy plugin files, ~/.copilot/config.json and settings.json,
the environment variables, `gh auth token`, and a pasted token. No `security`
subprocess remains in the adapter, and the probe cache collapses to the single
gh answer.
2026-08-31 02:09:44 -07:00
iamtoruk
7e37d33acc fix(menubar): keep the Copilot credential chain private to the adapter 2026-08-31 02:05:38 -07:00
iamtoruk
25c1218874 fix(menubar): find Copilot credentials from the Copilot CLI keychain, gh, env, and a pasted token (#1198)
Modern Copilot clients no longer write ~/.config/github-copilot/hosts.json or
apps.json, so the menubar reported "No GitHub Copilot credentials found" even
after signing in through VS Code and the Copilot CLI.

Discovery now walks an ordered read-only chain, first hit wins, every rung
tolerating an absent, locked, or malformed source: the legacy plugin files,
the Copilot CLI keychain item (service copilot-cli, read via /usr/bin/security
so no partition-list prompt can fire), ~/.copilot/config.json and
settings.json, COPILOT_GITHUB_TOKEN / GH_TOKEN / GITHUB_TOKEN, `gh auth token`,
and finally a token pasted into Settings. The two subprocess rungs are cached
for five minutes so eligibility checks and the 30s refresh cadence never spawn
per call, and hasCredential stays spawn-free.

VS Code's encrypted secret storage is deliberately untouched.

The no-credentials message now says usage tracking still works and names every
way to connect, and the Copilot settings pane gains a token field.
2026-08-31 02:05:03 -07:00
iamtoruk
286c1daf0d fix(menubar): poll the pointer so Capacity Dock hover works over any frontmost app
The #1170 hover synthesis rides on a global .mouseMoved monitor, but
macOS only delivers global mouse-moved events while the frontmost app
itself requests them. Chrome does, so hover appeared fixed there;
Finder and Terminal do not, so the dock went hover-dead over the
desktop and most native apps.

Add a 10 Hz pointer poll feeding the same passthrough + hover sync
path. A same-position diff guard makes idle ticks free, and the timer
lives and dies with the existing monitor lifecycle.
2026-08-30 15:12:20 -07:00
Resham Joshi
fcbe978206
Merge pull request #1172 from avs-io/codex/fix-claude-keychain-test-isolation
fix: isolate overridden Claude credential homes
2026-08-29 15:24:04 -07:00
iamtoruk
03b6cc4857 fix(menubar): harden the Cursor quota adapter
Require every Dependencies field explicitly so a test can never fall
through to the live adapter and read the local Cursor session. Surface
an unreadable local store as its own honest error instead of a network
failure. Read the state database off the main actor so a lock held by
Cursor cannot stall the UI. Report the tighter of the Auto and API
pools when Cursor omits the total, since averaging hides an exhausted
pool behind an idle one.
2026-08-29 14:17:15 -07:00
iamtoruk
1f1954f257 fix(menubar): disconnect drops the provider from the persisted dock selection
A credential-less adapter left in the dock selection was silently
reconnected by the next scheduled refresh, undoing the user's explicit
disconnect. Cursor is the first adapter that needs no saved credential,
so the disconnect intent now persists: the provider leaves the stored
selection without latching manual-selection mode.
2026-08-29 14:17:15 -07:00
Aditya Vikram Singh
bce49926c3 feat: add native Cursor quota connection 2026-08-29 23:48:42 +05:30
Aditya Vikram Singh
6ac0db4604 fix: isolate overridden Claude credential homes 2026-08-29 23:38:37 +05:30
Resham Joshi
91b4ad87b6
fix(menubar): keep Capacity Dock hover alive after app deactivation (#1170)
SwiftUI onHover rides on NSTrackingAreas that only deliver while the
app is active. The dock lives in a nonactivating panel inside an
accessory app, so the first click into another app killed every hover
interaction until relaunch.

Hover enter/exit is now synthesized in CapacityDockController from the
existing local and global mouse monitors, which observe the pointer
regardless of activation. Provider rows are resolved by inverting the
layoutDetail row-midpoint math (CapacityDockPlacement.providerRowIndex,
unit-tested), and the SwiftUI onHover plumbing is removed so the
controller is the single hover source. Row exits no longer cancel the
rail's pending collapse, and a drag drop re-derives hover so a
stationary pointer resting on a row still opens its bubble.
2026-08-29 18:42:27 +02:00
iamtoruk
5958294f67 fix(menubar): only list providers with a live quota adapter in settings
The Capacity Dock settings sidebar listed the whole 69-provider catalog, but
only 7 have a live CodeBurn quota adapter; the rest just read 'coming soon'.
Filter the settings provider list to live-adapter providers so users only see
what actually works.
2026-08-29 02:26:33 -07:00
Aditya Vikram Singh
84fea9d550
feat(menubar): add native Capacity Dock (#1167)
* feat(menubar): add native capacity dock

* fix(menubar): harden dock interaction boundaries

* feat(menubar): polish native Capacity Dock for launch

- Flush top-edge docking via NSHostingView safe-area opt-out; shielding
  window level so the rail necks into the physical top like the system notch
- Liquid Glass legibility scrim so labels stay readable on light wallpapers
- Weekly-else-monthly headline window; unified severity colour bands
  (green <50 / yellow / orange / red) across rings and detail bars
- Squircle gauge + graphite theme defaults; auto-show connected
  subscriptions (up to 5) until the user edits the set, then respect it
- Capacity Dock Settings menu item; re-centre the settings window; info
  glyph for the About row
- Trim dead provider-credential override fields down to apiKey only
- Reconcile and extend tests for all of the above

---------

Co-authored-by: iamtoruk <hello@agentseal.org>
2026-08-29 00:37:30 -07:00
Aditya Vikram Singh
daafb593ea
fix(menubar): recover parked status item (#1161) 2026-08-28 13:20:48 -07:00
ozymandiashh
7a2e03e44d
fix(macos): launch mise-installed CLI from Spotlight (#1138) 2026-08-25 04:20:00 -07:00
iamtoruk
42e39ecff8 test(menubar): align Gemini retired-tier expectation with the em-dash-free message 2026-08-24 10:08:33 -07:00
iamtoruk
44027ad11f polish(menubar): pane-named window title, single tagline, version dedupe, em-dash sweep
Settings window titles itself after the visible pane like System
Settings; the popover subtitle adopts the website tagline (Your AI
Bill, Itemized); About stops printing dev (dev) when version and build
match; remaining user-facing em-dashes across popover and service
strings become sentence breaks.
2026-08-24 09:48:16 -07:00
iamtoruk
5783d1a6ff polish(menubar): solid flame silhouette for the About sidebar row
The 0101-detail flame collapses into a faint smudge at 20px; the row
now uses a solid silhouette cut from the website flame's own outline.
2026-08-24 09:43:41 -07:00
iamtoruk
c6ba1d5189 polish(menubar): brand and window refinements from live review
Status item returns to the SF flame (the 0101 mark blurs at menubar
size); About hero shows the full-color binary flame and the About
sidebar row its bare white template, no chip; footer credits Resham
Joshi (iamtoruk) / AgentSeal, MIT; About tagline matches the website
(Your AI Bill, Itemized); UI copy drops em-dashes (currency rows use
middots); Settings window is resizable and fullscreen-capable and
recenters when restored off-screen.
2026-08-24 09:41:53 -07:00
iamtoruk
c595cf5589 feat(menubar): animated flame-gradient wordmark matching the website
Popover header now renders the full CodeBurn wordmark filled with the
site's .flame-text gradient (#ff6a00 > #ffda44 > #e8553a > #ff8c00,
300% width, 3s ease sweep). Static gradient under Reduce Motion.
2026-08-24 09:40:28 -07:00
iamtoruk
0213f7e9f8 feat(menubar): live Antigravity quota — local language-server probe, Settings pane, Plan tab
Ports the desktop app's Antigravity flow: ps+lsof discovery of the
local language server, Connect-RPC quota/status probes over loopback
HTTPS with the self-signed-cert trust override scoped strictly to
127.0.0.1, disconnected-state UX when the app is not running. Also
swaps all provider sidebar marks to the official LobeHub icon set.
2026-08-24 09:40:04 -07:00
iamtoruk
8908bce69e feat(menubar): CodexBar-faithful Settings shell, binary-flame status icon, resource-bundle packaging
Settings window now replicates CodexBar's System Settings layout
(880x620 fullSizeContentView window, transparent titlebar, fixed 260pt
sidebar, SF Symbol gradient chips, native search field, grouped About
pane with Updates/Links). Status item renders the 0101 flame template
with severity tints. package-app.sh ships the SwiftPM resource bundle,
fixing the Bundle.module launch crash. Sidebar layout modeled on
CodexBar (MIT, steipete/CodexBar).
2026-08-24 09:22:21 -07:00
iamtoruk
826342179e feat(menubar): live GitHub Copilot quota — service, Settings pane, Plan tab
Ports the desktop app's Copilot flow (editor-plugin OAuth token from
~/.config/github-copilot, copilot_internal/user snapshot) read-only,
with github.com-host preference and UI-string sanitization.
2026-08-24 09:10:09 -07:00
iamtoruk
71771a41a4 feat(menubar): System Settings-style sidebar Settings with provider logo rows
Replaces the tab strip with a NavigationSplitView shell: search field,
General/About tile rows, and a data-driven Providers section whose real
logo marks render gray at rest and accent-tinted on hover, with green
connected dots and an N-on counter. Bundles template icon assets.
2026-08-24 08:51:07 -07:00
iamtoruk
05fb99d9af feat(menubar): live Gemini quota — service, Settings pane, popover Plan tab
Ports the desktop app's Gemini Code Assist quota flow (loadCodeAssist
tier + retrieveUserQuota buckets, ~/.gemini/oauth_creds.json read-only,
in-memory-only refresh) to the menubar. Plan tab and empty-state gating
now treat Gemini as plan-capable.
2026-08-24 08:43:47 -07:00
iamtoruk
272da806e4 fix(menubar): no-output watchdog, lock signal cleanup, dead-pid takeover, serve orphan reaping
The 0.9.20 menubar never completed a fetch on a large corpus: the cache
version bump forced a full rehydration, DataClient's fixed 45s kill ended
it mid-transaction, cache-refresh-lock had no signal cleanup so the dead
holder's lock survived, and DEFAULT_WAIT_MS (30s) < DEFAULT_STALE_MS (90s)
meant no waiter could ever recover it.

A. Swift port of #1096's watchdog. CLIWatchdog holds the constants and the
   pure verdict; spawns and the resident serve child set CODEBURN_PROGRESS=1;
   the window restarts on any stdout/stderr byte; 45s silence, 10min cold
   floor until the first payload, 15min ceiling, SIGTERM then SIGKILL after
   5s. ServeConnection's fixed 60s warm cap becomes the same silence window,
   re-armed by each progress frame, and a spent death budget is now a
   5-minute cooldown instead of disabling the resident for the app run.

B. cache-refresh-lock arms SIGINT/SIGTERM cleanup the way session-cache does
   for hydrating.lock, so a SIGTERMed holder unlinks its own lock.

C. Staleness also opens on a dead holder pid, and the waiter budget derives
   from staleMs so it can never expire before the gate it waits for. A live
   holder - fresh heartbeat, pid answers signal 0 - is never taken from.
   parser.ts heartbeats through the lock wait, the one silent stretch left.

D. The app closes its end of a retired child's stdin (dropping the handle
   left the pipe alive inside the Process), reaps every serve child
   synchronously at quit, and records pid+argv so a crash-orphaned child is
   reaped next launch. serve's final exit no longer runs through a
   monkeypatched process.exit, and its post-drain cleanup is bounded.

Closes #1117
2026-08-23 05:35:17 -07:00
iamtoruk
affd40e230 payload: add-only stale marker for read-only stale menubar serves (#771)
isSessionHydrationComplete() (parser.ts, PR #937) already reports when a
read-only serve skipped or staled real on-disk changes; thread it through
buildMenubarPayload as an optional stale field, present and true only on a
stale serve, always absent otherwise, so older/newer CLI-app pairs stay
compatible. Mirrors the field into the desktop renderer types and the macOS
menubar's Codable payload model (data layer only, no view change).

Drafted with minimax/MiniMax-M3 via local gateway.
2026-08-22 11:38:46 -07:00
Resham Joshi
96fc940798
Merge pull request #1037 from avs-io/fix/menubar-keychain-credential-cache
fix(menubar): migrate Claude/Codex caches to namespaced Keychain
2026-08-19 12:05:47 -07:00
iamtoruk
dba9a45825 test(mac): stop the concurrent-timeout test starving the pool it waits on
The test blocked on a DispatchSemaphore with a 15s deadline, commented as
running "on a real thread, not the cooperative pool". Swift Testing invokes
synchronous test bodies from a task on the cooperative pool, so the wait
parked one of activeProcessorCount workers on the very task group it was
waiting for. With 16 cores locally there is slack; on the 3-core macos-latest
runner, alongside the rest of the parallel suite, the group made no progress
at all and the wait expired.

Await the group directly instead, which also lets the compiler reject the
blocking wait (unavailable from async contexts), and bound the test with
.timeLimit rather than a hand-rolled wall clock. Assert each child came back
with a signal status, so the test now proves the timeout killed every hung
process instead of only that the group returned.

Reproduced by parking all but 3 cooperative threads for the run: 3/3 failures
at 15.0s before, 3/3 passes after. 10x full suite under CPU load: 160/160.
2026-08-19 11:58:39 -07:00
iamtoruk
0dff0b66d5 docs(menubar): state what the Keychain move actually guarantees
The Codex settings copy implied the cached credential was app-private. It is
a normal login-Keychain item: reachable by programs running as you, with no
per-app ACL. The real win is that it is no longer a world-readable 0644 file,
so say that instead.

Also documents why readAfterSecuringPermissions repairs permissions before
validating content (validating first would read the secret while it is still
world-readable, which is the window the function exists to close), and why the
Keychain service names are deliberately not derived from CFBundleIdentifier
(the Electron app hardcodes the same strings).

Adds the #1037 changelog entry.
2026-08-19 11:43:44 -07:00
iamtoruk
0f7bfb3eb2 fix(menubar): make a failed disconnect leave a consistent state
disconnect() cleared the usage block before anyone knew whether the delete
had worked, and AppStore then returned early on failure — so a failed
disconnect cleared some state, left the rest, and still posted
subscriptionDisconnected. It also carried a second !isSuccess branch that the
early return had already made unreachable.

Both services now return the delete result and only clear the usage block on
success, so a failure changes nothing at all: the provider stays connected,
Disconnect stays available, and the banner asks for a retry. That matches the
success path's ordering instead of half-applying it.

Errors reaching the generic catches now render localizedDescription rather
than String(describing:), so a Keychain failure shows its message instead of
an enum dump with the raw item name in it.
2026-08-19 11:43:44 -07:00
iamtoruk
0fd8419bfd fix(menubar): keep the newer credential copy and drop the sticky retry flag
Three fixes in the store read path.

A locked keychain no longer reads as a disconnect. currentRecord() treated
any failure from readOurCache() as fatal, and a nil as "the item vanished",
which cleared isBootstrapCompleted. .unavailable now falls back to the last
known record and leaves the flag set.

Recency. A Keychain hit always won and the legacy file was then unlinked,
even when the file was newer. This service name has been in use since May
2026, so an upgrading install can hold a months-old item beside a file the
pre-migration build wrote today; the older token won and the newer copy was
deleted. Both stores now compare first (expiresAt for Claude, lastRefresh for
Codex) and adopt the later one before anything is removed. Codex matters most
here: serving a spent rotating refresh token ends in a terminal invalid_grant.

lastLegacyCleanupFailed is gone. It was set on every cleanup path and read
only by tests, never surfaced. The retry it was meant to signal already
happens, because the unlink is attempted on every successful read.

Also serializes migrate + unlink under the existing SafeFile.withExclusiveLock
so two menubar instances cannot race on the same legacy file, and drops a
leftover no-op local.
2026-08-19 11:43:32 -07:00
iamtoruk
e213e192b4 fix(menubar): never let a Keychain read raise UI on the refresh timer
Cache reads run on the background quota timer, so they must not be able to
put a panel on screen. Measured on macOS 15 against a throwaway keychain:
with the keychain locked, SecItemCopyMatching blocks on an unlock panel even
when the query carries kSecUseAuthenticationUI: ...Fail or a non-interactive
LAContext. Both of those govern the data-protection keychain; unlocking a
file-based keychain is something securityd drives itself. The only reliable
suppression is not issuing the read, so check lock state first and report
.unavailable instead.

.unavailable is separate from readFailed on purpose: a locked keychain means
"cannot look right now", not "the item is gone", and callers must not turn it
into a disconnect. It also carries a readable errorDescription so a -25308
reaching the UI reads as "Keychain unavailable" rather than a struct dump.

SecKeychainGetStatus is soft-deprecated with no replacement that reports
file-keychain lock state; annotating the warning away only moves it to the
call site, so it is left visible with a comment.

Adds the first test that touches a real Keychain, against a throwaway service
name no build reads, skipped when the host has no usable Keychain.
2026-08-19 11:43:22 -07:00
Aditya Vikram Singh
98d109d425 fix(menubar): fail closed on leftover JSON and partial Disconnect
Extra High MERGE AFTER FIX on 252ea92. Pathname chmod was
unverified. Disconnect hid retry when only the legacy file
survived. Secure read stopped at exactly maxBytes.

Tighten leftovers via opened-fd fchmod+fstat. Keep bootstrap
unless both Keychain and legacy deletes succeed. Read
maxBytes+1 so growth past the limit is rejected.
2026-08-19 20:56:07 +05:30
Aditya Vikram Singh
252ea92d3b fix(menubar): keep Keychain failure paths from leaving plaintext
A valid Keychain item plus a leftover JSON used to skip chmod, so a
failed unlink could leave 0644 secrets on disk. Failed Disconnect also
cleared bootstrap and hid the retry. Repair leftover files to 0600,
keep bootstrap when Keychain delete fails, revalidate the opened fd,
and loop the secure read.
2026-08-19 20:02:44 +05:30
Aditya Vikram Singh
cdaa5b7ed3 fix(menubar): migrate Claude/Codex caches to namespaced Keychain
Stop writing OAuth caches as Application Support JSON. Persist
CodeBurn-owned items in Keychain, secure-read and migrate leftover
0644 files only after read-back verification, and keep Claude from
storing a refresh token.
2026-08-19 15:21:28 +05:30
iamtoruk
185d6b3b31 mac: register the login item with SMAppService
The menubar told System Events to make its login item, so macOS asked for
Automation access on first launch. SMAppService.mainApp does it in-process
with no Automation grant. No AppleScript fallback: a failure must not bring
the prompt back. Package floor is macOS 14, so the 13+ API needs no
availability guard.

Fixes #1026
2026-08-18 07:48:03 -07:00
iamtoruk
d9a9486b6d docs(dsh): finish the provider registration checklist
docs/providers/NEW_PROVIDER.md items the PR had not reached yet, plus the two
surfaces that are functional rather than cosmetic:

- docs/providers/dsh.md and its row in the provider index, documenting the
  storage layout, the JSONL-backend-only scope (the opt-in SQLite persistence
  backend is not read), and that DSH is a developer preview whose format
  version 0 implies no compatibility.
- CHANGELOG entry under Unreleased.
- README provider count 40 -> 41 and a data-locations row.
- app/package.json: $HOME/.dsh in the snap personal-files allowlist, without
  which the Linux snap build cannot read DSH sessions at all.
- UsageDataChangeGuard: the DSH sessions root, without which the menubar never
  notices a new session and does not refresh.
- Bumps the dsh parse version, since the parser's attribution changed.
2026-08-17 10:59:58 -07:00
ozymandiashh
a95a2c5bf8 fix(desktop): close cache and lifecycle review gaps 2026-08-12 20:31:17 +03:00
ozymandiashh
d8d343e83a perf(desktop): share cache state and eliminate duplicate cold hydration 2026-08-12 17:16:41 +03:00
iamtoruk
6cd11ab436 fix(app,dash): punchcard tooltip never crops; remove the menubar Workflow strip
- Punchcard tooltips flipped below the cursor on the top rows (the
  overflow container clips anything above its own edge) and clamp
  horizontally near the strip edges. Applied to both the app and dash
  copies of the component.
- The menubar Workflow strip is removed (view, strip model, tests):
  the popover is the compact surface and the workflow metrics live in
  the desktop app, dash and TUI. The payload keeps emitting the block
  (add-only contract; the other surfaces read it).
2026-08-10 14:35:40 -07:00
iamtoruk
411d52f924 feat(app,menubar): surface parity — Spend punchcard in the app, Pull requests strip in the menubar
The release's new capabilities were unevenly surfaced: the Spend
punchcard existed only on the web dashboard, and the menubar decoded
nothing of the PR-attribution block the payload already carries.

- Desktop app: new Punchcard component (hour-of-day x weekday spend
  matrix, ported from the dash and restyled to the app's tokens) on the
  Spend page. Fed by a dedicated getTimeline bridge channel that fetches
  the payload WITH history.timeline; every other fetch keeps the lean
  --no-timeline path, and the serve child makes the extra fetch cheap.
  Hides gracefully when the payload has no timeline (older CLI).
- Menubar: PullRequestsSection renders the top three PRs by attributed
  spend under the Workflow strip; MenubarPayload now decodes the
  pullRequests block (decodeIfPresent, so older payloads are unchanged).
  Hidden when absent or empty.

Deliberately NOT ported: codex Tok/s (reads rollout files per session -
too heavy for payload cadence; stays a TUI/report analytics view) and
the punchcard in the menubar (a 7x24 matrix has no legible place in a
compact popover).

App suite 468 green (bridge channel pinned, mocks extended), swift
build + 156 tests green, CLI suite green.
2026-08-10 14:29:26 -07:00
iamtoruk
df05d7792e perf: web dashboard period prefetch + menubar serve client
Two more surfaces adopt the resident-serve pattern the desktop app got:

- Web dashboard: every period tab is prefetched sequentially right after
  startup, so the first click on 7d/30d/Month answers from the payload
  cache instead of paying a full parse; stale-while-revalidate rebuilds
  behind a served payload past 75% of the TTL so expiry never lands its
  multi-second parse on a user's click. Lifetime prefetches last.
- Menubar: ServeConnection (Swift actor) holds one codeburn serve --stdio
  child; status payload fetches route through it once warm, with the same
  contract as the app client — cold start and every failure keep the
  spawn path, three child deaths disable serve for the run, requests
  time out by killing the child, app termination shuts it down, and a
  pre-serve CLI (0.9.19) simply dies into permanent spawn fallback, so
  mixed-version installs degrade gracefully.

swift build clean, swift test 156/156, CLI tsc clean; verified live with
both the Electron app's and the menubar's serve children resident and
answering.
2026-08-10 11:17:59 -07:00
iamtoruk
1ba09dc423 docs(menubar): correct the activation-policy comment the #868 probe disproved 2026-08-10 06:21:37 -07:00
ozymandiashh
0c8cb9e8eb experiment(menubar): activate before setupStatusItem, no policy transition
Test build for #868. Keeps the activation policy pinned at .accessory
(LSUIElement makes the :92-96 call a no-op in packaged builds anyway) and
restores only the NSApp.activate(ignoringOtherApps: true) half of the
#147 fix, immediately before the status item is created.
2026-08-10 06:20:49 -07:00
iamtoruk
4e9c3771f0 feat(menubar): OpenClaude provider tab, change-guard watch root, docs index rows 2026-08-10 02:53:05 -07:00
Resham Joshi
44a94f52cf
Merge pull request #883 from ozymandiashh/feat/877-preferred-terminal
feat(menubar): add preferred terminal setting with Terminal.app fallback
2026-08-03 15:47:58 -07:00
ozymandiashh
1959550218 feat(menubar): add preferred terminal setting with Terminal.app fallback
Full Report and Optimize always opened Terminal.app. Add a closed
PreferredTerminal enum (Terminal.app, iTerm2), a General settings picker,
and graceful fallback: chosen terminal -> Terminal.app -> headless spawn.

Defaults to Terminal.app so existing users see no change.

The terminal is selected from a closed enum, never a user string, so the
`tell application "..."` target stays a compile-time literal. Commands are
still whitespace-joined argv validated token-by-token by CodeburnCLI.isSafe
before any interpolation, preserving the shell-injection invariant.

Only terminals with a real "run in a live window" scripting verb are listed:
Terminal.app has `do script`, iTerm2 has `write text` on a session. Ghostty,
WezTerm, Warp, Alacritty and kitty expose no equivalent, so they keep the
existing headless fallback rather than shipping a window that closes on exit.

The iTerm2 script targets `application "iTerm"`, not `"iTerm2"`. AppleScript
resolves the name of a not-yet-running app through LaunchServices by bundle
file name, and the bundle is iTerm.app. Measured on iTerm2 3.6.11: with the
app quit, `tell application "iTerm2"` fails to compile (-2741) while
`tell application "iTerm"` compiles, cold-launches iTerm2 and runs the
command. The `"iTerm2"` spelling only works while the app already happens to
be running.

Fallback is a chain that checks results rather than a single fire-and-forget
pick, because "installed" does not imply "scriptable": osascript can still
fail on a missing Automation approval or a broken bundle. Each candidate is
run, waited on and its exit status checked, off the main thread so the
popover stays responsive; only once every candidate has failed do we spawn
headless. Every step logs via NSLog, so a user who sees no window has a trail
in Console.app instead of an app that looks dead. The decision logic is
extracted into terminalChain/runFirstWorking so tests exercise
"primary failed -> fell back" without launching anything.

Document the setting in the README next to the other menubar defaults keys.

Closes #877
2026-08-04 00:19:00 +03:00
AgentSeal
6530546f73 fix(mac): use regular weight for the menubar title text
Render the macOS menu-bar title with `.regular` instead of `.medium`, matching
the visual weight of SwiftBar / MeetingBar. The flame symbol keeps its `.medium`
config. Applies the one-line change from #851.

Co-authored-by: Tim De Pauw <timdp@users.noreply.github.com>
2026-08-03 22:52:22 +02:00
AgentSeal
067174b885 Merge branch 'main' into feat/menubar-degraded-device-indicator 2026-08-03 21:34:54 +02:00
AgentSeal
ac8ff954cd Merge branch 'main' into feat/desktop-combined-scope 2026-08-03 21:33:03 +02:00