SLOPSHOPPER

dead-skills

Default bundle: global instructions, the Night City theme and mod, desktop notifications, Context7, plus core plugins from claude-plugins-official as…

spinnerguardstatusprocesstimer
v0.6.0no licenseupdated 2026-10-23deadmade/dead-claude-skills/plugins/default
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · dead-skills
› 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. ✻ Flatlined the bug for 42s · done 4:20 PM ✻ Spoofing the Blackwall… ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts ⚠ dead-skills: ◢◤ JACKED OUT ◢◤
README

dead-skills

The default bundle, on every machine:

  • Global rules: rules.md, added to the context at session start.
  • Context7 (.mcp.json): up-to-date library docs.
  • Night City: the theme and its mod, below.
  • Notifications: a desktop popup when a turn ends or Claude waits for you, below.
  • The core plugins as dependencies: claude-code-setup, claude-md-management, security-guidance and claude-security.

It no longer depends on superpowers, ponytail or code-review. superpowers and ponytail are skill packs inside the flows plugin that only its conductors read, so outside them Claude runs without ponytail's rules and band and without superpowers' session bootstrap. The installer removes old installs of both.

Night City, Context7 and the notifications were plugins of their own (night-city, mcp-basic, notify) until 0.3.0; the installer removes those and keeps Night City picked in /theme.

Night City

<img src="assets/mascot-512.png" width="192" align="right" alt="Onibi: a hooded netrunner in a respirator, her chrome cybereye a glowing eight-point spark">

A toned-down synthwave Claude Code theme, "Night City": a dusk-indigo background, lavender text, dusty-pink Claude, peach inline code and permission prompts, soft teal suggestions and bash, muted violet instead of grey for dim text. It is the house look: tui-review's pane, the notifications and the installer's screens use the same palette. Pick it in /theme (Claude Code does not let a plugin select a custom theme).

Beyond the theme:

  • While Night City is the selected theme, the terminal's own background turns the theme's dusk indigo, so the empty space matches (OSC 11, reset when Claude exits or you pick another theme). Linux; Windows Terminal untested. If Claude is killed, the color stays until you run printf '\033]111\007'.
  • Night City spinner words and turn-end verbs ("Jacked out for 3s").
  • Short status-line reactions: ▚▞ FLATLINE ▞▚ once per turn on a failing tool, ◢◤ JACKED OUT ◢◤ when a main turn ends.
  • A Night City ccstatusline config: Powerline segments with slanted edges in the theme's colours, icons, context as tokens and percent, the cost in eddies (€$1.23) and a ナイトシティ sign fading pink to teal. It needs a Nerd Font in the terminal. The installer writes it with the ccstatusline toggle and replaces the stock config earlier versions wrote.
  • The installer turns syntax highlighting off (syntaxHighlightingDisabled) and sets CLAUDE_CODE_TMUX_TRUECOLOR=1: inside tmux Claude Code otherwise draws in 256 colours and the theme's indigos turn grey.

Syntax highlighting: Claude Code colours code and diffs with a fixed Monokai palette for every dark theme, which a theme can't change, and it ignores the theme's diff colours while highlighting is on (anthropics/claude-code#57036). With it off (ctrl+t in /theme), diffs use Night City's mint and coral and code takes the theme's text colour.

The palette starts from the Cyberpunk Neon theme of ccthemes (MIT) and is retuned to softer synthwave colours; see THIRD_PARTY.md in the repository root.

Mascot

Onibi (鬼火, "demon fire": the ghost lights of Japanese folklore that drift through the dark and lead people to their end). The netrunner above is the theme's mascot, an original 64×64 pixel-art character in the theme's palette: a respirator with an LED bar, an undercut with a circuit tattoo, and a chrome plate whose cybereye is an eight-point spark, a nod to Claude rather than Anthropic's logo. assets/mascot.png is the 1x original, assets/mascot-512.png the 8x nearest-neighbour scale; she is also the notification icon. All are drawn by scripts/draw-mascot.mjs in the repository root; edit the drawing there and rerun it rather than editing the PNGs.

Contrast (WCAG)

Checked with relative luminance; all are at least 4.5:1. background is an undocumented token that the theme sets anyway.

Foregroundon backgroundon user baron bash bar
text #E6DCF713.4411.2911.15
permission #F5B97A10.208.578.46
error #F2788C6.615.555.48
inactive #9A90C26.025.065.00

text on the diff backgrounds: added 9.80, removed 10.56, added word 5.94, removed word 7.03, added dimmed 11.25, removed dimmed 12.22.

Notifications

A desktop notification when Claude finishes a turn or waits for you, so you can work in another window (or on another monitor) and come back when it's your move.

It fires on:

  • a finished turn of the main agent (Stop): a Night City line ("Come look at your Agent") and the first line of Claude's answer;
  • a permission prompt or an MCP elicitation (Notification): a Night City line ("Songbird requests your help") and Claude Code's message;
  • a question from Claude (PreToolUse on AskUserQuestion, which raises no notification of its own): a waiting line and the question.

The popup comes from Onibi, Night City's pixel-art netrunner: her face is the icon (icon.png, a brightened crop written by scripts/draw-mascot.mjs; never edit it by hand) and, on Linux, she is the sender. The title is the project folder, then the status in bold and Claude's line below it. Windows shows "Windows PowerShell" as the sender, since the toast borrows PowerShell's app id.

It stays quiet:

  • in tmux, when the session's pane is the active pane of the active window and a client showing that session has focus. This needs set -g focus-events on in your tmux config; without it tmux never knows about focus and every event pops up;
  • on Windows, when Windows Terminal is the foreground app (any tab counts);
  • for subagents, Stop-hook continuations and headless runs (claude -p, the Agent SDK, CI).

Outside tmux on Linux it always notifies. macOS gets no notifications.

Delivery

PlatformHowNeeds
Linuxbusctl --user to org.freedesktop.Notifications, falling back to notify-sendsystemd's busctl or libnotify's notify-send; a notification daemon
Windowsa WinRT toast from Windows PowerShell, under PowerShell's app idWindows PowerShell 5.1 (built in); notifications on for it in Settings

Talking to the notification daemon directly means tmux needs no allow-passthrough. The hook runs in the background (async), so a slow PowerShell start doesn't hold up the turn. Claude's text reaches the notifier as an argument or an environment variable, never as script source.

Claude Code's own notification

The installer sets "preferredNotifChannel": "notifications_disabled" in ~/.claude/settings.json, so Claude Code's own notification doesn't double up. Installed by hand, set it yourself.

Source 3 files
hooks/register.tsx 86 lines
1// Flavor: Night City spinner and turn-line words, status-line reactions, the terminal background.
2import type { EngineInterface as $, Register, Timer } from 'claude-code';
3
4import { RESET, SET, isNightCity, paint } from './background';
5import { SPINNER_WORDS, TURN_WORDS, pick } from './words';
6
7export const GLITCH_MS = 400;
8export const FLARE_MS = 600;
9// Reactions go in the plugin's own status line, which sits beside the user's statusLine command, not over it. Plain
10// text: the status line takes no colors without escape sequences, which a mod doesn't write.
11const GLITCH = '▚▞ FLATLINE ▞▚';
12const FLARE = '◢◤ JACKED OUT ◢◤';
13
14let glitched = false; // one glitch per main turn
15let reaction: Timer | undefined;
16
17function react($: $, text: string, ms: number) {
18  reaction?.cancel();
19  $.ui.status(text);
20  const mine = $.clock.after(ms, () => {
21    if (reaction === mine) $.ui.status(undefined);
22  });
23  reaction = mine;
24}
25
26// The terminal background follows the theme: Night City selected paints it, anything else resets it.
27let isTerminal = false;
28let isPainted = false;
29
30async function background($: $, on: boolean) {
31  if (on !== isPainted && (await paint($, on ? SET : RESET))) isPainted = on;
32}
33
34export const register: Register = (on) => {
35  glitched = false;
36  reaction = undefined;
37  isTerminal = false;
38  isPainted = false;
39
40  on('turn.start', (_$, e, next) => {
41    glitched = false;
42    return next(e);
43  });
44  on('tool.call', async ($, e, next) => {
45    const ran = await next(e);
46    if (ran.isError === true && !glitched && e.agentId === undefined) {
47      glitched = true;
48      react($, GLITCH, GLITCH_MS);
49    }
50    return ran;
51  }).catch((_$, e, next) => next(e));
52  on('turn.complete', ($, e, next) => {
53    if (e.agentId === undefined) react($, FLARE, FLARE_MS);
54    return next(e);
55  });
56
57  on('ui.render', { component: 'Spinner' }, (_$, e, next) =>
58    next({ ...e, props: { ...e.props, word: pick(SPINNER_WORDS, e.props.word) } }),
59  );
60  on('ui.render', { component: 'TurnDuration' }, (_$, e, next) =>
61    next({ ...e, props: { ...e.props, word: pick(TURN_WORDS, e.props.word) } }),
62  );
63
64  on('session.start', async ($, e, next) => {
65    const started = await next(e);
66    isTerminal = e.isInteractive && e.surface === 'terminal';
67    if (isTerminal) {
68      void $.config.list().then(
69        (rows) => background($, isNightCity(rows.find((row) => row.key === 'theme')?.value)),
70        () => {},
71      );
72    }
73    return started;
74  });
75  on('config.set', { key: 'theme' }, async ($, e, next) => {
76    const set = await next(e);
77    if (isTerminal && set.deny === undefined) void background($, isNightCity(set.value));
78    return set;
79  }).catch((_$, e, next) => next(e));
80  // After a /clear or a resume the process, and the terminal, go on.
81  on('session.end', async ($, e, next) => {
82    if (isPainted && e.reason !== 'clear' && e.reason !== 'resume') await background($, false);
83    return next(e);
84  });
85};
86
hooks/background.ts 60 lines
1// The terminal's own background: Claude Code leaves empty cells to the terminal, which no theme token reaches, so this
2// sends OSC 11 (set the background) and OSC 111 (reset it) to the terminal Claude runs in. A mod can't write to the
3// terminal itself, so a child process finds it and writes there.
4import type { EngineInterface as $ } from 'claude-code';
5
6// The theme's `background` token, so the empty cells match the painted ones.
7export const SET = ']11;#1A1626';
8export const RESET = ']111';
9
10// The child has no tty of its own: walk up to the first ancestor with one (Claude's) and write there.
11export const SH = `p=$PPID
12while [ "$p" -gt 1 ]; do
13  t=$(ps -o tty= -p "$p" | tr -d ' ')
14  case "$t" in ''|'?'|'??') p=$(ps -o ppid= -p "$p" | tr -d ' ') ;; *) printf '\\033%s\\007' "$1" > "/dev/$t"; exit 0 ;; esac
15done
16exit 1`;
17
18// Windows: no /dev/pts. The engine starts the child hidden (windowsHide), which gives it a new console of its own, so
19// it swaps that for its parent's (Claude's) before opening CONOUT$, and lets go again so its own exit leaves Claude's
20// console alone. CONOUT$ is opened through CreateFileW because .NET's FileStream won't open device names.
21export const PS = `$sig = '[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)] public static extern IntPtr CreateFileW(string n, uint a, uint s, IntPtr p, uint c, uint f, IntPtr t); [DllImport("kernel32.dll")] public static extern bool FreeConsole(); [DllImport("kernel32.dll")] public static extern bool AttachConsole(int p);'
22$k = Add-Type -MemberDefinition $sig -Name K -Namespace NightBg -PassThru
23$null = $k::FreeConsole()
24if (-not $k::AttachConsole(-1)) { exit 1 }
25$h = $k::CreateFileW('CONOUT$', 0x40000000, 3, [IntPtr]::Zero, 3, 0, [IntPtr]::Zero)
26if ($h -eq [IntPtr](-1)) { $null = $k::FreeConsole(); exit 1 }
27$fs = New-Object IO.FileStream((New-Object Microsoft.Win32.SafeHandles.SafeFileHandle($h, $true)), [IO.FileAccess]::Write)
28$b = [Text.Encoding]::ASCII.GetBytes([string][char]27 + $env:NIGHT_CITY_OSC + [char]7)
29$fs.Write($b, 0, $b.Length); $fs.Close(); $null = $k::FreeConsole()`;
30// Passed as -EncodedCommand (base64 of UTF-16LE; PS is ASCII), not on stdin: once FreeConsole drops the console,
31// PowerShell stops reading stdin and exits 0 without running the lines after it.
32export const PS_ENCODED = btoa([...PS].map((c) => c + '\0').join(''));
33
34// Absolute paths: a bare name is looked up in the session's working directory first on Windows, so a project could
35// plant its own `sh.exe` or `powershell.exe`.
36// shortcut: assumes Windows lives in C:\Windows; read %SystemRoot% if someone installs it elsewhere.
37export const SH_PATH = '/bin/sh';
38export const PS_PATH = 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
39
40// The plugin theme's stored value is `custom:<plugin>:night-city`, so `custom:dead-skills:night-city` (undocumented, read from the binary).
41export const isNightCity = (theme: unknown) => typeof theme === 'string' && theme.endsWith(':night-city');
42
43// Mods have no platform API: try the POSIX route, fall back to PowerShell when sh is missing or finds no tty.
44// shortcut: a crash or kill skips the reset and the terminal keeps the color until `printf '\033]111\007'`.
45export async function paint($: $, osc: string): Promise<boolean> {
46  try {
47    if ((await $.process.run([SH_PATH, '-c', SH, 'sh', osc])).exitCode === 0) return true;
48  } catch {}
49  try {
50    const r = await $.process.run([PS_PATH, '-NoProfile', '-NonInteractive', '-EncodedCommand', PS_ENCODED], {
51      env: { NIGHT_CITY_OSC: osc },
52    });
53    if (r.exitCode === 0) return true;
54    $.ui.log(`night-city: background not set: ${r.stderr.trim().slice(0, 200)}`, { to: 'debug' });
55  } catch (err) {
56    $.ui.log(`night-city: background not set: ${err}`, { to: 'debug' });
57  }
58  return false;
59}
60
hooks/words.ts 42 lines
1export const SPINNER_WORDS: readonly string[] = [
2  'Breaching ICE',
3  'Jacking in',
4  'Running the net',
5  'Spoofing the Blackwall',
6  'Slicing the firewall',
7  'Dialing the fixer',
8  'Scanning the braindance',
9  'Overclocking the deck',
10  'Pinging the netrunner',
11  'Hacking the mainframe',
12  'Rerouting through the grid',
13  'Splicing the datastream',
14  'Cracking the cipher',
15  'Riding the neon',
16];
17
18// Past tense, so each reads before "for 3s".
19export const TURN_WORDS: readonly string[] = [
20  'Jacked out',
21  'Flatlined the bug',
22  'Ghosted the ICE',
23  'Breached the Blackwall',
24  'Ran the net',
25  'Cleaned the trace',
26  'Fried the firewall',
27  'Cashed the eddies',
28  'Slipped past the corpos',
29  'Wired the deck',
30  'Sliced through',
31  'Zeroed the daemon',
32];
33
34// FNV-1a over the seed, so one seed always lands on one word.
35export function hash(seed: string): number {
36  let h = 0x811c9dc5;
37  for (const c of seed) h = Math.imul(h ^ c.codePointAt(0)!, 0x01000193) >>> 0;
38  return h;
39}
40
41export const pick = (words: readonly string[], seed: string) => words[hash(seed) % words.length]!;
42