The footer showed invented percentages. It now shows what two harnesses actually report, or says it does not know. lumbridge-core gains an append-only per-profile UsageLedger and a projection that labels every derived value estimated, withholds a burn rate from a single sample, withholds a window fraction with no reported ceiling, withholds an exhaustion estimate that lands after the reset, and reports an expired window as rolled over rather than freezing its last percentage. A missing fact renders as missing, never as zero. (0012) lumbridge-harness is the impure side: processes, clocks, and untrusted wire text in, observations out. Three adapters: - Codex's account/rateLimits/read over the app-server's JSON-RPC stdio. The client cannot express a request outside a two-variant enum and answers every server-to-client request with -32601, so a harness asking Lumbridge for a credential is refused by construction. (0013) - Claude Code's session transcripts, as a byte-offset tail follower that reports nothing until the backlog is read to EOF — a partially-read backlog is indistinguishable from a burst of spend, and the first run against 20 MB reported forty-six billion tokens an hour. The parser models four counters, so the conversations in those files are not representable. (0014) - Claude Code's five-hour and seven-day subscription windows, via a bridge installed as its statusLine command. 0014 had claimed no such surface existed; it does, and the record is corrected in place rather than quietly edited. Lumbridge does not read the OAuth credential to call the account usage endpoint, which is what comparable tools do — AGENTS.md forbids it, and 0015 says so rather than leaving the gap unexplained. Also in here: a capability-check ordering fix in the workspace reducer, where the applied-request replay table was consulted before the capability check and so answered questions the caller had no right to ask; the GPUI spike wired to the live probes with per-harness gauges and provenance chips; and a launcher that matches its own window by PID, because GPUI sets WM_NAME but not _NET_WM_NAME and a title match never succeeded. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
114 lines
3.9 KiB
Bash
Executable File
114 lines
3.9 KiB
Bash
Executable File
#!/bin/sh
|
|
# Installs the Lumbridge status-line bridge into Claude Code's settings.
|
|
#
|
|
# This is the one thing Lumbridge cannot do without touching the user's
|
|
# configuration, so it is a separate, explicit, reversible step rather than
|
|
# something a probe does on your behalf.
|
|
#
|
|
# It sets `statusLine` in $CLAUDE_CONFIG_DIR/settings.json (default
|
|
# ~/.claude/settings.json) to run scripts/claude-statusline-bridge.sh. Claude
|
|
# Code then pipes its rate-limit payload there on every turn, which is how
|
|
# Lumbridge learns the five-hour and seven-day subscription windows without
|
|
# ever reading a credential.
|
|
#
|
|
# install: scripts/install-claude-statusline.sh
|
|
# uninstall: scripts/install-claude-statusline.sh --uninstall
|
|
# preview: scripts/install-claude-statusline.sh --dry-run
|
|
#
|
|
# An existing statusLine is backed up and reported, never silently replaced.
|
|
|
|
set -eu
|
|
|
|
MODE=install
|
|
case "${1:-}" in
|
|
--uninstall) MODE=uninstall ;;
|
|
--dry-run) MODE=dry-run ;;
|
|
"") ;;
|
|
*) echo "usage: $0 [--uninstall|--dry-run]" >&2; exit 2 ;;
|
|
esac
|
|
|
|
CONFIG_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
|
|
SETTINGS="$CONFIG_DIR/settings.json"
|
|
HERE=$(cd "$(dirname "$0")" && pwd)
|
|
BRIDGE="$HERE/claude-statusline-bridge.sh"
|
|
|
|
if [ ! -x "$BRIDGE" ]; then
|
|
echo "bridge not executable: $BRIDGE" >&2
|
|
exit 1
|
|
fi
|
|
if ! command -v python3 >/dev/null 2>&1; then
|
|
echo "python3 is required to edit settings.json safely" >&2
|
|
exit 1
|
|
fi
|
|
|
|
python3 - "$SETTINGS" "$BRIDGE" "$MODE" <<'PYTHON'
|
|
import json
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import time
|
|
|
|
settings_path, bridge, mode = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
desired = {"type": "command", "command": bridge}
|
|
|
|
settings = {}
|
|
if os.path.exists(settings_path):
|
|
try:
|
|
with open(settings_path) as handle:
|
|
settings = json.load(handle)
|
|
except Exception as error:
|
|
print(f"refusing to edit unreadable settings: {error}", file=sys.stderr)
|
|
raise SystemExit(1)
|
|
if not isinstance(settings, dict):
|
|
print("refusing to edit: settings.json is not an object", file=sys.stderr)
|
|
raise SystemExit(1)
|
|
|
|
current = settings.get("statusLine")
|
|
|
|
if mode == "uninstall":
|
|
if isinstance(current, dict) and current.get("command") == bridge:
|
|
settings.pop("statusLine", None)
|
|
action = "removed the Lumbridge status line"
|
|
else:
|
|
print("no Lumbridge status line installed; nothing to do")
|
|
raise SystemExit(0)
|
|
elif isinstance(current, dict) and current.get("command") == bridge:
|
|
print("Lumbridge status line already installed; nothing to do")
|
|
raise SystemExit(0)
|
|
else:
|
|
if current is not None:
|
|
# Someone else's status line is here. Report it and stop rather than
|
|
# replacing a thing the user set up.
|
|
print("a different statusLine is already configured:", file=sys.stderr)
|
|
print(f" {json.dumps(current)}", file=sys.stderr)
|
|
print(
|
|
"remove it first, or merge the bridge into it by hand:\n"
|
|
f" {bridge}",
|
|
file=sys.stderr,
|
|
)
|
|
raise SystemExit(1)
|
|
settings["statusLine"] = desired
|
|
action = "installed the Lumbridge status line"
|
|
|
|
if mode == "dry-run":
|
|
print("would write:")
|
|
print(json.dumps({"statusLine": settings.get("statusLine")}, indent=2))
|
|
raise SystemExit(0)
|
|
|
|
os.makedirs(os.path.dirname(settings_path), exist_ok=True)
|
|
if os.path.exists(settings_path):
|
|
backup = f"{settings_path}.lumbridge-backup-{int(time.time())}"
|
|
shutil.copy2(settings_path, backup)
|
|
print(f"backed up existing settings to {backup}")
|
|
|
|
# Write via a temporary file so an interrupted run cannot leave settings.json
|
|
# truncated — this file configures the user's editor session.
|
|
temporary = f"{settings_path}.lumbridge-tmp"
|
|
with open(temporary, "w") as handle:
|
|
json.dump(settings, handle, indent=2)
|
|
handle.write("\n")
|
|
os.replace(temporary, settings_path)
|
|
print(f"{action} in {settings_path}")
|
|
print("restart or start a Claude Code session for it to take effect")
|
|
PYTHON
|