Skip to content

Latest commit

 

History

36 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ai-session

Small local launcher for AI CLI accounts that need isolated authentication state.

The first version keeps account state separate instead of copying or rewriting tokens. Each launched process receives only its profile-specific state directory:

  • Codex: CODEX_HOME
  • Claude Code: CLAUDE_CONFIG_DIR
  • Antigravity CLI: a profile-local HOME containing its .gemini state
  • OpenCode: XDG_CONFIG_HOME, XDG_DATA_HOME, and XDG_STATE_HOME

This prevents separate accounts from racing over one credential file and lets each official CLI own its refresh-token lifecycle. ai-session never reads, prints, or stores token contents.

Install

./install.sh

The binary is ai, built from cmd/ai. go install ./... produces the same thing in $(go env GOPATH)/bin.

The build must not pass -buildvcs=false: the commit stamp the Go toolchain embeds is what ai version and the TUI's update check compare against the repository, and a binary built without it can only report that it does not know.

ai self-update does not rebuild from this checkout — it keeps its own clone at ~/.config/ai/repo and rebuilds from that instead (see "Update check and self-update" below), so wherever you happen to run install.sh from doesn't matter to it either way.

Create profiles

ai profile add codex-personal codex
ai profile add codex-work codex
ai profile add claude-personal claude
ai profile add antigravity-personal antigravity
ai profile add opencode-go opencode
ai profile add deepseek opencode

Application profiles

A regular profile is for you, launched interactively. An app profile is for something else — a program of your own that spawns an agent CLI as a subprocess and points it at a state directory through its own static config, never asking ai what that path currently means. ai run can re-resolve a profile name on every launch; a program that isn't ai can't, so app profiles exist to give it a path that never changes while what it actually points at does.

ai profile add gemini-work antigravity
ai profile add gemini-personal antigravity
ai app add shiori gemini-work gemini-personal
ai app path shiori
# /home/you/.config/ai/profiles/apps/shiori

