Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
verify-statusline:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

- uses: oven-sh/setup-bun@v2

- name: Verify statusline generation
run: python3 scripts/verify_context.py
shell: bash
2 changes: 1 addition & 1 deletion README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ npx skills add webup/skills-cc -s webup-statusline -g

**effort アイコンの上書き**は `--effort-icon` で。プリセット:`arrow`(`↯`、デフォルト)、`bolt`(`ϟ`)、`flash`(`⚡`)、`reason`(`∴`)、`dot`(`◉`)、`none`(非表示)。任意の文字も受け付けます。

> ⚠️ **注意:** 生成されたスクリプトは JSON 解析に `jq` が必要です。スキルは `~/.claude/scripts/statusline.sh` を自動生成し `~/.claude/settings.json` を更新します。Claude Code を再起動すると反映されます。
> ⚠️ **注意:** 生成されたスクリプトは JSON 解析に `jq` が必要です。Windows では WinGet や scoop でインストールされた jq のパスを自動検出します。それでも jq が見つからない場合は、手動でディレクトリを PATH に追加してください。スキルは `~/.claude/scripts/statusline.sh` を自動生成し `~/.claude/settings.json` を更新します。Claude Code を再起動すると反映されます。

### 🎰 webup-buddy-reroll

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ The `context` column intentionally has no prefix icon in any theme — the color

**Override the effort icon** via `--effort-icon`. Presets: `arrow` (`↯`, default), `bolt` (`ϟ`), `flash` (`⚡`), `reason` (`∴`), `dot` (`◉`), `none` (hide). A raw character is also accepted.

> ⚠️ **Note:** The generated script requires `jq` for JSON parsing. The skill writes to `~/.claude/scripts/statusline.sh` and updates `~/.claude/settings.json` — restart Claude Code to see it.
> ⚠️ **Note:** The generated script requires `jq` for JSON parsing. On Windows, it auto-detects jq installed via WinGet or scoop; if jq is still not found, add its directory to your PATH manually. The skill writes to `~/.claude/scripts/statusline.sh` and updates `~/.claude/settings.json` — restart Claude Code to see it.

### 🎰 webup-buddy-reroll

Expand Down
2 changes: 1 addition & 1 deletion README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ npx skills add webup/skills-cc -s webup-statusline -g

**替换 effort 图标** 用 `--effort-icon`。预设值:`arrow`(`↯`,默认)、`bolt`(`ϟ`)、`flash`(`⚡`)、`reason`(`∴`)、`dot`(`◉`)、`none`(隐藏)。也可直接传入任意字符。

> ⚠️ **注意:** 生成的脚本需要 `jq` 解析 JSON。本技能会自动写入 `~/.claude/scripts/statusline.sh` 并更新 `~/.claude/settings.json` —— 重启 Claude Code 即可生效。
> ⚠️ **注意:** 生成的脚本需要 `jq` 解析 JSON。在 Windows 下,脚本会自动检测 WinGet 和 scoop 安装的 jq 路径;若仍无法找到 jq,请手动将其目录添加到 PATH。本技能会自动写入 `~/.claude/scripts/statusline.sh` 并更新 `~/.claude/settings.json` —— 重启 Claude Code 即可生效。

### 🎰 webup-buddy-reroll

Expand Down
106 changes: 106 additions & 0 deletions scripts/verify_context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""Verify that every statusline theme+element combo generates valid bash."""

import subprocess
import sys
import tempfile
import os

THEMES = ["gruvbox", "dracula", "robbyrussell", "minimal"]
ELEMENT_SETS = [
"model",
"model,context,effort,git,dir",
"model,context,cost,effort,style,git,dir,worktree,vim",
]
GENERATOR = "skills/webup-statusline/scripts/generate.mjs"

IS_WINDOWS = sys.platform == "win32"


def run(cmd, input=None):
return subprocess.run(cmd, capture_output=True, encoding="utf-8", errors="replace", input=input)


def bash_syntax_ok(script):
"""Check bash syntax via ``bash -n``.

Skipped on Windows: Git Bash's ``bash -n`` on GitHub Actions runners
returns exit code 1 with empty stderr for all scripts, making it
unreliable. Linux and macOS CI already cover this check.

