[Sync] LLM clients → Qwen3.6-35B-A3B-Thinking default, Ornith preset, fl GPU monitor abbrs
Re-export of private HEAD 73b4a65: default-model flip + 8k thinking budget in the aichat/opencode/pi client configs, the Ornith-1.5-35B-A3B preset and its gate bench in fl/.config/llamacpp, and the fl fish abbrs `vram` / `vramx` with the new gpumon.sh (sysfs VRAM/GTT monitor).
This commit is contained in:
1 parent
2fa113cd15
commit
93b4f695ce
15 files changed
+340
-18
No files matched your search
@@ -8,7 +8,7 @@ resize), trailing (lowest-priority) segments are dropped from the right until it
|
||||
Wide — everything:
|
||||
|
||||
```
|
||||
📁 ~/.dots │ 🌿 .dots main* │ 🤖 Fable 5 (1M) · xhigh │ 📝 84% (843k) │ 📊 34% 2h54m │ 💰 $0.40 $12.00/h │ ♻️ 91% 1h │ 💾 81%
|
||||
📁 ~/.dots │ 🌿 .dots main* │ 🤖 Fable 5 (1M) · xhigh │ 📝 84% (843k) │ 📊 34% 2h54m │ 💰 $0.40 $12.00/h │ 🔢 ↑63M ↓595k │ ♻️ 91% 1h │ 💾 81%
|
||||
```
|
||||
|
||||
Narrow — trailing segments trimmed:
|
||||
@@ -32,6 +32,7 @@ is supplementary to the main colored value.
|
||||
| 📝 | **Context** | `% of context window used` + `(Nk)` tokens | usage comes from the payload's `context_window` (`used_percentage` + `current_usage`, Claude Code 2.1.x); the transcript's last `message.usage` is only a fallback. Window from `context_window.context_window_size`, then `(1M …)` in the display name, else 200k — **not** from `model.id`, which arrives with any `[1m]` suffix already stripped. Adds a red **⚠compact** at ≥80% |
|
||||
| 📊 | **5h usage** | `% of the 5-hour rolling limit used` + time until it resets | from `rate_limits.five_hour`; Pro/Max only, and absent until the first API response of a session |
|
||||
| 💰 | **Cost** | `$` session cost so far + `$/h` burn rate | burn rate shown once the session exceeds ~30s |
|
||||
| 🔢 | **Session tokens** | `↑` input + `↓` output tokens used so far this session (cumulative, `k`/`M`) | summed over the transcript's assistant `message.usage` blocks, de-duplicated by message id (one line per content block shares the same usage). Input = `input + cache_creation + cache_read` — everything billed as input, so on a 1M session it dwarfs output (every call re-reads the cached prefix). Main transcript only: subagent usage lives in separate sidechain files. Incremental: a per-session state file `~/.claude/cache/statusline.tokens.<session>.json` remembers the byte offset already summed, so each refresh parses only the new tail (cold scan of a 6.5 MB transcript ≈ 70 ms, warm ≈ 0.1 ms); stale state files are pruned after 7 days once 50+ pile up. Omitted when there is no transcript; `↑` cyan, `↓` green, no thresholds |
|
||||
| ♻️ | **Prompt cache** | hit `%` (higher is better) + cache TTL (`5m` / `1h`), yellow `·cold` once it has expired | from the payload's `prompt_cache` (2.1.260+: `hit_ratio` 0–1, `ttl`, `warm`); computed from usage on older payloads. A `1h` TTL is what keeps a 10-min `/pr-loop` cadence warm |
|
||||
| 💾 | **Disk** | `% used` of the filesystem at the cwd | from `statvfs` |
|
||||
|
||||
|
||||
@@ -5,15 +5,21 @@
|
||||
{
|
||||
"context": "Chat",
|
||||
"bindings": {
|
||||
"shift+enter": "chat:newline"
|
||||
"shift+enter": "chat:newline",
|
||||
"ctrl+x v": "app:toggleTranscript"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Transcript",
|
||||
"bindings": {
|
||||
"ctrl+x v": "app:toggleTranscript"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Global",
|
||||
"bindings": {
|
||||
"alt+j": "scroll:lineDown",
|
||||
"alt+k": "scroll:lineUp",
|
||||
"ctrl+x v": "app:toggleTranscript"
|
||||
"alt+k": "scroll:lineUp"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
One line: vim mode | dir (repo-relative, hidden at the root) |
|
||||
git(repo/branch +dirty +ahead/behind) | model (+effort) |
|
||||
context% (+compact warn) | 5h usage (+reset eta) | cost (+burn rate) |
|
||||
prompt-cache hit% (+ttl, cold marker) | disk. If the
|
||||
session tokens (cumulative in/out) | prompt-cache hit% (+ttl, cold marker) | disk. If the
|
||||
rendered line would overflow the terminal, trailing (lowest-priority) segments are
|
||||
dropped until it fits.
|
||||
(Dormant, re-addable in main(): ram, cpu%, temp -- retired 2026-07-26 because the
|
||||
@@ -156,6 +156,133 @@ def last_usage(data):
|
||||
return {}
|
||||
|
||||
|
||||
# --- session token totals (transcript, incremental) ---------------------------
|
||||
TOKENS_STATE_DIR = os.path.expanduser("~/.claude/cache")
|
||||
TOKENS_RECENT_IDS = 20 # message ids remembered across runs for de-duplication
|
||||
TOKENS_STATE_MAX_AGE = 7 * 86400 # prune per-session state files older than this
|
||||
|
||||
|
||||
def fmt_tokens(n):
|
||||
"""1234 -> 1.2k, 843000 -> 843k, 1_234_567 -> 1.2M, 12_345_678 -> 12M.
|
||||
|
||||
Thresholds sit at the rounding boundaries (0.9995, 9.95) so a value never prints as
|
||||
"10.0k" or "1000k" -- it rolls into the shorter form / the next unit instead."""
|
||||
n = float(n)
|
||||
for unit, div in (("M", 1e6), ("k", 1e3)):
|
||||
v = n / div
|
||||
if v < 0.9995:
|
||||
continue
|
||||
return f"{v:.0f}{unit}" if v >= 9.95 else f"{v:.1f}{unit}"
|
||||
return f"{n:.0f}"
|
||||
|
||||
|
||||
def _tokens_state_path(data, path):
|
||||
sid = data.get("session_id") or os.path.splitext(os.path.basename(path))[0]
|
||||
sid = re.sub(r"[^A-Za-z0-9_-]", "_", str(sid))[:80]
|
||||
return os.path.join(TOKENS_STATE_DIR, f"statusline.tokens.{sid}.json")
|
||||
|
||||
|
||||
def _prune_tokens_state(keep):
|
||||
"""Drop stale per-session state files (one scandir, only when the dir has piled up)."""
|
||||
try:
|
||||
entries = [e for e in os.scandir(TOKENS_STATE_DIR) if e.name.startswith("statusline.tokens.")]
|
||||
if len(entries) < 50:
|
||||
return
|
||||
cutoff = time.time() - TOKENS_STATE_MAX_AGE
|
||||
for e in entries:
|
||||
if e.path != keep and e.stat().st_mtime < cutoff:
|
||||
os.unlink(e.path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def session_tokens(data):
|
||||
"""Cumulative (input, output) tokens this session, summed over the transcript's
|
||||
assistant `message.usage` blocks -- or None when there is no transcript.
|
||||
|
||||
Input = input + cache_creation + cache_read (everything the API billed as input), so
|
||||
on a 1M session it dwarfs output: every call re-reads the whole cached prefix.
|
||||
Only the main transcript is counted; subagent usage lives in separate sidechain files.
|
||||
|
||||
Incremental: the status line runs on every refresh and transcripts grow to tens of
|
||||
MB, so a per-session state file (~/.claude/cache/statusline.tokens.<session>.json)
|
||||
remembers the byte offset already summed and only the new tail is parsed. Lines are
|
||||
de-duplicated by message id: the transcript writes one line per content block of a
|
||||
turn, all carrying the same id and the same usage block. Only complete lines are
|
||||
consumed (the writer may be mid-line); a shrunken file (truncate/rotate) resets.
|
||||
"""
|
||||
path = data.get("transcript_path")
|
||||
if not path or not os.path.exists(path):
|
||||
return None
|
||||
state_path = _tokens_state_path(data, path)
|
||||
state = {"path": path, "offset": 0, "in": 0, "out": 0, "ids": []}
|
||||
try:
|
||||
with open(state_path) as fh:
|
||||
s = json.load(fh)
|
||||
if (
|
||||
isinstance(s, dict)
|
||||
and s.get("path") == path
|
||||
and all(isinstance(s.get(k), int) and s[k] >= 0 for k in ("offset", "in", "out"))
|
||||
and isinstance(s.get("ids"), list)
|
||||
):
|
||||
state = s
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
size = os.path.getsize(path)
|
||||
if size < state["offset"]: # truncated or rotated under us: start over
|
||||
state = {"path": path, "offset": 0, "in": 0, "out": 0, "ids": []}
|
||||
if size == state["offset"]:
|
||||
return state["in"], state["out"]
|
||||
|
||||
with open(path, "rb") as fh:
|
||||
fh.seek(state["offset"])
|
||||
chunk = fh.read()
|
||||
end = chunk.rfind(b"\n")
|
||||
if end < 0: # no complete new line yet
|
||||
return state["in"], state["out"]
|
||||
text = chunk[: end + 1].decode("utf-8", "ignore")
|
||||
|
||||
tin, tout = state["in"], state["out"]
|
||||
ids = [i for i in state["ids"] if isinstance(i, str)]
|
||||
seen = set(ids)
|
||||
for line in text.splitlines():
|
||||
if '"usage"' not in line:
|
||||
continue
|
||||
try:
|
||||
obj = json.loads(line)
|
||||
except Exception:
|
||||
continue
|
||||
msg = obj.get("message") if isinstance(obj, dict) else None
|
||||
usage = msg.get("usage") if isinstance(msg, dict) else None
|
||||
if not isinstance(usage, dict):
|
||||
continue
|
||||
mid = msg.get("id") or obj.get("requestId") or obj.get("uuid")
|
||||
if not isinstance(mid, str) or mid in seen:
|
||||
continue
|
||||
seen.add(mid)
|
||||
ids.append(mid)
|
||||
|
||||
def num(k):
|
||||
v = usage.get(k, 0)
|
||||
return int(v) if isinstance(v, (int, float)) else 0
|
||||
|
||||
tin += num("input_tokens") + num("cache_creation_input_tokens") + num("cache_read_input_tokens")
|
||||
tout += num("output_tokens")
|
||||
|
||||
state = {"path": path, "offset": state["offset"] + end + 1, "in": tin, "out": tout, "ids": ids[-TOKENS_RECENT_IDS:]}
|
||||
try:
|
||||
os.makedirs(TOKENS_STATE_DIR, exist_ok=True)
|
||||
tmp = f"{state_path}.{os.getpid()}.tmp"
|
||||
with open(tmp, "w") as fh:
|
||||
json.dump(state, fh)
|
||||
os.replace(tmp, state_path) # atomic: a concurrent refresh never reads a half-written file
|
||||
_prune_tokens_state(state_path)
|
||||
except Exception:
|
||||
pass
|
||||
return tin, tout
|
||||
|
||||
|
||||
# --- row 1: work ------------------------------------------------------------
|
||||
def vim_segment(data):
|
||||
"""Vim editor mode as a compact [I]/[N]/[V] (only present with editorMode: vim).
|
||||
@@ -376,6 +503,20 @@ def velocity_segment(data):
|
||||
return ""
|
||||
|
||||
|
||||
def tokens_segment(data):
|
||||
"""Cumulative session tokens: ↑ input (incl. cache reads/writes) and ↓ output."""
|
||||
try:
|
||||
totals = session_tokens(data)
|
||||
if not totals:
|
||||
return ""
|
||||
tin, tout = totals
|
||||
if not (tin or tout):
|
||||
return ""
|
||||
return "🔢 " + color(f"↑{fmt_tokens(tin)}", "cyan") + " " + color(f"↓{fmt_tokens(tout)}", "green")
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def cache_segment(usage, data=None):
|
||||
"""Prompt-cache hit % (higher is better) + the cache TTL, `·cold` when the cache has
|
||||
expired. Hit % from the 2.1.260+ payload `prompt_cache.hit_ratio` (a 0..1 fraction, null
|
||||
@@ -583,6 +724,7 @@ def main():
|
||||
context_segment(data, usage),
|
||||
usage_segment(data),
|
||||
cost_segment(data),
|
||||
tokens_segment(data),
|
||||
cache_segment(usage, data),
|
||||
) if s]
|
||||
system = [s for s in (
|
||||
|
||||
@@ -41,7 +41,7 @@ gui:
|
||||
# If true, wrap lines in the staging view to the width of the view. This
|
||||
# makes it much easier to work with diffs that have long lines, e.g.
|
||||
# paragraphs of markdown text.
|
||||
wrapLinesInStagingView: true
|
||||
wrapLinesInDiffView: true
|
||||
# One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
|
||||
language: auto
|
||||
# Format used when displaying time e.g. commit time.
|
||||
|
||||
@@ -30,7 +30,9 @@ pake
|
||||
bwrap
|
||||
dog
|
||||
nmap
|
||||
rustscan
|
||||
traceroute
|
||||
fuck
|
||||
atool
|
||||
aria2c
|
||||
|
||||
@@ -25,6 +25,7 @@ gittype
|
||||
toofan
|
||||
pastel
|
||||
hexyl
|
||||
binsider
|
||||
tmux
|
||||
zellij
|
||||
nvim
|
||||
@@ -61,7 +62,7 @@ bookokrat
|
||||
tennis
|
||||
llmfit
|
||||
termdraw
|
||||
torlink
|
||||
torlnk
|
||||
riptide
|
||||
quien
|
||||
eilmeldung
|
||||
@@ -73,3 +74,4 @@ netwatch
|
||||
gpk
|
||||
circumflex
|
||||
darya
|
||||
usbtree
|
||||
@@ -175,7 +175,8 @@ Beyond pointing at local models, these values deviate from pi's defaults:
|
||||
| `compaction.keepRecentTokens` | `4000` | 20000 | keep less verbatim, to pay for the larger reserve and leave room after a compaction for the next one |
|
||||
| `npmCommand` | `fnm exec --using=22 -- npm` | `npm` | pin extension npm installs to Node 22 via fnm |
|
||||
| `retry` / `httpIdleTimeoutMs` | long (1h provider timeout, 10m idle) | shorter | local models can be slow to first token |
|
||||
| `defaultThinkingLevel` | `off` | — | most local models here are non-reasoning |
|
||||
| `defaultThinkingLevel` | `medium` | — | the default model is a thinking preset (`Qwen3.6-35B-A3B-Thinking`); Alt+T cycles it per session |
|
||||
| `thinkingBudgets` | `512 / 1024 / 2048 / 3072` (minimal / low / medium / high) | 1024 / 2048 / 8192 / 16384 | per-request cap on the `<think>` block, sent only for models whose `compat.thinkingTokenBudgetField` is set (`Ornith`, as `thinking_budget_tokens`). pi clamps the budget to `maxTokens − 1024`, so on a 4096-token reply the stock values collapse to 3072 and leave the answer 1024 tokens — the recipe for "Response was truncated before completion." |
|
||||
| `packages` | `["npm:pi-vim"]` | `[]` | the modal editor — see the pi-vim section above |
|
||||
| `piVim` | mode colors, `clipboardMirror: "yank"`, `:` → pi bridge | — | package config, not a pi setting; ignored if the package is removed |
|
||||
| `collapseChangelog` | `true` | `false` | condensed changelog after an upgrade instead of the full screen |
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
"id": "Ornith-1.5-35B-A3B-IQ3_XXS",
|
||||
"name": "Ornith 1.5 35B · 24k · vision — agentic coder, thinking (~32 t/s)",
|
||||
"reasoning": true,
|
||||
"compat": { "thinkingFormat": "qwen-chat-template" },
|
||||
"compat": { "thinkingFormat": "qwen-chat-template", "thinkingTokenBudgetField": "thinking_budget_tokens" },
|
||||
"input": ["text", "image"],
|
||||
"contextWindow": 24576,
|
||||
"maxTokens": 4096,
|
||||
@@ -244,7 +244,7 @@
|
||||
"id": "Ornith-1.5-35B-A3B-IQ3_XXS",
|
||||
"name": "Ornith 1.5 35B · 24k · vision — agentic coder, thinking (~32 t/s)",
|
||||
"reasoning": true,
|
||||
"compat": { "thinkingFormat": "qwen-chat-template" },
|
||||
"compat": { "thinkingFormat": "qwen-chat-template", "thinkingTokenBudgetField": "thinking_budget_tokens" },
|
||||
"input": ["text", "image"],
|
||||
"contextWindow": 24576,
|
||||
"maxTokens": 4096,
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
{
|
||||
"defaultProvider": "duskadiy",
|
||||
"defaultModel": "Qwen3.6-35B-A3B-Thinking",
|
||||
"defaultProvider": "localcpp",
|
||||
"defaultModel": "Ornith-1.5-35B-A3B-IQ3_XXS",
|
||||
"defaultThinkingLevel": "medium",
|
||||
"thinkingBudgets": {
|
||||
"minimal": 512,
|
||||
"low": 1024,
|
||||
"medium": 2048,
|
||||
"high": 3072
|
||||
},
|
||||
"theme": "catppuccin-mocha",
|
||||
"quietStartup": false,
|
||||
"collapseChangelog": true,
|
||||
@@ -58,7 +64,7 @@
|
||||
"ex": "mode"
|
||||
}
|
||||
},
|
||||
"lastChangelogVersion": "0.85.1",
|
||||
"lastChangelogVersion": "1.0.0",
|
||||
"packages": [
|
||||
"npm:pi-vim"
|
||||
]
|
||||
|
||||
Reference in new issue
Block a user