Paste that path into shiori's own config wherever it expects an agent's home directory (HOME for Antigravity, CLAUDE_CONFIG_DIR for Claude Code, CODEX_HOME for Codex — whatever shiori's backend reads). The first member listed becomes active. To switch which one shiori actually uses:

ai app use shiori gemini-personal

ai app path shiori never changes; what it resolves to does. Nothing is copied and no new credential store is created — the app profile is a symlink at ai app path shiori, and switching just repoints it at a different member's real profile directory (ai app list shows the current mapping). Members don't have to share a provider: each provider's env vars address a subpath of its own profile directory (.../gemini-work/home, .../codex-work/codex, and so on), and the symlink stands for the whole directory, so whichever provider-shaped path shiori's config already points into resolves correctly as long as the active member is that provider. Switching to a member of a different provider than shiori was last configured for means updating shiori's own backend selection too — that part is shiori's integration, not something ai can do on its behalf.

There's no live switching: ai app use only has to be right for shiori's next launch, not for one already running with the old target open.

Ordinary ai commands also accept an app name in place of a profile name, resolving to whichever member is currently active — useful for testing an app profile interactively without touching shiori at all:

ai run shiori          # launches gemini-personal, same as ai run gemini-personal
ai login shiori
ai install shiori

The terminal title, AI_PROFILE, and any status line still name the real member (gemini-personal), not the app (shiori) — that's the identity actually paying for the session, and showing it as-is means every existing indicator works unchanged.

App names and profile names share one namespace: you can't name an app the same as an existing profile (or vice versa), and apps itself is reserved. There's no ai app rm yet, matching plain profiles, which have no delete command either — and a profile currently used by an app can't be renamed from the TUI, since that would leave the app's symlink pointing nowhere.

Install the provider CLIs

ai-session isolates state; it does not ship the CLIs themselves. A profile can be fully configured and still fail at launch because its command was never installed, and exec's own message for that names neither the profile nor the way out. The selected-profile panel therefore shows a cli line — where the command resolves, or not installed — press i — and ai install runs the provider's own installer:

ai install claude-personal   # a profile: installs its provider's CLI
ai install codex             # or a bare provider, before any profile exists
Provider Installer
Codex curl -fsSL https://chatgpt.com/codex/install.sh | sh
Claude Code curl -fsSL https://claude.ai/install.sh | bash
Antigravity curl -fsSL https://antigravity.google/cli/install.sh | bash
OpenCode, DeepSeek curl -fsSL https://opencode.ai/install | bash

These are each vendor's own documented script. The launcher deliberately keeps no package list of its own: it would be one more thing to keep correct, and wrong here means installing the wrong software.

Two details differ from typing the one-liner yourself. The script is downloaded before it is run rather than piped straight into a shell — the same thing ends up executing, but a truncated response cannot half-execute and the script is on disk to read when an install goes wrong. And it runs in your ordinary environment, not a profile's: the CLI binary is shared by every profile, and Antigravity's private HOME would otherwise bury the install inside one profile's state directory. In the TUI, i shows the exact command and asks before running it.

A provider with no known installer is refused rather than guessed at.

Log in through the isolated profile:

ai login codex-personal
ai login codex-work
ai login claude-personal
ai login antigravity-personal
ai login opencode-go

Update a profile's CLI with its supported updater:

ai update codex-personal
ai update claude-personal
ai update antigravity-personal
ai update opencode-go

This runs codex update, claude update, agy update, or opencode upgrade respectively.

ai run uses the credentials already stored in that profile. A new profile has no credentials until you run ai login <profile>; the login flow is performed by the official CLI and may open a browser or ask for a device code. The launcher intentionally does not copy the account from your normal Codex, Claude, or Antigravity configuration.

Run a profile:

ai run codex-personal
ai run codex-work exec -- "review this repository"
ai run claude-personal
ai run antigravity-personal
ai run opencode-go

Codex and Claude profiles can be launched concurrently from multiple terminals. Every launch creates a temporary directory beneath the profile's instances/ directory for its PID lock and an instance.json recording the folder it was launched in, while all instances continue to use the profile's single CODEX_HOME or CLAUDE_CONFIG_DIR. This means one login per profile and the same settings and session history in every instance. The instance directory is removed when the CLI exits and reclaimed automatically after a crash.

Antigravity and OpenCode profiles remain exclusive: a second launch is refused while the first is running because their file-backed OAuth stores are not known to coordinate token refreshes across processes.

Antigravity does not expose a config-home override, so its entire HOME is the profile's home/ directory. On Linux, ai-session also clears DBUS_SESSION_BUS_ADDRESS for the child so agy uses its profile-local ~/.gemini/antigravity-cli/antigravity-oauth-token instead of silently sharing one account through Secret Service. Commands run by Antigravity inherit that private home too; project paths are unchanged, but host-home resources such as ~/.ssh should be referenced by absolute path when needed.

The keyring bypass is Linux-specific; until agy exposes a portable keyring or config-home override, OAuth profiles on macOS and Windows may still resolve to the CLI's shared OS-keyring entry.

The run command is optional when the first argument is a profile name. These are equivalent, and any following arguments are passed to the profile's configured command:

ai claude-personal
ai run claude-personal
ai codex-work exec -- "review this repository"

If the bare argument is not an existing profile, ai returns an error instead of guessing a provider or creating a profile.

Knowing which profile you are in

Once the provider CLI starts it owns the screen, and nothing in its own interface says which account is paying for the session. Two markers cover that:

  • The terminal title. A launch sets the window or tab title to ai · <profile> (<provider>) and restores the previous title on exit, using the xterm title stack. A terminal without that stack simply keeps the title ai set, which still names the right profile. Nothing is written when output is redirected, so piped output stays clean.

  • AI_PROFILE and AI_PROVIDER. Every launched process gets both, for every provider, so a shell prompt, a CLI statusline, or a tmux status bar can render the profile however you like:

    # a Claude Code statusline, in the profile's own settings.json
    echo "[$AI_PROFILE] $(basename "$PWD")"

    They are also listed by ai env <profile>. An inherited pair is stripped before a launch, so a session started from inside another session reports its own profile rather than its parent's.

Each profile can also store default arguments. They are prepended to arguments provided at launch, but are not used for login or integration commands. Set them in the interactive profile editor; shell-style quotes group values with spaces without invoking a shell. The same editor accepts a short note for each profile. Arguments typed for a single launch with p in the TUI are appended after the stored defaults.

Showing the profile inside the CLI

The terminal title and the TUI header both stop being visible the moment the provider CLI takes over the screen. ai integrate statusline <profile> gives a profile the best indicator its provider supports:

ai integrate statusline claude-personal
ai integrate statusline antigravity-personal
ai integrate statusline codex-work

Claude Code and Antigravity render it themselves. The command merges a statusLine into that profile's own settings.json, keeping every other key, and the line reads AI_PROFILE out of the launch environment so it names the profile actually in use. An existing statusLine is refused rather than replaced: it is yours, and overwriting it would silently drop whatever it showed.

Codex and OpenCode have no equivalent, so those profiles are marked "indicator": "tmux" in profiles.json and launch inside a tmux session whose status bar sits above the CLI:

 ai · codex-work (codex)                        ~/projects/ai-session
──────────────────────────────────────────────────────────────────────

The bar survives whatever the CLI draws, which a terminal escape sequence cannot: these CLIs take the alternate screen and manage their own scroll regions. tmux's prefix is disabled so the session stays furniture rather than a multiplexer to think about.

Two details matter for correctness. Each wrapped launch gets its own tmux socket, because tmux runs a server and an existing one would hand the CLI that server's environment instead of the profile's isolated directories. The socket lives under XDG_RUNTIME_DIR rather than beside the lock, because a Unix socket path is capped near 108 bytes and the per-instance lock directory is long enough to exceed that on its own. Stopping a wrapped instance kills its tmux server, not just the client.

Codex also configures its own terminal title, so it will overwrite the one ai sets; the tmux bar is the reliable indicator there.

Update check and self-update

The TUI asks GitHub whether the build is behind the repository as its main screen appears, and says so in the title bar when it is:

↑ 3 commits behind main · U

Nothing is shown when the build is current, and a check that could not run stays quiet rather than nagging. Press r to force a fresh check and see its answer, whatever it is, in the status line. The same answer is available from the command line:

ai version
ai 696fc1c (built 2026-08-28T11:47:41Z)
1 commit behind main · go install ./...

The comparison is between the commit the Go toolchain stamped into the binary and the head of main, so there is no release process to keep up with. A binary built with -buildvcs=false carries no revision and cannot be compared; ai version says so and names the fix. This is why install.sh no longer passes that flag.

masshirodev/ai-session is private, so the check needs a token. It reads GH_TOKEN, then GITHUB_TOKEN, then falls back to gh auth token, and disables itself when none of the three answers. The token is only sent to api.github.com in an Authorization header; it is never logged or written to disk. Answers are cached for six hours in update-check.json beside profiles.json, so a launch does not spend a request every time.

Press U, or run ai self-update, to apply it. Both rebuild from a dedicated clone at ~/.config/ai/repo — not this checkout, and not wherever install.sh happened to run from — so updating never depends on where you keep your own copy. It is created on first use and kept on main from then on:

› git clone git@github.com:masshirodev/ai-session.git ~/.config/ai/repo   # only if missing
› git checkout main                                                       # only if not already there
› git pull --ff-only
› sh ./install.sh

The first two lines are silent when there is nothing to do — most updates are just the last two. install.sh is preferred over a bare go install so there is one definition of how ai is built, including the VCS stamp the update check itself depends on. The pull is fast-forward-only, and a refused pull stops there: rebuilding after it would reinstall the build that is already running and report it as an update.

The update is a keypress rather than something that happens on its own — the check is automatic, replacing your own binary without asking is not. Once it does run, though, finishing it is not left half-done: on success ai reopens itself, so the session that asked for the update ends up running the binary it just built rather than the one still sitting in memory. ai self-update at a shell prompt lands you in the TUI; U from inside the TUI ends back in the TUI, just on the new build. A failed update does not reopen anything — the error stays on screen.

AI_SOURCE_DIR overrides the managed clone with an arbitrary checkout instead, for testing self-update itself against a branch that has not been pushed yet. It is deliberately not persisted anywhere, so it cannot be set once and forgotten: set it only for the invocation that needs it.

AI_SOURCE_DIR=~/projects/ai-session ai self-update

A directory without a go.mod and a .git is refused rather than pulled and built in, same as the managed clone. Unlike the managed clone, an AI_SOURCE_DIR override is never switched to main on your behalf — it is there so you can point at whatever branch you are testing.

Move a profile to another computer

Install age on both computers, then export the profile as an encrypted bundle:

ai profile export codex-work codex-work.ai-profile.age
ai profile import codex-work.ai-profile.age

The export includes only that profile's metadata and isolated CLI state. age prompts for the encryption passphrase; the launcher never prints or stores it. Imports refuse to replace an existing profile. Treat the bundle like a password backup and delete it after transferring it if it is no longer needed. API keys provided through environment variables, such as DEEPSEEK_API_KEY, are not included and must be transferred separately through a secret manager. Codex's runtime-only codex/tmp tree is omitted; Codex recreates its helper symlinks there on the destination machine. OpenCode plugin node_modules directories are also omitted because they are host-specific and reproducible; reinstall them from each plugin's package.json and lockfile after importing. Antigravity's generated caches, built-ins, updater files, logs, and helper binaries are omitted for the same reason; its settings, conversations, and OAuth token remain in the bundle.

Do not actively use the same imported profile on multiple computers: provider refresh tokens may rotate when used.

Running ai without arguments opens an interactive Bubble Tea TUI: one fullscreen screen with a title bar, three columns, and a key bar.

  • Profiles, on the left — every account with its provider and the two quota figures, then which of them are logged in, and the launch folder pinned to the bottom.
  • The selected profile, in the middle — how it launches, what quota it has left and when that resets, how busy it has been over the last day, and what it was last working on.
  • What is live, on the right — every running instance across every profile with the conversation it has open and how long it has been up, then a short log of what the launcher has done.

The title bar names the profile under the cursor next to the profile count, so the screen says which account is in play rather than leaving it to the row highlight, and it carries the launch folder and any pending update.

Everything else — the profile editor, the folder and argument prompts, the delete confirmation, and the instance pickers — opens as a box over that screen, so the cockpit stays put while you answer.

Narrow terminals fold columns away rather than squeezing them: under 118 columns the live panel merges into the middle, and under 78 the whole thing stacks into one column. Short terminals drop panels in order of what a glance can afford to lose — the activity histogram first, then the log, then the recent list.

Providers are colour-coded, a profile with active launches is marked ▶ running or ▶ N running, and the AUTH block shows whether the profile has logged in:

  • ● yes — the provider's credential file exists in the isolated state directory, so ai run can use it.
  • ● key — a deepseek profile with DEEPSEEK_API_KEY exported, or an Antigravity profile with modelProvider: "gemini" and GEMINI_API_KEY. These authenticate from the environment rather than a stored OAuth file.
  • ○ no — no credentials yet; press l to log in.
  • · ? — the provider has no known credential location, so the launcher does not guess.

The cli field shows where the profile's command resolves on PATH, or not installed — press i. See Install the provider CLIs.

The model field shows the model the profile will start with, read from that CLI's own settings inside the isolated directory. means the provider has no discoverable answer yet:

Provider Source
Codex codex/config.toml, top-level model
Claude Code claude/settings.json, model
OpenCode state/opencode/model.json, most recent entry; falls back to model in config/opencode/opencode.json[c]

OpenCode's state file is preferred because it holds the model last chosen in the TUI, which is what OpenCode restores on the next start; the config default only applies before anything has been picked.

The 5H and 7D columns, and the QUOTA meters beside them, show the remaining five-hour and weekly quotas reported by the selected account's own local CLI cache, along with when each window rolls over. Codex is read from the newest rate-limit events in that profile's session logs; Claude Code is read from its cached usage utilization. Expired or unavailable windows show . Antigravity, OpenCode, and DeepSeek do not currently expose comparable local quota caches that this launcher reads. This is intentionally profile-local and does not use OpenUsage, so multiple accounts for the same provider stay separate. Credential files are never opened.

The check only tests whether the file exists, and reads the API key variable only to see whether it is empty; ai-session still never opens or prints credentials. A profile whose token has expired therefore keeps showing ● yes until the official CLI asks you to log in again.

The TUI starts in the directory where you ran ai. Press c to set another launch folder without leaving the TUI; relative paths resolve from the current launch folder and ~ is supported. The chosen folder applies to subsequent CLI launches in that TUI session and does not change the parent shell's directory.

ACTIVITY counts sessions touched per hour over the last day, from the timestamps on the profile's own transcripts. It is deliberately not labelled as quota: no provider records what a limit cost at a given hour, so the histogram measures the one thing that is actually on disk. RECENT SESSIONS reads the same transcripts for what each conversation was about, skipping the preamble both CLIs write before the first thing you actually typed. Two shapes are handled, because the CLIs use both: a block sent ahead of the prompt is skipped whole (environment dumps, harness reminders, Codex's AGENTS.md instruction dump), while a block wrapped around the prompt is unwrapped instead — Codex's IDE integration leads with your open tabs and labels the part you typed, so skipping the message would lose the prompt with it. R turns that panel into a picker and resumes the row you choose.

Use the arrow keys or j/k to select a profile:

  • Enter runs the selected profile.
  • p runs it with extra arguments typed at the prompt.
  • R resumes one of the recent sessions, in the folder it ran in.
  • h hijacks a running instance: it opens that instance's conversation in this terminal, leaving the original process running.
  • H hands a session to another account, for when one has run out of quota.
  • A turns auto-swap on or off. Off by default.
  • l logs in to the selected profile.
  • i installs the selected provider's CLI, after showing the command.
  • u updates the selected provider CLI.
  • U updates ai-session itself, after showing the checkout and the steps.
  • c changes the folder used for subsequent CLI launches.
  • a adds a profile.
  • e edits its name, provider, command, default arguments, or note.
  • r refreshes locally cached usage percentages and re-runs the update check.
  • x deletes it and its isolated state after confirmation.
  • K selects a running instance to stop; Enter stops that instance, while a or y stops every instance for the selected profile.
  • / filters the profile list by name or provider. Enter keeps the filter and hands the keys back, so a search is a way to reach one account among many rather than a mode to dismiss before acting; Escape clears it.
  • ? opens the key pane, and any key closes it.
  • q or Escape quits.

The key bar along the bottom drops entries from the end until it fits, so on a narrow terminal it is not the whole list — and the entries it drops are exactly the ones nobody has learned yet. ? is the whole list, grouped by what each key acts on: a conversation, a profile, a provider's CLI, or ai-session itself. It folds to one column rather than clipping when the terminal is too narrow for two.

Both instance pickers — h and K — list each running instance with the conversation it has open and the folder it was launched in, so two instances of the same profile can be told apart by what they are doing rather than by PID. The resume picker — R — lists transcripts rather than processes, so its rows are dated instead of numbered.

Handing a session to another account

H is for the case where one CLI's limit is spent and the work is not finished. It reads the outgoing conversation, reduces it to a brief, and starts another account on that brief in the same folder.

Nothing is copied into either CLI's state directory. The conversation stays where it was recorded; what moves is a markdown file under ~/.config/ai/handoffs/.

The brief has three parts:

  1. What was asked — every request you typed, in order, with the CLIs' own injected blocks stripped and the half-written copy of an interrupted message dropped.
  2. Where the previous agent left off — its last few substantial messages. Substance is measured by length, which is crude but free: a model's final messages are usually its shortest, so taking the tail by position picks the line between two tool calls rather than a conclusion.
  3. The repository, as of the handoff — branch, uncommitted changes, diff against HEAD, recent commits. This is read from git at handoff time rather than reconstructed from the transcript, because what a transcript records depends on how the previous agent happened to hold its tools. On one session here, recovering the touched files from tool arguments found two of the six that were actually edited, because the work went through shell heredocs and no tool argument ever named a path. Git does not have that problem.

The full transcript is named at the end as a path, not pasted in. That is the difference from handing over the profile folder and saying "continue": the next agent can read it, but does not have to. On this machine a 1140 KB transcript (~292k tokens) reduced to a 5 KB brief (~1.3k tokens).

There is no model anywhere in that path, and that is forced rather than chosen: the premise is that you are out of quota, so the outgoing CLI cannot summarise itself, and asking the incoming one to summarise means reading the transcript — the cost being avoided. Extraction is mechanical. Press e at the confirmation to edit the brief before it goes; you know what mattered.

Destinations are ranked by the quota window that runs out soonest, since a weekly allowance with room is no help at the moment the five-hour one is spent. An account whose quota is unknown sorts below a measured one.

A handoff is recorded in ~/.config/ai/lineage.json, and RECENT SESSIONS marks the source row with → <account>. The chain reads forwards only: a handoff is a baton pass, not a fork, so there is never a newer branch on the other side to reconcile.

Auto-swap

A toggles auto-swap, which is off by default and persisted in profiles.json as settings.auto_swap.

With it on, H skips the destination question and sends the work to whichever account has the most quota left. It does not skip two other things:

  • Which session is leaving is always asked. That is the one thing the tool cannot infer safely.
  • The brief is still shown before anything launches. Writing a file and starting a process are different promises, and with auto-swap on this is the frame where you find out where the work went.

