# Use Oh My Posh for the GitHub Copilot CLI statusline

> Source: <https://gist.github.com/shanselman/9623ac74888a07ba82f63f5310fda11b>
> Published: 2026-05-05 23:32:22+00:00

GitHub Copilot CLI has an experimental statusline feature that can run a local command and render the command's output at the bottom of the Copilot terminal UI.

If you already use Oh My Posh, you can use the same engine, colors, and segments to render a Copilot-aware statusline in a few minutes.

This guide shows a Windows + PowerShell setup, but the idea is portable:

- Copilot calls a local script.
- Copilot sends session state to the script as JSON on stdin.
- The script maps useful values into environment variables.
- Oh My Posh renders a small statusline theme using those variables.

If you want a coding agent to set this up for you, point it at this gist and give it this prompt:

```
Set up GitHub Copilot CLI's experimental statusline using Oh My Posh by following this gist.

Use my existing Oh My Posh theme as inspiration, but create a small statusline-specific theme instead of blindly reusing my full shell prompt. Keep it fast. Do not include secrets, tokens, private URLs, personal data, customer data, or anything that should not appear in screenshots.

Acceptance criteria:
1. Create %USERPROFILE%\.copilot\statusline.cmd.
2. Create %USERPROFILE%\.copilot\statusline.ps1.
3. Create %USERPROFILE%\.copilot\statusline.omp.json.
4. Update %USERPROFILE%\.copilot\settings.json with statusLine.command pointing at statusline.cmd.
5. Enable the STATUS_LINE feature flag.
6. Test the command by piping a sample Copilot payload into statusline.cmd.
7. Tell me to run /restart in Copilot CLI if it is already open.
```

The agent should preserve any existing Copilot settings, merge into `feature_flags.enabled`

instead of replacing the array, and back up `settings.json`

before editing it.

Example shape:

```
<git branch> <runtime/tooling> <context tokens> <context gauge> <duration> <line changes>
```

For example:

```
main +2/-1 | .NET 10.0 | 123.5k/200.0k | ######.... | 00:12:34 | +42/-8
```

The exact appearance depends on your Oh My Posh theme and Nerd Font.

- GitHub Copilot CLI with the experimental statusline feature.
- PowerShell 7:
`pwsh`

- Oh My Posh installed and available on
`PATH`

. - A terminal font that supports Nerd Font glyphs if your theme uses icons.

Check Oh My Posh:

```
oh-my-posh version
New-Item -ItemType Directory -Force "$env:USERPROFILE\.copilot" | Out-Null
```

Create this file:

```
%USERPROFILE%\.copilot\statusline.cmd
```

Contents:

```
@echo off
pwsh -NoProfile -ExecutionPolicy Bypass -File "%~dp0statusline.ps1"
```

Why the wrapper? In testing, Copilot's `statusLine.command`

setting was most reliable when it pointed at a command/script path. Putting `pwsh -File ...`

directly in the JSON setting can be less reliable on Windows. The wrapper also preserves stdin, which is how Copilot sends the payload.

Create this file:

```
%USERPROFILE%\.copilot\statusline.ps1
```

Contents:

