Skip to content

Commit 4c9eabb

Browse files
committed
Release 0.7.0: favorite agent + Ctrl+Alt+A keybinding, install-status indicator
- Mark a favorite agent with the inline star button; launch it with Ctrl+Alt+A (Cmd+Alt+A on macOS, remappable). No favorite set -> picker + offer to remember. Stored in the new superCli.favoriteAgent setting. - Show agents whose CLI is not on PATH as dimmed and "not installed" (best-effort, no process spawned; skipped under superCli.useWsl). - New command-utils helper executableExistsOnPath (PATH + PATHEXT scan) with tests.
1 parent e8e9dec commit 4c9eabb

9 files changed

Lines changed: 294 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@
33
All notable changes to this project are documented here. The format is based on
44
[Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
55

6+
## [0.7.0]
7+
8+
- **Favorite agent.** Mark an agent as your favorite with the star (★) button next to it in the
9+
sidebar, and launch it from anywhere with **`Ctrl+Alt+A`** (`Cmd+Alt+A` on macOS; remappable in
10+
Keyboard Shortcuts). When no favorite is set, the shortcut opens the picker and offers to remember
11+
your choice. Stored in the new `superCli.favoriteAgent` setting.
12+
- **Install-status indicator.** Agents whose CLI isn't found on your `PATH` are shown dimmed and
13+
marked *not installed* (best-effort, no process spawned). Not shown under `superCli.useWsl`, since
14+
the Windows `PATH` doesn't reflect what's installed inside WSL.
15+
616
## [0.6.2]
717

818
- Added an **Open VSX** badge to the README (Super CLI is published on Open VSX for Cursor, Windsurf, VSCodium, and Gitpod users).

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ You can also open the Extensions view in VS Code (or Cursor, Antigravity, Windsu
3636
- **One launcher for every agent.** A **Super CLI** view in the activity bar lists all configured
3737
agents; click one to open it in a terminal beside your editor. A toolbar button and the
3838
**Super CLI: Launch Coding Agent** command open a quick pick of the same list.
39+
- **Favorite agent, one keystroke.** Click the star (★) next to an agent to mark it as your favorite,
40+
then launch it anywhere with **`Ctrl+Alt+A`** (`Cmd+Alt+A` on macOS; remap it in Keyboard
41+
Shortcuts). With no favorite set the shortcut opens the picker and offers to remember your choice.
42+
- **Install status at a glance.** Agents whose CLI isn't on your `PATH` are shown dimmed and marked
43+
*not installed*, so you know what to install before launching.
3944
- **Built-in presets.** Claude Code, Codex, GitHub Copilot CLI, Cursor, Droid, Grok, Kilo, Antigravity,
4045
OpenCode, Command Code, Crush, Hermes, and MiMo Code are available out of the box. (Gemini CLI was
4146
retired by Google and replaced by Antigravity.)
@@ -103,6 +108,7 @@ an untrusted repository cannot inject commands.
103108
| --- | --- | --- |
104109
| `superCli.agents` | `[]` | Your agents (added to or overriding the built-ins). |
105110
| `superCli.useBuiltins` | `true` | Include the built-in agent presets. |
111+
| `superCli.favoriteAgent` | `""` | Id of the agent launched by `Ctrl+Alt+A`. Set it with the ★ button in the sidebar rather than by hand. |
106112
| `superCli.terminalLocation` | `beside` | Open the terminal `beside` the editor or in the `panel`. |
107113
| `superCli.useWsl` | `false` | On Windows, open agents in a WSL terminal instead of the default shell. Ignored on macOS/Linux. |
108114

@@ -118,6 +124,9 @@ settings.
118124
directory.
119125
- **Nothing happens on launch.** Make sure the workspace is trusted — the launcher is disabled in
120126
untrusted workspaces because it runs terminal commands.
127+
- **"Not installed" looks wrong.** The indicator is a best-effort check of your `PATH` and doesn't
128+
spawn the CLI. It isn't shown when `superCli.useWsl` is on, because the Windows `PATH` doesn't
129+
reflect what's installed inside WSL. Use **Refresh Agents** after installing a CLI.
121130
- **Companion editor extensions (Command Code, Claude Code).** Super CLI keeps your editor free of
122131
per-CLI companion extensions. It launches **Command Code** with `autoInstallExtension: false` in
123132
`~/.commandcode/config.json`, and **Claude Code** with the `CLAUDE_CODE_IDE_SKIP_AUTO_INSTALL=1`

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "Super CLI — AI Coding Agent Launcher (Claude Code, Codex, Copilot)",
44
"description": "Launch any AI coding-agent CLI — Claude Code, Codex, GitHub Copilot, Cursor, Crush, OpenCode and more — from one sidebar and side terminal. Works in VS Code, Cursor, Antigravity & Windsurf on Windows, macOS & Linux.",
55
"publisher": "mikesoft",
6-
"version": "0.6.2",
6+
"version": "0.7.0",
77
"repository": {
88
"type": "git",
99
"url": "https://github.com/TheStreamCode/super-cli.git"
@@ -102,6 +102,23 @@
102102
"category": "Super CLI",
103103
"icon": "$(play)"
104104
},
105+
{
106+
"command": "superCli.launchFavorite",
107+
"title": "Launch Favorite Agent",
108+
"category": "Super CLI"
109+
},
110+
{
111+
"command": "superCli.setFavorite",
112+
"title": "Set as Favorite",
113+
"category": "Super CLI",
114+
"icon": "$(star-empty)"
115+
},
116+
{
117+
"command": "superCli.unsetFavorite",
118+
"title": "Remove Favorite",
119+
"category": "Super CLI",
120+
"icon": "$(star-full)"
121+
},
105122
{
106123
"command": "superCli.updateAgent",
107124
"title": "Update Agent",
@@ -147,16 +164,34 @@
147164
"group": "inline@1"
148165
},
149166
{
150-
"command": "superCli.updateAgent",
151-
"when": "view == superCli.agents && viewItem == agent-updatable",
167+
"command": "superCli.setFavorite",
168+
"when": "view == superCli.agents && viewItem =~ /^agent(?!.*favorite)/",
152169
"group": "inline@2"
170+
},
171+
{
172+
"command": "superCli.unsetFavorite",
173+
"when": "view == superCli.agents && viewItem =~ /favorite/",
174+
"group": "inline@2"
175+
},
176+
{
177+
"command": "superCli.updateAgent",
178+
"when": "view == superCli.agents && viewItem =~ /updatable/",
179+
"group": "inline@3"
153180
}
154181
],
155182
"commandPalette": [
156183
{
157184
"command": "superCli.launchAgent",
158185
"when": "false"
159186
},
187+
{
188+
"command": "superCli.setFavorite",
189+
"when": "false"
190+
},
191+
{
192+
"command": "superCli.unsetFavorite",
193+
"when": "false"
194+
},
160195
{
161196
"command": "superCli.updateAgent",
162197
"when": "false"
@@ -167,6 +202,13 @@
167202
}
168203
]
169204
},
205+
"keybindings": [
206+
{
207+
"command": "superCli.launchFavorite",
208+
"key": "ctrl+alt+a",
209+
"mac": "cmd+alt+a"
210+
}
211+
],
170212
"configuration": {
171213
"title": "Super CLI",
172214
"properties": {
@@ -280,6 +322,12 @@
280322
"default": true,
281323
"description": "Include the built-in agent presets (Claude Code, Codex, Copilot, Grok, Kilo, Antigravity, OpenCode, Command Code, Cursor, Droid, Crush, Hermes, MiMo Code) in the launcher."
282324
},
325+
"superCli.favoriteAgent": {
326+
"type": "string",
327+
"scope": "machine",
328+
"default": "",
329+
"markdownDescription": "Id of the favorite agent launched by the `Launch Favorite Agent` command (default keybinding `Ctrl+Alt+A`, `Cmd+Alt+A` on macOS; remap it from Keyboard Shortcuts). Set this with the star (★) button next to an agent in the sidebar rather than editing it by hand."
330+
},
283331
"superCli.terminalLocation": {
284332
"type": "string",
285333
"enum": [

src/command-utils.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,53 @@ export function resolveHomePath(p: string, homedir: string): string {
109109
return p;
110110
}
111111

112+
const DEFAULT_PATHEXT = '.COM;.EXE;.BAT;.CMD';
113+
114+
/** Returns the executable extensions to try on Windows (the bare name plus each PATHEXT entry). */
115+
function resolveWindowsExtensions(pathExt: string | undefined): string[] {
116+
const entries = (pathExt && pathExt.trim() ? pathExt : DEFAULT_PATHEXT)
117+
.split(';')
118+
.map((entry) => entry.trim())
119+
.filter(Boolean);
120+
121+
return ['', ...entries];
122+
}
123+
124+
/**
125+
* Best-effort check that a command's executable resolves on PATH, without spawning a process.
126+
* On Windows the bare name is also tried with each PATHEXT extension. `fileExists` is injected so
127+
* the logic stays pure and testable; production passes `fs.existsSync`.
128+
*/
129+
export function executableExistsOnPath(
130+
command: string,
131+
env: Record<string, string | undefined>,
132+
platform: string,
133+
fileExists: (filePath: string) => boolean,
134+
): boolean {
135+
const executable = extractExecutable(command);
136+
if (!executable) {
137+
return false;
138+
}
139+
140+
const isWindows = platform === 'win32';
141+
const extensions = isWindows ? resolveWindowsExtensions(env.PATHEXT) : [''];
142+
const existsWithExt = (base: string): boolean => extensions.some((ext) => fileExists(base + ext));
143+
144+
// A path-qualified command is checked directly, not searched on PATH.
145+
if (/[\\/]/.test(executable)) {
146+
return existsWithExt(executable);
147+
}
148+
149+
const pathValue = env.PATH ?? env.Path ?? '';
150+
const delimiter = isWindows ? ';' : ':';
151+
const separator = isWindows ? '\\' : '/';
152+
153+
return pathValue
154+
.split(delimiter)
155+
.filter((dir) => dir.length > 0)
156+
.some((dir) => existsWithExt(dir.replace(/[\\/]+$/, '') + separator + executable));
157+
}
158+
112159
/** Adds only the keys from `defaults` that are absent in `existing`; reports whether anything changed. */
113160
export function mergeMissingDefaults(
114161
existing: Record<string, unknown>,

src/extension.ts

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import * as fs from 'node:fs';
12
import * as vscode from 'vscode';
23
import { type Agent, BUILTIN_AGENTS, resolveAgents } from './agents.js';
4+
import { executableExistsOnPath } from './command-utils.js';
35
import { AgentTreeDataProvider } from './tree.js';
46
import { launchAgent, openExtensionSettings, updateAgent } from './terminal.js';
57

@@ -17,15 +19,53 @@ function getEffectiveAgents(): Agent[] {
1719
return resolveAgents(BUILTIN_AGENTS, userAgents, useBuiltins);
1820
}
1921

22+
/** Returns the id of the user's favorite agent, or '' when none is set. */
23+
function getFavoriteId(): string {
24+
return vscode.workspace.getConfiguration(SETTINGS_NAMESPACE).get<string>('favoriteAgent', '');
25+
}
26+
27+
/** Persists the favorite agent id (or clears it when empty) at the user (global) level. */
28+
async function setFavoriteId(id: string): Promise<void> {
29+
await vscode.workspace
30+
.getConfiguration(SETTINGS_NAMESPACE)
31+
.update('favoriteAgent', id || undefined, vscode.ConfigurationTarget.Global);
32+
}
33+
2034
interface AgentQuickPickItem extends vscode.QuickPickItem {
2135
agent: Agent;
2236
}
2337

2438
export function activate(context: vscode.ExtensionContext): void {
25-
const treeProvider = new AgentTreeDataProvider(getEffectiveAgents);
39+
// Best-effort "installed / not installed" status per agent, keyed by id (undefined = unknown).
40+
const installStatus = new Map<string, boolean>();
41+
42+
const treeProvider = new AgentTreeDataProvider(
43+
getEffectiveAgents,
44+
getFavoriteId,
45+
(id) => installStatus.get(id),
46+
);
2647
const treeView = vscode.window.createTreeView('superCli.agents', { treeDataProvider: treeProvider });
2748

28-
const launchCommand = vscode.commands.registerCommand('superCli.launch', async () => {
49+
// Recomputes install status off the activation path. Under WSL the host PATH is not representative,
50+
// so status is left unknown rather than reported as missing.
51+
const refreshInstallStatus = (): void => {
52+
setTimeout(() => {
53+
installStatus.clear();
54+
const useWsl = vscode.workspace.getConfiguration(SETTINGS_NAMESPACE).get<boolean>('useWsl', false)
55+
&& process.platform === 'win32';
56+
57+
if (!useWsl) {
58+
for (const agent of getEffectiveAgents()) {
59+
installStatus.set(agent.id, executableExistsOnPath(agent.command, process.env, process.platform, fs.existsSync));
60+
}
61+
}
62+
63+
treeProvider.refresh();
64+
}, 0);
65+
};
66+
67+
// Shows the agent picker and launches the chosen agent; optionally offers to remember it as favorite.
68+
const runLaunchQuickPick = async (offerFavorite: boolean): Promise<void> => {
2969
const agents = getEffectiveAgents();
3070

3171
if (agents.length === 0) {
@@ -46,9 +86,37 @@ export function activate(context: vscode.ExtensionContext): void {
4686
{ placeHolder: 'Select a coding agent to launch' },
4787
);
4888

49-
if (picked) {
50-
await launchAgent(picked.agent, context, terminalSequence++);
89+
if (!picked) {
90+
return;
91+
}
92+
93+
await launchAgent(picked.agent, context, terminalSequence++);
94+
95+
if (offerFavorite) {
96+
const choice = await vscode.window.showInformationMessage(
97+
`Set "${picked.agent.label}" as your favorite agent? You can then launch it with Ctrl+Alt+A.`,
98+
'Set Favorite',
99+
);
100+
101+
if (choice === 'Set Favorite') {
102+
await setFavoriteId(picked.agent.id);
103+
}
104+
}
105+
};
106+
107+
const launchCommand = vscode.commands.registerCommand('superCli.launch', () => runLaunchQuickPick(false));
108+
109+
const launchFavoriteCommand = vscode.commands.registerCommand('superCli.launchFavorite', async () => {
110+
const favoriteId = getFavoriteId();
111+
const favorite = favoriteId ? getEffectiveAgents().find((agent) => agent.id === favoriteId) : undefined;
112+
113+
if (favorite) {
114+
await launchAgent(favorite, context, terminalSequence++);
115+
return;
51116
}
117+
118+
// No favorite set (or it no longer exists): let the user pick one and offer to remember it.
119+
await runLaunchQuickPick(true);
52120
});
53121

54122
const launchAgentCommand = vscode.commands.registerCommand('superCli.launchAgent', async (agent?: Agent) => {
@@ -59,6 +127,23 @@ export function activate(context: vscode.ExtensionContext): void {
59127
await launchAgent(agent, context, terminalSequence++);
60128
});
61129

130+
const setFavoriteCommand = vscode.commands.registerCommand('superCli.setFavorite', async (agent?: Agent) => {
131+
if (!agent) {
132+
return;
133+
}
134+
135+
await setFavoriteId(agent.id);
136+
void vscode.window.setStatusBarMessage(`${agent.label} is now the favorite agent`, 2500);
137+
});
138+
139+
const unsetFavoriteCommand = vscode.commands.registerCommand('superCli.unsetFavorite', async (agent?: Agent) => {
140+
if (!agent) {
141+
return;
142+
}
143+
144+
await setFavoriteId('');
145+
});
146+
62147
const updateAgentCommand = vscode.commands.registerCommand('superCli.updateAgent', async (agent?: Agent) => {
63148
if (!agent) {
64149
return;
@@ -68,6 +153,7 @@ export function activate(context: vscode.ExtensionContext): void {
68153
});
69154

70155
const refreshCommand = vscode.commands.registerCommand('superCli.refresh', () => {
156+
refreshInstallStatus();
71157
treeProvider.refresh();
72158
});
73159

@@ -76,15 +162,26 @@ export function activate(context: vscode.ExtensionContext): void {
76162
});
77163

78164
const configWatcher = vscode.workspace.onDidChangeConfiguration((event) => {
79-
if (event.affectsConfiguration('superCli.agents') || event.affectsConfiguration('superCli.useBuiltins')) {
165+
if (event.affectsConfiguration('superCli.agents') || event.affectsConfiguration('superCli.useBuiltins')
166+
|| event.affectsConfiguration('superCli.useWsl')) {
167+
refreshInstallStatus();
168+
}
169+
170+
if (event.affectsConfiguration('superCli.agents') || event.affectsConfiguration('superCli.useBuiltins')
171+
|| event.affectsConfiguration('superCli.favoriteAgent')) {
80172
treeProvider.refresh();
81173
}
82174
});
83175

176+
refreshInstallStatus();
177+
84178
context.subscriptions.push(
85179
treeView,
86180
launchCommand,
181+
launchFavoriteCommand,
87182
launchAgentCommand,
183+
setFavoriteCommand,
184+
unsetFavoriteCommand,
88185
updateAgentCommand,
89186
refreshCommand,
90187
openSettingsCommand,

0 commit comments

Comments
 (0)