SLOPSHOPPER

sisomewhere-usage

Saves Claude Code's plan limits (5-hour and weekly) to a local file for the SI Somewhere agent

new
v0.1.0no licenseupdated 2026-10-10shissncg/claude-mods/mods/sisomewhere-usage
A shopper browsing a rack in a slop shop
README

Interesting Claude mods

Small Claude Code mods: TypeScript hook modules that run before, after or instead of what Claude Code does.

ModWhat it does
turn-timestampEnds every turn with ✻ 2026-10-03 19:05 CDT · 3s · 1 tool call, so scrollback in multi-day sessions shows when each turn finished. In the terminal it replaces Claude Code's own end-of-turn line (✻ Churned for 1s); in the desktop Code tab, which draws no such line, it goes under Claude's last reply block. Display only.
ci-status/ci opens a pane listing the repo's latest GitHub Actions runs (via gh), refreshes every 30s, shows CI: N running in the status line and a toast when a run finishes.
sisomewhere-agentsClassic command hooks (no TypeScript) that record each session's state (working and on which tool, needs you, done) to ~/.config/sisomewhere/agents/, one file per session. The SI Somewhere daemon shows them on the desk display's Agents page. The same script feeds Codex, Grok and Antigravity sessions: python3 mods/sisomewhere-agents/install_other_tools.py adds it to their hook configs (--uninstall removes it). Silent: never blocks a tool call. Superseded on machines that run the SI Somewhere Go agent (sisomewhere install, in that repo's agent/), which hooks every harness itself and switches this plugin off.
sisomewhere-usageSaves Claude Code's plan limits (the 5-hour and weekly windows: percent used, when each resets) to ~/.config/sisomewhere/usage/ whenever a window moves, one file per Claude config folder. The SI Somewhere agent sends them to app.sisomewhere.cc and the desk display. Claude Code hands these figures to mods only, so this needs no login, token or API call. No UI.

Set up a machine

Installs every mod in setup/mods.conf (mine and the community ones I use) and restores their saved settings, such as the neon-cyber skin:

git clone https://github.com/shissncg/claude-mods ~/Dev/claude-mods
~/Dev/claude-mods/setup.sh
  • ./setup.sh --dev registers this checkout as the marketplace instead of GitHub, so edits under mods/ load without pushing.
  • ./setup.sh save copies this machine's mod settings into setup/store/; commit them to carry them to other machines.
  • To add a mod, add a line to setup/mods.conf. Running the script again only installs what's missing.

Plugins turned on at claude.ai sync by themselves and don't need to be listed.

To install just one of mine:

claude plugin marketplace add shissncg/claude-mods
claude plugin install turn-timestamp@shissncg-mods

Or try one for a session without installing:

claude --plugin-dir mods/turn-timestamp --plugin-dir mods/ci-status

Check one with claude plugin validate mods/<name>. Mods need a recent Claude Code build: older CLIs report "session.start" is not an event.

Source 1 files
hooks/register.ts 46 lines
1import type { Register } from 'claude-code'
2
3// Claude Code's plan limits (the 5-hour and weekly windows, percent used and
4// when each resets), saved to ~/.config/sisomewhere/usage/ whenever a window
5// moves, so the SI Somewhere agent can show them on every screen. Claude Code
6// hands these figures to mods and status lines only; reading them here needs
7// no login, token or API call. One file per Claude config folder, so a second
8// account (CLAUDE_CONFIG_DIR) keeps its own:
9//
10//   {"config_dir": "/Users/me/.claude", "updated": 1791600000,
11//    "limits": [{"kind": "five_hour", "percent_used": 23.5, "resets_at": "2026-10-10T03:00:00Z"}]}
12//
13// Display only: it never blocks or changes anything, and a failed write is dropped.
14
15type Limit = { kind: string; percent_used: number; resets_at: string | null }
16
17export function fileName(configDir: string): string {
18  return `claude-${configDir.replace(/\/+$/, '').replace(/[^A-Za-z0-9._-]/g, '_')}.json`
19}
20
21export const register: Register = on => {
22  on('session.measure', async ($, e, next) => {
23    const result = await next(e)
24    if (!e.changed.includes('rateLimits') || e.rateLimits.length === 0) {
25      return result
26    }
27    try {
28      const home = await $.env.get('HOME')
29      if (!home) {
30        return result
31      }
32      const configDir = (await $.env.get('CLAUDE_CONFIG_DIR')) || `${home}/.claude`
33      const limits: Limit[] = e.rateLimits.map(limit => ({
34        kind: limit.kind,
35        percent_used: limit.percentUsed,
36        resets_at: limit.resetsAt ?? null,
37      }))
38      const body = { config_dir: configDir, updated: Math.floor(Date.now() / 1000), limits }
39      await $.fs.write(`${home}/.config/sisomewhere/usage/${fileName(configDir)}`, JSON.stringify(body) + '\n')
40    } catch {
41      // the next measurement writes again
42    }
43    return result
44  })
45}
46