SLOPSHOPPER

self-context

Lets a session read how full its context window is (tokens, window, percentage, and whether auto-compact is on), to decide at a break between two pieces of…

newguardtool
A shopper browsing a rack in a slop shop
README

self-context

Lets a session read how full its context window is, so at the break between two pieces of work it can decide whether to compact now, rather than have auto-compact run in the middle of the next piece. Part of claude-code-self-tools.

Tool

context_usage (Claude sees mcp__self-context__context_usage), with no arguments. It answers with one line, such as:

Context: 123,456 of 200,000 tokens (62%). Auto-compact is on. To compact at this break, call compact_after_turn.

  • The figures are the ones the status line shows: the input tokens of the model's last response, against the model's window. A new or just-compacted conversation has no reading until the model's next response, and the tool says so.
  • Whether auto-compact is on comes from the /config menu's autoCompact row.
  • The last sentence appears only when self-compact's compact_after_turn is available.

How it behaves

  • Read-only. It changes no setting and runs no command.
  • Subagents are refused. The figures are the main conversation's, and Claude Code has no reading of a subagent's own window, so a subagent is told so rather than handed figures that aren't its own.

Install

/plugin marketplace add moefingers/claude-code-self-tools
/plugin install self-context@claude-code-self-tools
Source 1 files
hooks/register.ts 74 lines
1// A session reads how full its context window is, to decide at the break between two pieces of
2// work whether to compact now rather than let auto-compact run in the middle of the next one.
3// Read-only. The figures are the main conversation's, the ones the status line shows: the API has
4// no reading of a subagent's own window, so a subagent is refused rather than handed figures that
5// are not its own.
6import type { EngineInterface, Register } from 'claude-code'
7
8/** The compaction tool of the self-compact plugin, named when it is installed. */
9const COMPACT_TOOL = 'mcp__self-compact__compact_after_turn'
10
11const DESCRIPTION = [
12  "Reports how full this conversation's context window is: tokens used, the window's size, the percentage, and whether auto-compact is on.",
13  'Call it at the break between two pieces of work, to decide whether to compact now, before the next piece starts, rather than have auto-compact run in the middle of it.',
14  'If the compact_after_turn tool is available, that is how to compact at the break.',
15  'Not needed mid-piece or on every turn. Read-only: it changes nothing.',
16].join(' ')
17
18/** A whole number with thousands separators, written out: the module has no locale to ask. */
19const grouped = (n: number): string => String(Math.round(n)).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
20
21/** Whether auto-compact is on, as the /config menu's row says; undefined when there is no such row. */
22async function autoCompact($: EngineInterface): Promise<boolean | undefined> {
23  try {
24    const row = (await $.config.list()).find(r => r.key === 'autoCompact')
25    return typeof row?.value === 'boolean' ? row.value : undefined
26  } catch {
27    return undefined
28  }
29}
30
31/** Whether the self-compact tool is one the model can call now. */
32async function canCompact($: EngineInterface): Promise<boolean> {
33  try {
34    return (await $.tool.list()).some(t => t.name === COMPACT_TOOL)
35  } catch {
36    return false
37  }
38}
39
40async function reading($: EngineInterface): Promise<string> {
41  const [{ context }, auto, compact] = await Promise.all([$.session.usage(), autoCompact($), canCompact($)])
42  const window = `${grouped(context.window)} tokens`
43  const used =
44    context.tokens === undefined
45      ? `Context: no reading yet. The window holds ${window}; it is measured from the model's next response, so a new or just-compacted conversation has none.`
46      : `Context: ${grouped(context.tokens)} of ${window} (${context.percent ?? Math.round((context.tokens * 100) / context.window)}%).`
47  const autoLine =
48    auto === undefined ? 'Auto-compact: not listed in /config.' : `Auto-compact is ${auto ? 'on' : 'off'}.`
49  const how = compact ? ' To compact at this break, call compact_after_turn.' : ''
50  return `${used} ${autoLine}${how}`
51}
52
53export const register: Register = on => {
54  on('session.start', async ($, e, next) => {
55    const started = await next(e)
56    await $.tool.register({
57      name: 'context_usage',
58      description: DESCRIPTION,
59      inputSchema: { type: 'object', properties: {}, additionalProperties: false },
60      isDeferred: false,
61    })
62    return started
63  })
64
65  on('tool.call', { tool: 'mcp__self-context__context_usage' }, async ($, e) => {
66    if (e.agentId !== undefined) {
67      return {
68        deny: "A subagent has no reading of its own window: these figures are the main conversation's. A subagent compacts on its own near its limit; to close a piece cleanly, finish it and return its result.",
69      }
70    }
71    return { result: await reading($) }
72  }).catch(($, e, next) => (next.called ? next(e) : { deny: 'self-context: reading the context window failed.' }))
73}
74