0

Tùy chỉnh status line trong Claude Code

Tùy chỉnh status line trong Claude Code

Một phiên làm việc dài với Claude Code rất dễ khiến bạn "mù thông tin". Bạn không theo dõi được cửa sổ ngữ cảnh (context window) đã đầy tới đâu, đã dùng hết bao nhiêu phần giới hạn sử dụng, đang chạy mô hình nào, và — nếu bạn giữ nhiều hơn một tài khoản — tài khoản nào đang hoạt động. Rồi bạn đâm vào tường giữa chừng: hết ngữ cảnh, bị khóa vì chạm giới hạn, hoặc chợt nhận ra mình đã làm việc nhầm tài khoản.

Claude Code có sẵn cách xử lý: một status line tùy chỉnh — một dải chữ ở đáy màn hình mà bạn toàn quyền kiểm soát. Bài này nói về những gì đáng đưa lên đó, và cách tôi dựng cái của mình.

Vấn đề: mù thông tin

Mặc định, status line cho bạn biết rất ít. Với một tác vụ nhanh thì ổn. Với một phiên hai tiếng thì không, vì những thứ cắn bạn lại chính là những thứ bạn không thấy được:

  • Cửa sổ ngữ cảnh. Nó đầy dần khi bạn làm việc. Khi đầy, Claude bắt đầu quên phần đầu của cuộc trò chuyện. Nếu không có gì cho bạn thấy nó đang ở mức 85%, thì dấu hiệu rắc rối đầu tiên chính là rắc rối.
  • Giới hạn sử dụng. Trên gói Pro và Max có một hạn mức 5 tiếng và một hạn mức theo tuần. Không có gì cảnh báo khi bạn tiến sát — bạn chỉ đơn giản bị cắt. Biết mình đang ở 90% của cửa sổ 5 tiếng, và khi nào nó reset, sẽ thay đổi cách bạn phân bổ công việc.
  • Mô hình và mức effort. Lệnh /model vừa nãy có thật sự áp dụng không? Tôi đang ở mức effort cao đốt token nhanh, hay mức thấp? Rất dễ quên, mà đoán sai thì tốn kém.
  • Tài khoản nào. Nếu bạn chạy một tài khoản cá nhân và một tài khoản công việc trên cùng một máy, hai terminal trông y hệt nhau. Chạy một tác vụ công việc trên gói cá nhân là lỗi thật sự mà không có tín hiệu trực quan nào ngăn lại.
  • Nó có đang chạy không? Bạn khởi động một tác vụ, chuyển tab đi đọc gì đó, rồi quay lại — Claude vẫn đang suy nghĩ, hay đã chờ bạn năm phút rồi?

Mặc định thì chẳng thứ nào trong số này hiện ra. Status line là nơi bạn làm cho chúng hiện ra.

Status line hoạt động như thế nào

Cơ chế đơn giản một cách dễ chịu. Trong settings.json bạn trỏ Claude Code tới một lệnh:

"statusLine": {
  "type": "command",
  "command": "~/.claude/statusline.sh",
  "refreshInterval": 2
}

Chỉ vậy thôi. Cứ vài giây, Claude Code chạy script của bạn. Nó bơm một khối JSON vào standard input của script mô tả phiên hiện tại, và bất cứ thứ gì script của bạn in ra sẽ nằm trên status line. Không API, không plugin — chỉ là một script đọc JSON rồi in ra một chuỗi.

Khối JSON mang theo những thứ hữu ích. Dạo nhanh qua các trường tôi dùng, lấy thẳng từ script của mình:

input=$(cat)   # JSON đến qua stdin

model=$(echo "$input" | jq -r '.model.display_name')
task=$(echo  "$input" | jq -r '.session_name')
pct=$(echo   "$input" | jq -r '.context_window.used_percentage')
dir=$(echo   "$input" | jq -r '.workspace.current_dir // .cwd')
sess=$(echo  "$input" | jq -r '.rate_limits.five_hour.used_percentage')
week=$(echo  "$input" | jq -r '.rate_limits.seven_day.used_percentage')
effort=$(echo "$input" | jq -r '.effort.level')

Vậy là phần trăm ngữ cảnh, cả hai giới hạn sử dụng (kèm thời điểm reset), mô hình, mức effort, thư mục làm việc, và tên tác vụ của phiên — tất cả được trao cho bạn miễn phí. Việc của bạn chỉ là bày biện chúng cho gọn gàng.

Những gì đáng đưa lên

Đây là dòng mà script của tôi dựng nên, từ trái sang phải, và vì sao mỗi phần xứng đáng có chỗ.

Phần trăm ngữ cảnh — kèm một màu sắc hét lên trước khi quá muộn. Một con số trơ rất dễ bị lờ đi; một màu sắc thì không. Tôi tô nó xanh lá, rồi vàng, rồi đỏ khi nó đầy dần:

p=$(printf '%.0f' "$pct")
if   [ "$p" -ge 80 ]; then c=$RED
elif [ "$p" -ge 50 ]; then c=$YELLOW
else                       c=$GREEN
fi
ctx_seg="▪${c}${p}%"

Giờ "ngữ cảnh sắp đầy" là một con số đỏ ở khóe mắt tôi, không phải một bất ngờ. Khi nó chuyển đỏ, tôi biết đã đến lúc gói lại hoặc /compact.

Giới hạn sử dụng — cả phần trăm lẫn thời điểm reset. Đây là phần cứu tôi nhiều nhất. Cả cửa sổ 5 tiếng lẫn 7 ngày đều cho thấy tôi đã dùng bao nhiêu và khi nào chúng reset:

