feat: self-healing symlinks, project bindings, prompt-indicator, logs (0.22.0)#23
Merged
Conversation
added 6 commits
July 22, 2026 11:06
… (0.22.0)
Ships the reviewable, verified subset of the shell-UX work. Four features, each
built on master and checked against real profiles:
- Self-healing symlinks: sync/rebuild prunes a profile's dangling links (source
entry gone) and links to entries that became private. Adapter-managed links
(codex's aimux.config.toml overlay and plugins) are exempt — they come from
extraLinks, not the shared-entry loop.
- Project -> profile bindings: an optional `bindings` list maps a directory
pattern to a profile. Relative patterns anchor to $HOME, never process.cwd(),
so a binding means the same directory wherever aimux is invoked from.
- `aimux prompt-indicator` (alias `prompt`) for shell/Starship prompts.
- `aimux logs [session]` to view/grep claude and codex transcripts, colored only
on a TTY so piping to a file or grep yields clean text.
Fixes a doctor/sync regression along the way: codex's `plugins` link was matched
by the prune loop but is absent from the codex share allowlist, so every sync
unlinked and recreated it ("repaired plugins" on every run) and doctor reported
the same entry as both a conflict and valid.
Deliberately NOT included from the original branch, and why:
- SQLite WAL pragma: a no-op. The session-index DB is a symlink to the source
that codex already keeps in WAL, and it shelled out to an undeclared `sqlite3`
binary whose absence was indistinguishable from success.
- `claude attach`: no such subcommand exists (verified against claude 2.1.217),
and the TUI defaulted Enter on a live session to it.
- Rate-limit auto-failover: detected rate limits by substring-matching the
model's own reply ("429", "rate limit"), with no loop bound and no CLI filter
on the failover target. Needs a redesign against the real stream protocol.
Verified: 305 tests pass, tsc clean, and on real profiles `doctor` reports all
four healthy (it pruned two genuinely dangling links), `rebuild` of a codex
profile is now idempotent, and `aimux logs` emits no escape codes when piped.
A binding made bare `aimux use` skip the picker entirely inside a bound directory, silently removing the interactive choice with no way back. --pick restores it on demand.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ships the reviewable, verified subset of the shell-UX work from #22. Each feature was rebuilt on current
masterand checked against real profiles on a live machine.What's in
Self-healing symlinks.
sync/aimux rebuildnow prunes a profile's stale links — a symlink pointing at a source entry that no longer exists, or at an entry that has since become private. Before this they piled up silently andaimux doctorcould only report them; you had to delete them by hand.Project → profile bindings. An optional
bindingslist inconfig.yamlmaps a directory pattern to a profile, so entering a project selects the right subscription.*and**are supported — use~/work/**to include subdirectories, since a bare~/workmatches only that directory. Relative patterns anchor to$HOME, neverprocess.cwd(), so a binding means the same directory whereveraimuxis run from.aimux prompt-indicator(aliasprompt) — prints the active profile for a shell prompt or Starship.--format '[aimux: %s]'wraps it; every%sis substituted and the name is inserted literally.aimux logs [session]— view or grep a session transcript (--last,-g <query>) for both claude and codex formats. Colors go only to a TTY, so piping to a file orgrepgives clean text;NO_COLORis respected.Bug fixed along the way
aimux doctorreported a healthy codex profile as broken. codex'spluginslink is adapter-managed (created byextraLinks) and deliberately outside the codex share allowlist, but the prune loop matched it anyway. Result: every sync unlinked and recreated it — a spuriousrepaired pluginson everyaimux run <codex-profile>— and the health sweep listed the same entry as both aconflictandvalid. Adapter-managed links are now exempt from both sweeps.Deliberately left out of #22, and why
sqlite3binary whose absence was indistinguishable from success (spawnSyncreturns{error}rather than throwing, and the result was never checked).claude attach— no such subcommand exists (checked against claude 2.1.217). The TUI made it the default action for Enter on a live background session, so the most common interaction returned exit code 1."429"/"rate limit"/"overloaded", with no loop bound and no CLI filter on the target profile. Asking about HTTP 429 handling would bounce a session between subscriptions indefinitely, each hop a billed turn. This needs a redesign against the real stream protocol, not a patch.Also note: #22 could not be built at all —
src/core/index.tsexported./logs.js, butlogs.tswas never committed (it existed only as an untracked file locally, which is why tests looked green). Those files are included here.Verification
tscclean. The codexpluginsregression test was confirmed to fail without the fix (expected [ 'plugins' ] to not include 'plugins') and pass with it.expandHome()already resolves a bare relative path against the cwd, so the initialisAbsoluteguard never fired. Fixed by testing the raw pattern.aimux doctornow reports all four healthy — the self-healing pruned two genuinely dangling links (plans,tasks) that had been stale since April.aimux rebuildof a codex profile is idempotent (repaired: 0on repeat runs).aimux logs --last | grepemits zero escape codes.Bumps 0.21.1 → 0.22.0.