Skip to content

feat: self-healing symlinks, project bindings, prompt-indicator, logs (0.22.0)#23

Merged
Shahinyanm merged 6 commits into
masterfrom
feature/shell-ux-safe
Jul 22, 2026
Merged

feat: self-healing symlinks, project bindings, prompt-indicator, logs (0.22.0)#23
Shahinyanm merged 6 commits into
masterfrom
feature/shell-ux-safe

Conversation

@Shahinyanm

Copy link
Copy Markdown
Member

Ships the reviewable, verified subset of the shell-UX work from #22. Each feature was rebuilt on current master and checked against real profiles on a live machine.

What's in

Self-healing symlinks. sync / aimux rebuild now 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 and aimux doctor could only report them; you had to delete them by hand.

Project → profile bindings. An optional bindings list in config.yaml maps 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 ~/work matches only that directory. Relative patterns anchor to $HOME, never process.cwd(), so a binding means the same directory wherever aimux is run from.

aimux prompt-indicator (alias prompt) — prints the active profile for a shell prompt or Starship. --format '[aimux: %s]' wraps it; every %s is 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 or grep gives clean text; NO_COLOR is respected.

Bug fixed along the way

aimux doctor reported a healthy codex profile as broken. codex's plugins link is adapter-managed (created by extraLinks) and deliberately outside the codex share allowlist, but the prune loop matched it anyway. Result: every sync unlinked and recreated it — a spurious repaired plugins on every aimux run <codex-profile> — and the health sweep listed the same entry as both a conflict and valid. Adapter-managed links are now exempt from both sweeps.

Deliberately left out of #22, and why

  • SQLite WAL pragma — a no-op. The session-index DB is a symlink to the source DB that codex already keeps in WAL, and the code shelled out to an undeclared sqlite3 binary whose absence was indistinguishable from success (spawnSync returns {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.
  • Rate-limit auto-failover — it decided a rate limit had occurred by substring-matching the model's own reply for "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.ts exported ./logs.js, but logs.ts was never committed (it existed only as an untracked file locally, which is why tests looked green). Those files are included here.

Verification

  • 305 tests pass, tsc clean. The codex plugins regression test was confirmed to fail without the fix (expected [ 'plugins' ] to not include 'plugins') and pass with it.
  • The bindings test caught a real mistake in my first attempt: expandHome() already resolves a bare relative path against the cwd, so the initial isAbsolute guard never fired. Fixed by testing the raw pattern.
  • On real profiles: aimux doctor now reports all four healthy — the self-healing pruned two genuinely dangling links (plans, tasks) that had been stale since April. aimux rebuild of a codex profile is idempotent (repaired: 0 on repeat runs). aimux logs --last | grep emits zero escape codes.

Bumps 0.21.1 → 0.22.0.

shahinyanm 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.
@Shahinyanm
Shahinyanm merged commit c7b46af into master Jul 22, 2026
@Shahinyanm
Shahinyanm deleted the feature/shell-ux-safe branch July 22, 2026 07:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant