The active account, always one glance away.

The AISW extension is a thin client of the aisw CLI. This spec defines every state it shows in VS Code and Cursor, the CLI JSON that drives each state, the exact copy, and the assets needed to ship.

Status label
The context name when it matches exactly. Otherwise the profiles that are actually active.
Workspace mismatch
Warning colors on the status bar item, plus one notification per window with a one-click switch.
Terminal flows
Shell integration reads the exit code, then the extension refreshes and verifies on its own. Requires engines.vscode ^1.93.

Principles

These four rules settle most design questions. When a proposal breaks one, the proposal changes.

  • The CLI is the authority.The extension never reads credential files or the keyring, and never infers state the CLI didn't report. Every label comes from a JSON field named in this spec.
  • Color means risk.The status bar item gets a warning background only when the wrong account could authenticate in this repo. Other problems use an icon and leave the background alone.
  • One click per switch, never zero.Opening a workspace, focusing the window or changing settings can refresh the display, but only a user action can change accounts.
  • Success is quiet, problems are specific.Confirmations go to the status bar for five seconds. Notifications are for problems, and each one names the cause and offers the fix as a button.

Status bar item

One item on the left (primary) side, because account state applies to the whole window. Its priority puts it right after the source control branch. The item's name is AISW Account, so users can hide it from the status bar context menu.

StateItemDriven byClick runs
LoadingAISWActivation, before the handshake completes. Never shown longer than the 30 s CLI timeout.Nothing
Context exactclient-acmestatus --context → context.status == "exact", label from context.activeaisw.switchContext
Context driftacme · driftcontext.status == "drift" with exactly one entry in context.drift_candidates. With several, use the profile label below.aisw.switchContext, candidate listed first as “Reapply acme”
Ambiguouswork · 2 contextscontext.status == "ambiguous", first name from context.matches. The tooltip lists all matches.aisw.switchContext
No contextclaude:work +2context.status == "none". First tool in tools[] order with a non-null active_profile; +n counts the other tools that have one.aisw.switchProfile
Nothing activeNo active profileProfiles exist in list, but every active_profile is null.aisw.switchProfile
First runAISW: Set Uplist has no profiles for any tool.aisw.getStarted (walkthrough)
Workspace mismatch colorpersonal ≠ client-acmeworkspace status → status is mismatch, unmanaged (left side reads none) or ambiguous_active. Right side is expected_context.aisw.switchToWorkspaceContext
Binding brokenclient-acmeworkspace status → invalid_context. The label stays the normal one; the icon flags it.aisw.switchContext; the tooltip offers “Rebind in Terminal”
SwitchingSwitching to client-acme…A mutation is in flight. Other AISW commands queue behind it.Nothing
Not installedInstall aiswNo binary at any location in the detection order.aisw.installCli
InstallingInstalling aisw…An install or update command is running in the AISW terminal.aisw.showTerminal
Setting points nowhereAISW: Check binaryPathaisw.binaryPath is set, but the file is missing or not executable.Opens the setting
CLI too old or newAISW: Update aiswversion/capabilities → unsupported cli_api_version, schema version, or a missing feature flag.aisw.installCli in update mode (too old) or aisw.diagnose (too new)
Remote windowAISWvscode.env.remoteName is set. No CLI process starts, including from commands.Tooltip only
CLI errorAISW: ErrorAny other CliError from a read. The tooltip shows error.message.aisw.showOutput

Tooltip

