SLOPSHOPPER

welcome

Greets you with the shared wordmark above the prompt until the first message

newband
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · welcome
› 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 [fn] ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
[fn]
README

Welcome to my NixOS configuration

Prerequisites

  • You have to change the hardware-configuration.nix inside the profile to match your hardware

How to use

  • Clone the repository
git clone https://github.com/s1n7ax/nixos.git
  • Update flakes
nix flake update
  • Install the system
sudo nixos-rebuild switch --upgrade --flake ./#desktop
                                              #^^^^^^^^ profile name here

Rebuilding Profiles

Available Profiles

This configuration supports multiple profiles:

  • desktop: Full desktop environment with gaming, multimedia, and development tools
  • server: Minimal server profile with most desktop features disabled
  • macbook: nix-darwin profile for Apple Silicon macOS (built with darwin-rebuild, not nixos-rebuild)

Rebuild Commands

Desktop Profile
# Build and switch to desktop profile
sudo nixos-rebuild switch --upgrade --flake ./#desktop

# Test desktop configuration without switching
sudo nixos-rebuild test --flake ./#desktop

# Build desktop configuration without switching
sudo nixos-rebuild build --flake ./#desktop
Server Profile
# Build and switch to server profile
sudo nixos-rebuild switch --upgrade --flake ./#server

# Test server configuration without switching
sudo nixos-rebuild test --flake ./#server

# Build server configuration without switching
sudo nixos-rebuild build --flake ./#server
macOS (macbook Profile)

This profile is managed by nix-darwin, so use darwin-rebuild instead of nixos-rebuild. The flake output is named macbook, not the machine's hostname — always select it with #macbook.

First-time bootstrap (before darwin-rebuild exists on the system):

# Run darwin-rebuild straight from the nix-darwin flake to install it.
# --extra-experimental-features is required if nix-command/flakes aren't
# already enabled in the stock macOS Nix install.
sudo nix run --extra-experimental-features 'nix-command flakes' \
  nix-darwin/nix-darwin-26.05#darwin-rebuild -- switch --flake ~/nixos#macbook

After the first successful switch, darwin-rebuild is installed at /run/current-system/sw/bin. Subsequent rebuilds:

sudo strips the Nix directories from PATH via secure_path, so a plain sudo darwin-rebuild fails with command not found. Invoke it by full path:

# Build and switch to the macbook profile
sudo /run/current-system/sw/bin/darwin-rebuild switch --flake ~/nixos#macbook

# Build the macbook configuration without switching
sudo /run/current-system/sw/bin/darwin-rebuild build --flake ~/nixos#macbook

# Check what would change (dry run)
sudo /run/current-system/sw/bin/darwin-rebuild switch --flake ~/nixos#macbook --dry-run

Note: the --flake ~/nixos#macbook argument is required. A bare darwin-rebuild switch defaults to the flake at /etc/nix-darwin keyed by hostname and will fail with a missing-darwinConfigurations attribute error.

Host toolchain

Dev tooling runs natively on macOS: the macbook profile imports the same home-manager tree as the Linux profiles and enables the feature set in profile/macbook/host-options.nix (neovim, fish, kitty, git/gh/acli, the language toolchains, CLI utilities). There is no linux-builder VM — Docker is provided by colima, started by home-manager when features.virtualization.docker is on.

Secrets are not available on macOS: the secrets flake input and the sops modules are imported only by profile/common, which the macbook profile does not use.

Convenience aliases (see system/home-manager/applications/alias.nix, Darwin half):

mac-update    # cd ~/nixos && nix flake update
mac-rebuild   # darwin-rebuild switch --flake ~/nixos#macbook
mac-clean     # garbage-collect and optimise the store

Additional Commands

# Apply home-manager changes only (faster for user-level changes)
home-manager switch --flake ./#desktop

# Update flakes to latest versions
nix flake update

# Check flake syntax and evaluate
nix flake check

# Show what would be built/downloaded (dry run)
nixos-rebuild switch --flake ./#desktop --dry-run

