@oorn/claw-statusline 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ {
2
+ "env": {
3
+ "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
4
+ },
5
+ "statusLine": {
6
+ "type": "command",
7
+ "command": "bash ~/.claude/statusline-command.sh"
8
+ },
9
+ "skipDangerousModePermissionPrompt": true,
10
+ "preferences": {
11
+ "tmuxSplitPanes": true
12
+ },
13
+ "voiceEnabled": true,
14
+ "voice": {
15
+ "enabled": true,
16
+ "mode": "hold"
17
+ }
18
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(cp /Users/petereai/.claude/rules/git-commits.md /Users/petereai/Desktop/projects/petereaI/dot-claude/.claude/rules/git-commits.md)",
5
+ "Bash(git -C /Users/petereai/Desktop/projects/petereaI/dot-claude add .claude/rules/git-commits.md)",
6
+ "Bash(git -C /Users/petereai/Desktop/projects/petereaI/dot-claude commit -m \"Add global rule to suppress AI attribution in git commits\")",
7
+ "Bash(git -C /Users/petereai/Desktop/projects/petereaI/dot-claude push)",
8
+ "Bash(mkdir -p /Users/petereai/Desktop/projects/petereaI/dot-claude/.claude/rules)",
9
+ "Bash(rm /Users/petereai/Desktop/projects/petereaI/dot-claude/.claude/rules/git-commits.md)",
10
+ "Bash(rm /Users/petereai/.claude/rules/git-commits.md)"
11
+ ]
12
+ }
13
+ }
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env bash
2
+ # Claude Code status line — usage bar style
3
+ # Receives JSON on stdin from Claude Code
4
+
5
+ input=$(cat)
6
+ ESC=$'\033'
7
+
8
+ # --- Parse JSON ---
9
+ model=$(echo "$input" | jq -r '.model.display_name // "Claude"')
10
+ model_id=$(echo "$input" | jq -r '.model.id // ""')
11
+ used_pct=$(echo "$input" | jq -r '.context_window.used_percentage // "0"')
12
+ total_tokens=$(echo "$input" | jq -r '.context_window.context_window_size // "0"')
13
+ five_pct=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
14
+ five_reset=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty')
15
+ week_pct=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
16
+
17
+ # --- Mode detection ---
18
+ # Subscription plans populate rate_limits; API key usage does not
19
+ if [ -n "$five_pct" ] || [ -n "$week_pct" ]; then
20
+ mode="subscription"
21
+ else
22
+ mode="api"
23
+ fi
24
+
25
+ # --- Build progress bar ---
26
+ bar_width=20
27
+ used_int=$(printf "%.0f" "$used_pct")
28
+ filled=$(( used_int * bar_width / 100 ))
29
+ empty=$(( bar_width - filled ))
30
+
31
+ # Color based on usage
32
+ if [ "$used_int" -lt 50 ]; then
33
+ bar_color="${ESC}[32m" # green
34
+ elif [ "$used_int" -lt 80 ]; then
35
+ bar_color="${ESC}[33m" # yellow
36
+ else
37
+ bar_color="${ESC}[31m" # red
38
+ fi
39
+
40
+ # Build bar string
41
+ bar_filled=""
42
+ bar_empty=""
43
+ for ((i=0; i<filled; i++)); do bar_filled+="█"; done
44
+ for ((i=0; i<empty; i++)); do bar_empty+=" "; done
45
+
46
+ # Format token counts (used/total)
47
+ format_tokens() {
48
+ local t=$1
49
+ if [ "$t" -ge 1000 ] 2>/dev/null; then
50
+ echo "$(( t / 1000 ))k"
51
+ else
52
+ echo "$t"
53
+ fi
54
+ }
55
+
56
+ used_tokens=$(( total_tokens * used_int / 100 ))
57
+ used_display=$(format_tokens "$used_tokens")
58
+ total_display=$(format_tokens "$total_tokens")
59
+
60
+ # --- Right-side info: rate limits (subscription) or cost estimate (API) ---
61
+ right_info=""
62
+
63
+ if [ "$mode" = "subscription" ]; then
64
+ # Rate limits — subscription mode
65
+ if [ -n "$five_pct" ]; then
66
+ five_int=$(printf "%.0f" "$five_pct")
67
+ countdown=""
68
+ if [ -n "$five_reset" ]; then
69
+ now=$(date +%s)
70
+ secs_left=$(( five_reset - now ))
71
+ if [ "$secs_left" -gt 0 ]; then
72
+ hrs=$(( secs_left / 3600 ))
73
+ mins=$(( (secs_left % 3600) / 60 ))
74
+ countdown=" (${hrs}h${mins}m)"
75
+ else
76
+ countdown=" (now)"
77
+ fi
78
+ fi
79
+ right_info="${ESC}[36m5h:${five_int}%${countdown}${ESC}[0m"
80
+ fi
81
+ if [ -n "$week_pct" ]; then
82
+ week_int=$(printf "%.0f" "$week_pct")
83
+ [ -n "$right_info" ] && right_info="$right_info "
84
+ right_info="${right_info}${ESC}[36m7d:${week_int}%${ESC}[0m"
85
+ fi
86
+ elif [[ "$model_id" == claude-* ]]; then
87
+ # Cost estimate — Anthropic API mode only
88
+ # Pricing per million tokens (input/output). Approximate 70/30 split.
89
+ input_price_per_mtok=3.0
90
+ output_price_per_mtok=15.0
91
+
92
+ case "$model_id" in
93
+ claude-opus-4*)
94
+ input_price_per_mtok=15.0
95
+ output_price_per_mtok=75.0
96
+ ;;
97
+ claude-sonnet-4*|claude-sonnet-3-7*)
98
+ input_price_per_mtok=3.0
99
+ output_price_per_mtok=15.0
100
+ ;;
101
+ claude-haiku-4*|claude-haiku-3*)
102
+ input_price_per_mtok=0.8
103
+ output_price_per_mtok=4.0
104
+ ;;
105
+ esac
106
+
107
+ # Estimate cost using 70/30 input/output split of used tokens
108
+ cost=$(awk -v tokens="$used_tokens" \
109
+ -v in_price="$input_price_per_mtok" \
110
+ -v out_price="$output_price_per_mtok" \
111
+ 'BEGIN {
112
+ input_tok = tokens * 0.70
113
+ output_tok = tokens * 0.30
114
+ cost = (input_tok * in_price + output_tok * out_price) / 1000000
115
+ printf "~$%.4f", cost
116
+ }')
117
+
118
+ right_info="${ESC}[36m${cost}${ESC}[0m"
119
+ # else: non-Anthropic API — tokens already shown in the bar, skip right_info
120
+ fi
121
+
122
+ if [ -n "$right_info" ]; then
123
+ right_info=" ${ESC}[90m|${ESC}[0m ${right_info}"
124
+ fi
125
+
126
+ # --- Output ---
127
+ printf "%s ${ESC}[90m|${ESC}[0m [%s%s%s${ESC}[0m] %s%% ${ESC}[90m|${ESC}[0m %s/%s%s\n" \
128
+ "$model" \
129
+ "$bar_color" \
130
+ "$bar_filled" \
131
+ "$bar_empty" \
132
+ "$used_int" \
133
+ "$used_display" \
134
+ "$total_display" \
135
+ "$right_info"
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # claw-statusline
2
+
3
+ Shared Claude Code configuration for the team — statusline, settings, and rules.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npx @oorn/claw-statusline init
9
+ ```
10
+
11
+ `jq` is required for the statusline. The installer will attempt to install it automatically via the available package manager on your system (Homebrew, apt, dnf, yum, pacman, zypper, apk, winget, Chocolatey, or Scoop). If auto-install fails, you'll be shown the manual command.
12
+
13
+ To update to the latest version:
14
+
15
+ ```bash
16
+ npx @oorn/claw-statusline@latest init
17
+ ```
18
+
19
+ ## What gets installed
20
+
21
+ | File | Destination | Description |
22
+ |------|-------------|-------------|
23
+ | `settings.json` | `~/.claude/settings.json` | Claude Code preferences |
24
+ | `statusline-command.sh` | `~/.claude/statusline-command.sh` | Status line script |
25
+
26
+ ## Statusline
27
+
28
+ The status line is mode-aware — it adapts based on whether you're on a subscription plan or using an API key.
29
+
30
+ **Subscription (Pro/Max):**
31
+ ```
32
+ Claude Sonnet 4.6 | [████ ] 42% | 84k/200k | 5h:37% (1h22m) 7d:15%
33
+ ```
34
+
35
+ **Anthropic API key (`claude-*` models):**
36
+ ```
37
+ Claude Sonnet 4.6 | [████ ] 42% | 84k/200k | ~$0.5544
38
+ ```
39
+
40
+ **3rd-party API (Gemini, MiniMax, etc.):**
41
+ ```
42
+ Gemini 2.5 Pro | [███████████ ] 55% | 550k/1000k
43
+ ```
44
+
45
+ | Segment | Description |
46
+ |---------|-------------|
47
+ | Model name | Current model display name |
48
+ | `[████ ]` | Context window usage bar (green < 50%, yellow < 80%, red ≥ 80%) |
49
+ | `42%` | Context used percentage |
50
+ | `84k/200k` | Used tokens / total context window size |
51
+ | `5h:37% (1h22m)` | 5-hour rate limit usage + countdown to reset *(subscription only)* |
52
+ | `7d:15%` | 7-day rate limit usage *(subscription only)* |
53
+ | `~$0.5544` | Estimated session cost *(Anthropic API only)* |
54
+
55
+ Cost is estimated using a 70/30 input/output token split with per-model pricing:
56
+
57
+ | Model | Input | Output |
58
+ |-------|-------|--------|
59
+ | Opus 4.x | $15/MTok | $75/MTok |
60
+ | Sonnet 4.x / 3.7 | $3/MTok | $15/MTok |
61
+ | Haiku 4.x / 3.x | $0.8/MTok | $4/MTok |
62
+
63
+ For 3rd-party models the right section is omitted — token usage is already visible in the bar.
64
+
65
+ ### How it works
66
+
67
+ Claude Code invokes `statusline-command.sh` after each response, passing a JSON payload via stdin. The script parses the JSON with `jq` and prints a formatted line with ANSI colors.
68
+
69
+ The script is wired up in `settings.json`:
70
+
71
+ ```json
72
+ "statusLine": {
73
+ "type": "command",
74
+ "command": "bash ~/.claude/statusline-command.sh"
75
+ }
76
+ ```
77
+
78
+ ### Available JSON fields
79
+
80
+ The full payload Claude Code sends to the script includes:
81
+
82
+ ```json
83
+ {
84
+ "model": { "id": "claude-sonnet-4-6", "display_name": "Sonnet 4.6" },
85
+ "context_window": {
86
+ "used_percentage": 21,
87
+ "remaining_percentage": 79,
88
+ "context_window_size": 200000,
89
+ "total_input_tokens": 44,
90
+ "total_output_tokens": 7202,
91
+ "current_usage": {
92
+ "input_tokens": 1,
93
+ "output_tokens": 28,
94
+ "cache_creation_input_tokens": 219,
95
+ "cache_read_input_tokens": 34414
96
+ }
97
+ },
98
+ "rate_limits": {
99
+ "five_hour": { "used_percentage": 18, "resets_at": 1775725200 },
100
+ "seven_day": { "used_percentage": 17, "resets_at": 1776067200 }
101
+ },
102
+ "cost": {
103
+ "total_cost_usd": 0.67,
104
+ "total_lines_added": 86,
105
+ "total_lines_removed": 79
106
+ },
107
+ "cwd": "/Users/you/project",
108
+ "version": "2.1.97"
109
+ }
110
+ ```
111
+
112
+ ## Updating the config
113
+
114
+ 1. Edit files in `.claude/`
115
+ 2. Bump version in `package.json`
116
+ 3. Update this README to reflect the change
117
+ 4. Publish and push:
118
+ ```bash
119
+ npm publish --access public && git push
120
+ ```
package/bin/cli.js ADDED
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, mkdirSync, copyFileSync, readdirSync, statSync, readFileSync } from 'fs';
4
+ import { join, dirname } from 'path';
5
+ import { homedir, platform } from 'os';
6
+ import { fileURLToPath } from 'url';
7
+ import { spawnSync } from 'child_process';
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const { version: VERSION } = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
11
+ const CLAUDE_DIR = join(homedir(), '.claude');
12
+ const SRC_DIR = join(__dirname, '..', '.claude');
13
+
14
+ const args = process.argv.slice(2);
15
+
16
+ if (args.includes('--version') || args.includes('-v')) {
17
+ console.log(VERSION);
18
+ process.exit(0);
19
+ }
20
+
21
+ if (args[0] !== 'init') {
22
+ console.log(`Usage: npx @oorn/claw-statusline init`);
23
+ process.exit(1);
24
+ }
25
+
26
+ // --- init ---
27
+
28
+ const green = (s) => `\x1b[32m${s}\x1b[0m`;
29
+ const dim = (s) => `\x1b[90m${s}\x1b[0m`;
30
+ const bold = (s) => `\x1b[1m${s}\x1b[0m`;
31
+
32
+ console.log(`\n${bold('claw-statusline')} — installing Claude Code config\n`);
33
+
34
+ function installFile(src, dest) {
35
+ const destDir = dirname(dest);
36
+ if (!existsSync(destDir)) mkdirSync(destDir, { recursive: true });
37
+ copyFileSync(src, dest);
38
+ const rel = dest.replace(homedir(), '~');
39
+ console.log(` ${green('✓')} ${rel}`);
40
+ }
41
+
42
+ function installDir(srcDir, destDir) {
43
+ if (!existsSync(srcDir)) return;
44
+ for (const file of readdirSync(srcDir)) {
45
+ const src = join(srcDir, file);
46
+ if (statSync(src).isFile()) {
47
+ installFile(src, join(destDir, file));
48
+ }
49
+ }
50
+ }
51
+
52
+ // Top-level files
53
+ for (const file of ['settings.json', 'statusline-command.sh']) {
54
+ const src = join(SRC_DIR, file);
55
+ if (existsSync(src)) installFile(src, join(CLAUDE_DIR, file));
56
+ }
57
+
58
+ // rules/
59
+ installDir(join(SRC_DIR, 'rules'), join(CLAUDE_DIR, 'rules'));
60
+
61
+ // skills/
62
+ const skillsSrc = join(SRC_DIR, 'skills');
63
+ if (existsSync(skillsSrc)) {
64
+ for (const skill of readdirSync(skillsSrc)) {
65
+ const skillDir = join(skillsSrc, skill);
66
+ if (statSync(skillDir).isDirectory()) {
67
+ installDir(skillDir, join(CLAUDE_DIR, 'skills', skill));
68
+ }
69
+ }
70
+ }
71
+
72
+ // --- jq check ---
73
+ const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
74
+
75
+ function hasCmd(cmd) {
76
+ return spawnSync(cmd, ['--version'], { stdio: 'ignore' }).status === 0;
77
+ }
78
+
79
+ function tryInstall(label, ...args) {
80
+ console.log(` ${yellow('!')} jq not found — installing via ${label}…`);
81
+ return spawnSync(args[0], args.slice(1), { stdio: 'inherit' }).status === 0;
82
+ }
83
+
84
+ function installJq() {
85
+ const os = platform();
86
+
87
+ if (os === 'darwin') {
88
+ if (hasCmd('brew')) return tryInstall('Homebrew', 'brew', 'install', 'jq');
89
+ if (hasCmd('port')) return tryInstall('MacPorts', 'sudo', 'port', 'install', 'jq');
90
+ }
91
+
92
+ if (os === 'linux') {
93
+ if (hasCmd('apt-get')) return tryInstall('apt', 'sudo', 'apt-get', 'install', '-y', 'jq');
94
+ if (hasCmd('dnf')) return tryInstall('dnf', 'sudo', 'dnf', 'install', '-y', 'jq');
95
+ if (hasCmd('yum')) return tryInstall('yum', 'sudo', 'yum', 'install', '-y', 'jq');
96
+ if (hasCmd('pacman')) return tryInstall('pacman', 'sudo', 'pacman', '-S', '--noconfirm', 'jq');
97
+ if (hasCmd('zypper')) return tryInstall('zypper', 'sudo', 'zypper', 'install', '-y', 'jq');
98
+ if (hasCmd('apk')) return tryInstall('apk', 'sudo', 'apk', 'add', 'jq');
99
+ }
100
+
101
+ if (os === 'win32') {
102
+ if (hasCmd('winget')) return tryInstall('winget', 'winget', 'install', '--id', 'jqlang.jq', '-e', '--silent');
103
+ if (hasCmd('choco')) return tryInstall('Chocolatey', 'choco', 'install', 'jq', '-y');
104
+ if (hasCmd('scoop')) return tryInstall('Scoop', 'scoop', 'install', 'jq');
105
+ }
106
+
107
+ return false;
108
+ }
109
+
110
+ if (!hasCmd('jq')) {
111
+ const installed = installJq();
112
+ if (installed) {
113
+ console.log(` ${green('✓')} jq installed`);
114
+ } else {
115
+ console.log(`\n ${yellow('!')} jq is required for the statusline but could not be installed automatically.`);
116
+ console.log(` ${dim('Install it manually:')}`);
117
+ console.log(` ${dim(' macOS: brew install jq')}`);
118
+ console.log(` ${dim(' Ubuntu: sudo apt install jq')}`);
119
+ console.log(` ${dim(' Fedora: sudo dnf install jq')}`);
120
+ console.log(` ${dim(' Arch: sudo pacman -S jq')}`);
121
+ console.log(` ${dim(' Alpine: sudo apk add jq')}`);
122
+ console.log(` ${dim(' Windows: winget install jqlang.jq')}`);
123
+ }
124
+ } else {
125
+ console.log(` ${green('✓')} jq`);
126
+ }
127
+
128
+ console.log(`\n${dim('Claude Code will pick up changes automatically.')}`);
129
+ console.log(`${dim('To update: npx @oorn/claw-statusline@latest init')}\n`);
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@oorn/claw-statusline",
3
+ "version": "1.2.0",
4
+ "description": "Claude Code statusline, settings, and rules installer",
5
+ "bin": {
6
+ "claw-statusline": "bin/cli.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ ".claude/"
11
+ ],
12
+ "scripts": {
13
+ "prepublishOnly": "node bin/cli.js --version"
14
+ },
15
+ "keywords": [
16
+ "claude",
17
+ "claude-code",
18
+ "dotfiles"
19
+ ],
20
+ "author": "oorn",
21
+ "type": "module",
22
+ "license": "MIT",
23
+ "engines": {
24
+ "node": ">=18"
25
+ }
26
+ }