syntaur 0.4.0 → 0.4.1
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.
- package/README.md +218 -54
- package/dist/index.js +378 -149
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/statusline/statusline.sh +137 -46
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "syntaur",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Project workflow CLI with dashboard, Claude Code plugin, and Codex plugin",
|
|
5
5
|
"homepage": "https://github.com/prong-horn/syntaur#readme",
|
|
6
6
|
"repository": {
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"node": ">=20.0.0"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
+
"@inquirer/prompts": "^8.4.2",
|
|
57
58
|
"better-sqlite3": "^11.0.0",
|
|
58
59
|
"chokidar": "^4.0.0",
|
|
59
60
|
"commander": "^13.0.0",
|
package/statusline/statusline.sh
CHANGED
|
@@ -1,62 +1,102 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Syntaur statusline for Claude Code.
|
|
3
3
|
#
|
|
4
|
-
# Reads JSON from stdin per Claude Code's statusLine contract and
|
|
5
|
-
# single line
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
4
|
+
# Reads JSON from stdin per Claude Code's statusLine contract and renders a
|
|
5
|
+
# user-configurable single line composed of the segments listed in
|
|
6
|
+
# $HOME/.syntaur/statusline.config.json
|
|
7
|
+
# with shape:
|
|
8
|
+
# { "segments": ["wrap","git","assignment","model","ctx","session"],
|
|
9
|
+
# "separator": " · ",
|
|
10
|
+
# "wrap": "/optional/path/to/inner-statusline.sh" }
|
|
10
11
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
12
|
+
# Available segments:
|
|
13
|
+
# wrap stdout of an external script (composes another statusline)
|
|
14
|
+
# git repo:branch (+ dirty marker + ahead/behind counts)
|
|
15
|
+
# assignment active syntaur assignment (project/slug or standalone/uuid — title)
|
|
16
|
+
# session Claude session id, last 8 chars prefixed by "…"
|
|
17
|
+
# model Claude model display name
|
|
18
|
+
# ctx context window fill bar, e.g. "ctx:[####------] 42%"
|
|
19
|
+
# cwd basename of the current working directory
|
|
16
20
|
#
|
|
21
|
+
# If the config file is absent, falls back to a sensible default set.
|
|
17
22
|
# Never fails the terminal — always exits 0.
|
|
18
23
|
|
|
19
24
|
set -o pipefail 2>/dev/null || true
|
|
20
25
|
|
|
21
26
|
INPUT=$(cat)
|
|
22
27
|
|
|
23
|
-
# Degrade cleanly if jq is unavailable. Emit just the marker so users notice.
|
|
24
28
|
if ! command -v jq >/dev/null 2>&1; then
|
|
25
29
|
printf '%s' '(syntaur: jq missing)'
|
|
26
30
|
exit 0
|
|
27
31
|
fi
|
|
28
32
|
|
|
33
|
+
CONFIG_FILE="$HOME/.syntaur/statusline.config.json"
|
|
34
|
+
# Fall back to a simple one-line conf file for backward compat with earlier
|
|
35
|
+
# install-statusline versions that only stored a wrap target.
|
|
36
|
+
LEGACY_CONF="$HOME/.syntaur/statusline.conf"
|
|
37
|
+
|
|
38
|
+
# --- Load config ---
|
|
39
|
+
SEGMENTS_RAW=""
|
|
40
|
+
SEPARATOR=" · "
|
|
41
|
+
WRAP_PATH=""
|
|
42
|
+
|
|
43
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
44
|
+
SEGMENTS_RAW=$(jq -r '(.segments // []) | join(",")' "$CONFIG_FILE" 2>/dev/null)
|
|
45
|
+
SEP_FROM_CONF=$(jq -r '.separator // empty' "$CONFIG_FILE" 2>/dev/null)
|
|
46
|
+
[ -n "$SEP_FROM_CONF" ] && SEPARATOR="$SEP_FROM_CONF"
|
|
47
|
+
WRAP_PATH=$(jq -r '.wrap // empty' "$CONFIG_FILE" 2>/dev/null)
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Env var always takes precedence for wrap (useful for testing).
|
|
51
|
+
[ -n "$SYNTAUR_STATUSLINE_WRAP" ] && WRAP_PATH="$SYNTAUR_STATUSLINE_WRAP"
|
|
52
|
+
|
|
53
|
+
# Legacy conf: first non-empty, non-comment line is wrap path.
|
|
54
|
+
if [ -z "$WRAP_PATH" ] && [ -f "$LEGACY_CONF" ]; then
|
|
55
|
+
WRAP_PATH=$(awk 'NF && !/^#/{print; exit}' "$LEGACY_CONF" 2>/dev/null)
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Default segment set if none configured.
|
|
59
|
+
if [ -z "$SEGMENTS_RAW" ]; then
|
|
60
|
+
# Default: include wrap as leading segment only if a wrap path is set.
|
|
61
|
+
if [ -n "$WRAP_PATH" ]; then
|
|
62
|
+
SEGMENTS_RAW="wrap,git,assignment,session"
|
|
63
|
+
else
|
|
64
|
+
SEGMENTS_RAW="git,assignment,session"
|
|
65
|
+
fi
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# --- Extract stdin fields ---
|
|
29
69
|
SESSION_ID=""
|
|
30
70
|
CWD=""
|
|
71
|
+
MODEL=""
|
|
72
|
+
USED_PCT=""
|
|
31
73
|
|
|
32
74
|
if [ -n "$INPUT" ]; then
|
|
33
75
|
SESSION_ID=$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
|
|
34
76
|
CWD=$(printf '%s' "$INPUT" | jq -r '.workspace.current_dir // .cwd // empty' 2>/dev/null)
|
|
77
|
+
MODEL=$(printf '%s' "$INPUT" | jq -r '.model.display_name // empty' 2>/dev/null)
|
|
78
|
+
USED_PCT=$(printf '%s' "$INPUT" | jq -r '.context_window.used_percentage // empty' 2>/dev/null)
|
|
35
79
|
fi
|
|
36
80
|
|
|
37
81
|
[ -z "$CWD" ] && CWD="$PWD"
|
|
38
82
|
|
|
39
|
-
# ---
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
WRAP_PATH=$(awk 'NF && !/^#/{print; exit}' "$HOME/.syntaur/statusline.conf" 2>/dev/null)
|
|
44
|
-
fi
|
|
45
|
-
WRAPPED_OUT=""
|
|
83
|
+
# --- Compute each available segment value (cheap, unconditional). ---
|
|
84
|
+
|
|
85
|
+
# wrap
|
|
86
|
+
WRAP_SEG=""
|
|
46
87
|
if [ -n "$WRAP_PATH" ] && [ -r "$WRAP_PATH" ]; then
|
|
47
|
-
# Run the wrapped script with the same stdin and a 2-second timeout (best-effort).
|
|
48
88
|
if command -v timeout >/dev/null 2>&1; then
|
|
49
|
-
|
|
89
|
+
WRAP_SEG=$(printf '%s' "$INPUT" | timeout 2 bash "$WRAP_PATH" 2>/dev/null)
|
|
50
90
|
else
|
|
51
|
-
|
|
91
|
+
WRAP_SEG=$(printf '%s' "$INPUT" | bash "$WRAP_PATH" 2>/dev/null)
|
|
52
92
|
fi
|
|
53
|
-
#
|
|
54
|
-
|
|
93
|
+
# Take last non-empty line (collapse to single row).
|
|
94
|
+
WRAP_SEG=$(printf '%s' "$WRAP_SEG" | tr -d '\r' | awk 'NF{line=$0} END{print line}' 2>/dev/null)
|
|
55
95
|
fi
|
|
56
96
|
|
|
57
|
-
#
|
|
97
|
+
# git — repo:branch[*] +ahead -behind
|
|
58
98
|
GIT_SEG=""
|
|
59
|
-
if [ -
|
|
99
|
+
if [ -d "$CWD" ]; then
|
|
60
100
|
GIT_ROOT=$(git --no-optional-locks -C "$CWD" rev-parse --show-toplevel 2>/dev/null)
|
|
61
101
|
if [ -n "$GIT_ROOT" ]; then
|
|
62
102
|
REPO=$(basename "$GIT_ROOT")
|
|
@@ -65,34 +105,47 @@ if [ -n "$CWD" ] && [ -d "$CWD" ]; then
|
|
|
65
105
|
SHORT=$(git --no-optional-locks -C "$CWD" rev-parse --short HEAD 2>/dev/null)
|
|
66
106
|
[ -n "$SHORT" ] && BRANCH="detached@$SHORT"
|
|
67
107
|
fi
|
|
108
|
+
|
|
109
|
+
DIRTY=""
|
|
110
|
+
if ! git --no-optional-locks -C "$CWD" diff --quiet 2>/dev/null \
|
|
111
|
+
|| ! git --no-optional-locks -C "$CWD" diff --cached --quiet 2>/dev/null; then
|
|
112
|
+
DIRTY="*"
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
AHEAD_BEHIND=""
|
|
116
|
+
UPSTREAM=$(git --no-optional-locks -C "$CWD" rev-parse --abbrev-ref --symbolic-full-name "@{u}" 2>/dev/null)
|
|
117
|
+
if [ -n "$UPSTREAM" ]; then
|
|
118
|
+
AHEAD=$(git --no-optional-locks -C "$CWD" rev-list --count "$UPSTREAM..HEAD" 2>/dev/null)
|
|
119
|
+
BEHIND=$(git --no-optional-locks -C "$CWD" rev-list --count "HEAD..$UPSTREAM" 2>/dev/null)
|
|
120
|
+
[ "${AHEAD:-0}" -gt 0 ] 2>/dev/null && AHEAD_BEHIND=" +${AHEAD}"
|
|
121
|
+
[ "${BEHIND:-0}" -gt 0 ] 2>/dev/null && AHEAD_BEHIND="${AHEAD_BEHIND} -${BEHIND}"
|
|
122
|
+
fi
|
|
123
|
+
|
|
68
124
|
if [ -n "$BRANCH" ]; then
|
|
69
|
-
GIT_SEG="$REPO:$BRANCH"
|
|
125
|
+
GIT_SEG="${REPO}:${BRANCH}${DIRTY}${AHEAD_BEHIND}"
|
|
70
126
|
else
|
|
71
127
|
GIT_SEG="$REPO"
|
|
72
128
|
fi
|
|
73
129
|
fi
|
|
74
130
|
fi
|
|
75
131
|
|
|
76
|
-
#
|
|
132
|
+
# assignment — project/slug — title (or standalone/uuid-prefix — title)
|
|
77
133
|
ASSIGNMENT_SEG=""
|
|
78
134
|
CONTEXT_FILE="$CWD/.syntaur/context.json"
|
|
79
135
|
if [ -f "$CONTEXT_FILE" ]; then
|
|
80
136
|
PROJECT_SLUG=$(jq -r '.projectSlug // empty' "$CONTEXT_FILE" 2>/dev/null)
|
|
81
137
|
ASSIGNMENT_SLUG=$(jq -r '.assignmentSlug // empty' "$CONTEXT_FILE" 2>/dev/null)
|
|
82
138
|
ASSIGNMENT_DIR=$(jq -r '.assignmentDir // empty' "$CONTEXT_FILE" 2>/dev/null)
|
|
83
|
-
|
|
84
139
|
TITLE=""
|
|
85
140
|
if [ -n "$ASSIGNMENT_DIR" ] && [ -f "$ASSIGNMENT_DIR/assignment.md" ]; then
|
|
86
141
|
TITLE=$(awk '/^title:/{sub(/^title:[[:space:]]*"?/,""); sub(/"?[[:space:]]*$/,""); print; exit}' "$ASSIGNMENT_DIR/assignment.md" 2>/dev/null)
|
|
87
142
|
fi
|
|
88
|
-
|
|
89
143
|
LABEL=""
|
|
90
144
|
if [ -n "$PROJECT_SLUG" ] && [ -n "$ASSIGNMENT_SLUG" ]; then
|
|
91
145
|
LABEL="$PROJECT_SLUG/$ASSIGNMENT_SLUG"
|
|
92
146
|
elif [ -n "$ASSIGNMENT_SLUG" ]; then
|
|
93
147
|
LABEL="standalone/${ASSIGNMENT_SLUG:0:8}"
|
|
94
148
|
fi
|
|
95
|
-
|
|
96
149
|
if [ -n "$LABEL" ] && [ -n "$TITLE" ]; then
|
|
97
150
|
ASSIGNMENT_SEG="$LABEL — $TITLE"
|
|
98
151
|
elif [ -n "$LABEL" ]; then
|
|
@@ -100,7 +153,7 @@ if [ -f "$CONTEXT_FILE" ]; then
|
|
|
100
153
|
fi
|
|
101
154
|
fi
|
|
102
155
|
|
|
103
|
-
#
|
|
156
|
+
# session — last 8 chars
|
|
104
157
|
SESSION_SEG=""
|
|
105
158
|
if [ -n "$SESSION_ID" ]; then
|
|
106
159
|
LEN=${#SESSION_ID}
|
|
@@ -111,23 +164,61 @@ if [ -n "$SESSION_ID" ]; then
|
|
|
111
164
|
fi
|
|
112
165
|
fi
|
|
113
166
|
|
|
114
|
-
#
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
167
|
+
# model
|
|
168
|
+
MODEL_SEG=""
|
|
169
|
+
[ -n "$MODEL" ] && MODEL_SEG="$MODEL"
|
|
170
|
+
|
|
171
|
+
# ctx — fill bar
|
|
172
|
+
CTX_SEG=""
|
|
173
|
+
if [ -n "$USED_PCT" ]; then
|
|
174
|
+
USED_INT=$(printf "%.0f" "$USED_PCT" 2>/dev/null || echo "$USED_PCT")
|
|
175
|
+
if [ -n "$USED_INT" ] && [ "$USED_INT" -ge 0 ] 2>/dev/null; then
|
|
176
|
+
FILLED=$(( USED_INT / 10 ))
|
|
177
|
+
[ "$FILLED" -gt 10 ] && FILLED=10
|
|
178
|
+
EMPTY=$(( 10 - FILLED ))
|
|
179
|
+
BAR=""
|
|
180
|
+
i=0
|
|
181
|
+
while [ "$i" -lt "$FILLED" ]; do BAR="${BAR}#"; i=$((i+1)); done
|
|
182
|
+
i=0
|
|
183
|
+
while [ "$i" -lt "$EMPTY" ]; do BAR="${BAR}-"; i=$((i+1)); done
|
|
184
|
+
CTX_SEG="ctx:[${BAR}] ${USED_INT}%"
|
|
185
|
+
fi
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
# cwd — basename
|
|
189
|
+
CWD_SEG=""
|
|
190
|
+
[ -n "$CWD" ] && CWD_SEG=$(basename "$CWD")
|
|
191
|
+
|
|
192
|
+
# --- Emit the selected segments in order ---
|
|
193
|
+
OUT=""
|
|
194
|
+
# Split SEGMENTS_RAW on commas using IFS.
|
|
195
|
+
OLD_IFS="$IFS"
|
|
196
|
+
IFS=','
|
|
197
|
+
for name in $SEGMENTS_RAW; do
|
|
198
|
+
IFS="$OLD_IFS"
|
|
199
|
+
# Trim whitespace
|
|
200
|
+
name=$(printf '%s' "$name" | awk '{$1=$1; print}')
|
|
201
|
+
value=""
|
|
202
|
+
case "$name" in
|
|
203
|
+
wrap) value="$WRAP_SEG" ;;
|
|
204
|
+
git) value="$GIT_SEG" ;;
|
|
205
|
+
assignment) value="$ASSIGNMENT_SEG" ;;
|
|
206
|
+
session) value="$SESSION_SEG" ;;
|
|
207
|
+
model) value="$MODEL_SEG" ;;
|
|
208
|
+
ctx) value="$CTX_SEG" ;;
|
|
209
|
+
cwd) value="$CWD_SEG" ;;
|
|
210
|
+
*) value="" ;;
|
|
211
|
+
esac
|
|
212
|
+
if [ -n "$value" ]; then
|
|
213
|
+
if [ -z "$OUT" ]; then
|
|
214
|
+
OUT="$value"
|
|
120
215
|
else
|
|
121
|
-
|
|
216
|
+
OUT="${OUT}${SEPARATOR}${value}"
|
|
122
217
|
fi
|
|
123
218
|
fi
|
|
219
|
+
IFS=','
|
|
124
220
|
done
|
|
221
|
+
IFS="$OLD_IFS"
|
|
125
222
|
|
|
126
|
-
|
|
127
|
-
printf '%s · %s' "$WRAPPED_OUT" "$SYNTAUR_PARTS"
|
|
128
|
-
elif [ -n "$WRAPPED_OUT" ]; then
|
|
129
|
-
printf '%s' "$WRAPPED_OUT"
|
|
130
|
-
else
|
|
131
|
-
printf '%s' "$SYNTAUR_PARTS"
|
|
132
|
-
fi
|
|
223
|
+
printf '%s' "$OUT"
|
|
133
224
|
exit 0
|