From dd89547b2ddd4b3bb402a2d8db9cdef531636a4b Mon Sep 17 00:00:00 2001 From: Antonio Paulino Date: Thu, 26 Feb 2026 20:29:53 +0000 Subject: [PATCH 1/6] feat(ui): add extension-driven status badges to footer --- packages/cli/src/config/extension-manager.ts | 1 + packages/cli/src/config/extension.ts | 5 ++ packages/cli/src/ui/AppContainer.tsx | 4 ++ packages/cli/src/ui/components/Footer.tsx | 8 +++ .../cli/src/ui/contexts/UIStateContext.tsx | 6 ++ .../src/ui/hooks/useExtensionStatusBadges.ts | 61 +++++++++++++++++++ packages/core/src/config/config.ts | 38 ++++++++++++ 7 files changed, 123 insertions(+) create mode 100644 packages/cli/src/ui/hooks/useExtensionStatusBadges.ts diff --git a/packages/cli/src/config/extension-manager.ts b/packages/cli/src/config/extension-manager.ts index 56152cd6e16..e8c49248ff7 100644 --- a/packages/cli/src/config/extension-manager.ts +++ b/packages/cli/src/config/extension-manager.ts @@ -886,6 +886,7 @@ Would you like to attempt to install via "git clone" instead?`, themes: config.themes, rules, checkers, + ui: config.ui, }; } catch (e) { debugLogger.error( diff --git a/packages/cli/src/config/extension.ts b/packages/cli/src/config/extension.ts index 815cf23ecec..cb915d859b6 100644 --- a/packages/cli/src/config/extension.ts +++ b/packages/cli/src/config/extension.ts @@ -8,6 +8,7 @@ import type { MCPServerConfig, ExtensionInstallMetadata, CustomTheme, + ExtensionUIContributions, } from '@google/gemini-cli-core'; import * as fs from 'node:fs'; import * as path from 'node:path'; @@ -33,6 +34,10 @@ export interface ExtensionConfig { * These themes will be registered when the extension is activated. */ themes?: CustomTheme[]; + /** + * UI contributions provided by this extension. + */ + ui?: ExtensionUIContributions; } export interface ExtensionUpdateInfo { diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index b89d0b83c08..a7989915dca 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -133,6 +133,7 @@ import { useMcpStatus } from './hooks/useMcpStatus.js'; import { useApprovalModeIndicator } from './hooks/useApprovalModeIndicator.js'; import { useSessionStats } from './contexts/SessionContext.js'; import { useGitBranchName } from './hooks/useGitBranchName.js'; +import { useExtensionStatusBadges } from './hooks/useExtensionStatusBadges.js'; import { useConfirmUpdateRequests, useExtensionUpdates, @@ -430,6 +431,7 @@ export const AppContainer = (props: AppContainerProps) => { // Additional hooks moved from App.tsx const { stats: sessionStats } = useSessionStats(); const branchName = useGitBranchName(config.getTargetDir()); + const statusBadges = useExtensionStatusBadges(config); // Layout measurements const mainControlsRef = useRef(null); @@ -2341,6 +2343,7 @@ Logging in with Google... Restarting Gemini CLI to continue. ...pendingGeminiHistoryItems, ]), hintBuffer: '', + statusBadges, }), [ isThemeDialogOpen, @@ -2461,6 +2464,7 @@ Logging in with Google... Restarting Gemini CLI to continue. adminSettingsChanged, newAgents, showIsExpandableHint, + statusBadges, ], ); diff --git a/packages/cli/src/ui/components/Footer.tsx b/packages/cli/src/ui/components/Footer.tsx index 3fc830c1b70..02a575fea7c 100644 --- a/packages/cli/src/ui/components/Footer.tsx +++ b/packages/cli/src/ui/components/Footer.tsx @@ -95,6 +95,14 @@ export const Footer: React.FC = () => { )} )} + {uiState.statusBadges.map((badge, i) => ( + + {' '} + + {badge.text} + + + ))} {debugMode && ( {' ' + (debugMessage || '--debug')} diff --git a/packages/cli/src/ui/contexts/UIStateContext.tsx b/packages/cli/src/ui/contexts/UIStateContext.tsx index 79464271b89..ae71e4afd9f 100644 --- a/packages/cli/src/ui/contexts/UIStateContext.tsx +++ b/packages/cli/src/ui/contexts/UIStateContext.tsx @@ -66,6 +66,11 @@ export interface QuotaState { validationRequest: ValidationDialogRequest | null; } +export interface StatusBadge { + text: string; + color?: string; +} + export interface UIState { history: HistoryItem[]; historyManager: UseHistoryManagerReturn; @@ -191,6 +196,7 @@ export interface UIState { text: string; type: TransientMessageType; } | null; + statusBadges: StatusBadge[]; } export const UIStateContext = createContext(null); diff --git a/packages/cli/src/ui/hooks/useExtensionStatusBadges.ts b/packages/cli/src/ui/hooks/useExtensionStatusBadges.ts new file mode 100644 index 00000000000..dedd61a929f --- /dev/null +++ b/packages/cli/src/ui/hooks/useExtensionStatusBadges.ts @@ -0,0 +1,61 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { useState, useEffect, useCallback } from 'react'; +import { spawnAsync, type Config } from '@google/gemini-cli-core'; +import { type StatusBadge } from '../contexts/UIStateContext.js'; + +export function useExtensionStatusBadges(config: Config): StatusBadge[] { + const [badges, setBadges] = useState([]); + + const fetchBadges = useCallback(async () => { + const activeExtensions = config + .getExtensionLoader() + .getExtensions() + .filter((ext) => ext.isActive && ext.ui?.badges && ext.ui.badges.length > 0); + + const newBadges: StatusBadge[] = []; + + const badgePromises = activeExtensions.flatMap((ext) => { + if (!ext.ui?.badges) return []; + + return ext.ui.badges.map(async (badgeConfig) => { + try { + const { stdout } = await spawnAsync( + badgeConfig.command, + badgeConfig.args ?? [], + ); + const text = stdout.toString().trim(); + if (text) { + newBadges.push({ + text: badgeConfig.icon ? `${badgeConfig.icon} ${text}` : text, + color: badgeConfig.color, + }); + } + } catch (_e) { + // Ignore failing badge commands silently so they don't break the UI + } + }); + }); + + await Promise.allSettled(badgePromises); + setBadges(newBadges); + }, [config]); + + useEffect(() => { + void fetchBadges(); + + // Poll every 30 seconds for all extension badges + // Future enhancement: Respect `intervalMs` from individual badge configs + const interval = setInterval(() => { + void fetchBadges(); + }, 30000); + + return () => clearInterval(interval); + }, [fetchBadges]); + + return badges; +} diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 32d74479e72..1e8c3435caa 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -303,6 +303,40 @@ export interface BrowserAgentCustomConfig { visualModel?: string; } +/** + * A dynamic status badge contributed by an extension. + */ +export interface ExtensionUIBadge { + /** + * The command to execute to get the badge text. + * Should return a plain string to stdout. + */ + command: string; + /** + * Optional arguments for the command. + */ + args?: string[]; + /** + * Optional icon to display before the text. + */ + icon?: string; + /** + * Optional polling interval in milliseconds. Defaults to 30000. + */ + intervalMs?: number; + /** + * Optional semantic color for the badge. + */ + color?: string; +} + +export interface ExtensionUIContributions { + /** + * Badges to display in the footer. + */ + badges?: ExtensionUIBadge[]; +} + /** * All information required in CLI to handle an extension. Defined in Core so * that the collection of loaded, active, and inactive extensions can be passed @@ -337,6 +371,10 @@ export interface GeminiCLIExtension { * Safety checkers contributed by this extension. */ checkers?: SafetyCheckerRule[]; + /** + * UI contributions provided by this extension. + */ + ui?: ExtensionUIContributions; } export interface ExtensionInstallMetadata { From 311eb19c10d7f1d2a7e6ae78ffecadf003bc70aa Mon Sep 17 00:00:00 2001 From: Antonio Paulino Date: Thu, 26 Feb 2026 20:31:51 +0000 Subject: [PATCH 2/6] docs(extensions): document dynamic status badges in footer --- docs/extensions/reference.md | 21 +++++++++++++- docs/extensions/writing-extensions.md | 40 ++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/docs/extensions/reference.md b/docs/extensions/reference.md index 2c2b730126d..9c938cf282c 100644 --- a/docs/extensions/reference.md +++ b/docs/extensions/reference.md @@ -122,7 +122,17 @@ The manifest file defines the extension's behavior and configuration. } }, "contextFileName": "GEMINI.md", - "excludeTools": ["run_shell_command"] + "excludeTools": ["run_shell_command"], + "ui": { + "badges": [ + { + "command": "sh", + "args": ["${extensionPath}/status.sh"], + "color": "accent", + "intervalMs": 30000 + } + ] + } } ``` @@ -157,6 +167,15 @@ The manifest file defines the extension's behavior and configuration. `"excludeTools": ["run_shell_command(rm -rf)"]` will block the `rm -rf` command. Note that this differs from the MCP server `excludeTools` functionality, which can be listed in the MCP server config. +- `ui`: Configuration for UI contributions. + - `badges`: An array of status badge definitions to display in the CLI footer. + - `command`: The executable command to run to get the badge text. + - `args`: An array of arguments to pass to the command. + - `icon`: An optional icon to display before the badge text. + - `intervalMs`: The interval (in milliseconds) at which to poll the command + for updates. Defaults to `30000`. + - `color`: The semantic color for the badge text (e.g., `primary`, `accent`, + `warning`, `error`). When Gemini CLI starts, it loads all the extensions and merges their configurations. If there are any conflicts, the workspace configuration takes diff --git a/docs/extensions/writing-extensions.md b/docs/extensions/writing-extensions.md index 213d77542ed..e91d2eb64b5 100644 --- a/docs/extensions/writing-extensions.md +++ b/docs/extensions/writing-extensions.md @@ -22,6 +22,7 @@ which features your extension needs. | **[Context file (`GEMINI.md`)](reference.md#contextfilename)** | A markdown file containing instructions that are loaded into the model's context at the start of every session. | Use this to define the "personality" of your extension, set coding standards, or provide essential knowledge that the model should always have. | CLI provides to model | | **[Agent skills](../cli/skills.md)** | A specialized set of instructions and workflows that the model activates only when needed. | Use this for complex, occasional tasks (like "create a PR" or "audit security") to avoid cluttering the main context window when the skill isn't being used. | Model | | **[Hooks](../hooks/index.md)** | A way to intercept and customize the CLI's behavior at specific lifecycle events (e.g., before/after a tool call). | Use this when you want to automate actions based on what the model is doing, like validating tool arguments, logging activity, or modifying the model's input/output. | CLI | +| **[UI contributions](#step-8-add-ui-contributions)** | Dynamic visual elements added to the CLI interface, such as status badges in the footer. | Use this to surface environmental context, such as the active cloud project, virtual environment, or system status, directly in the CLI's persistent status bar. | CLI | | **[Custom themes](reference.md#themes)** | A set of color definitions to personalize the CLI UI. | Use this to provide a unique visual identity for your extension or to offer specialized high-contrast or thematic color schemes. | User (via /theme) | ## Step 1: Create a new extension @@ -278,7 +279,44 @@ Skills are activated only when needed, which saves context tokens. Gemini CLI automatically discovers skills bundled with your extension. The model activates them when it identifies a relevant task. -## Step 8: Release your extension +## Step 8: Add UI contributions + +UI contributions let you add dynamic visual elements to the Gemini CLI +interface. The most common use case is adding "status badges" to the footer to +surface environmental context. + +1. Open `gemini-extension.json`. +2. Add a `ui` object with a `badges` array: + + ```json + { + "name": "my-first-extension", + "version": "1.0.0", + "ui": { + "badges": [ + { + "command": "sh", + "args": ["${extensionPath}/status.sh"], + "color": "accent", + "intervalMs": 30000 + } + ] + } + } + ``` + +3. Create the script file (for example, `status.sh`) that returns the text to + display: + + ```bash + #!/bin/bash + echo "active-context" + ``` + +Gemini CLI executes these commands asynchronously in the background and renders +the output as a badge in the persistent footer. + +## Step 9: Release your extension When your extension is ready, share it with others via a Git repository or GitHub Releases. Refer to the [Extension Releasing Guide](./releasing.md) for From 85a0be5b587b5747580fa5f4c39d56c30f1d9da9 Mon Sep 17 00:00:00 2001 From: Antonio Paulino Date: Thu, 26 Feb 2026 20:38:11 +0000 Subject: [PATCH 3/6] fix(ui): address security and race condition concerns in Extension UI API --- packages/cli/src/config/extensions/consent.ts | 5 ++ .../src/ui/hooks/useExtensionStatusBadges.ts | 54 +++++++++++-------- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/packages/cli/src/config/extensions/consent.ts b/packages/cli/src/config/extensions/consent.ts index 9a63054d122..ca1a406f98e 100644 --- a/packages/cli/src/config/extensions/consent.ts +++ b/packages/cli/src/config/extensions/consent.ts @@ -179,6 +179,11 @@ async function extensionConsentString( '⚠️ This extension contains Hooks which can automatically execute commands.', ); } + if (sanitizedConfig.ui?.badges && sanitizedConfig.ui.badges.length > 0) { + output.push( + '⚠️ This extension contributes UI Badges which will automatically execute commands in the background to update their status.', + ); + } if (skills.length > 0) { output.push(`\n${chalk.bold('Agent Skills:')}`); output.push('\nThis extension will install the following agent skills:\n'); diff --git a/packages/cli/src/ui/hooks/useExtensionStatusBadges.ts b/packages/cli/src/ui/hooks/useExtensionStatusBadges.ts index dedd61a929f..eca1fdbac59 100644 --- a/packages/cli/src/ui/hooks/useExtensionStatusBadges.ts +++ b/packages/cli/src/ui/hooks/useExtensionStatusBadges.ts @@ -15,45 +15,55 @@ export function useExtensionStatusBadges(config: Config): StatusBadge[] { const activeExtensions = config .getExtensionLoader() .getExtensions() - .filter((ext) => ext.isActive && ext.ui?.badges && ext.ui.badges.length > 0); - - const newBadges: StatusBadge[] = []; + .filter( + (ext) => ext.isActive && ext.ui?.badges && ext.ui.badges.length > 0, + ); const badgePromises = activeExtensions.flatMap((ext) => { if (!ext.ui?.badges) return []; - return ext.ui.badges.map(async (badgeConfig) => { - try { - const { stdout } = await spawnAsync( - badgeConfig.command, - badgeConfig.args ?? [], - ); - const text = stdout.toString().trim(); - if (text) { - newBadges.push({ - text: badgeConfig.icon ? `${badgeConfig.icon} ${text}` : text, - color: badgeConfig.color, - }); + return ext.ui.badges.map( + async (badgeConfig): Promise => { + try { + const { stdout } = await spawnAsync( + badgeConfig.command, + badgeConfig.args ?? [], + ); + const text = stdout.toString().trim(); + if (text) { + return { + text: badgeConfig.icon ? `${badgeConfig.icon} ${text}` : text, + color: badgeConfig.color, + }; + } + } catch (_e) { + // Ignore failing badge commands silently so they don't break the UI } - } catch (_e) { - // Ignore failing badge commands silently so they don't break the UI - } - }); + return null; + }, + ); }); - await Promise.allSettled(badgePromises); + const results = await Promise.allSettled(badgePromises); + const newBadges: StatusBadge[] = results + .filter( + (result): result is PromiseFulfilledResult => + result.status === 'fulfilled' && result.value !== null, + ) + .map((result) => result.value); + setBadges(newBadges); }, [config]); useEffect(() => { void fetchBadges(); - + // Poll every 30 seconds for all extension badges // Future enhancement: Respect `intervalMs` from individual badge configs const interval = setInterval(() => { void fetchBadges(); }, 30000); - + return () => clearInterval(interval); }, [fetchBadges]); From 720f79e06c906913b503f20e27f27d10a3f092f4 Mon Sep 17 00:00:00 2001 From: Antonio Paulino Date: Fri, 27 Feb 2026 10:25:30 +0000 Subject: [PATCH 4/6] feat(ui): support declarative env and command badge types --- packages/cli/src/config/extensions/consent.ts | 5 +++- .../src/ui/hooks/useExtensionStatusBadges.ts | 30 ++++++++++++------- packages/core/src/config/config.ts | 16 +++++++--- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/packages/cli/src/config/extensions/consent.ts b/packages/cli/src/config/extensions/consent.ts index ca1a406f98e..017c8090b2a 100644 --- a/packages/cli/src/config/extensions/consent.ts +++ b/packages/cli/src/config/extensions/consent.ts @@ -179,7 +179,10 @@ async function extensionConsentString( '⚠️ This extension contains Hooks which can automatically execute commands.', ); } - if (sanitizedConfig.ui?.badges && sanitizedConfig.ui.badges.length > 0) { + const hasCommandBadges = sanitizedConfig.ui?.badges?.some( + (b) => b.type === 'command' || b.command, + ); + if (hasCommandBadges) { output.push( '⚠️ This extension contributes UI Badges which will automatically execute commands in the background to update their status.', ); diff --git a/packages/cli/src/ui/hooks/useExtensionStatusBadges.ts b/packages/cli/src/ui/hooks/useExtensionStatusBadges.ts index eca1fdbac59..6d0b389328d 100644 --- a/packages/cli/src/ui/hooks/useExtensionStatusBadges.ts +++ b/packages/cli/src/ui/hooks/useExtensionStatusBadges.ts @@ -25,16 +25,26 @@ export function useExtensionStatusBadges(config: Config): StatusBadge[] { return ext.ui.badges.map( async (badgeConfig): Promise => { try { - const { stdout } = await spawnAsync( - badgeConfig.command, - badgeConfig.args ?? [], - ); - const text = stdout.toString().trim(); - if (text) { - return { - text: badgeConfig.icon ? `${badgeConfig.icon} ${text}` : text, - color: badgeConfig.color, - }; + if (badgeConfig.type === 'env' && badgeConfig.envVar) { + const val = process.env[badgeConfig.envVar]; + if (val) { + return { + text: badgeConfig.icon ? `${badgeConfig.icon} ${val}` : val, + color: badgeConfig.color, + }; + } + } else if (badgeConfig.type === 'command' && badgeConfig.command) { + const { stdout } = await spawnAsync( + badgeConfig.command, + badgeConfig.args ?? [], + ); + const text = stdout.toString().trim(); + if (text) { + return { + text: badgeConfig.icon ? `${badgeConfig.icon} ${text}` : text, + color: badgeConfig.color, + }; + } } } catch (_e) { // Ignore failing badge commands silently so they don't break the UI diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 1e8c3435caa..5d787e4488b 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -308,20 +308,28 @@ export interface BrowserAgentCustomConfig { */ export interface ExtensionUIBadge { /** - * The command to execute to get the badge text. - * Should return a plain string to stdout. + * The type of badge to render. + * 'command' executes a shell command. 'env' reads an environment variable. */ - command: string; + type: 'command' | 'env'; + /** + * The command to execute (Required if type === 'command'). + */ + command?: string; /** * Optional arguments for the command. */ args?: string[]; + /** + * The environment variable to read (Required if type === 'env'). + */ + envVar?: string; /** * Optional icon to display before the text. */ icon?: string; /** - * Optional polling interval in milliseconds. Defaults to 30000. + * Optional polling interval in milliseconds for commands. Defaults to 30000. */ intervalMs?: number; /** From 7ab08dc996880193f8297e9e9b687b3ee8b58f6c Mon Sep 17 00:00:00 2001 From: Antonio Paulino Date: Fri, 27 Feb 2026 10:55:26 +0000 Subject: [PATCH 5/6] feat(ui): add format:basename support for env badges --- docs/extensions/reference.md | 10 ++++++- .../src/ui/hooks/useExtensionStatusBadges.ts | 27 ++++++++++--------- packages/core/src/config/config.ts | 5 ++++ 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/docs/extensions/reference.md b/docs/extensions/reference.md index 9c938cf282c..0cea4391ae6 100644 --- a/docs/extensions/reference.md +++ b/docs/extensions/reference.md @@ -169,8 +169,16 @@ The manifest file defines the extension's behavior and configuration. functionality, which can be listed in the MCP server config. - `ui`: Configuration for UI contributions. - `badges`: An array of status badge definitions to display in the CLI footer. - - `command`: The executable command to run to get the badge text. + - `type`: The type of badge. Use `"command"` to execute a shell command, or + `"env"` to read an environment variable. + - `command`: The executable command to run to get the badge text (Required + if `type` is `"command"`). - `args`: An array of arguments to pass to the command. + - `envVar`: The name of the environment variable to read (Required if `type` + is `"env"`). + - `format`: Optional formatting to apply to the result. Currently supports + `"basename"` (extracts the last segment of a path, useful for + `VIRTUAL_ENV`). - `icon`: An optional icon to display before the badge text. - `intervalMs`: The interval (in milliseconds) at which to poll the command for updates. Defaults to `30000`. diff --git a/packages/cli/src/ui/hooks/useExtensionStatusBadges.ts b/packages/cli/src/ui/hooks/useExtensionStatusBadges.ts index 6d0b389328d..b7ad9284ca2 100644 --- a/packages/cli/src/ui/hooks/useExtensionStatusBadges.ts +++ b/packages/cli/src/ui/hooks/useExtensionStatusBadges.ts @@ -7,6 +7,7 @@ import { useState, useEffect, useCallback } from 'react'; import { spawnAsync, type Config } from '@google/gemini-cli-core'; import { type StatusBadge } from '../contexts/UIStateContext.js'; +import * as path from 'node:path'; export function useExtensionStatusBadges(config: Config): StatusBadge[] { const [badges, setBadges] = useState([]); @@ -25,26 +26,26 @@ export function useExtensionStatusBadges(config: Config): StatusBadge[] { return ext.ui.badges.map( async (badgeConfig): Promise => { try { + let val: string | undefined; + if (badgeConfig.type === 'env' && badgeConfig.envVar) { - const val = process.env[badgeConfig.envVar]; - if (val) { - return { - text: badgeConfig.icon ? `${badgeConfig.icon} ${val}` : val, - color: badgeConfig.color, - }; - } + val = process.env[badgeConfig.envVar]; } else if (badgeConfig.type === 'command' && badgeConfig.command) { const { stdout } = await spawnAsync( badgeConfig.command, badgeConfig.args ?? [], ); - const text = stdout.toString().trim(); - if (text) { - return { - text: badgeConfig.icon ? `${badgeConfig.icon} ${text}` : text, - color: badgeConfig.color, - }; + val = stdout.toString().trim(); + } + + if (val) { + if (badgeConfig.format === 'basename') { + val = path.basename(val); } + return { + text: badgeConfig.icon ? `${badgeConfig.icon} ${val}` : val, + color: badgeConfig.color, + }; } } catch (_e) { // Ignore failing badge commands silently so they don't break the UI diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 5d787e4488b..497513eaecb 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -324,6 +324,11 @@ export interface ExtensionUIBadge { * The environment variable to read (Required if type === 'env'). */ envVar?: string; + /** + * Optional formatting to apply to the result. + * 'basename': Returns the last portion of a path. + */ + format?: 'basename'; /** * Optional icon to display before the text. */ From af3ce3fa17c7e923181e5220cb74a39117a94dd5 Mon Sep 17 00:00:00 2001 From: Antonio Paulino Date: Fri, 27 Feb 2026 10:57:18 +0000 Subject: [PATCH 6/6] docs(extensions): update tutorial with final badge schema --- docs/extensions/writing-extensions.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/extensions/writing-extensions.md b/docs/extensions/writing-extensions.md index e91d2eb64b5..9ddbeb72419 100644 --- a/docs/extensions/writing-extensions.md +++ b/docs/extensions/writing-extensions.md @@ -295,6 +295,7 @@ surface environmental context. "ui": { "badges": [ { + "type": "command", "command": "sh", "args": ["${extensionPath}/status.sh"], "color": "accent",