Skip to content
Open
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
1 change: 1 addition & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"strict": false,
"skills": [
"./skills/webup-buddy-reroll",
"./skills/webup-model-price",
"./skills/webup-statusline"
]
}
Expand Down
17 changes: 16 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
pull_request:

jobs:
verify-statusline:
verify-skills:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
Expand All @@ -16,6 +16,21 @@ jobs:

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

- name: Install jq on Windows
if: runner.os == 'Windows'
run: |
choco install jq -y
"C:\ProgramData\chocolatey\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
shell: powershell

- name: Verify model price skill
run: python3 scripts/verify_model_price.py
shell: bash

- name: Verify statusline replay fixture
run: python3 scripts/verify_statusline_replay.py
shell: bash

- name: Verify statusline generation
run: python3 scripts/verify_context.py
shell: bash
21 changes: 19 additions & 2 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@ npx skills add webup/skills-cc -s webup-statusline -g

## 🎮 スキル一覧

### 💵 webup-model-price

[`models.dev`](https://models.dev) からモデル価格を取得して正規化します。provider 指定、複数プラットフォーム比較、catalog キャッシュ、token 数からの費用見積もりに対応します。

```bash
# Anthropic Claude の価格
/webup-model-price claude-opus-4-7

# 完全一致する全プロバイダーの価格を比較
npx -y bun skills/webup-model-price/scripts/price.mjs --model claude-opus-4-7 --all

# token 数から費用を見積もる
npx -y bun skills/webup-model-price/scripts/price.mjs --provider anthropic --model claude-opus-4-7 --input-tokens 100000 --output-tokens 10000
```

ステータスラインのコスト再計算でも使えるほか、単体で価格確認や provider 比較にも使えます。

### 📊 webup-statusline

カスタム Claude Code ステータスラインを生成・インストール。**カラム** と **テーマ** を選ぶだけ。`context` と `effort` の 2 カラムは **レベルに応じて色が変わる** ので、一目で状況が分かります。
Expand Down Expand Up @@ -74,7 +91,7 @@ npx skills add webup/skills-cc -s webup-statusline -g
|--------|----------|----------|
| `model` | アクティブモデル名 | 常時 |
| `context` | コンテキスト進捗バー + % — **残量に応じて色が変化** | 常時 |
| `cost` | セッション API 支出 `$X.XX`(ゴールド、例:`$0.42`) | `cost.total_cost_usd` が四捨五入して ≥ $0.01 の時 |
| `cost` | 現在モデルの models.dev 単価で再計算したセッション支出 `$X.XX`(ゴールド、例:`$0.42`)。再計算できない場合は Claude Code の `cost.total_cost_usd` にフォールバック | 表示値が四捨五入して ≥ $0.01 の時 |
| `effort` | 推論努力レベル — **強度で色分け**(`low`/`medium`/`high`/`xhigh`/`max` 対応) | `~/.claude/settings.json` で `effortLevel` が設定されている時 |
| `style` | 出力スタイル名(例:`Explanatory`、`Learning`)— Claude のブランドカラーに合わせたパープル | `output_style.name` が `default` 以外の時 |
| `dir` | リポジトリディレクトリ名(ワークツリー内では元リポジトリ名) | 常時 |
Expand Down Expand Up @@ -107,7 +124,7 @@ npx skills add webup/skills-cc -s webup-statusline -g

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

> ⚠️ **注意:** 生成されたスクリプトは JSON 解析に `jq` が必要です。Windows では WinGetscoop でインストールされた jq のパスを自動検出します。それでも jq が見つからない場合は、手動でディレクトリを PATH に追加してください。スキルは `~/.claude/scripts/statusline.sh` を自動生成し `~/.claude/settings.json` を更新します。Claude Code を再起動すると反映されます。
> ⚠️ **注意:** 生成されたスクリプトは JSON 解析に `jq` が必要です。Windows では WinGet、Chocolatey、scoop でインストールされた jq のパスを自動検出します。それでも jq が見つからない場合は、手動でディレクトリを PATH に追加してください。スキルは `~/.claude/scripts/statusline.sh` を自動生成し `~/.claude/settings.json` を更新します。Claude Code を再起動すると反映されます。`cost` カラムは `prompt_id` ごとの再計算結果をローカル状態ディレクトリに記録し、ステータスラインの再描画で二重計上しないようにします

### 🎰 webup-buddy-reroll

Expand Down
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@ npx skills add webup/skills-cc -s webup-statusline -g

## 🎮 Skills

### 💵 webup-model-price

Look up normalized model prices from [`models.dev`](https://models.dev), with provider-aware search, cached catalog data, and token-cost estimation.

```bash
# Anthropic-hosted Claude price
/webup-model-price claude-opus-4-7

# Compare all providers/platforms with an exact match
npx -y bun skills/webup-model-price/scripts/price.mjs --model claude-opus-4-7 --all

# Estimate usage cost from token counts
npx -y bun skills/webup-model-price/scripts/price.mjs --provider anthropic --model claude-opus-4-7 --input-tokens 100000 --output-tokens 10000
```

This skill powers the statusline cost recalculation path while staying useful on its own for model price checks and provider comparisons.

### 📊 webup-statusline

Generate and install a custom Claude Code status line. Pick your **columns**, pick a **theme** — that's it. Two columns (`context` and `effort`) also **change color based on level**, so the bar tells you at a glance where you stand.
Expand Down Expand Up @@ -74,7 +91,7 @@ Healthy session — 88% remaining, `effortLevel: medium`, default output style (
|--------|---------------|--------------|
| `model` | Active model name | Always |
| `context` | Context window progress bar + percentage — **color scales with remaining capacity** | Always |
| `cost` | Session API spend as `$X.XX` in gold (e.g. `$0.42`) | When `cost.total_cost_usd` rounds to ≥ $0.01 |
| `cost` | Recalculated session spend as `$X.XX` in gold (e.g. `$0.42`) using the current model's models.dev price, with Claude Code's `cost.total_cost_usd` as fallback | When the displayed value rounds to ≥ $0.01 |
| `effort` | Reasoning effort level — **colored by intensity** (supports `low`/`medium`/`high`/`xhigh`/`max`) | When `effortLevel` is set in `~/.claude/settings.json` |
| `style` | Output style name (e.g. `Explanatory`, `Learning`) — shown in purple to match Claude's brand hue | When `output_style.name` is anything other than `default` |
| `dir` | Repo directory basename (original repo when inside a worktree) | Always |
Expand Down Expand Up @@ -107,7 +124,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. 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.
> ⚠️ **Note:** The generated script requires `jq` for JSON parsing. On Windows, it auto-detects jq installed via WinGet, Chocolatey, 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
21 changes: 19 additions & 2 deletions README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@ npx skills add webup/skills-cc -s webup-statusline -g

## 🎮 技能列表

### 💵 webup-model-price

从 [`models.dev`](https://models.dev) 查询并标准化模型价格,支持指定 provider、跨平台比较、缓存 catalog,以及根据 token 数估算费用。

```bash
# 查询 Anthropic Claude 价格
/webup-model-price claude-opus-4-7

# 查看所有精确匹配的平台价格
npx -y bun skills/webup-model-price/scripts/price.mjs --model claude-opus-4-7 --all

# 根据 token 数估算费用
npx -y bun skills/webup-model-price/scripts/price.mjs --provider anthropic --model claude-opus-4-7 --input-tokens 100000 --output-tokens 10000
```

这个技能会被状态栏的费用重算逻辑复用,也可以单独用于查价和比较平台价格。

### 📊 webup-statusline

生成并安装自定义 Claude Code 状态栏。选择 **字段** 和 **主题**,仅此而已。其中两个字段(`context` 和 `effort`)会 **随级别动态换色**,一眼看清当前状态。
Expand Down Expand Up @@ -74,7 +91,7 @@ npx skills add webup/skills-cc -s webup-statusline -g
|------|----------|----------|
| `model` | 活跃模型名 | 始终显示 |
| `context` | 上下文进度条 + 百分比 —— **颜色随剩余容量变化** | 始终显示 |
| `cost` | 会话累计消费 `$X.XX`(金色,例如 `$0.42`) | 当 `cost.total_cost_usd` 四舍五入后 ≥ $0.01 |
| `cost` | 按当前模型的 models.dev 单价重算会话消费 `$X.XX`(金色,例如 `$0.42`),无法重算时回退到 Claude Code 的 `cost.total_cost_usd` | 当显示值四舍五入后 ≥ $0.01 |
| `effort` | 推理努力度 —— **按强度着色**(支持 `low`/`medium`/`high`/`xhigh`/`max`) | 当 `~/.claude/settings.json` 中设置了 `effortLevel` |
| `style` | 输出样式名(例如 `Explanatory`、`Learning`)—— 紫色呼应 Claude 品牌色 | 当 `output_style.name` 不是 `default` 时 |
| `dir` | 仓库目录名(在工作树中显示原仓库名) | 始终显示 |
Expand Down Expand Up @@ -107,7 +124,7 @@ npx skills add webup/skills-cc -s webup-statusline -g

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

> ⚠️ **注意:** 生成的脚本需要 `jq` 解析 JSON。在 Windows 下,脚本会自动检测 WinGet 和 scoop 安装的 jq 路径;若仍无法找到 jq,请手动将其目录添加到 PATH。本技能会自动写入 `~/.claude/scripts/statusline.sh` 并更新 `~/.claude/settings.json` —— 重启 Claude Code 即可生效。
> ⚠️ **注意:** 生成的脚本需要 `jq` 解析 JSON。在 Windows 下,脚本会自动检测 WinGet、Chocolatey 和 scoop 安装的 jq 路径;若仍无法找到 jq,请手动将其目录添加到 PATH。本技能会自动写入 `~/.claude/scripts/statusline.sh` 并更新 `~/.claude/settings.json` —— 重启 Claude Code 即可生效。`cost` 字段会把每个 `prompt_id` 的重算费用记录到本地状态目录,避免状态栏重复刷新时重复计费

### 🎰 webup-buddy-reroll

Expand Down
107 changes: 107 additions & 0 deletions scripts/verify_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import sys
import tempfile
import os
import json

THEMES = ["gruvbox", "dracula", "robbyrussell", "minimal"]
ELEMENT_SETS = [
Expand All @@ -13,6 +14,7 @@
"model,context,cost,effort,style,git,dir,worktree,vim",
]
GENERATOR = "skills/webup-statusline/scripts/generate.mjs"
PRICE_FIXTURE = os.path.abspath("tests/fixtures/models-dev-catalog.json")

IS_WINDOWS = sys.platform == "win32"

Expand Down Expand Up @@ -45,6 +47,106 @@ def bash_syntax_ok(script):
os.unlink(path)


def run_statusline_script(script, payload, env):
fd, path = tempfile.mkstemp(suffix=".sh")
try:
with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as f:
f.write(script)
return subprocess.run(
["bash", path],
input=json.dumps(payload),
capture_output=True,
encoding="utf-8",
errors="replace",
env={**os.environ, **env},
)
finally:
os.unlink(path)


def statusline_payload(model_id, display_name, prompt_id):
return {
"session_id": "verify-model-switch",
"prompt_id": prompt_id,
"model": {
"id": model_id,
"display_name": display_name,
},
"workspace": {
"current_dir": os.getcwd(),
},
"cost": {
"total_cost_usd": 0.01,
},
"context_window": {
"remaining_percentage": 90,
"total_input_tokens": 100000,
"total_output_tokens": 10000,
"context_window_size": 200000,
"current_usage": {
"input_tokens": 100000,
"output_tokens": 10000,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0,
},
},
}


def model_switch_cost_ok(script):
"""Verify cost is recomputed from per-model prices and repeated refreshes do not double-count."""
if IS_WINDOWS:
return True, "(skipped on Windows — Linux/macOS CI covers execution)"

with tempfile.TemporaryDirectory() as state_dir:
env = {
"WEBUP_MODEL_PRICE_CATALOG": PRICE_FIXTURE,
"WEBUP_STATUSLINE_STATE_DIR": state_dir,
}

missing = run_statusline_script(
script,
statusline_payload("unknown-model", "Unknown Model", "prompt-missing"),
env,
)
if missing.returncode != 0:
return False, f"missing-price fallback render failed: {missing.stderr.strip()}"
if "$0.01" not in missing.stdout:
return False, f"missing-price fallback expected Claude Code $0.01, got: {missing.stdout!r}"

first = run_statusline_script(
script,
statusline_payload("claude-sonnet-4-6", "Claude Sonnet 4.6", "prompt-1"),
env,
)
if first.returncode != 0:
return False, f"first render failed: {first.stderr.strip()}"
if "$0.45" not in first.stdout:
return False, f"first render expected $0.45, got: {first.stdout!r}"

second = run_statusline_script(
script,
statusline_payload("claude-opus-4-7", "Claude Opus 4.7", "prompt-2"),
env,
)
if second.returncode != 0:
return False, f"second render failed: {second.stderr.strip()}"
if "$1.20" not in second.stdout:
return False, f"second render expected $1.20, got: {second.stdout!r}"

repeat = run_statusline_script(
script,
statusline_payload("claude-opus-4-7", "Claude Opus 4.7", "prompt-2"),
env,
)
if repeat.returncode != 0:
return False, f"repeat render failed: {repeat.stderr.strip()}"
if "$1.20" not in repeat.stdout:
return False, f"repeat render expected $1.20 without double-counting, got: {repeat.stdout!r}"

return True, ""


def main():
errors = []
for theme in THEMES:
Expand Down Expand Up @@ -92,6 +194,11 @@ def main():
if el == "vim" and "vim.mode" not in script:
errors.append(f"FAIL missing vim field in {label}")

if theme == "minimal" and elements == "model,context,cost,effort,style,git,dir,worktree,vim":
ok, detail = model_switch_cost_ok(script)
if not ok:
errors.append(f"FAIL model switch cost ledger {label}: {detail}")

if errors:
for e in errors:
print(e, file=sys.stderr)
Expand Down
97 changes: 97 additions & 0 deletions scripts/verify_model_price.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/usr/bin/env python3
"""Verify models.dev price lookup behavior."""

import json
import subprocess
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
PRICE = ROOT / "skills/webup-model-price/scripts/price.mjs"
FIXTURE = ROOT / "tests/fixtures/models-dev-catalog.json"


def run_price(*args):
return subprocess.run(
["bun", str(PRICE), "--catalog", str(FIXTURE), *args],
capture_output=True,
encoding="utf-8",
errors="replace",
)


def assert_ok(result, label):
if result.returncode != 0:
raise AssertionError(f"{label} failed:\nSTDOUT={result.stdout}\nSTDERR={result.stderr}")


def main():
errors = []

try:
r = run_price("--provider", "anthropic", "--model", "claude-opus-4-7")
assert_ok(r, "anthropic exact lookup")
data = json.loads(r.stdout)
assert data["provider"] == "anthropic"
assert data["model"] == "claude-opus-4-7"
assert data["costPerMillion"] == {
"input": 5,
"output": 25,
"cacheRead": 0.5,
"cacheWrite": 6.25,
}
except Exception as exc:
errors.append(f"FAIL exact provider lookup: {exc}")

try:
r = run_price("--model", "claude-opus-4-7", "--all")
assert_ok(r, "all provider lookup")
data = json.loads(r.stdout)
providers = [item["provider"] for item in data["matches"]]
assert providers == ["anthropic", "openrouter", "mirror"], providers
except Exception as exc:
errors.append(f"FAIL all provider lookup: {exc}")

try:
r = run_price("--model", "anthropic/claude-opus-4.7", "--all")
assert_ok(r, "provider-prefixed lookup")
data = json.loads(r.stdout)
assert [item["provider"] for item in data["matches"]] == ["openrouter"]
except Exception as exc:
errors.append(f"FAIL provider-prefixed lookup: {exc}")

try:
r = run_price("--model", "claude-opus-4-7", "--no-default-provider")
if r.returncode == 0:
raise AssertionError("ambiguous lookup unexpectedly succeeded")
if "ambiguous" not in r.stderr.lower() or "anthropic" not in r.stderr or "openrouter" not in r.stderr:
raise AssertionError(f"unexpected ambiguity message: {r.stderr}")
except Exception as exc:
errors.append(f"FAIL ambiguous lookup: {exc}")

try:
r = run_price(
"--provider", "anthropic",
"--model", "claude-opus-4-7",
"--input-tokens", "100000",
"--output-tokens", "10000",
"--cache-read-tokens", "20000",
"--cache-write-tokens", "5000",
)
assert_ok(r, "cost estimate")
data = json.loads(r.stdout)
assert data["estimatedCostUsd"] == 0.79125, data["estimatedCostUsd"]
except Exception as exc:
errors.append(f"FAIL cost estimate: {exc}")

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

print("Model price lookup checks passed")


if __name__ == "__main__":
main()
Loading
Loading