SLOPSHOPPER

statusline

Shows the main loop's model and its context window fill under the prompt

newspinner
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · statusline
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ⟨Claude Code's own drawing⟩ claude-opus-5-5 │ 49% 97400/200000

Draws

Prompt hint
⟨Claude Code's own drawing⟩ claude-opus-5-5 │ 49% 97400/200000
README

dotfiles

Configurations managed with Nix.

Installation

Follow the fresh Darwin installation guide. The flake has a private SSH input, so a new machine must commission 1Password and verify access to dotfiles-private before the first nix develop invocation.

After installing the prerequisites and initializing the colocated Jujutsu workspace, the activation sequence is:

nix develop --accept-flake-config --command ./x.sh nix-blueprints
nix develop --accept-flake-config --command ./x.sh nix-switch-system --blueprint <blueprint-id>
nix develop --accept-flake-config --command ./x.sh nix-switch-home --blueprint <blueprint-id>

Home Manager creates dotfiles/blueprint-id under the XDG config root (normally ~/.config), so later commands can invoke ./x.sh directly without --blueprint.

Usage

  • ./x.sh nix-<cmd>-home [--blueprint <blueprint-id>] - Home Manager: eval/build/diff/activate/switch
  • ./x.sh nix-<cmd>-system [--blueprint <blueprint-id>] - system: eval/build/diff/activate/switch
  • ./x.sh nix-eval-all - evaluate every canonical blueprint without a marker
  • ./x.sh nix-blueprints - list configured blueprint IDs
  • ./x.sh fmt - format all files
  • ./x.sh fmt-check - check formatting without modifying files
  • ./x.sh lint - check Nix, tracked .sh and .bash files, and Python

Blueprint-dependent evaluation, build, and switch commands use an explicit --blueprint first, then the Home Manager-managed blueprint marker. Builds use blueprint-specific result-home-<blueprint-id> and result-system-<blueprint-id> links so activation cannot consume another blueprint's result.

Reference

Source 2 files
hooks/register.tsx 68 lines
1import { atom, read, update } from "claude-code";
2import type {
3  EngineInterface,
4  Register,
5  SessionContextUsage,
6} from "claude-code";
7
8import type { StatuslineLine } from "../types";
9
10// The session's model and context window fill, for example
11// `claude-opus-5-5 │ 42% 84000/200000`, kept for the hint row to draw.
12const line = atom(
13  { plugin: "statusline", key: "line" } as const,
14  null as StatuslineLine,
15);
16
17export const register: Register = (on) => {
18  on("session.start", async ($, e, next) => {
19    const started = await next(e);
20    await refreshLine($, (await $.session.usage()).context);
21    return started;
22  });
23
24  // Refreshes the model after a `/model` switch, which lands between turns.
25  on("turn.start", async ($, e, next) => {
26    const turn = await next(e);
27    await refreshLine($, (await $.session.usage()).context);
28    return turn;
29  });
30
31  on("session.measure", async ($, e, next) => {
32    const measured = await next(e);
33    await refreshLine($, e.context);
34    return measured;
35  });
36
37  // A row of its own under the engine's hint row: a tail on that row is cut at
38  // its end, and dropped entirely on a narrow terminal.
39  on("ui.render", { component: "PromptHint" }, async ($, e, next) => {
40    const hint = await next(e);
41    const text = await read($, line);
42    if (text === null) return hint;
43
44    const { Box, Text } = $.ui.resolve(e);
45    return (
46      <Box flexDirection="column">
47        {hint}
48        <Text dimColor>{text}</Text>
49      </Box>
50    );
51  });
52};
53
54async function refreshLine(
55  $: EngineInterface,
56  context: SessionContextUsage,
57): Promise<void> {
58  const text = `${await $.session.model()} │ ${contextFill(context)}`;
59  await update($, line, () => text);
60}
61
62// `tokens` and `percent` stay absent until the live window's first response.
63function contextFill(context: SessionContextUsage): string {
64  if (context.tokens === undefined || context.percent === undefined)
65    return "0%";
66  return `${context.percent}% ${context.tokens}/${context.window}`;
67}
68
types/index.d.ts 9 lines
1// The hint line text, or null until the session has measured itself.
2export type StatuslineLine = string | null;
3
4declare module "claude-code" {
5  interface PluginState {
6    statusline: { line: StatuslineLine };
7  }
8}
9