AISW: client-acme
Claude Codeacme
Codex CLIacme-ci
Gemini CLIacme
Antigravity—
Matches this workspace (bound by github.com/acme/*)

A MarkdownString with supportThemeIcons. Its isTrusted is { enabledCommands: [...] } listing only AISW command IDs.

  • The table always lists all four tools in tools[] order. A tool with no active profile shows an em dash.
  • The workspace line comes from workspace status: matched_rule on a match, expected_context on a mismatch, and nothing for no_expected_context.
  • On drift, the table shows the candidate's profile beside the active one for the tools that differ.
  • accessibilityInformation.label reads the whole state as a sentence, for example “AISW account: context client-acme, matches this workspace.”

Installing aisw

Most people meet the extension right after installing it from the Marketplace, and at that moment aisw is usually missing. The extension never bundles or downloads the binary itself (ADR-0001). It finds an existing install, or runs the official installer the user picks in a visible terminal, then picks up the result without a restart.

  • Find before asking.Check the setting, then PATH, then the places the official installers write to. Many “missing” binaries are installed, just not on the PATH VS Code started with.
  • Install in plain sight.Install commands run in the AISW terminal, so the user can read exactly what runs. The extension only starts the command the user chose.
  • Offer what works here.Methods are filtered by operating system and by the tools found on the machine. A Mac without Homebrew never sees a Homebrew command.
  • No restart.When the install command ends, the extension finds the new binary itself, even if the new PATH only applies to new shells.

How the extension finds aisw

OrderWhereIf found
1aisw.binaryPath, when setUsed as is. If the file is missing or not executable, show the invalid-setting state. No fallback, so a wrong setting is never hidden.
2aisw on the extension host's PATHUsed as is.
3~/.local/bin/aisw (install script)
/opt/homebrew/bin/aisw, /usr/local/bin/aisw, /home/linuxbrew/.linuxbrew/bin/aisw (Homebrew)
~/.cargo/bin/aisw (Cargo)
Used for this session. The tooltip and diagnostics show the path; nothing is written to settings.
4Nothing foundShow the Install aisw state.

Step 3 checks only these fixed absolute paths, never a path inside the workspace. Every candidate must pass the version --json handshake before it is used. Detection runs again when an install command ends, when the AISW terminal closes, when aisw.binaryPath changes, and on window focus while aisw is missing.

First run

Walkthrough, step 1. VS Code opens a new extension's walkthrough on install, so this is the first thing most users see. The step completes on onContext:aisw.cliInstalled. Its media panel shows what happens after the click: the command runs in the AISW terminal and the status bar moves on to Set Up.
main Install aisw
AISW needs the aisw command-line tool to switch your accounts.
Source: AISW
Not installed. An information notification, not an error: this is the normal state right after installing the extension. Shown once per session, and not shown while the walkthrough is open. The status bar item stays until aisw is found.
Install aisw
Choose how to install aisw on macOS
  • Homebrewbrew install burakdede/tap/aiswrecommendedUpdates with brew upgrade.
  • Install scriptcurl -fsSL …/install.sh | shPuts aisw in ~/.local/bin and checks its SHA-256.
  • Cargocargo install aisw --lockedBuilds from source with your Rust toolchain.
  • Locate an Existing aisw…
  • Open Installation Guide
Install picker, macOS and Linux. Homebrew appears only when brew is found, and Cargo only when cargo is found; both use the same lookup as aisw. The first available method is focused. Selecting one runs it in the AISW terminal straight away; the full command is in the description.
ProblemsOutputTerminalAISW
~ $ sh -c "curl -fsSL https://raw.githubusercontent.com/burakdede/aisw/main/install.sh | sh"
Installing aisw latest (aarch64-apple-darwin)
  Install dir:  /Users/you/.local/bin
  Downloading binary...
  Downloading checksum...
  Verifying checksum...

aisw latest installed to /Users/you/.local/bin/aisw

Note: /Users/you/.local/bin is not in your PATH.
~ $
Install script in the terminal. The real installer output, abridged. The PATH note is why detection checks ~/.local/bin: the new binary works in VS Code at once, without editing shell files or restarting.
Locate aisw. showOpenDialog with openLabel: "Use This aisw". The file must pass the handshake before it is saved to aisw.binaryPath in user settings. If it fails: “That file isn't a compatible aisw: reason.”

After the install command

aisw 0.3.10 is installed. Import the accounts you're already signed into?
Source: AISW
Exit 0 and aisw found. Import Logins runs aisw init --no-shell-hook in the AISW terminal. aisw detects the current Claude Code, Codex, Gemini and Antigravity logins and asks before importing each one; shell files stay untouched. The status bar then shows the imported profiles, which is the first real payoff.
AISW: no saved context matches
Claude Codework
Codex CLIwork
Gemini CLI—
Antigravity—
Using ~/.local/bin/aisw, which isn't on VS Code's PATH.

Installed off PATH. The install script writes to ~/.local/bin, which VS Code may not see until it restarts. The extension uses the binary anyway and says where it came from. Diagnostics suggest adding the directory to PATH.
Installing aisw with Homebrew failed (exit code 1). The terminal shows what happened.
Source: AISW
Non-zero exit. Try Another Method reopens the picker without the method that failed. Exit 0 with no aisw found afterwards gets the same notification with “finished, but aisw wasn't found”.
Finish the install in the terminal, then check again.
Source: AISW
No shell integration. Without an exit code the extension can't tell when the command ends. It shows this right after sending the command, and still checks again on its own when the terminal closes or the window regains focus.

Wrong path and outdated versions

AISW: Check binaryPath
aisw.binaryPath points to /usr/local/opt/aisw/bin/aisw, but there's no file there.
Source: AISW
Setting points nowhere. Kept separate from “not installed”, because the fix is different. Clear Setting removes the user setting and runs detection again.
Update aisw
aisw 0.2.4 is installed; this extension needs 0.3.10 or later
  • Homebrewbrew upgrade aiswinstalled withFound at /opt/homebrew/bin/aisw.
  • Install scriptcurl -fsSL …/install.sh | shInstalls a second copy in ~/.local/bin.
  • Open Release Notes
Too old. The same picker, with update commands. The method that installed the current binary comes first, judged from its path (Homebrew prefix or Cellar, ~/.cargo/bin, ~/.local/bin). Cargo updates use cargo install aisw --locked --force.

Switching

Both pickers use createQuickPick, so they can show busy while the list refreshes. A selected row runs exactly one CLI mutation. The status bar then switches to its spinner until the refresh and the scoped verify finish.

Switch Context
Select a context (active: personal)
  • client-acmeclaude:acme · codex:acme-ci · gemini:acmethis workspace
  • personalclaude:me · codex:mecontexts
  • ossclaude:me · gemini:oss
  • Switch a Single Profile…
Switch Context. The workspace's expected_context comes first under a “this workspace” separator. A check marks context.active. Descriptions come from context list mappings, in tool:profile form.
Switch Profile
wo
  • workoauthClaude Code
  • work-apiapi_key
  • workchatgptCodex CLI
  • Add Profile…
Switch Profile. Grouped by tool with separators. The description is the auth field from list. Filtering is on the profile name; the group label stays visible. The agy key is shown as “Antigravity” and invoked as antigravity.

Empty states

Switch Context
No contexts yet
  • Create a Context in the Terminal…aisw context create
  • Switch a Single Profile…
The create item types aisw context create into a new AISW terminal without pressing Enter (sendText(text, false)), so the user finishes the flags. Refresh happens when the command ends.
Switch Profile
No profiles yet
  • Add Profile…sign in or import in the terminal
  • Get Started with AISW
With no profiles, the picker teaches the next step instead of showing an empty list.

After the switch

The adapter runs use or context use with --json, then re-reads status, then verifies only the tools listed in result.affected_tools. Tools the switch didn't touch never block a successful switch.

main client-acme Switched to client-acme · 3 tools verified
Verified. window.setStatusBarMessage for 5 s. No toast.
Switched to client-acme, but Codex CLI didn't verify: tool binary not found on PATH.
Source: AISW
Verified with issues. The text joins the failing tool's issues[0]; the full report goes to the output channel. If result.warnings is not empty, the same notification lists them.

Notifications

Every notification names the problem in its first clause and puts the fix on the primary button. Remediation buttons come from the CLI's error.remediation when kind == "run_command" and safe == true. Clicking one opens the AISW terminal with that command.

This workspace expects the client-acme context, but personal is active.
Source: AISW
Workspace mismatch. Shown at most once per window for each pair of active and expected contexts. “Don't Ask” is stored in workspaceState; the warning color on the status bar stays. Disabled by aisw.workspaceMismatch.notify.
Couldn't switch to client-acme: profile 'acme' not found for claude. Nothing was changed.
Source: AISW
Switch failed. The message is the first line of error.message. “Nothing was changed” relies on the CLI's transactional rollback and is shown only for ok: false envelopes.
AISW needs the aisw command-line tool to switch your accounts.
Source: AISW
Not installed. Information, not an error. Once per session. Install aisw opens the install picker; the whole flow is in Installing aisw.
aisw 0.2.4 is too old for this extension. Update aisw to 0.3.10 or later.
Source: AISW
Incompatible CLI. Update aisw opens the install picker in update mode. If the CLI's API is newer than the extension supports, the message says “Update the AISW extension” instead. The extension never guesses a fallback.

Verify Active Accounts

2 tools need attention: Codex CLI (fail), Gemini CLI (warn).
Source: AISW
verify --json exits 1 when summary.status == "fail", and its output is a report, not an envelope. The adapter must parse stdout on a non-zero exit. Tools with stored_profiles == 0 are left out, so a tool you never set up doesn't raise a warning.
ProblemsOutputAISW
[info] aisw 0.3.10 · CLI API 1 · schema 1 · progress 1
[info] status --context  exit 0  212 ms
[info] context use client-acme  exit 0  388 ms
[warn] verify  exit 1  301 ms
       codex  fail  tool binary not found on PATH
              → Install codex or run 'aisw doctor --json'
Output channel. Created with createOutputChannel('AISW', { log: true }). Each command logs its subcommand, exit code and duration. Arguments are limited to tool, profile and context names; secrets never appear in arguments.

Profile lifecycle

Adding a profile can involve OAuth or an API key, so it runs in the integrated terminal and the CLI keeps its native prompts. Removing a profile handles no secrets, so it runs in the background behind a native confirmation, with Undo.

Add Profile

Add Profile1/2
Which tool is this account for?
  • Claude Codeoauth, api_key, from_env, from_live
  • Codex CLIoauth, api_key, from_env, from_live
  • Gemini CLIoauth, api_key, from_env, from_live
  • Antigravityoauth, from_live
Step 1 lists tools from capabilities.tools, with auth_methods as the description. Step 2 asks for the profile name, validated as 1–32 letters, numbers, hyphens or underscores, and rejected if the name already exists in list.
ProblemsOutputTerminalAISW
~/src/client-acme-api $ aisw add claude work
How do you want to authenticate Claude Code?
  › Sign in with browser (OAuth)
    Use an API key
Waiting for browser sign-in…
Saved Claude Code profile 'work'.
~/src/client-acme-api $
The terminal is created with name: "AISW", iconPath: ThemeIcon('account'), color: ThemeColor('terminal.ansiBlue') and isTransient: true. The command runs through shellIntegration.executeCommand(binary, args), which quotes arguments correctly on every shell.
Added Claude Code profile work.
Source: AISW
Exit 0. onDidEndTerminalShellExecution matches the execution. The extension then refreshes and verifies the tool before showing this.
aisw add exited with code 1. The terminal shows what happened.
Source: AISW
Non-zero exit. If exitCode is undefined (Ctrl+C, or a shell that doesn't report it), the extension refreshes anyway and says “Couldn't read the result of aisw add. AISW refreshed its state.”

If shell integration hasn't activated within 3 s, the extension falls back to sendText with POSIX shell quoting, and refreshes when the terminal closes. That fallback can't read an exit code, so it shows no success notification.

Remove Profile

showWarningMessage(…, { modal: true, detail }). A modal is justified here because the action is destructive. The active profile is shown in the picker but disabled, with the description “switch away first”.
Removed Claude Code profile work.
Source: AISW
Runs remove <tool> <name> --yes --json. Undo runs backup restore <result.backup_ids[0]> --yes --json. I verified that this round trip restores the profile on aisw 0.3.10.

Commands and settings

All commands declare category: "AISW", so the palette shows “AISW: Switch Context” while menus show the short title. Context keys hide commands that can't run.

Command IDTitleIconEnablement
aisw.switchContextSwitch Context arrow-swapaisw.state == ready
aisw.switchProfileSwitch Profile accountaisw.state == ready
aisw.switchToWorkspaceContext newSwitch to Workspace Context repoaisw.workspaceMismatch
aisw.verifyVerify Active Accounts pass-filledaisw.state == ready
aisw.refreshRefresh refreshalways (no-op in remote)
aisw.addProfileAdd Profile… addaisw.state == ready
aisw.removeProfileRemove Profile… trashaisw.hasProfiles
aisw.installCli newInstall or Update aisw… cloud-download!isRemote
aisw.locateCli newLocate aisw… folder-opened!isRemote
aisw.diagnoseShow CLI Diagnostics infoalways (not in remote)
aisw.showOutput newShow Output outputalways
aisw.getStarted newGet Started bookalways

Context keys set via setContext: aisw.state (loading | ready | cliMissing | installing | binaryPathInvalid | incompatible | remote | error), aisw.cliInstalled, aisw.workspaceMismatch, aisw.hasProfiles, aisw.hasContexts. In remote windows every command, including Refresh, is a no-op. No default keybindings ship, to avoid conflicts; the README suggests one.

Settings

SettingDefaultScopeWhy
aisw.binaryPath"" (PATH)machineSecurity. With the default scope, a cloned repo's .vscode/settings.json can point this setting at a binary inside the repo, and it runs on window focus. Machine scope blocks that. Also list the setting in capabilities.untrustedWorkspaces.restrictedConfigurations.
aisw.workspaceMismatch.notify newtruewindowTurns off the mismatch notification. The warning color on the status bar stays.
aisw.refreshDebounceMs500windowDebounce for refreshes on focus.

Get Started walkthrough

Contributed through contributes.walkthroughs. Its steps complete on real state, not on clicks:

  1. Install the aisw CLI. Buttons run aisw.installCli and aisw.locateCli. Completes on onContext:aisw.cliInstalled.
  2. Import the accounts you're signed into. Runs aisw init --no-shell-hook in the AISW terminal, with Add Profile as the alternative. Completes on onContext:aisw.hasProfiles.
  3. Group profiles into a context. Completes on onContext:aisw.hasContexts.
  4. Bind this repository. Types aisw workspace bind . --context into the AISW terminal.

Icons and assets

Dark Marketplace
Light Marketplace
Extensions list

integrations/vscode/media/icon.png: a 256 × 256 RGBA PNG cut from website/public/aisw-logo.png. The baked-in drop shadow is removed, the corners are transparent, and a 1 px navy hairline at 16% keeps the white tile visible on light pages. The manifest requires at least 128 px and recommends 256 px for Retina screens.

The status bar, Quick Picks and notifications use codicons only, because the status bar guidelines ask for familiar icons. The brand appears in the Marketplace listing and the walkthrough.

Arrow blue
#077FFE
Arrow cyan
#02CDFD
Prompt navy
#001436
// integrations/vscode/package.json
"icon": "media/icon.png",
"galleryBanner": { "color": "#001436", "theme": "dark" },
"keywords": ["claude code", "codex", "gemini", "account switcher", "profiles"],
"engines": { "vscode": "^1.93.0" },
"capabilities": {
  "virtualWorkspaces": false,
  "untrustedWorkspaces": { "supported": "limited", "restrictedConfigurations": ["aisw.binaryPath"] }
}

Pin @types/vscode to the engines minimum (~1.93.0). The lockfile currently resolves 1.140, which lets code compile against APIs that older VS Code and Cursor builds don't have.

Codicon map

MeaningCodiconUsed in
Account state accountStatus bar, terminal icon, Switch Profile
Work in progress sync~spinStatus bar while loading or switching
Wrong account could run warningStatus bar mismatch, broken binding, verify issues
Extension can't work errorCLI missing, incompatible, read failure
Something to install cloud-downloadStatus bar when aisw is missing, install picker
Not available here circle-slashRemote window
Bound to this repo repoWorkspace row in Switch Context
Active row checkQuick Pick rows, success status message
Verified pass-filledTooltip workspace line, Verify command

Implementation

The current extension.ts mixes state, rendering and commands in one class. Split it so each state in this spec is a pure function of CLI JSON and can be tested from fixtures without VS Code.

src/
  cliAdapter.ts      spawn, envelope parsing, verify-report parsing, timeouts
  contract.ts        payload types, version and feature checks
  state.ts           deriveViewState(status, list, contexts, workspace) → ViewState  (pure)
  statusBar.ts       render(ViewState) → text, icon, background, tooltip, a11y label
  pickers.ts         Switch Context, Switch Profile, Add, Remove Quick Picks
  terminalFlows.ts   AISW terminal, shell-integration execution, exit handling
  notifications.ts   copy deck plus remediation-to-button mapping
  refresh.ts         scheduler: handshake cache, sequence guard, mutation queue
  extension.ts       activation and wiring only

Refresh lifecycle

stateDiagram-v2
  [*] --> Loading: activate
  Loading --> Remote: env.remoteName set
  Loading --> CliMissing: ENOENT
  Loading --> Incompatible: version or feature check fails
  Loading --> Ready: handshake ok and reads ok
  Ready --> Ready: focus (debounced): status --context + workspace status
  Ready --> Switching: user picks a context or profile
  Switching --> Ready: refresh + verify affected_tools
  Switching --> Ready: ok false (rollback), error toast
  CliMissing --> Loading: binaryPath changed or Refresh
  Incompatible --> Loading: binaryPath changed or Refresh
  • Handshake cache. Run version and capabilities once per binaryPath. Run them again only after a setting change, an explicit Refresh, or an unsupported_command error. A focus refresh then costs two processes instead of six.
  • Sequence guard. Each refresh takes a sequence number. A result is rendered only if no newer refresh has started.
  • Mutation queue. One mutation at a time. While one runs, focus refreshes are skipped.
  • Scoped verify. Parse the report on exit 1, then check only tools[] entries whose tool is in affected_tools.

Fixture matrix

One fixture directory per state in the status bar table. Each holds the JSON for status --context, list, context list and workspace status. deriveViewState is tested against every fixture, and the real-CLI compatibility test seeds an isolated AISW_HOME to produce at least the exact, none, mismatch and verify-fail states.

Checklist

Ordered by dependency. The first four items unblock everything else.

  • #257Parse the verify report on a non-zero exit, and scope verification after a switch to affected_tools.
  • #257Make aisw.binaryPath machine-scoped and declare limited support for untrusted workspaces.
  • #257Apply the remote guard in createAdapter() so no command starts a CLI process remotely.
  • #257Binary detection order, install and update picker, Locate, and post-install import, as in Installing aisw.
  • #256Add the fixture matrix and deriveViewState tests.
  • #258Status bar states, tooltip and accessibility label, as specified above.
  • #258Grouped Quick Picks, empty states, the workspace row, and aisw.switchToWorkspaceContext.
  • #258Mismatch notification with once-per-window logic and the notify setting.
  • #258Handshake cache, sequence guard, mutation queue.
  • #259Shell-integration terminal flow with exit handling and the sendText fallback; bump engines to ^1.93.
  • #259Background remove with a modal confirmation and Undo through backup restore.
  • #260Icon, gallery banner, keywords, walkthrough, and pinned @types/vscode.
  • #260Screenshots of every state in VS Code and Cursor, light and dark, attached to the PR.