# Garbage collect old generations
sudo nix-collect-garbage -d
Source 2 files
hooks/register.tsx 109 lines
1/**
2 * Claude Code start screen. Its logo can't be replaced, so the shared
3 * wordmark and greeting are drawn in the band above the prompt, until the
4 * first prompt, slash command or `!` command lands in the transcript
5 * (`prompt.submit` misses the last two).
6 *
7 * The hook sandbox has no Node and no local time zone, so the clock, host and
8 * branch come from `date`, `uname` and `git`, read once at session start.
9 *
10 * `banner.ts` and `identity.ts` are copied beside this file at build time
11 * (see `../../default.nix`).
12 */
13import { atom, read, update } from "claude-code";
14import type { EngineInterface, Register } from "claude-code";
15import type { Welcome } from "../types";
16import {
17  dateline,
18  gradients,
19  greeting,
20  mocha,
21  project,
22  shade,
23  signature,
24  wordmark,
25  wordmarkWidth,
26} from "./banner";
27
28const welcome = atom({ plugin: "welcome", key: "welcome" } as const, null);
29const isGreeted = atom({ plugin: "welcome", key: "isGreeted" } as const, false);
30
31const gutter = 3;
32const minimumTextWidth = 30;
33
34async function lookAround($: EngineInterface, cwd: string): Promise<Welcome> {
35  const [clock, host, branch, home] = await Promise.all([
36    $.process.run(["date", "+%Y %m %d %H %M"]),
37    $.process.run(["uname", "-n"]),
38    $.process.run(["git", "-C", cwd, "branch", "--show-current"]),
39    $.env.get("HOME"),
40  ]);
41  const [year = 1970, month = 1, day = 1, hour = 0, minute = 0] = clock.stdout.trim().split(" ").map(Number);
42  const branchName = branch.exitCode === 0 ? branch.stdout.trim() : "";
43  const place = project(cwd, home ?? undefined);
44
45  return {
46    hour,
47    date: dateline(new Date(year, month - 1, day, hour, minute)),
48    where: signature(branchName ? `${place} \ue0a0 ${branchName}` : place, host.stdout.trim()),
49  };
50}
51
52export const register: Register = (on) => {
53  on("session.start", async ($, e, next) => {
54    if (e.isInteractive) {
55      const found = await lookAround($, e.cwd);
56      await update($, welcome, () => found);
57    }
58
59    return next(e);
60  });
61
62  on("session.append", async ($, e, next) => {
63    const isFirstMove = e.agentId === undefined && (e.door === "prompt" || e.door === "command");
64    if (isFirstMove) await update($, isGreeted, () => true);
65
66    return next(e);
67  });
68
69  on("ui.render", { component: "AbovePrompt" }, async ($, e, next) => {
70    const [state, greeted] = await Promise.all([read($, welcome), read($, isGreeted)]);
71
72    if (state === null || greeted || e.props.hasSurvey || e.props.view.agentId !== undefined) {
73      return next(e);
74    }
75
76    const { Box, Text } = $.ui.resolve(e);
77    const isWide = e.props.bodyColumns >= wordmarkWidth + gutter + minimumTextWidth;
78    const lines = [
79      <Text bold color={gradients.claude.at(-1)} wrap="truncate-end">
80        {greeting(state.hour)}
81      </Text>,
82      <Text color={mocha.subtext0} wrap="truncate-end">
83        {state.date}
84      </Text>,
85      <Text color={mocha.overlay1} wrap="truncate-end">
86        {state.where}
87      </Text>,
88    ];
89
90    return (
91      <Box flexDirection="column">
92        {wordmark.map((art, row) => (
93          <Box flexDirection="row">
94            {isWide && (
95              <Text>
96                {shade(art, gradients.claude).map(({ text, color }) => (
97                  <Text color={color}>{text}</Text>
98                ))}
99                {" ".repeat(wordmarkWidth - [...art].length + gutter)}
100              </Text>
101            )}
102            {lines[row]}
103          </Box>
104        ))}
105      </Box>
106    );
107  });
108};
109
types/index.d.ts 13 lines
1/** What the band shows, read once at session start. */
2export type Welcome = {
3  hour: number;
4  date: string;
5  where: string;
6};
7
8declare module "claude-code" {
9  interface PluginState {
10    welcome: { welcome: Welcome | null; isGreeted: boolean };
11  }
12}
13