Returns (ok: bool, detail: str).
"""
if IS_WINDOWS:
return True, "(skipped on Windows — Linux/macOS CI covers bash -n)"

fd, path = tempfile.mkstemp(suffix=".sh")
try:
with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as f:
f.write(script)
br = subprocess.run(["bash", "-n", path], capture_output=True, text=True)
if br.returncode == 0:
return True, ""
return False, br.stderr.strip()
finally:
os.unlink(path)


def main():
errors = []
for theme in THEMES:
for elements in ELEMENT_SETS:
label = f"{theme} / {elements}"
r = run(["bun", GENERATOR, "--elements", elements, "--theme", theme])
if r.returncode != 0:
errors.append(f"FAIL generate {label}: {r.stderr.strip()}")
continue

script = r.stdout

# 1. bash syntax check (non-Windows only; Windows runners skip this)
ok, detail = bash_syntax_ok(script)
if not ok:
errors.append(f"FAIL bash -n {label}: {detail}")

# 2. must contain jq auto-detect block
if "command -v jq" not in script:
errors.append(f"FAIL missing jq detect block in {label}")

# 3. must contain shebang
if not script.startswith("#!/bin/bash"):
errors.append(f"FAIL missing shebang in {label}")

# 4. must contain at least one jq invocation per requested element
for el in elements.split(","):
el = el.strip()
if el == "model" and "model.display_name" not in script:
errors.append(f"FAIL missing model field in {label}")
if el == "context" and "remaining_percentage" not in script:
errors.append(f"FAIL missing context field in {label}")
if el == "effort" and "effortLevel" not in script:
errors.append(f"FAIL missing effort field in {label}")
if el == "git" and "branch --show-current" not in script:
errors.append(f"FAIL missing git field in {label}")
if el == "dir" and "short_dir" not in script:
errors.append(f"FAIL missing dir field in {label}")
if el == "cost" and "total_cost_usd" not in script:
errors.append(f"FAIL missing cost field in {label}")
if el == "style" and "output_style" not in script:
errors.append(f"FAIL missing style field in {label}")
if el == "worktree" and "is_worktree" not in script:
errors.append(f"FAIL missing worktree field in {label}")
if el == "vim" and "vim.mode" not in script:
errors.append(f"FAIL missing vim field in {label}")

if errors:
for e in errors:
print(e, file=sys.stderr)
print(f"\n{len(errors)} check(s) failed", file=sys.stderr)
sys.exit(1)

combos = len(THEMES) * len(ELEMENT_SETS)
print(f"All {combos} theme×element combos passed")


if __name__ == "__main__":
main()
4 changes: 2 additions & 2 deletions skills/webup-statusline/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ This skill generates a bash script tailored to your preferences and installs it

## Prerequisites

- **jq** — required by the generated status line script to parse JSON input from Claude Code
- **jq** — required by the generated status line script to parse JSON input from Claude Code. On Windows, the script auto-detects jq installed via WinGet or scoop; if jq is still not found, add its directory to your PATH manually.
- **Bun** — required to run the generator. Use `npx -y bun` if not installed globally.

## Usage
Expand Down Expand Up @@ -186,5 +186,5 @@ Claude Opus 4.7 · low · skills-cc · main

- Generated script is saved to `~/.claude/scripts/statusline.sh`
- Running the skill again overwrites the existing script — just re-run to change theme or columns
- The script uses `jq` to parse JSON input — make sure it's installed
- The script uses `jq` to parse JSON input — make sure it's installed. On Windows, the script auto-detects WinGet and scoop jq paths; if jq is still not found, add it to PATH manually.
- Git dirty detection uses `--no-optional-locks` to avoid interfering with other git operations
19 changes: 19 additions & 0 deletions skills/webup-statusline/scripts/generate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,25 @@ function buildScript() {
p(`# Claude Code status line — ${t.name} theme`)
p(`# Generated by webup-statusline skill`)
p('')

// Auto-detect jq on Windows (WinGet/scoop paths may not be on PATH)
const jqPathBlock = [
'# Ensure jq is available (Windows: WinGet/scoop installs may not be on PATH)',
'if ! command -v jq >/dev/null 2>&1; then',
' for _jq_dir in \\',
' "/c/Users/$USERNAME/AppData/Local/Microsoft/WinGet/Links" \\',
' "/c/Users/$USERNAME/AppData/Local/Microsoft/WinGet/Packages/jqlang.jq_Microsoft.Winget.Source_8wekyb3d8bbwe" \\',
' "$HOME/scoop/shims" \\',
' ; do',
' if [ -d "$_jq_dir" ] && { [ -x "$_jq_dir/jq" ] || [ -x "$_jq_dir/jq.exe" ]; }; then',
' export PATH="$PATH:$_jq_dir"',
' break',
' fi',
' done',
'fi',
]
for (const line of jqPathBlock) p(line)

p('# Colors')
p("readonly RST='\\033[0m'")
p(`readonly C_MODEL='${t.model}'`)
Expand Down
Loading