coder-config 0.54.3-beta → 0.54.5
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/lib/constants.js +1 -1
- package/lib/workstreams.js +12 -1
- package/package.json +1 -1
- package/ui/routes/statuslines.js +222 -5
- package/ui/server.cjs +7 -0
package/lib/constants.js
CHANGED
package/lib/workstreams.js
CHANGED
|
@@ -1675,7 +1675,18 @@ _coder_workstream_cd() {
|
|
|
1675
1675
|
[ -z "$result" ] && return 0
|
|
1676
1676
|
count=$(echo "$result" | grep -o '"count":[0-9]*' | cut -d: -f2)
|
|
1677
1677
|
current=$(echo "$result" | grep -o '"current":true' || echo "")
|
|
1678
|
-
[ -n "$current" ]
|
|
1678
|
+
if [ -n "$current" ]; then
|
|
1679
|
+
# Already on this workstream — silently sync the color env var if it drifted
|
|
1680
|
+
# (e.g. workstream activated before color was added, or color edited since).
|
|
1681
|
+
local cur_color
|
|
1682
|
+
cur_color=$(echo "$result" | grep -o '"color":"[^"]*"' | head -1 | cut -d'"' -f4)
|
|
1683
|
+
if [ -n "$cur_color" ]; then
|
|
1684
|
+
[ "$CODER_WORKSTREAM_COLOR" != "$cur_color" ] && export CODER_WORKSTREAM_COLOR="$cur_color"
|
|
1685
|
+
else
|
|
1686
|
+
[ -n "$CODER_WORKSTREAM_COLOR" ] && unset CODER_WORKSTREAM_COLOR
|
|
1687
|
+
fi
|
|
1688
|
+
return 0
|
|
1689
|
+
fi
|
|
1679
1690
|
[ "$count" = "0" ] && return 0
|
|
1680
1691
|
if [ "$count" = "1" ]; then
|
|
1681
1692
|
local name id color
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coder-config",
|
|
3
|
-
"version": "0.54.
|
|
3
|
+
"version": "0.54.5",
|
|
4
4
|
"description": "Configuration manager for AI coding tools - Claude Code, Gemini CLI, Codex CLI, Antigravity. Manage MCPs, rules, permissions, memory, and workstreams.",
|
|
5
5
|
"author": "regression.io",
|
|
6
6
|
"main": "config-loader.js",
|
package/ui/routes/statuslines.js
CHANGED
|
@@ -62,7 +62,115 @@ echo "$OUT"
|
|
|
62
62
|
`,
|
|
63
63
|
|
|
64
64
|
full: `#!/bin/bash
|
|
65
|
-
# Full: model,
|
|
65
|
+
# Full: model, context bar, 5H/7D rate-limit bars, lines, branch, cost, workstream
|
|
66
|
+
input=$(cat)
|
|
67
|
+
MODEL=$(echo "$input" | jq -r '.model.display_name // "?"')
|
|
68
|
+
MODEL_SHORT=$(echo "$MODEL" | cut -c1-12)
|
|
69
|
+
[ "\${#MODEL}" -gt 12 ] && MODEL_SHORT="\${MODEL_SHORT}…"
|
|
70
|
+
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
|
|
71
|
+
CTX_USED=$(echo "$input" | jq -r '((.context_window.current_usage.input_tokens // 0) + (.context_window.current_usage.cache_creation_input_tokens // 0) + (.context_window.current_usage.cache_read_input_tokens // 0))')
|
|
72
|
+
CTX_MAX=$(echo "$input" | jq -r '.context_window.context_window_size // 200000')
|
|
73
|
+
LINES_ADD=$(echo "$input" | jq -r '.cost.total_lines_added // 0')
|
|
74
|
+
LINES_REM=$(echo "$input" | jq -r '.cost.total_lines_removed // 0')
|
|
75
|
+
COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
|
|
76
|
+
RL_5H_PCT=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty' | cut -d. -f1)
|
|
77
|
+
RL_5H_RESET=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty')
|
|
78
|
+
RL_7D_PCT=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty' | cut -d. -f1)
|
|
79
|
+
|
|
80
|
+
GREEN='\\033[32m'; YELLOW='\\033[33m'; RED='\\033[31m'
|
|
81
|
+
CYAN='\\033[36m'; DIM='\\033[2m'; RESET='\\033[0m'
|
|
82
|
+
[ "$PCT" -ge 90 ] && BAR_COLOR="$RED" || { [ "$PCT" -ge 70 ] && BAR_COLOR="$YELLOW" || BAR_COLOR="$GREEN"; }
|
|
83
|
+
|
|
84
|
+
FILLED=$((PCT / 10)); EMPTY=$((10 - FILLED))
|
|
85
|
+
BAR=""; [ "$FILLED" -gt 0 ] && BAR="$BAR$(printf '●%.0s' $(seq 1 $FILLED))"
|
|
86
|
+
[ "$EMPTY" -gt 0 ] && BAR="$BAR$(printf '○%.0s' $(seq 1 $EMPTY))"
|
|
87
|
+
|
|
88
|
+
make_block_bar() {
|
|
89
|
+
local pct=\$1 color=\$2 width=8
|
|
90
|
+
[ -z "\$pct" ] && pct=0
|
|
91
|
+
local filled=\$((pct * width / 100))
|
|
92
|
+
[ "\$filled" -gt "\$width" ] && filled=\$width
|
|
93
|
+
[ "\$filled" -eq 0 ] && [ "\$pct" -gt 0 ] && filled=1
|
|
94
|
+
local empty=\$((width - filled))
|
|
95
|
+
local out=""
|
|
96
|
+
[ "\$filled" -gt 0 ] && out="\${color}\$(printf '▰%.0s' \$(seq 1 \$filled))\${RESET}"
|
|
97
|
+
[ "\$empty" -gt 0 ] && out="\$out\${DIM}\$(printf '▰%.0s' \$(seq 1 \$empty))\${RESET}"
|
|
98
|
+
printf '%s' "\$out"
|
|
99
|
+
}
|
|
100
|
+
time_until_hm() {
|
|
101
|
+
local resets=\$1
|
|
102
|
+
local now; now=\$(date +%s)
|
|
103
|
+
local diff=\$((resets - now))
|
|
104
|
+
[ "\$diff" -lt 0 ] && diff=0
|
|
105
|
+
local h=\$((diff / 3600))
|
|
106
|
+
local m=\$(((diff % 3600) / 60))
|
|
107
|
+
printf '%dH %dM' "\$h" "\$m"
|
|
108
|
+
}
|
|
109
|
+
rl_color() {
|
|
110
|
+
local pct=\$1
|
|
111
|
+
[ -z "\$pct" ] && { printf '%s' "$GREEN"; return; }
|
|
112
|
+
if [ "\$pct" -ge 90 ]; then printf '%s' "$RED"
|
|
113
|
+
elif [ "\$pct" -ge 70 ]; then printf '%s' "$YELLOW"
|
|
114
|
+
else printf '%s' "$GREEN"; fi
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fmt_tokens() {
|
|
118
|
+
local n=\$1
|
|
119
|
+
if [ "\$n" -ge 1000000 ]; then
|
|
120
|
+
awk "BEGIN {v=\$n/1000000; if (v == int(v)) printf \\"%dM\\", v; else printf \\"%.1fM\\", v}"
|
|
121
|
+
else
|
|
122
|
+
local k=\$(( (n + 500) / 1000 ))
|
|
123
|
+
if [ "\$k" -ge 1000 ]; then
|
|
124
|
+
awk "BEGIN {v=\$k/1000; if (v == int(v)) printf \\"%dM\\", v; else printf \\"%.1fM\\", v}"
|
|
125
|
+
else
|
|
126
|
+
printf '%dK' "\$k"
|
|
127
|
+
fi
|
|
128
|
+
fi
|
|
129
|
+
}
|
|
130
|
+
CTX_K=$(fmt_tokens $CTX_USED)
|
|
131
|
+
MAX_K=$(fmt_tokens $CTX_MAX)
|
|
132
|
+
COST_FMT=$(printf '$%.3f' $COST)
|
|
133
|
+
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo '')
|
|
134
|
+
|
|
135
|
+
WS_TAG=""
|
|
136
|
+
if [ -n "$CODER_WORKSTREAM" ]; then
|
|
137
|
+
case "$CODER_WORKSTREAM_COLOR" in
|
|
138
|
+
red) WS_COLOR='\\033[38;5;203m' ;;
|
|
139
|
+
orange) WS_COLOR='\\033[38;5;208m' ;;
|
|
140
|
+
yellow) WS_COLOR='\\033[38;5;221m' ;;
|
|
141
|
+
green) WS_COLOR='\\033[38;5;120m' ;;
|
|
142
|
+
cyan) WS_COLOR='\\033[38;5;87m' ;;
|
|
143
|
+
blue) WS_COLOR='\\033[38;5;111m' ;;
|
|
144
|
+
purple) WS_COLOR='\\033[38;5;177m' ;;
|
|
145
|
+
pink) WS_COLOR='\\033[38;5;213m' ;;
|
|
146
|
+
gray) WS_COLOR='\\033[38;5;245m' ;;
|
|
147
|
+
*) WS_COLOR="$CYAN" ;;
|
|
148
|
+
esac
|
|
149
|
+
WS_TAG=" | \${WS_COLOR}◆ \${CODER_WORKSTREAM}\${RESET}"
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
OUT="\${CYAN}\${MODEL_SHORT}\${RESET} \${BAR_COLOR}\${BAR}\${RESET} \${DIM}\${CTX_K}/\${MAX_K}\${RESET} (\${PCT}%)"
|
|
153
|
+
|
|
154
|
+
if [ -n "$RL_5H_PCT" ]; then
|
|
155
|
+
C5=$(rl_color "$RL_5H_PCT")
|
|
156
|
+
B5=$(make_block_bar "$RL_5H_PCT" "$C5")
|
|
157
|
+
OUT="$OUT | \${DIM}5H:\${RESET} \${B5} \${RL_5H_PCT}%"
|
|
158
|
+
[ -n "$RL_5H_RESET" ] && OUT="$OUT \${DIM}$(time_until_hm "$RL_5H_RESET")\${RESET}"
|
|
159
|
+
fi
|
|
160
|
+
if [ -n "$RL_7D_PCT" ]; then
|
|
161
|
+
C7=$(rl_color "$RL_7D_PCT")
|
|
162
|
+
B7=$(make_block_bar "$RL_7D_PCT" "$C7")
|
|
163
|
+
OUT="$OUT | \${DIM}7D:\${RESET} \${B7} \${RL_7D_PCT}%"
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
[ "$LINES_ADD" != "0" ] || [ "$LINES_REM" != "0" ] && OUT="$OUT | \${GREEN}+\${LINES_ADD}\${RESET} \${RED}-\${LINES_REM}\${RESET}"
|
|
167
|
+
[ -n "$BRANCH" ] && OUT="$OUT | \${CYAN}\${BRANCH}\${RESET}"
|
|
168
|
+
OUT="$OUT | \${YELLOW}\${COST_FMT}\${RESET}\${WS_TAG}"
|
|
169
|
+
echo -e "$OUT"
|
|
170
|
+
`,
|
|
171
|
+
|
|
172
|
+
classic: `#!/bin/bash
|
|
173
|
+
# Classic: model, colored context bar, token counts, lines, branch, duration, cost, workstream
|
|
66
174
|
input=$(cat)
|
|
67
175
|
MODEL=$(echo "$input" | jq -r '.model.display_name // "?"')
|
|
68
176
|
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
|
|
@@ -88,8 +196,6 @@ HOURS=$((DUR_MS / 3600000)); MINS=$(((DUR_MS % 3600000) / 60000))
|
|
|
88
196
|
COST_FMT=$(printf '$%.3f' $COST)
|
|
89
197
|
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo '')
|
|
90
198
|
|
|
91
|
-
# Workstream tag (if active). CODER_WORKSTREAM and CODER_WORKSTREAM_COLOR
|
|
92
|
-
# are set by coder-config when a workstream is activated.
|
|
93
199
|
WS_TAG=""
|
|
94
200
|
if [ -n "$CODER_WORKSTREAM" ]; then
|
|
95
201
|
case "$CODER_WORKSTREAM_COLOR" in
|
|
@@ -188,8 +294,15 @@ const PRESETS = [
|
|
|
188
294
|
{
|
|
189
295
|
id: 'full',
|
|
190
296
|
name: 'Full',
|
|
191
|
-
description: '
|
|
192
|
-
preview: '
|
|
297
|
+
description: 'Model, context bar, 5H/7D rate-limit bars with reset timers, lines, branch, cost, workstream',
|
|
298
|
+
preview: 'Opus 4.7 ●●●○○○○○○○ 74K/1M (37%) | 5H: ▰▰▰▰▰▰▰▰ 42% 2H 29M | 7D: ▰▰▰▰▰▰▰▰ 15% | +146 -13 | main | $0.142',
|
|
299
|
+
category: 'Git',
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
id: 'classic',
|
|
303
|
+
name: 'Classic',
|
|
304
|
+
description: 'Original full layout: model, context bar, token counts, lines, branch, duration, cost, workstream',
|
|
305
|
+
preview: '* Opus 4.7 | ●●●●○○○○○○ 74.4K/200.0K | 63% left | +146 -13 | main | 5h 2m | $0.142 | ◆ coder-config',
|
|
193
306
|
category: 'Git',
|
|
194
307
|
},
|
|
195
308
|
{
|
|
@@ -251,11 +364,114 @@ function ensureScriptDir() {
|
|
|
251
364
|
function writeScript(presetId, content) {
|
|
252
365
|
ensureScriptDir();
|
|
253
366
|
const p = scriptPath(presetId);
|
|
367
|
+
// Backup any existing script before overwriting (timestamped, never collides).
|
|
368
|
+
if (fs.existsSync(p)) {
|
|
369
|
+
const existing = fs.readFileSync(p, 'utf8');
|
|
370
|
+
if (existing !== content) {
|
|
371
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
372
|
+
fs.writeFileSync(`${p}.${ts}.bak`, existing, 'utf8');
|
|
373
|
+
}
|
|
374
|
+
}
|
|
254
375
|
fs.writeFileSync(p, content, 'utf8');
|
|
255
376
|
fs.chmodSync(p, 0o755);
|
|
377
|
+
// Track this exact content as a "known shipped version" so future
|
|
378
|
+
// auto-migrations can tell user edits apart from old templates.
|
|
379
|
+
recordShippedHash(presetId, content);
|
|
256
380
|
return p;
|
|
257
381
|
}
|
|
258
382
|
|
|
383
|
+
// ---------------------------------------------------------------------------
|
|
384
|
+
// Auto-migration: refresh installed preset scripts when the bundled template
|
|
385
|
+
// changes between versions, but only if the on-disk script matches one of the
|
|
386
|
+
// previously-shipped versions (i.e. the user hasn't customized it).
|
|
387
|
+
// ---------------------------------------------------------------------------
|
|
388
|
+
|
|
389
|
+
const SHIPPED_HASHES_FILE = path.join(STATUSLINES_DIR, '.shipped-hashes.json');
|
|
390
|
+
|
|
391
|
+
// Historical content hashes for each preset, seeded so users who installed a
|
|
392
|
+
// preset before the hash-tracking system was added still get auto-migrated.
|
|
393
|
+
// Append (never edit) when a preset's bundled template changes.
|
|
394
|
+
const LEGACY_HASHES = {
|
|
395
|
+
full: [
|
|
396
|
+
'0ac48c7d993dd0c6e49b1f537934e418a5e65b0838afc587bafde31cc45877da', // pre-2026-04 model+duration variant
|
|
397
|
+
],
|
|
398
|
+
minimal: ['d818c74b391732f5aae948fbc9c154b6cba8e56952a1727d3dfa78602823c601'],
|
|
399
|
+
'context-bar': ['643bf0f54baa70ec72d85f8800dee24712c31eae8b6b0030e597d0c1e4ed0ae0'],
|
|
400
|
+
'git-context': ['b186ea21e4d14fb905f8682e5dfe7b3b2050a93b7801226002052aaa7bafd451'],
|
|
401
|
+
'cost-tracker': ['83364c860744a6cf7fb5f07b061ae13a1919fc7e72f35597dd383e2de3371895'],
|
|
402
|
+
multiline: ['49939b21f4691171bcfbe5c0ca252bcf9ffb772f335a14712bb9beccf67920b9'],
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
function sha256(s) {
|
|
406
|
+
return require('crypto').createHash('sha256').update(s).digest('hex');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function loadShippedHashes() {
|
|
410
|
+
if (!fs.existsSync(SHIPPED_HASHES_FILE)) return {};
|
|
411
|
+
try { return JSON.parse(fs.readFileSync(SHIPPED_HASHES_FILE, 'utf8')); } catch { return {}; }
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function saveShippedHashes(data) {
|
|
415
|
+
ensureScriptDir();
|
|
416
|
+
fs.writeFileSync(SHIPPED_HASHES_FILE, JSON.stringify(data, null, 2), 'utf8');
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function recordShippedHash(presetId, content) {
|
|
420
|
+
const data = loadShippedHashes();
|
|
421
|
+
const list = data[presetId] || [];
|
|
422
|
+
const h = sha256(content);
|
|
423
|
+
if (!list.includes(h)) {
|
|
424
|
+
list.push(h);
|
|
425
|
+
data[presetId] = list;
|
|
426
|
+
try { saveShippedHashes(data); } catch {}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Run on server startup. For each preset whose script is installed AND whose
|
|
432
|
+
* settings.json points at it, refresh the file from the latest bundled template
|
|
433
|
+
* if the on-disk hash matches a previously-shipped version. User-edited scripts
|
|
434
|
+
* (unknown hash) are left alone.
|
|
435
|
+
*/
|
|
436
|
+
function autoMigratePresets() {
|
|
437
|
+
try {
|
|
438
|
+
const settings = readSettings();
|
|
439
|
+
const cmd = commandPathInSettings(settings);
|
|
440
|
+
if (!cmd) return;
|
|
441
|
+
const presetId = matchPresetFromCommand(cmd);
|
|
442
|
+
if (presetId === 'disabled' || presetId === 'custom') return;
|
|
443
|
+
|
|
444
|
+
const latest = SCRIPTS[presetId];
|
|
445
|
+
if (!latest) return;
|
|
446
|
+
const p = scriptPath(presetId);
|
|
447
|
+
if (!fs.existsSync(p)) return;
|
|
448
|
+
|
|
449
|
+
const onDisk = fs.readFileSync(p, 'utf8');
|
|
450
|
+
if (onDisk === latest) {
|
|
451
|
+
// Already up-to-date — make sure its hash is recorded.
|
|
452
|
+
recordShippedHash(presetId, latest);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const shipped = [
|
|
457
|
+
...(loadShippedHashes()[presetId] || []),
|
|
458
|
+
...(LEGACY_HASHES[presetId] || []),
|
|
459
|
+
];
|
|
460
|
+
const onDiskHash = sha256(onDisk);
|
|
461
|
+
if (!shipped.includes(onDiskHash)) {
|
|
462
|
+
// User-edited — record latest as known but don't overwrite.
|
|
463
|
+
recordShippedHash(presetId, latest);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Safe to refresh: writeScript backs up + records new hash.
|
|
468
|
+
writeScript(presetId, latest);
|
|
469
|
+
console.log(`[statuslines] auto-migrated preset "${presetId}" to latest template`);
|
|
470
|
+
} catch (e) {
|
|
471
|
+
// Never let migration crash startup.
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
259
475
|
function commandPathInSettings(settings) {
|
|
260
476
|
return settings?.statusLine?.command || null;
|
|
261
477
|
}
|
|
@@ -357,4 +573,5 @@ module.exports = {
|
|
|
357
573
|
getCurrentStatusline,
|
|
358
574
|
getPresetScript,
|
|
359
575
|
setStatusline,
|
|
576
|
+
autoMigratePresets,
|
|
360
577
|
};
|
package/ui/server.cjs
CHANGED
|
@@ -303,6 +303,13 @@ class ConfigUIServer {
|
|
|
303
303
|
} catch (e) {
|
|
304
304
|
// Ignore migration errors
|
|
305
305
|
}
|
|
306
|
+
try {
|
|
307
|
+
// Refresh installed statusline preset scripts when bundled templates
|
|
308
|
+
// change between versions (skipped if user has customized the script).
|
|
309
|
+
routes.statuslines.autoMigratePresets();
|
|
310
|
+
} catch (e) {
|
|
311
|
+
// Ignore migration errors
|
|
312
|
+
}
|
|
306
313
|
}
|
|
307
314
|
|
|
308
315
|
async handleRequest(req, res) {
|