s=$(printf '%.0f' "$sess")
reset_txt="($(date -d @"$sess_reset" +'%H:%M'))"   # ví dụ (16:30)
sess_seg="▪${sc}${s}%${reset_txt}"

Ở mức 88% với reset lúc 16:30, tôi có thể quyết định chậm lại hoặc cứ chờ hết — thay vì bị một cú khóa bất ngờ. Cửa sổ theo tuần hiển thị thêm thứ trong tuần, vì reset của nó cách xa vài ngày chứ không phải vài giờ.

Mô hình và effort. Tên mô hình, với hậu tố ồn ào (1M context) được rút gọn thành [1M]. Mức effort được tô theo cường độ, để một chữ max đỏ nổi bật hơn hẳn một chữ low xám:

case "$effort" in
  low)       ec=$GRAY   ;;
  medium)    ec=$GREEN  ;;
  high)      ec=$YELLOW ;;
  xhigh|max) ec=$RED    ;;
esac

Đường dẫn và tác vụ. Thư mục hiện tại với $HOME được thu gọn thành ~, và tên của phiên (cắt bớt để một tiêu đề dài không nuốt trọn cả dòng).

Kết quả là một dòng dày đặc, liếc một cái là thấy: trạng thái chạy, tác vụ, đường dẫn, mô hình, effort, tổ chức, ngữ cảnh, phiên, tuần. Màu sắc gánh phần nặng — tôi không đọc status line cho lắm, mà chỉ để ý khi một phần của nó chuyển đỏ.

Dữ liệu không có trong stdin

Có hai thứ tôi muốn nhưng không nằm trong JSON. Cả hai đều giải quyết được, và chúng là phần thú vị nhất của thiết lập này.

"Nó có đang chạy không?" — theo dõi bằng hook

Khối stdin mô tả phiên, nhưng nó không nói Claude có đang thật sự làm việc lúc này hay không. Nên tôi tự theo dõi điều đó bằng hai hook trong settings.json. Khi tôi gửi một prompt, một hook UserPromptSubmit ghi "running" vào một file tạm được đặt tên theo session id; khi Claude xong, một hook Stop ghi "idle":

"UserPromptSubmit": [{ "hooks": [{ "type": "command",
  "command": "sid=$(cat | jq -r '.session_id'); echo running > /tmp/claude-sl-$sid.state" }]}],
"Stop": [{ "hooks": [{ "type": "command",
  "command": "sid=$(cat | jq -r '.session_id'); echo idle > /tmp/claude-sl-$sid.state" }]}]

Rồi status line chỉ việc đọc file đó:

state="idle"
[ -f "/tmp/claude-sl-$sid.state" ] && state=$(cat "/tmp/claude-sl-$sid.state")

Một chữ ● running xanh lá hoặc ✓ idle xám ở đầu dòng. Giờ liếc một cái là biết đến lượt tôi hay lượt Claude — kể cả từ bên kia phòng. Bài học có thể khái quát: nếu dữ liệu không có trong stdin, một hook có thể cất nó ở đâu đó cho script của bạn đọc.

Tài khoản nào — đọc từ file cấu hình tài khoản

Tên tổ chức cũng không có trong stdin, nhưng Claude Code lưu nó trong một file cấu hình. Tôi đọc từ đó — và, quan trọng là, tôi tôn trọng CLAUDE_CONFIG_DIR để một tài khoản thứ hai trỏ về cấu hình của chính nó thay vì cái mặc định:

config_dir="${CLAUDE_CONFIG_DIR:-$HOME}"
org=$(jq -r '.oauthAccount.organizationName' "$config_dir/.claude.json")

Chính một dòng đó làm cho status line trung thực khi bạn chạy nhiều tài khoản: terminal cá nhân hiện tổ chức cá nhân, terminal công việc hiện tổ chức công việc. Không còn phải đoán mình đang ở đăng nhập nào.

Một lưu ý về refresh và tốc độ

refreshInterval quyết định script của bạn chạy thường xuyên đến đâu — tôi dùng 2 giây. Vì nó chạy thường xuyên như vậy, hãy giữ script nhanh. Cái của tôi chỉ định dạng chuỗi và đọc vài lần bằng jq trên dữ liệu nó đã có sẵn; nó không gọi mạng. Một status line chậm khiến cả terminal thấy ì ạch, nên hãy kìm ý muốn gọi bất cứ thứ gì nặng trong đó.

Kết lại

Status line mặc định giả định bạn không cần biết nhiều. Trong một phiên dài, bạn cần — ngữ cảnh đầy tới đâu, bạn cách giới hạn sử dụng bao xa, đang lái mô hình và tài khoản nào, và có đang tới lượt bạn gõ hay không. Tất cả chỉ cách bạn một script và một khối JSON.

Bắt đầu nhỏ thôi: in phần trăm ngữ cảnh với một ngưỡng màu. Chỉ riêng cái đó đã đáng đồng tiền ngay lần đầu nó chuyển đỏ trước khi bạn đâm vào tường. Rồi thêm những phần hợp với cách bạn làm việc — giới hạn sử dụng nếu bạn hay sống sát hạn mức, tên tài khoản nếu bạn tung hứng nhiều đăng nhập, một chỉ báo trạng thái chạy nếu bạn hay chuyển tab. Status line rất rẻ để thay đổi, nên hãy tinh chỉnh nó cho tới khi một cái liếc mắt nói với bạn mọi thứ mà lẽ ra bạn phải học theo cách khó khăn.

Link tham khảo

Bài viết gốc (tiếng Anh): A Claude Code Status Line That Tells You What Matters


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.