Auto-swap does not watch a running session and switch mid-flight. While a CLI owns the terminal the launcher is not running, so it has nothing to watch with.

H can hand off from Claude Code and Codex, which are the providers whose transcripts are read. It can hand off to Claude Code and Codex, which are the providers whose opening-prompt syntax is known. Another provider chosen as a destination is refused with the brief's path, rather than launched with the brief silently dropped.

Resuming and hijacking

R offers the conversations RECENT SESSIONS has already read off disk, and reopens the chosen one by id in the folder it ran in. Both halves matter: the id names the conversation, and every provider looks for that id under the folder it belongs to, so resuming from anywhere else reaches a different conversation or none at all. A recorded folder that has since been moved or deleted is reported rather than quietly swapped for the current launch folder.

This is a wider offer than the provider's own resume flow, which only ever sees the folder it was started in. The panel has read every folder the account has worked in, so a conversation from another project is one keypress away instead of a cd away.

The list is always the selected profile's own, and it is reopened under that profile's environment. One account cannot resume another's conversation: a session id only exists inside the isolated state directory that recorded it, so the same id under a different profile finds nothing.

What RECENT SESSIONS reads is per provider. Codex and Claude Code keep a transcript file per conversation, which is what gets parsed for a title. OpenCode keeps its own SQLite database (opencode.db) with title, folder, and timestamp as plain columns — no parsing needed, and resuming picks the exact conversation by id (opencode --session <id>), the same as Codex and Claude. Antigravity is not read yet: its conversation store is a per-conversation SQLite database whose readable metadata carries ids but not a title, and the title lives in a protobuf blob with no published schema — resuming a specific Antigravity conversation already works from a running instance (see h below), but there is no picker for a stopped one.

With nothing recorded — an account that has not run yet, or Antigravity, whose transcripts this launcher does not read — R falls back to the provider's own resume flow in the current launch folder:

Provider Command
Codex codex resume (session picker)
Claude Code claude --resume (session picker)
Antigravity agy --continue
OpenCode opencode --continue

Antigravity and OpenCode continue the last session for the current workspace instead of opening a picker.

h chooses from processes rather than from transcripts. It reads the running instances the launcher already tracks, resolves the session each one has open, and reopens exactly that session in the folder the instance was launched from — which is what makes it the key for a conversation that is still going, where R is the key for one that has stopped. Session titles come from the provider itself and are best effort:

Provider Source
Codex the newest session log recorded for that folder
Claude Code claude agents --json, matched on the recorded PID
Antigravity not available; the instance is listed without a title
OpenCode not available; the instance is listed without a title

Hijacking an Antigravity or OpenCode profile is refused for the same reason a second launch is: its credential store is exclusive while the first process is running.

The selected-profile panel shows its default arguments and note. In the profile editor, Enter or Tab advances through all fields; on the final field it saves. Escape cancels and Ctrl-U clears the current field.

DeepSeek should be configured in the OpenCode profile using OpenCode's normal provider setup or an environment variable. The launcher preserves ordinary environment variables, including DEEPSEEK_API_KEY, but removes shared CODEX_HOME, CLAUDE_CONFIG_DIR, XDG paths, and any inherited AI_PROFILE or AI_PROVIDER before adding the selected profile's values.

OpenUsage integration

OpenUsage remains the usage/limits layer. Install its official integrations inside each isolated profile:

ai integrate openusage claude-personal
ai integrate openusage codex-personal
ai integrate openusage codex-work
ai integrate openusage opencode-go
ai integrate openusage deepseek

The launcher deliberately does not modify OpenUsage's database or copy tokens. It runs OpenUsage's supported installer with the selected profile environment, so Codex and Claude hooks are installed beside that profile's own state. Login, integration, and export remain exclusive operations and are refused while any instance is running. Antigravity and OpenCode also retain this exclusive lock for ordinary runs. If a launcher is interrupted, its lock is reclaimed automatically after all PIDs recorded in it have exited. Different profiles can still run at once. In the TUI, select a running profile and press K to choose an individual CLI process by PID, or stop all of that profile's instances. Each lock records the launcher PID on its first line and the child CLI PID on its second line; orphaned locks can therefore be reclaimed after an interrupted SSH session.

License

MIT. See LICENSE.

About

AI session manager

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages