Last active
May 22, 2026 13:28
-
-
Save dfevre/250bc8bc28149a2c125b35434347f235 to your computer and use it in GitHub Desktop.
GitHub Copilot CLI custom statusline — Tokyo Moon colours, Nerd Fonts, git status, session name, model + context usage
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # GitHub Copilot CLI — Custom Statusline | |
| # | |
| # A Tokyo Moon-themed powerline statusline for GitHub Copilot CLI. | |
| # | |
| # Layout: | |
| # [cwd] > [branch +x !x ?x] session name ····· +lines -lines [model [ctx%]] | |
| # | |
| # Dependencies: | |
| # - Nerd Fonts (https://www.nerdfonts.com/) — required for powerline glyphs and icons | |
| # - Tokyo Moon colour palette — colours are hardcoded to match the theme | |
| # | |
| # Setup: | |
| # 1. Save this file to ~/.copilot/statusline.sh | |
| # 2. chmod +x ~/.copilot/statusline.sh | |
| # 3. Add to ~/.copilot/settings.json: | |
| # | |
| # "statusLine": { | |
| # "type": "command", | |
| # "command": "~/.copilot/statusline.sh" | |
| # } | |
| # | |
| input=$(cat) | |
| COLUMNS=$(stty size < /dev/tty 2>/dev/null | awk '{print $2}') | |
| echo "$input" | COLUMNS="$COLUMNS" python3 -c " | |
| import sys, json, subprocess, os, re, shutil | |
| d = json.load(sys.stdin) | |
| name = d.get('session_name', '') | |
| cwd = d.get('cwd', os.getcwd()) | |
| # Tokyo Moon colours | |
| blue_bg = '48;2;130;170;255' | |
| blue_fg = '38;2;130;170;255' | |
| dark_bg = '48;2;59;66;97' | |
| dark_fg = '38;2;59;66;97' | |
| text_dark = '38;2;30;32;48' | |
| text_light = '38;2;200;211;245' | |
| def strip_ansi(s): | |
| return re.sub(r'\033\[[^m]*m', '', s) | |
| def visible_len(s): | |
| return len(re.sub(r'[\uE000-\uF8FF]', ' ', strip_ansi(s))) | |
| # Git info | |
| git_info = None | |
| try: | |
| branch = subprocess.check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], cwd=cwd, stderr=subprocess.DEVNULL).decode().strip() | |
| git_info = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'], cwd=cwd, stderr=subprocess.DEVNULL).decode().strip() if branch == 'HEAD' else branch | |
| ls = subprocess.check_output(['git', 'status', '--porcelain'], cwd=cwd, stderr=subprocess.DEVNULL).decode().splitlines() | |
| staged_count = sum(1 for l in ls if len(l) > 0 and l[0] in 'MADRC') | |
| modified_count = sum(1 for l in ls if len(l) > 1 and l[1] in 'MD') | |
| untracked_count = sum(1 for l in ls if l.startswith('??')) | |
| ind = '' | |
| if staged_count: ind += f' \u271a{staged_count}' | |
| if modified_count: ind += f' !{modified_count}' | |
| if untracked_count: ind += f' ?{untracked_count}' | |
| try: | |
| ahead, behind = subprocess.check_output( | |
| ['git', 'rev-list', '--count', '--left-right', '@{upstream}...HEAD'], | |
| cwd=cwd, stderr=subprocess.DEVNULL | |
| ).decode().split() | |
| if int(behind): ind += f' \u2191{behind}' | |
| if int(ahead): ind += f' \u2193{ahead}' | |
| except Exception: | |
| pass | |
| git_info += ind | |
| except Exception: | |
| pass | |
| home = os.path.expanduser('~') | |
| display_cwd = cwd.replace(home, '~') if cwd.startswith(home) else cwd | |
| model = d.get('model', {}).get('display_name', '') | |
| ctx_used = d.get('context_window', {}).get('current_context_used_percentage', '') | |
| model_label = f'{model} [{ctx_used}%]' if model and ctx_used != '' else model | |
| term_width = int(os.environ.get('COLUMNS', 0)) or shutil.get_terminal_size((80, 24)).columns | |
| try: | |
| import fcntl, termios, struct | |
| for fd in (0, 1, 2): | |
| try: | |
| hw = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, b' ')) | |
| if hw[1] > 0: | |
| term_width = hw[1] | |
| break | |
| except Exception: | |
| pass | |
| except Exception: | |
| pass | |
| # Left: [blue: cwd] -> [dark: git] -> [session name, no bg] | |
| left = f'\033[{blue_bg}m\033[{text_dark}m \uF07C {display_cwd} ' | |
| if git_info: | |
| left += f'\033[{dark_bg}m\033[{blue_fg}m\uE0B0\033[{blue_fg}m \uE0A0 {git_info} \033[0m\033[{dark_fg}m\uE0B0' | |
| else: | |
| left += f'\033[{dark_bg}m\033[{blue_fg}m\uE0B0\033[0m\033[{dark_fg}m\uE0B0' | |
| if name: | |
| left += f'\033[0m\033[{text_light}m \uF02B {name} ' | |
| lines_added = d.get('cost', {}).get('total_lines_added', 0) | |
| lines_removed = d.get('cost', {}).get('total_lines_removed', 0) | |
| lines_text = f' +{lines_added} -{lines_removed} ' if (lines_added or lines_removed) else '' | |
| # Right: [dark: lines +/-] [blue: model [ctx%]] | |
| model_text = f' \uF135 {model_label} ' if model_label else '' | |
| lines_seg = f'\033[{dark_fg}m\uE0B2\033[{dark_bg}m\033[{blue_fg}m{lines_text}' if lines_text else '' | |
| model_seg = f'\033[{blue_fg}m\uE0B2\033[{blue_bg}m\033[{text_dark}m{model_text}\033[0m' if model_text else '' | |
| right = lines_seg + model_seg | |
| right_len = (visible_len(lines_text) + 1 if lines_text else 0) + (visible_len(model_text) + 1 if model_text else 0) | |
| left_len = visible_len(left) | |
| padding = max(0, term_width - left_len - right_len) | |
| print(left + ' ' * padding + right) | |
| " |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Looks like this -