``` php
$ErrorActionPreference = 'Stop'
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()

function Format-TokenCount {
    param([Nullable[double]]$Value)

    if ($null -eq $Value) { return '?' }
    if ($Value -ge 1000000) { return ('{0:0.0}m' -f ($Value / 1000000)) }
    if ($Value -ge 1000) { return ('{0:0.0}k' -f ($Value / 1000)) }
    return ([int]$Value).ToString()
}

function Format-Duration {
    param([Nullable[double]]$Milliseconds)

    if ($null -eq $Milliseconds -or $Milliseconds -le 0) { return '00:00:00' }
    $duration = [TimeSpan]::FromMilliseconds($Milliseconds)
    return '{0:00}:{1:00}:{2:00}' -f [int]$duration.TotalHours, $duration.Minutes, $duration.Seconds
}

function New-Gauge {
    param([Nullable[double]]$Percent)

    if ($null -eq $Percent) { return '..........' }
    $bounded = [Math]::Max(0, [Math]::Min(100, [Math]::Round($Percent)))
    $filled = [int][Math]::Floor($bounded / 10)
    return ('#' * $filled) + ('.' * (10 - $filled))
}

$payload = [Console]::In.ReadToEnd()

try {
    $json = $payload | ConvertFrom-Json
} catch {
    Write-Host -NoNewline 'Copilot status unavailable'
    exit 0
}

$context = $json.context_window
$cost = $json.cost

$currentTokens = if ($null -ne $context.current_context_tokens) {
    [double]$context.current_context_tokens
} else {
    $null
}

$contextLimit = if ($null -ne $context.displayed_context_limit) {
    [double]$context.displayed_context_limit
} else {
    $null
}

$contextPercent = if ($null -ne $context.current_context_used_percentage) {
    [double]$context.current_context_used_percentage
} elseif ($null -ne $context.used_percentage) {
    [double]$context.used_percentage
} else {
    $null
}

$linesAdded = if ($null -ne $cost.total_lines_added) { [int]$cost.total_lines_added } else { 0 }
$linesRemoved = if ($null -ne $cost.total_lines_removed) { [int]$cost.total_lines_removed } else { 0 }

$env:COPILOT_STATUS_CONTEXT = "$(Format-TokenCount $currentTokens)/$(Format-TokenCount $contextLimit)"
$env:COPILOT_STATUS_GAUGE = New-Gauge $contextPercent
$env:COPILOT_STATUS_DURATION = Format-Duration $cost.total_duration_ms
$env:COPILOT_STATUS_CHANGES = if ($linesAdded -or $linesRemoved) { "+$linesAdded/-$linesRemoved" } else { '' }

$theme = Join-Path $PSScriptRoot 'statusline.omp.json'
$cwd = if ($json.cwd) { [string]$json.cwd } else { (Get-Location).Path }

try {
    $output = & oh-my-posh print primary --config $theme --pwd $cwd --force --escape=false 2>$null
    if ([string]::IsNullOrWhiteSpace($output)) {
        throw 'Oh My Posh returned no output.'
    }

    Write-Host -NoNewline $output.TrimEnd()
} catch {
    $changes = if ($env:COPILOT_STATUS_CHANGES) { " $($env:COPILOT_STATUS_CHANGES)" } else { '' }
    Write-Host -NoNewline "ctx $($env:COPILOT_STATUS_CONTEXT) $($env:COPILOT_STATUS_GAUGE) time $($env:COPILOT_STATUS_DURATION)$changes"
}
```

Create this file:

```
%USERPROFILE%\.copilot\statusline.omp.json
```

Contents:

```
{
  "$schema": "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json",
  "version": 3,
  "final_space": false,
  "blocks": [
    {
      "type": "prompt",
      "alignment": "left",
      "segments": [
        {
          "type": "git",
          "style": "diamond",
          "leading_diamond": "<",
          "trailing_diamond": ">",
          "foreground": "#193549",
          "background": "#FFA500",
          "template": " {{ .HEAD }}{{ if .Working.Changed }} +{{ .Working.String }}{{ end }} ",
          "properties": {
            "fetch_status": true,
            "fetch_upstream_icon": true
          }
        },
        {
          "type": "dotnet",
          "style": "powerline",
          "powerline_symbol": ">",
          "foreground": "#ffffff",
          "background": "#6CA35E",
          "template": " .NET {{ if .Unsupported }}!{{ else }}{{ .Full }}{{ end }} ",
          "properties": {
            "fetch_version": true
          }
        },
        {
          "type": "text",
          "style": "powerline",
          "powerline_symbol": ">",
          "foreground": "#193549",
          "background": "#FFA500",
          "template": " ctx {{ .Env.COPILOT_STATUS_CONTEXT }} "
        },
        {
          "type": "text",
          "style": "powerline",
          "powerline_symbol": ">",
          "foreground": "#ffffff",
          "background": "#6CA35E",
          "template": " {{ .Env.COPILOT_STATUS_GAUGE }} "
        },
        {
          "type": "text",
          "style": "powerline",
          "powerline_symbol": ">",
          "foreground": "#ffffff",
          "background": "#0184bc",
          "template": " {{ .Env.COPILOT_STATUS_DURATION }} "
        },
        {
          "type": "text",
          "style": "diamond",
          "trailing_diamond": ">",
          "foreground": "#ffffff",
          "background": "#a1108c",
          "template": "{{ if .Env.COPILOT_STATUS_CHANGES }} {{ .Env.COPILOT_STATUS_CHANGES }} {{ end }}"
        }
      ]
    }
  ]
}
```

This intentionally uses plain ASCII separators so it works everywhere. If you already use a Nerd Font, replace the diamonds and separators with your favorite powerline glyphs.

For example:

```
"leading_diamond": "\ue0b6",
"trailing_diamond": "\ue0b0",
"powerline_symbol": "\ue0b0"
```

Edit:

```
%USERPROFILE%\.copilot\settings.json
```

Add or merge this:

```
{
  "statusLine": {
    "type": "command",
    "command": "C:\\Users\\YOURUSER\\.copilot\\statusline.cmd",
    "padding": 1
  },
  "feature_flags": {
    "enabled": [
      "STATUS_LINE"
    ]
  },
  "experimental": true
}
```

Replace `YOURUSER`

with your Windows username.

If you already have `feature_flags.enabled`

, add `STATUS_LINE`

to the existing array instead of replacing it.

Restart Copilot CLI:

```
/restart
```

Create a sample payload:

``` php
$sample = @'
{
  "cwd": "C:\\src\\my-repo",
  "context_window": {
    "current_context_tokens": 123456,
    "displayed_context_limit": 200000,
    "current_context_used_percentage": 61.7
  },
  "cost": {
    "total_duration_ms": 754000,
    "total_lines_added": 42,
    "total_lines_removed": 8
  }
}
'@

$sample | & "$env:USERPROFILE\.copilot\statusline.cmd"
```

If that renders, Copilot should be able to render it too.

Copilot sends JSON to your command over stdin. The script reads that JSON:

``` php
$payload = [Console]::In.ReadToEnd()
$json = $payload | ConvertFrom-Json
```

Then it turns useful fields into environment variables:

```
$env:COPILOT_STATUS_CONTEXT = "123.5k/200.0k"
$env:COPILOT_STATUS_GAUGE = "######...."
$env:COPILOT_STATUS_DURATION = "00:12:34"
$env:COPILOT_STATUS_CHANGES = "+42/-8"
```

Oh My Posh templates can read those values:

```
"template": " ctx {{ .Env.COPILOT_STATUS_CONTEXT }} "
```

Finally, the script asks Oh My Posh to render the mini theme:

```
oh-my-posh print primary --config $theme --pwd $cwd --force --escape=false
```

The `--pwd`

value matters. It lets normal Oh My Posh segments, such as `git`

, render for the repository Copilot is working in.

Do not point the statusline directly at your full interactive shell theme first. A full prompt theme can be too wide or too slow for a statusline.

Instead:

- Copy two or three favorite segments from your existing theme.
- Keep any expensive or network-backed segments out at first.
- Add Copilot-specific text segments using
`.Env.COPILOT_STATUS_*`

. - Test with the sample payload.
- Add more segments only if the command stays fast.

Good statusline segments:

`git`

- language/runtime version segments, such as
`dotnet`

,`node`

,`python`

, or`go`

- simple text segments using Copilot environment variables
- short path or folder segments

Segments to be careful with:

- anything that makes network calls
- anything that scans large directories
- anything that can prompt for credentials

The statusline command needs to finish quickly. If it times out, Copilot may show no statusline at all.

Check:

`STATUS_LINE`

is enabled.`statusLine.command`

points to the`.cmd`

wrapper.- You restarted Copilot CLI after changing settings.
- The command works with the sample payload.

Use the wrapper path in `settings.json`

:

```
"command": "C:\\Users\\YOURUSER\\.copilot\\statusline.cmd"
```

Avoid putting a full command with arguments directly in the setting.

For this use case, call:

```
oh-my-posh print primary --config $theme --pwd $cwd --force --escape=false
```

Avoid adding `--shell pwsh`

unless you have verified it still uses the intended statusline theme.

Use a Nerd Font in your terminal profile, or keep the theme ASCII-only.

Remove slow segments and retest. Start with only text segments. Then add `git`

. Then add runtime segments. Add network-backed segments last, with strict timeouts.

Treat your statusline like anything else printed in a terminal: it can appear in screenshots, recordings, livestreams, and logs. Do not render secrets, tokens, private URLs, customer names, personal data, or other sensitive values.

If a segment needs a private URL or token, keep that value in a local config file or environment variable and do not commit it to a repo or gist.

The statusline feature is just a small command contract:

``` php
Copilot JSON on stdin -> your script -> one line of text on stdout
```

Oh My Posh is a great renderer for that one line because it already knows how to draw prompt segments, colors, icons, and repository-aware context.
