SLOPSHOPPER

nit-band

Claude Code mod: learn to say your daily work in the language you want to work in. Your prompts, the way a teammate would say them, right above the prompt…

newbandtoastpromptmodel
★ 1v0.1.1MITupdated 2026-10-086igtree/nit-band
A shopper browsing a rack in a slop shop
README

nit-band 💬

English | 日本語

A Claude Code mod: learn to say your daily work in the language you want to work in.

You already describe your work to your coding agent all day, in your own language. nit-band shows each prompt the way a teammate would say it in the language you are learning, plus the one expression worth learning. Save the ones you like, and your phrasebook becomes the vocabulary of your own job. Japanese → English by default.

It stays out of the transcript, so your agent's context and replies are untouched.

What it looks like

You send:

昨日のデプロイ以降エラーが増えているので、原因と影響範囲を調べて

Above the prompt, while your agent works:

in English: "Errors have spiked since yesterday's deploy. Can you investigate the root cause and blast radius?"  [Save] [Hide]
key: 影響範囲 → blast radius
  • Save adds it to ~/.nit/phrasebook.md.
  • Hide clears it. Your next prompt clears it too.

Prompts already in English, slash commands, code and very short prompts are skipped.

Install

Requires Claude Code 2.1.287 or later (mods).

/plugin marketplace add 6igtree/nit-band
/plugin install nit-band@nit-band

Languages

Japanese → English by default. Change both in /config:

  • Language you work in: the language you usually write prompts in (default Japanese).
  • Language you are learning: the language nit-band shows them in (default English).

Pick from English, Japanese, Chinese, Korean, Spanish, French, German, Portuguese, Vietnamese and Hindi; for example English → Japanese for engineers abroad working with a team in Japan. Prompts already in the language you are learning are skipped.

Cost and privacy

Each prompt you type is sent to Haiku once, through your own Claude Code session and credentials. Nothing goes anywhere else. The call runs in the background and never delays your prompt.

See also

nit: the skill version. Your agent replies as an English-speaking teammate and gently fixes the English you write. nit-band shares its phrasebook.

License

MIT

Source 2 files
hooks/register.tsx 95 lines
1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Line } from '../types'
5
6const current = atom({ plugin: 'nit-band', key: 'line' } as const, null)
7
8export function system(speak: string, learn: string): string {
9  return `You help someone who works in ${speak} learn how people at work say the same things in ${learn}.
10You get one message they wrote to their coding agent; you never answer it. Code, commands, paths and error messages are not prose; ignore them.
11
12If the message is already in ${learn}, or has no prose, answer {"skip":true}.
13
14Otherwise give how a teammate at a ${learn}-speaking company would say the whole message: natural, short, workplace ${learn}.
15Also pick the ONE expression from the message most worth learning: workplace or engineering wording that does not translate word for word (for Japanese to English, for example: 影響範囲 → blast radius, 切り戻す → roll back). Put the words from their message in "from" and the ${learn} in "to". Leave "key" out when every word is basic.
16
17Answer with JSON only:
18{"text":"<the whole message in ${learn}>","key":{"from":"<words from their message>","to":"<in ${learn}>"}}`
19}
20
21// Fenced blocks and inline code are not prose.
22export function prose(text: string): string {
23  return text.replace(/```[\s\S]*?```/g, ' ').replace(/`[^`]*`/g, ' ').replace(/\s+/g, ' ').trim()
24}
25
26export function parse(reply: string): Line | null {
27  const json = reply.match(/\{[\s\S]*\}/)
28  if (!json) return null
29  try {
30    const r = JSON.parse(json[0])
31    if (typeof r.text !== 'string' || !r.text.trim()) return null
32    const from = typeof r.key?.from === 'string' ? r.key.from.trim() : ''
33    const to = typeof r.key?.to === 'string' ? r.key.to.trim() : ''
34    return { text: r.text.trim(), key: from && to ? { from, to } : null }
35  } catch {}
36  return null
37}
38
39export const register: Register = (on, options) => {
40  const speak = String(options.speak ?? '').trim() || 'Japanese'
41  const learn = String(options.learn ?? '').trim() || 'English'
42  let generation = 0
43
44  on('prompt.submit', ($, e, next) => {
45    const mine = ++generation
46    // ponytail: one Haiku call per prompt, never awaited, so the prompt is never delayed or blocked.
47    void (async () => {
48      await update($, current, () => null)
49      const text = prose(e.text)
50      if (e.origin.kind !== 'composer' || text.startsWith('/') || text.length < 6) return
51      const r = await $.model.complete({ model: 'haiku', system: system(speak, learn), prompt: text.slice(0, 2000), maxTokens: 400, effort: 'low' })
52      const line = r.isAnswered ? parse(r.text) : null
53      if (line && mine === generation) await update($, current, () => line)
54    })().catch(() => {})
55
56    return next(e)
57  })
58
59  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
60    const line = await read($, current)
61    if (line === null || e.props.hasSurvey) return next(e)
62
63    const { Box, Button, Text } = $.ui.resolve(e)
64
65    const save = async () => {
66      const home = await $.env.get('HOME')
67      const path = `${home}/.nit/phrasebook.md`
68      const day = new Date().toISOString().slice(0, 10)
69      const learned = line.key ? ` ${line.key.from} → ${line.key.to} ·` : ''
70      const pair = speak === 'Japanese' && learn === 'English' ? '' : ` ${speak} → ${learn} ·`
71      const old = (await $.fs.exists(path)) ? await $.fs.read(path) : ''
72      await $.fs.write(path, `${old}- ${day} ·${pair}${learned} "${line.text}"\n`)
73      $.ui.toast('Saved to ~/.nit/phrasebook.md')
74      await update($, current, () => null)
75    }
76
77    return (
78      <Box flexDirection="column">
79        <Box>
80          <Text dimColor>
81            in {learn}: "{line.text}"{' '}
82          </Text>
83          <Button key="save" label="Save" onPress={save} />
84          <Button key="hide" label="Hide" onPress={() => update($, current, () => null)} />
85        </Box>
86        {line.key ? (
87          <Text>
88            key: {line.key.from} → {line.key.to}
89          </Text>
90        ) : null}
91      </Box>
92    )
93  })
94}
95
types/index.d.ts 8 lines
1export type Line = { text: string; key: { from: string; to: string } | null }
2
3declare module 'claude-code' {
4  interface PluginState {
5    'nit-band': { line: Line | null }
6  }
7}
8