{"slug": "use-oh-my-posh-for-the-github-copilot-cli-statusline", "title": "Use Oh My Posh for the GitHub Copilot CLI statusline", "summary": "GitHub Copilot CLI's experimental statusline feature can now be rendered using Oh My Posh, allowing developers to display session state—including git branch, token usage, duration, and line changes—directly in the Copilot terminal UI. A Windows and PowerShell setup uses a local script that receives Copilot session data as JSON via stdin, maps it to environment variables, and feeds it into a custom Oh My Posh theme. The configuration requires three files in `%USERPROFILE%\\.copilot\\`—a command wrapper, a PowerShell parser, and a JSON theme—along with updates to Copilot's settings and feature flags.", "body_md": "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.\n\nIf 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.\n\nThis guide shows a Windows + PowerShell setup, but the idea is portable:\n\n- Copilot calls a local script.\n- Copilot sends session state to the script as JSON on stdin.\n- The script maps useful values into environment variables.\n- Oh My Posh renders a small statusline theme using those variables.\n\nIf you want a coding agent to set this up for you, point it at this gist and give it this prompt:\n\n```\nSet up GitHub Copilot CLI's experimental statusline using Oh My Posh by following this gist.\n\nUse 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.\n\nAcceptance criteria:\n1. Create %USERPROFILE%\\.copilot\\statusline.cmd.\n2. Create %USERPROFILE%\\.copilot\\statusline.ps1.\n3. Create %USERPROFILE%\\.copilot\\statusline.omp.json.\n4. Update %USERPROFILE%\\.copilot\\settings.json with statusLine.command pointing at statusline.cmd.\n5. Enable the STATUS_LINE feature flag.\n6. Test the command by piping a sample Copilot payload into statusline.cmd.\n7. Tell me to run /restart in Copilot CLI if it is already open.\n```\n\nThe agent should preserve any existing Copilot settings, merge into `feature_flags.enabled`\n\ninstead of replacing the array, and back up `settings.json`\n\nbefore editing it.\n\nExample shape:\n\n```\n<git branch> <runtime/tooling> <context tokens> <context gauge> <duration> <line changes>\n```\n\nFor example:\n\n```\nmain +2/-1 | .NET 10.0 | 123.5k/200.0k | ######.... | 00:12:34 | +42/-8\n```\n\nThe exact appearance depends on your Oh My Posh theme and Nerd Font.\n\n- GitHub Copilot CLI with the experimental statusline feature.\n- PowerShell 7:\n`pwsh`\n\n- Oh My Posh installed and available on\n`PATH`\n\n. - A terminal font that supports Nerd Font glyphs if your theme uses icons.\n\nCheck Oh My Posh:\n\n```\noh-my-posh version\nNew-Item -ItemType Directory -Force \"$env:USERPROFILE\\.copilot\" | Out-Null\n```\n\nCreate this file:\n\n```\n%USERPROFILE%\\.copilot\\statusline.cmd\n```\n\nContents:\n\n```\n@echo off\npwsh -NoProfile -ExecutionPolicy Bypass -File \"%~dp0statusline.ps1\"\n```\n\nWhy the wrapper? In testing, Copilot's `statusLine.command`\n\nsetting was most reliable when it pointed at a command/script path. Putting `pwsh -File ...`\n\ndirectly in the JSON setting can be less reliable on Windows. The wrapper also preserves stdin, which is how Copilot sends the payload.\n\nCreate this file:\n\n```\n%USERPROFILE%\\.copilot\\statusline.ps1\n```\n\nContents:\n\n``` php\n$ErrorActionPreference = 'Stop'\n[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()\n\nfunction Format-TokenCount {\n    param([Nullable[double]]$Value)\n\n    if ($null -eq $Value) { return '?' }\n    if ($Value -ge 1000000) { return ('{0:0.0}m' -f ($Value / 1000000)) }\n    if ($Value -ge 1000) { return ('{0:0.0}k' -f ($Value / 1000)) }\n    return ([int]$Value).ToString()\n}\n\nfunction Format-Duration {\n    param([Nullable[double]]$Milliseconds)\n\n    if ($null -eq $Milliseconds -or $Milliseconds -le 0) { return '00:00:00' }\n    $duration = [TimeSpan]::FromMilliseconds($Milliseconds)\n    return '{0:00}:{1:00}:{2:00}' -f [int]$duration.TotalHours, $duration.Minutes, $duration.Seconds\n}\n\nfunction New-Gauge {\n    param([Nullable[double]]$Percent)\n\n    if ($null -eq $Percent) { return '..........' }\n    $bounded = [Math]::Max(0, [Math]::Min(100, [Math]::Round($Percent)))\n    $filled = [int][Math]::Floor($bounded / 10)\n    return ('#' * $filled) + ('.' * (10 - $filled))\n}\n\n$payload = [Console]::In.ReadToEnd()\n\ntry {\n    $json = $payload | ConvertFrom-Json\n} catch {\n    Write-Host -NoNewline 'Copilot status unavailable'\n    exit 0\n}\n\n$context = $json.context_window\n$cost = $json.cost\n\n$currentTokens = if ($null -ne $context.current_context_tokens) {\n    [double]$context.current_context_tokens\n} else {\n    $null\n}\n\n$contextLimit = if ($null -ne $context.displayed_context_limit) {\n    [double]$context.displayed_context_limit\n} else {\n    $null\n}\n\n$contextPercent = if ($null -ne $context.current_context_used_percentage) {\n    [double]$context.current_context_used_percentage\n} elseif ($null -ne $context.used_percentage) {\n    [double]$context.used_percentage\n} else {\n    $null\n}\n\n$linesAdded = if ($null -ne $cost.total_lines_added) { [int]$cost.total_lines_added } else { 0 }\n$linesRemoved = if ($null -ne $cost.total_lines_removed) { [int]$cost.total_lines_removed } else { 0 }\n\n$env:COPILOT_STATUS_CONTEXT = \"$(Format-TokenCount $currentTokens)/$(Format-TokenCount $contextLimit)\"\n$env:COPILOT_STATUS_GAUGE = New-Gauge $contextPercent\n$env:COPILOT_STATUS_DURATION = Format-Duration $cost.total_duration_ms\n$env:COPILOT_STATUS_CHANGES = if ($linesAdded -or $linesRemoved) { \"+$linesAdded/-$linesRemoved\" } else { '' }\n\n$theme = Join-Path $PSScriptRoot 'statusline.omp.json'\n$cwd = if ($json.cwd) { [string]$json.cwd } else { (Get-Location).Path }\n\ntry {\n    $output = & oh-my-posh print primary --config $theme --pwd $cwd --force --escape=false 2>$null\n    if ([string]::IsNullOrWhiteSpace($output)) {\n        throw 'Oh My Posh returned no output.'\n    }\n\n    Write-Host -NoNewline $output.TrimEnd()\n} catch {\n    $changes = if ($env:COPILOT_STATUS_CHANGES) { \" $($env:COPILOT_STATUS_CHANGES)\" } else { '' }\n    Write-Host -NoNewline \"ctx $($env:COPILOT_STATUS_CONTEXT) $($env:COPILOT_STATUS_GAUGE) time $($env:COPILOT_STATUS_DURATION)$changes\"\n}\n```\n\nCreate this file:\n\n```\n%USERPROFILE%\\.copilot\\statusline.omp.json\n```\n\nContents:\n\n```\n{\n  \"$schema\": \"https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json\",\n  \"version\": 3,\n  \"final_space\": false,\n  \"blocks\": [\n    {\n      \"type\": \"prompt\",\n      \"alignment\": \"left\",\n      \"segments\": [\n        {\n          \"type\": \"git\",\n          \"style\": \"diamond\",\n          \"leading_diamond\": \"<\",\n          \"trailing_diamond\": \">\",\n          \"foreground\": \"#193549\",\n          \"background\": \"#FFA500\",\n          \"template\": \" {{ .HEAD }}{{ if .Working.Changed }} +{{ .Working.String }}{{ end }} \",\n          \"properties\": {\n            \"fetch_status\": true,\n            \"fetch_upstream_icon\": true\n          }\n        },\n        {\n          \"type\": \"dotnet\",\n          \"style\": \"powerline\",\n          \"powerline_symbol\": \">\",\n          \"foreground\": \"#ffffff\",\n          \"background\": \"#6CA35E\",\n          \"template\": \" .NET {{ if .Unsupported }}!{{ else }}{{ .Full }}{{ end }} \",\n          \"properties\": {\n            \"fetch_version\": true\n          }\n        },\n        {\n          \"type\": \"text\",\n          \"style\": \"powerline\",\n          \"powerline_symbol\": \">\",\n          \"foreground\": \"#193549\",\n          \"background\": \"#FFA500\",\n          \"template\": \" ctx {{ .Env.COPILOT_STATUS_CONTEXT }} \"\n        },\n        {\n          \"type\": \"text\",\n          \"style\": \"powerline\",\n          \"powerline_symbol\": \">\",\n          \"foreground\": \"#ffffff\",\n          \"background\": \"#6CA35E\",\n          \"template\": \" {{ .Env.COPILOT_STATUS_GAUGE }} \"\n        },\n        {\n          \"type\": \"text\",\n          \"style\": \"powerline\",\n          \"powerline_symbol\": \">\",\n          \"foreground\": \"#ffffff\",\n          \"background\": \"#0184bc\",\n          \"template\": \" {{ .Env.COPILOT_STATUS_DURATION }} \"\n        },\n        {\n          \"type\": \"text\",\n          \"style\": \"diamond\",\n          \"trailing_diamond\": \">\",\n          \"foreground\": \"#ffffff\",\n          \"background\": \"#a1108c\",\n          \"template\": \"{{ if .Env.COPILOT_STATUS_CHANGES }} {{ .Env.COPILOT_STATUS_CHANGES }} {{ end }}\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nThis 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.\n\nFor example:\n\n```\n\"leading_diamond\": \"\\ue0b6\",\n\"trailing_diamond\": \"\\ue0b0\",\n\"powerline_symbol\": \"\\ue0b0\"\n```\n\nEdit:\n\n```\n%USERPROFILE%\\.copilot\\settings.json\n```\n\nAdd or merge this:\n\n```\n{\n  \"statusLine\": {\n    \"type\": \"command\",\n    \"command\": \"C:\\\\Users\\\\YOURUSER\\\\.copilot\\\\statusline.cmd\",\n    \"padding\": 1\n  },\n  \"feature_flags\": {\n    \"enabled\": [\n      \"STATUS_LINE\"\n    ]\n  },\n  \"experimental\": true\n}\n```\n\nReplace `YOURUSER`\n\nwith your Windows username.\n\nIf you already have `feature_flags.enabled`\n\n, add `STATUS_LINE`\n\nto the existing array instead of replacing it.\n\nRestart Copilot CLI:\n\n```\n/restart\n```\n\nCreate a sample payload:\n\n``` php\n$sample = @'\n{\n  \"cwd\": \"C:\\\\src\\\\my-repo\",\n  \"context_window\": {\n    \"current_context_tokens\": 123456,\n    \"displayed_context_limit\": 200000,\n    \"current_context_used_percentage\": 61.7\n  },\n  \"cost\": {\n    \"total_duration_ms\": 754000,\n    \"total_lines_added\": 42,\n    \"total_lines_removed\": 8\n  }\n}\n'@\n\n$sample | & \"$env:USERPROFILE\\.copilot\\statusline.cmd\"\n```\n\nIf that renders, Copilot should be able to render it too.\n\nCopilot sends JSON to your command over stdin. The script reads that JSON:\n\n``` php\n$payload = [Console]::In.ReadToEnd()\n$json = $payload | ConvertFrom-Json\n```\n\nThen it turns useful fields into environment variables:\n\n```\n$env:COPILOT_STATUS_CONTEXT = \"123.5k/200.0k\"\n$env:COPILOT_STATUS_GAUGE = \"######....\"\n$env:COPILOT_STATUS_DURATION = \"00:12:34\"\n$env:COPILOT_STATUS_CHANGES = \"+42/-8\"\n```\n\nOh My Posh templates can read those values:\n\n```\n\"template\": \" ctx {{ .Env.COPILOT_STATUS_CONTEXT }} \"\n```\n\nFinally, the script asks Oh My Posh to render the mini theme:\n\n```\noh-my-posh print primary --config $theme --pwd $cwd --force --escape=false\n```\n\nThe `--pwd`\n\nvalue matters. It lets normal Oh My Posh segments, such as `git`\n\n, render for the repository Copilot is working in.\n\nDo 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.\n\nInstead:\n\n- Copy two or three favorite segments from your existing theme.\n- Keep any expensive or network-backed segments out at first.\n- Add Copilot-specific text segments using\n`.Env.COPILOT_STATUS_*`\n\n. - Test with the sample payload.\n- Add more segments only if the command stays fast.\n\nGood statusline segments:\n\n`git`\n\n- language/runtime version segments, such as\n`dotnet`\n\n,`node`\n\n,`python`\n\n, or`go`\n\n- simple text segments using Copilot environment variables\n- short path or folder segments\n\nSegments to be careful with:\n\n- anything that makes network calls\n- anything that scans large directories\n- anything that can prompt for credentials\n\nThe statusline command needs to finish quickly. If it times out, Copilot may show no statusline at all.\n\nCheck:\n\n`STATUS_LINE`\n\nis enabled.`statusLine.command`\n\npoints to the`.cmd`\n\nwrapper.- You restarted Copilot CLI after changing settings.\n- The command works with the sample payload.\n\nUse the wrapper path in `settings.json`\n\n:\n\n```\n\"command\": \"C:\\\\Users\\\\YOURUSER\\\\.copilot\\\\statusline.cmd\"\n```\n\nAvoid putting a full command with arguments directly in the setting.\n\nFor this use case, call:\n\n```\noh-my-posh print primary --config $theme --pwd $cwd --force --escape=false\n```\n\nAvoid adding `--shell pwsh`\n\nunless you have verified it still uses the intended statusline theme.\n\nUse a Nerd Font in your terminal profile, or keep the theme ASCII-only.\n\nRemove slow segments and retest. Start with only text segments. Then add `git`\n\n. Then add runtime segments. Add network-backed segments last, with strict timeouts.\n\nTreat 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.\n\nIf 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.\n\nThe statusline feature is just a small command contract:\n\n``` php\nCopilot JSON on stdin -> your script -> one line of text on stdout\n```\n\nOh 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.", "url": "https://wpnews.pro/news/use-oh-my-posh-for-the-github-copilot-cli-statusline", "canonical_source": "https://gist.github.com/shanselman/9623ac74888a07ba82f63f5310fda11b", "published_at": "2026-05-05 23:32:22+00:00", "updated_at": "2026-05-30 01:42:37.208985+00:00", "lang": "en", "topics": ["ai-tools", "ai-products", "ai-agents", "generative-ai", "ai-infrastructure"], "entities": ["GitHub Copilot CLI", "Oh My Posh", "Windows", "PowerShell"], "alternates": {"html": "https://wpnews.pro/news/use-oh-my-posh-for-the-github-copilot-cli-statusline", "markdown": "https://wpnews.pro/news/use-oh-my-posh-for-the-github-copilot-cli-statusline.md", "text": "https://wpnews.pro/news/use-oh-my-posh-for-the-github-copilot-cli-statusline.txt", "jsonld": "https://wpnews.pro/news/use-oh-my-posh-for-the-github-copilot-cli-statusline.jsonld"}}