project-switch
-
v0.20.1 - Restoring the macOS menu bar for the webview
The macOS webview window had a subtle but irritating problem: because it runs from a bare binary that tao promotes to a foreground app at startup, it had no main menu — and a regular macOS app with no main menu leaves the left side of the system menu bar dead, so the Apple menu and the app menu didn’t respond to clicks. Worse, without an Edit menu the standard Cmd+X/C/V/A shortcuts didn’t work inside the page either.
The fix installs a minimal main menu when
NSApplicationfinishes launching (the sameStartCause::Initmoment I already use to set the dock icon, since anything done earlier is discarded by the promotion). It’s just two menus: an app menu with Quit, and an Edit menu wiring Cut/Copy/Paste/Select All to their standard selectors — which both revives the menu bar and restores clipboard shortcuts in the webview. This pulled in theNSMenu/NSMenuItemfeatures fromobjc2-app-kit. -
v0.20 - Webview polish and tighter webserver scoping
A day of sanding down the webview window and the tray-managed webserver.
The webview gained two conveniences. It now titles itself by the command that opened it on macOS —
summon_or_openthreads the command key through to a new--titleflag, so a window opened from, say, adashboardcommand reads “dashboard” instead of the generic constant. Windows deliberately ignores the title: the constant doubles as the handle thatFindWindowWuses to find and reuse the single window, so renaming it would break window reuse. (A small follow-up fix renamed a shadowed local there so the title parameter wasn’t accidentally swallowed.) The webview also reloads on Cmd+R / Ctrl+R now, via an injected key handler — which neatly doubles as a retry button, since callinglocation.reload()on WKWebView’s or WebView2’s own error page re-requests the URL that failed.Then external links. On macOS, WKWebView doesn’t route
target=_blankanchor clicks orwindow.opencalls through wry’s navigation handlers, so links in the page — like the URLs the assist terminal prints — simply did nothing. An injectedLINK_SCRIPTnow captures both, forwards external http(s) URLs to the host over IPC, and lets same-origin links navigate normally; the host opens them in the default browser.On the webserver side, two scoping fixes. Stopping the server used to
pkill -fon the command string, which would also catch unrelatedassist/claudeprocesses whose arguments happened to contain that substring. It now identifies the server by the port it listens on (lsof -ti tcp:3100) and waits, polling, until it’s actually gone. I also discovered the macOS server needed an interactive login shell (-ilc, not just-lc): version managers like fnm initialise in.zshrc, which a non-interactive shell never sources, so the server was resolving the wrong Node and sometimes couldn’t findassiston PATH at all. Finally, the hotkey service’s own process matching switched frompkill -f/pgrep -fto the exact-name-xform, so it stops matching unrelated command lines. -
v0.19 - Real logo icons, and macOS parity for the webview and webserver
The visible change today was getting rid of the hand-drawn “PS” square I’d been using as an icon. The tray and menu-bar icon now decode a bundled
logo-tray.png(same artwork on both platforms), and I pulled all the icon handling into a sharedsrc/icon.rsso the launcher window, the webview window, and the tray all draw from one place. On macOS the dock gets a squircle-masked variant of the fulllogo.png: the logo is inset on a transparent canvas at Apple’s icon grid ratio (~80.5% of the tile) with its corners rounded into the system squircle, premultiplied so the corner feathering reads correctly through AppKit’s bitmap initializer. The result sits at the same scale and shape as native dock icons instead of looking like a bare executable.Two follow-on fixes gave the windows their icons too: neither egui’s launcher window nor tao’s webview window adopts the exe’s embedded icon on its own, so the Windows taskbar was showing the default executable mark. Both now set the app logo explicitly via
with_icon/with_window_icon.The larger piece of work was bringing macOS up to parity with Windows. The tray-managed assist webserver is now cross-platform: I collapsed the old Windows-only implementation and the macOS no-op stubs into a single
hotkey/src/webserver.rs, with only the command construction varying by platform — Windows runs it inside WSL viawsl.exe -- bash -lc, while macOS runs it natively through the user’s login shell ($SHELL -lc), since there’s no WSL to go through. Logs land in~/Library/Logs/project-switchon macOS and%LOCALAPPDATA%on Windows. The webview window got the same treatment, so the borderless in-app browser works on macOS as well.One macOS build gotcha worth recording: the cross-compiled (zigbuild) binaries carry a linker ad-hoc signature that the OS code-signing monitor rejects at launch with a SIGKILL, so
build.shnow re-signs both binaries ad-hoc with the nativecodesignbefore restarting the hotkey service. -
v0.18 - Pinned commands, external links, and a tray-managed webserver
Three features today, all building on recent work.
First, commands can now be
pinned: trueto force them to the top of the recent list when the launcher opens with empty input, regardless of when they were last used. The ordering logic lives in a sharedorder_recent_keysso the CLI list view and the GUI launcher stay identical: accessed pins first (by recency), then pins that have never been used (in config order, so they surface even with no history), then everything else by recency. Like the other per-command flags it merges across the project/client/global layers and is omitted from serialized YAML when false.Second, the webview window now behaves sensibly with links that point elsewhere. I capture the configured URL’s origin, and any navigation or
target=_blank/window.opento a different http(s) origin is cancelled and handed to the system browser instead — same-origin navigation, and non-http(s) targets likeabout:or error pages, stay inside the webview. If handing the link to the browser fails, an injected, self-dismissing toast in the corner of the page says so rather than failing silently. This meant adding anorigin_ofhelper to the URL utils.Third, the tray (the hotkey service) can now own the lifecycle of my WSL
assistwebserver. A newwebserver:config section (enabled,command,distro) drives a “Webserver” submenu in the tray with Enabled / Restart / Open in browser / View logs. When enabled, the tray launches the command inside WSL viabash -lc "exec ..."at startup — first killing any orphan from a previous tray instance that didn’t exit cleanly, since that would still be holding port 3100 and cause anEADDRINUSE. Toggling the menu item flips the config and the tray manages start/stop accordingly. -
v0.17 - A borderless in-app webview window
Some of the things I open are local web apps that I’d rather see in a clean, chromeless window than in a browser tab. So commands can now carry
webview: true, and instead of launching a browser they open the URL in a reusable, borderless WebView2 window. It’s mutually exclusive withcommand— config validation rejects setting both — and it merges across the project/client/global layers like any other command field.The window runs as its own process via a hidden
webview <url>subcommand, deliberately, so itstaoevent loop never collides with the egui launcher’s. There’s only ever one of these windows:summon_or_openusesFindWindowWon a fixed window title to spot an existing instance and just foregrounds it (restoring it first if minimised), otherwise it spawns a fresh process and grants itAllowSetForegroundWindowso it can raise itself.The fiddly part was that a borderless window with a WebView2 child has no chrome and no way to be dragged — the child window swallows every mouse message, so the host never gets a
WM_NCHITTESTover those pixels. The fix is an injected JavaScript shim: it treats the top strip as a drag handle and the window edges as resize handles, sets the right resize cursors, skips interactive elements so the app’s own header buttons still work, and forwards gestures over wry’s IPC channel. The host turns those intodrag_window/drag_resize_windowcalls. The window also remembers where it was: its physical-pixel rect is saved to~/.project-switch-webview.ymland reapplied withSetWindowPoson next open (which sidesteps tao’s cross-DPI mis-placement), unless the monitor it lived on has since been disconnected, in which case it falls back to the default placement.One build wrinkle: the GNU cross-compile target can’t statically link the MSVC WebView2 loader, so
docker-build.shnow finds and shipsWebView2Loader.dllnext to the exe. -
v0.16.2 - Finding MSIX and Store apps on Windows
Store-packaged and MSIX apps were missing from the Windows launcher. The reason is that they don’t drop a
.lnkinto the Start Menu like classic win32 apps do — they register through an app manifest instead, so my directory-scanning approach never saw them.The fix reaches into
shell:AppsFoldervia COM. A newcollect_packaged_appsopensFOLDERID_AppsFolder, enumerates it withIEnumShellItems, and pulls each item’s AUMID and display name throughIShellItem::GetDisplayName(carefully freeing the COM-allocated strings withCoTaskMemFree). Packaged apps are identifiable by theirPackageFamilyName!AppIdAUMID format, so I filter on the!and skip anything without it — those are plain win32 apps already covered by the existing.lnk/.urlscan. Matches get stored with ashell:AppsFolder\<aumid>path so they launch correctly. The packaged apps are merged into the scanned entries, deduplicated by lowercased name against what the directory scan already found, and the combined list is sorted alphabetically. This pulled in two newwindowscrate features,Win32_UI_ShellandWin32_System_Com. -
v0.16.1 - Two macOS launcher fixes
A pair of macOS-specific fixes. The first was a self-inflicted one: to stop the launcher flashing on the primary display before jumping to a target monitor, I’d been spawning the window off-screen at
(-32000, -32000)and repositioning it on the first frame. That repositioning relies onmonitor_physical_rect, which is a no-op outside Windows — so on macOS the window started off-screen and simply stayed there, invisible forever. I gated the off-screen trick behind#[cfg(windows)]so other platforms just show the window where it lands.The second fix taught the app shortcut scanner about
/System/Applications. macOS moved a lot of its bundled apps — Find My, Music, TV and friends — out of/Applicationsand into the system directory, so they were missing from the launcher entirely. Adding it as a non-recursive scan path alongside/Applications,/Applications/Utilities, and~/Applicationsbrings those back. -
v0.16 - Clients, with projects nested underneath
The big change today was rethinking the core model. What I used to call a “project” is now a “client”, and each client can contain its own
projects:array. The motivation was real-world structure: I work with a client who has several distinct apps, and I wanted to switch into “client X / web” or “client X / api” and get the right command set for each, without duplicating all the shared client-level links.That meant a precedence chain for commands:
project > client > global. When a project is active its commands win, falling back to the client’s, then to global. The same layering applies to the browser choice, so a command can override the project, which overrides the client, which overrides the global default. I reworkedresolve_currentto return the client and the optional nested project together, and threaded that throughlist,current, andswitch.switchnow presents clients first; if the chosen client has nested projects, a second prompt lets me pick the client itself (shown as<name> (client)) or one of its projects.currentand the launcher header both display the active selection asclient / project.To avoid breaking every existing config, old files using
projects:/currentProject:are migrated in place toclients:/currentClient:on first load, and included configs get the same treatment. The merge rules for shared configs were extended to cover nested projects (matched by name, and a project can’t itself nest further). I also dropped theaddcommand entirely — it never kept pace with the config schema, and editing~/.project-switch.ymldirectly is simpler and more honest about what’s going on. The README and example configs were rewritten throughout to reflect the new vocabulary. -
v0.15.3 - Enter opens the item you actually highlighted
Fixed an annoying bug in the launcher: arrowing down to an item and pressing Enter would open the first item in the list instead of the one I’d highlighted. The GUI was handing the raw input text to
resolve_itemrather than looking at the selected entry, so navigation effectively didn’t count once you committed.The fix is a new
selected_action_inputonWindowStatethat resolves the action string from the currently selected entry. If the first word of my input matches the item’s key exactly, it keeps the full input so typed args survive (e.g.g some textstill searches for “some text”); otherwise it falls back to the item’s key, so a partial filter likejicorrectly launchesjira. It returnsNonefor empty or out-of-bounds selections and for non-Itementries like expressions and paths, which the caller already handles separately. The Enter handler inrender_launchernow routes through it. I backfilled a batch of regression tests covering navigation, preserved args, partial filters, recents, and the empty-list case. -
v0.15.2 - Error logging and argument handling fixes
I added error logging to a file and a GUI error dialog so that when something goes wrong in the launcher, there’s both a persistent log and a visible notification rather than a silent failure.
I also fixed two command execution bugs: commands were being launched through
wt.exe(Windows Terminal) when they should have gone throughcmd, and the GUI launcher was dropping user-supplied arguments when opening URL commands. Both are now working correctly. -
v0.15.1 - Monitor positioning and selection fix
I fixed the launcher window appearing on the wrong monitor — it now reads monitor configuration and positions itself on the correct display. The hotkey config gained a monitor selection option so multi-monitor setups work predictably.
I also fixed a frustrating bug where pressing Enter always opened the first item in the list regardless of arrow-key selection. The selected item now correctly responds to keyboard navigation.
-
v0.15 - History tracking and recent actions
I added a history system to
project-switchthat records executed actions, calculator expressions, and file path navigations. When opening the launcher, recently used items now appear at the top of the list so frequently accessed projects and commands are just a keystroke away.I also fixed the half-second delay between pressing the hotkey and the GUI list appearing — the hotkey listener now communicates with the GUI process more directly. Calculator mode got an improvement too: incomplete expressions like
=5+no longer show an error, they just display nothing until the expression is valid. -
v0.14.2 - Reliable focus stealing on hotkey
I fixed the launcher window not reliably getting focus when activated via the hotkey on Windows. The window state machine now handles focus requests more aggressively to ensure the input field is ready for typing immediately after the hotkey press.
Also tidied up the build script to clean individual binary files rather than entire directories, avoiding accidental deletion of other build artefacts.
-
v0.14.1 - macOS support and focus handling
I spent the day getting the new native UI working on macOS. The hotkey service now launches the GUI directly on macOS instead of going through a terminal intermediary, and I configured both the hotkey and GUI processes to hide their Dock icons so they run as background-only apps.
I also fixed the window hiding on focus loss — it was crashing in certain edge cases, so I added a safer approach. On the Windows side, I fixed URL opening from the UI when there’s no exact match in the project list, so typed-in URLs now open correctly in the browser.
-
v0.14 - Native UI launcher with egui
Big day — I built out the native GUI launcher for
project-switchusingegui/eframe. The work was orchestrated through Conductor across three phases.Phase 1 evaluated UI frameworks (settling on
eguiovericed), defined the merged binary crate structure, and extracted a shared data model from the existinglistcommand into a reusable launcher module. I also wrote state machine tests for the window lifecycle.Phase 2 implemented the actual launcher window — a filterable list of project commands that pops up on hotkey press. I added a calculator mode (prefix input with
=), file path browsing with directory navigation, and action execution for opening URLs, running commands, and launching apps.Phase 3 polished the rough edges: fixing path entry behaviour, showing both “open” and “browse” options for directories, deduplicating path input, launching terminal commands through
wt.exe, and tweaking the app colour scheme. Finally, I merged the hotkey daemon into the main binary so there’s now a singleproject-switchexecutable that handles both the CLI and the background hotkey service with system tray icon. -
v0.13 - Hotkey trampoline and URL argument fix
I added a trampoline mechanism to the hotkey service so that on startup it copies itself to
LOCALAPPDATAand re-launches from there. This means the binary can be updated in-place without the running process holding a lock on the original file — useful for keeping the auto-start shortcut pointing at a stable path.I also fixed an issue where forward slashes in URL arguments were being stripped when launching commands. The
listcommand now correctly preserves slashes so URLs with paths pass through intact.On the planning side, I set up Conductor — an AI agent orchestration framework — with code styleguides, product guidelines, and a spec for a native UI track that would merge the hotkey service and launcher into a single windowed process.
-
v0.12 - Terminal command field for project commands
Added a
commandfield to project commands as an alternative tourl. Previously, if you wanted to run a terminal command likecargo build --releasefrom the project switcher, you had to use theurlfield and hope no browser was configured — which was confusing and fragile. Now there’s explicit intent:commandalways runs in the terminal,urlalways opens in a browser.The config validation enforces mutual exclusivity — you can’t set both
urlandcommandon the same entry, andcommandcan’t be paired withbrowsersince that wouldn’t make sense. Theargsfield works with both: forcommandentries it appends to the shell invocation, forurlentries it gets URL-encoded and appended to the URL. Also extracted amerge_argshelper to deduplicate the argument merging logic that was repeated in a few places. -
v0.11.1 - Auto-encode user args in URLs
Fixed URL encoding for user-supplied arguments on browser commands. Yesterday’s refactor removed the
url_encodeconfig field but didn’t replace it with automatic encoding. Now when a command opens in a browser and the user provides arguments (e.g. a search query), those args are automatically URL-encoded withurlencoding::encodebefore being appended to the URL. This means thesearchglobal command just works — type your query and spaces get encoded as%20without needing any config flag. -
v0.11 - Browser args rework and debug flag
Reworked how command arguments are handled when launching browsers. Previously,
argson a command were appended to the URL (with an optionalurl_encodeflag), which was a confusing overload. Nowargsare treated as browser flags passed directly to the browser process. This is a cleaner model — if a command has a browser configured, the args go to the browser; if it’s a terminal command, the args go to the shell. Removed theurl_encodefield and theurlencodingdependency since they’re no longer needed.Added
#[serde(deny_unknown_fields)]to the config structs (Config,Project,ProjectCommand) so typos or stale fields in YAML config files produce clear errors instead of being silently ignored.Also added a
--debugflag to thelistcommand that prints the full resolved command before executing it — handy for troubleshooting when a command isn’t launching the way you expect. And cleaned upmain.rsto print errors with a “Press Enter to exit” prompt instead of returningResultfrommain, which gives better feedback when running in Windows Terminal where the window would otherwise close immediately. -
v0.10.1 - URL space encoding fix for Windows
Fixed a bug where URLs containing spaces would break when opened in a browser on Windows. PowerShell’s
Start-Processwas splitting the URL at space boundaries, so the browser would only receive a truncated URL. The fix encodes spaces as%20before passing them through, and also switches the argument format to use PowerShell array syntax (@()) instead of a single quoted string — this ensures browser arguments and the URL are passed as distinct parameters rather than getting concatenated together. -
v0.10 - Inline math calculator
Added an inline math calculator to the
listcommand. Typing=followed by a math expression (like=5+3*2) evaluates it live in the autocomplete suggestions usingmeval. Integer results display without decimal places, and invalid expressions show an error inline. It’s a small quality-of-life feature that saves opening a separate calculator.Also fixed a CRLF line ending issue that was breaking the Docker build on Linux, added a
.gitattributesto enforce LF endings, and deduplicated shortcuts that appeared multiple times from overlapping scan directories. -
v0.9 - Cross-platform hotkey and macOS support
Made the hotkey listener cross-platform — it now works on both macOS and Windows. On macOS, it uses Core Graphics event taps to listen for the ALT+SPACE combination and launches
project-switch listin a new Terminal window. The platform-specific code is cleanly separated intoplatform/macos.rsandplatform/windows.rsmodules behind conditional compilation.Added macOS
.appbundle scanning to the shortcuts feature. It scans/Applicationsand~/Applicationsfor.appbundles and surfaces them alongside Windows.lnkshortcuts in the autocomplete, usingopen -ato launch them.Fixed the hotkey to fire only on key press (not release), cleaned up the Docker build pipeline by removing bash as a dependency, and added cargo registry volume caching for the lint container.
-
v0.8.1 - Cross-compilation fixes and config auto-create
Fixed the Docker cross-compilation pipeline to work on ARM hosts (like Apple Silicon Macs). The Dockerfile now uses conditional platform targeting so it can build Windows and Linux x86_64 binaries regardless of the host architecture. Added a
build.shscript for non-Windows environments that skips the lint Docker image.Also added auto-creation of the config file — if
~/.project-switch.ymldoesn’t exist when the program starts, it creates one with sensible defaults instead of erroring. Added cargo registry caching in the Docker build to speed up repeated builds. -
v0.8 - File path autocomplete and fixes
Added file path autocomplete to the
listcommand. When you start typing a Windows path (drive letter likeC:\or a UNC path), the autocomplete switches to browsing the filesystem instead of matching commands. It auto-expands when there’s a single directory match and displays directories and files with distinct styling. This makesproject-switchusable as a quick file/folder opener too.Fixed multi-word shortcut selection — shortcuts with spaces in their names weren’t being matched properly in the autocomplete. Also added a fallback so the
listcommand works even without a current project set, showing only global commands and shortcuts.On the hotkey side, added logic to kill any prior
project-switch-hotkey.exeinstances on startup so you don’t end up with duplicate listeners, and the hotkey now creates a default config file if one doesn’t exist. -
v0.7 - Config includes, system tray, and shortcut indexing
Packed day with three big features landing.
First, added config file includes. The main
~/.project-switch.ymlcan now specify anincludepath pointing to another YAML file, and the two get merged at load time. Project-specific commands overlay base commands by key, and projects merge by name. This lets me keep a shared config in a git repo and machine-specific overrides locally.Building on that, the hotkey listener now auto-syncs the included config’s git repo in the background — it does a
git pullon startup and periodically, then stages, commits, and pushes any local changes. All git operations run withCREATE_NO_WINDOWso nothing flashes on screen.Second, migrated the hotkey listener from a plain console app to a proper Windows system tray application using
tray-iconandmuda. It shows a notification area icon with a right-click “Exit” menu, and runs with#![windows_subsystem = "windows"]so there’s no console window. Generated a simple icon using a Python script with Pillow.Third, added Windows shortcut (
.lnk) indexing. Thelistcommand now scans configured shortcut directories and surfaces.lnkfiles alongside regular commands in the autocomplete. This means Start Menu shortcuts and other Windows shortcuts are searchable from the same prompt.Also added
cargo fmt,clippy, andcargo auditas verification checks, committedCargo.lockfor reproducible builds, and wired the build script to restart the hotkey listener after a successful build. -
v0.6 - ALT+SPACE hotkey listener
Built a companion binary,
project-switch-hotkey, that runs in the background on Windows and listens for a global ALT+SPACE hotkey. When triggered, it kills any runningproject-switch.exeinstances and launchesproject-switch listin a new Windows Terminal tab. The idea is to make project switching feel as instant as an app launcher — press the hotkey from anywhere and you’re immediately in the selection prompt.The hotkey registration uses the Windows
RegisterHotKeyAPI withMOD_ALT | MOD_NOREPEAT, and the message loop polls withPeekMessageWon a 50ms sleep. It handles graceful shutdown viactrlcand wraps the launched command incmd /c ... & exit /b 0so the terminal tab closes cleanly even whentaskkillforce-kills a prior instance.It’s a standalone crate under
hotkey/(not a workspace member) and gets cross-compiled through the existing Docker pipeline alongside the main binary. -
v0.5 - Direct URL opening support
Added the ability to type a URL directly into the
listprompt or pass one toopen, without needing it to be a configured command. If the input doesn’t match any command key but looks like a URL (starts withhttp,www., or matches adomain.tldpattern), it gets opened in the project’s configured browser. Bare domains likeexample.comautomatically get anhttps://prefix.Also deprecated the
opencommand in favour oflist— it’s now hidden from help output. Theopencommand still works but includes the same URL fallback behaviour for backwards compatibility. -
v0.4.1 - Autocomplete fixes and YAML preservation
Fixed a couple of annoying issues with the
listcommand’s autocomplete. Selecting a suggestion was inserting the full formatted string including ANSI colour codes and the URL arrow, so I added an ANSI stripping function and taught the completion handler to extract just the command key from highlighted suggestions. Also fixed input parsing to handle cases where the formatted suggestion text leaked into the user’s input.The other fix was around YAML field ordering. Previously, saving the config would re-serialise the entire struct which shuffled fields into alphabetical order — not great when you’ve carefully organised your config file. Now the config manager preserves the raw YAML on load and only patches the changed fields (
currentProject,projects) back into it on save, keeping everything else untouched.Added
skip_serializing_ifannotations across the config structs soNonefields andfalsedefaults don’t clutter the output. -
v0.4 - Command args, global commands, and browser profiles
Big batch of enhancements to
project-switchtoday. Added support for command arguments — you can now type extra text after a command key and it gets appended to the URL (with optional URL encoding via aurl_encodeconfig flag). This is great for search URLs where you want to pass a query string directly from the prompt.Introduced global commands in the config file. These are available across all projects and show up alongside project-specific commands in the
listview, with project commands taking priority over globals when there’s a key collision.Browser profiles are now supported too. The browser string in the config can include arguments like
firefox -P work, and the launcher correctly parses and passes those through on Windows, macOS, and Linux.Also reworked the
listcommand to useinquire’s autocomplete instead of a select menu, so you can type to filter commands and append arguments inline. Commands that aren’t URLs are now treated as terminal commands and spawned directly in the shell. -
v0.3 - List command and build script
Added a
listsubcommand that shows an interactive menu of all openable items from the current project. It displays each command’s key alongside a truncated URL, and opens the selected item in the configured browser. This makes it much quicker to jump to project resources compared to remembering exact command keys foropen.Extracted browser-opening logic into a shared
utils::browsermodule since bothopenandlistneed it. Also fixed the browser launch on Windows — switched fromcmd /C startto PowerShell’sStart-Process, which handles URLs with special characters more reliably.Created a
build.ps1script that stops any runningproject-switch.exeprocesses, cleans thebin/folder, and runs the Docker cross-compilation build. Cleaned updocker-compose.ymlto remove unused profiles and volumes. -
v0.2 - Rewriting project-switch in Rust
Rewrote
project-switchentirely from TypeScript to Rust. The Node.js version had noticeable startup latency (~100-200ms) which felt sluggish for something that should be instant. The Rust version starts in under 5ms.Ported all four subcommands (
switch,add,current,open) to Rust usingclapfor argument parsing,serde_yamlfor config file handling, andinquirefor interactive prompts. The config format stays the same —~/.project-switch.ymlwith projects, commands, and browser overrides — so it’s a drop-in replacement.Set up a Docker-based cross-compilation pipeline that builds both Windows and Linux binaries from a single
docker-compose up buildcommand, usinggcc-mingw-w64for the Windows target. -
v0.1 - Project switch genesis
Started
project-switchtoday — a CLI tool for quickly switching between project directories. I’ve been wanting a way to jump between repos without hunting through nested folders, and this scratches that itch.Built it as a TypeScript project with three subcommands:
switchfor fuzzy-matching a project name and changing to its directory,addfor registering new projects interactively, andopenfor launching a project directly in VS Code. Configuration lives in a YAML file at~/.project-switch.ymlwhere each project has a name, path, and optional tags.The
switchcommand presents an interactive list usingenquirerwhen there are multiple matches, and jumps straight there on an exact match. Theaddcommand walks through prompts for name, path, and tags, with the path defaulting to the current directory. Spent a bit of time fixing a path resolution bug for the config file location.