-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·156 lines (133 loc) · 5.36 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·156 lines (133 loc) · 5.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#!/usr/bin/env bash
#
# Installer for the Claude Code powerline statusline.
#
# Copies statusline.sh into your Claude config directory and merges the
# `statusLine` block into settings.json without disturbing other settings.
# Existing settings.json is validated before any change and backed up first;
# the merge is written atomically.
#
# Usage:
# ./install.sh [--no-icons] [--force] [--dir DIR]
#
# --no-icons Install with STATUSLINE_ICONS=0 (plain-text fallback for
# terminals without a Nerd Font).
# --force Overwrite an existing statusLine without prompting.
# --dir DIR Claude config directory. Defaults to $CLAUDE_CONFIG_DIR,
# then ~/.claude.
# -h, --help Show this help.
set -euo pipefail
# --- helpers ----------------------------------------------------------------
die() { printf 'error: %s\n' "$1" >&2; exit 1; }
info() { printf '%s\n' "$1"; }
usage() {
# Print the leading comment block (from line 3 until the first non-comment
# line), stripping the leading "# ".
awk 'NR>=3 { if (/^#/) { sub(/^# ?/, ""); print } else exit }' "$0"
exit "${1:-0}"
}
# Back up a file before it is overwritten. Uses a timestamped .bak suffix so
# running the installer more than once never clobbers an earlier backup.
backup_file() {
local f="$1" b
b="$f.bak.$(date +%Y%m%d%H%M%S)"
cp "$f" "$b"
info "Backed up $f -> $b"
}
# --- parse arguments --------------------------------------------------------
no_icons=0
force=0
config_dir="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
while [ $# -gt 0 ]; do
case "$1" in
--no-icons) no_icons=1 ;;
--force) force=1 ;;
--dir)
[ $# -ge 2 ] || die "--dir requires a path argument"
config_dir="$2"; shift ;;
--dir=*) config_dir="${1#--dir=}" ;;
-h|--help) usage 0 ;;
*) die "unknown argument: $1 (try --help)" ;;
esac
shift
done
# --- preflight --------------------------------------------------------------
command -v jq >/dev/null 2>&1 || die "jq is required but not found.
Install it first: macOS: brew install jq Debian/Ubuntu: apt install jq"
# Resolve the directory this installer lives in, so statusline.sh is found
# regardless of where the script is invoked from.
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
src="$script_dir/statusline.sh"
[ -f "$src" ] || die "statusline.sh not found next to this installer (looked in $script_dir)"
# Absolutize the config dir so the command we write into settings.json is
# an absolute path even if --dir was given as relative.
mkdir -p "$config_dir" || die "could not create config dir: $config_dir"
config_dir=$(CDPATH= cd -- "$config_dir" && pwd)
dest="$config_dir/statusline.sh"
settings="$config_dir/settings.json"
# --- install the script -----------------------------------------------------
# Back up a pre-existing script at the destination before overwriting it,
# unless it is byte-identical to what we are about to install.
if [ -f "$dest" ] && ! cmp -s "$src" "$dest"; then
backup_file "$dest"
fi
cp "$src" "$dest"
chmod +x "$dest"
info "Installed script: $dest"
# Build the command string for settings.json. The destination path is quoted
# so a config dir containing spaces still runs correctly.
if [ "$no_icons" -eq 1 ]; then
command_str="STATUSLINE_ICONS=0 bash \"$dest\""
else
command_str="bash \"$dest\""
fi
# --- merge settings.json ----------------------------------------------------
# The desired statusLine object, built safely from variables (no string
# interpolation into JSON).
status_block=$(jq -n --arg cmd "$command_str" \
'{type: "command", command: $cmd}')
if [ -f "$settings" ]; then
# Refuse to touch a file that isn't valid JSON.
jq empty "$settings" 2>/dev/null || die "$settings exists but is not valid JSON. Fix or remove it, then re-run."
# If a statusLine already exists and differs, confirm before replacing.
if jq -e 'has("statusLine")' "$settings" >/dev/null; then
if [ "$force" -eq 0 ]; then
current=$(jq -c '.statusLine' "$settings")
if [ "$current" = "$(echo "$status_block" | jq -c '.')" ]; then
info "settings.json already points at this statusline. Nothing to change."
exit 0
fi
info "An existing statusLine is configured in $settings:"
info " $current"
if [ -t 0 ]; then
printf 'Replace it? [y/N] '
read -r reply
case "$reply" in
[yY]|[yY][eE][sS]) ;;
*) die "aborted; settings.json left unchanged" ;;
esac
else
die "refusing to overwrite existing statusLine in non-interactive mode (re-run with --force)"
fi
fi
fi
# Back up before any write.
backup_file "$settings"
# Merge atomically: write to a temp file, validate, then move into place.
tmp=$(mktemp "$settings.XXXXXX")
trap 'rm -f "$tmp"' EXIT
jq --argjson block "$status_block" '.statusLine = $block' "$settings" >"$tmp"
jq empty "$tmp" || die "internal error: produced invalid JSON, settings.json left unchanged"
mv "$tmp" "$settings"
trap - EXIT
else
# No settings file yet: create one containing just the statusLine.
jq -n --argjson block "$status_block" '{statusLine: $block}' >"$settings"
fi
info "Configured statusLine in: $settings"
info ""
info "Done. Submit any input in Claude Code to see the statusline."
if [ "$no_icons" -eq 0 ]; then
info "If icons show as boxes (□), your terminal lacks a Nerd Font."
info "Re-run with --force --no-icons for a plain-text fallback."
fi