Bricks that become a building

2026-07-22 · 7 min read essay textserve

I didn't plan a suite. I planned a fix.

textaccounts came first. I had a work account and a personal account for Claude Code, and they shared ~/.claude/: sessions, memory, MCP registrations, auth tokens, settings. The fix was straightforward once I noticed that Claude Code reads CLAUDE_CONFIG_DIR. Register separate directories as named profiles, switch between them by setting the variable. A few hundred lines of Python, a fish function to mutate the parent shell's environment, done.

textserve came next, for a different problem. I had twelve shell scripts, one per MCP server, each hand-rolling credential injection and registration with Claude. A credential rotated; I updated three scripts; the fourth failed silently two weeks later. The fix: a registry, a fleet CLI, idempotent start and stop commands, health probes before registration. Profiles for grouping servers by context.

textsessions came after that. Sessions accumulated across repos and accounts: hundreds of JSONL files, no cross-repo view, no triage. The fix: index everything, build a TUI, treat the pile as an inbox.

Three tools. Three separate problems. Three separate months. I didn't set out to build a suite.

The seam

What I noticed, somewhere during the third tool, is that they all respect the same constraint. textserve reads $CLAUDE_CONFIG_DIR to know which account's Claude registration it's managing. textsessions reads it to know which profile a session belongs to. textaccounts sets it. Switch accounts with textaccounts, and textserve and textsessions both follow automatically: they're all looking at the same variable.

The seam is CLAUDE_CONFIG_DIR. Every tool that respects it composes for free.

This wasn't planned. It emerged from each tool being built correctly for its own scope. A fleet manager that doesn't know which account it's managing is half a tool. A session browser that doesn't distinguish sessions by profile is confusing. They each needed the seam. The fact that they share it means they stack.

Three layers

The three tools cover independent layers. You can install any one without the others.

Tool Layer What it manages
textaccounts auth which Claude identity is active; sets CLAUDE_CONFIG_DIR
textserve tools which MCP servers are running and registered for that identity
textsessions sessions cross-repo session triage and navigation, scoped to the active profile

Why three CLIs

The alternative is a platform: one big tool with subcommands for accounts, MCP, and sessions, maybe a dashboard. Coherent on the day it ships, incoherent six months later when one piece needs to evolve faster than the others, or someone only needs one layer, or a fourth tool comes along that doesn't fit the taxonomy.

Each brick is complete on its own. You can use textaccounts with no textserve. You can use textserve with no MCP servers configured yet. The integration is opt-in and needs no glue code, because there's no glue: there's a seam, and they all fit it. textaccounts doesn't know textserve exists. The coordination happens at the OS level, not in the code.

No roadmap

The bricks approach is possible here because it's a personal project. Pitch "three separate CLIs" in a company design review and you lose the room on slide two. There's no sprint estimate for three separate tools when one dashboard would close the ticket. No stakeholder wants to approve a seam they can't see on a diagram. The usual forces compress everything toward a single surface that can be demoed, versioned, and handed to a team.

Personal projects don't have that gravity. You can ship a fix instead of a product. You can add a second tool that doesn't know the first exists, because there's no roadmap that requires them to be coherent from the start. The experimental direction is available by default: not because you planned for it, but because no one else needed to agree.

That's the part of this kind of work I like most. Not the freedom to build badly, but the freedom to find out what shape a thing actually wants to be, before locking it into the shape that's easiest to explain in a meeting.

Install

textaccounts and textsessions are Python tools, installable from GitHub:

uv tool install git+https://github.com/paperworlds/textaccounts
uv tool install git+https://github.com/paperworlds/textsessions

textserve is a Go binary. Clone and build:

git clone https://github.com/paperworlds/textserve
cd textserve && bash install.sh

install.sh builds the binary and symlinks it to ~/.local/bin/textserve.

The commands

textaccounts registers config directories as named profiles and switches between them. Register your existing Claude directories:

textaccounts adopt work ~/.claude-work
textaccounts adopt personal ~/.claude-personal

Switch profiles. On fish, source the companion function once in config.fish so the switch can mutate the parent shell's environment:

textaccounts install fish | source  # add to config.fish
textaccounts switch work

Verify isolation is working:

textaccounts status
# active: work (~/.claude-work)
# claude: v2.x — per-profile keychain isolation: yes

textserve manages the MCP servers Claude Code sees. Declare servers and profile groupings in ~/.textserve/registry.yaml, then converge to a profile in one command:

# ~/.textserve/registry.yaml
profiles:
  work:
    servers: [github, linear, snowflake]
  personal:
    servers: [github, obsidian]

servers:
  github:
    image: ghcr.io/paperworlds/mcp-github:latest
    transport: stdio
    credentials:
      GITHUB_TOKEN: op://Personal/GitHub/token
  # ...
textserve bundle use work
# starting: linear, snowflake (github already running)
# registered: 3 servers

bundle use brings up servers in the profile that aren't running and brings down servers that aren't in the profile. One command to reshape the full tool surface. (textserve profile use is a kept alias if that's already in your muscle memory.)

textsessions indexes the JSONL files Claude writes and gives you a cross-repo TUI for triage. First run:

textsessions init     # discover repos under ~/.claude*/projects/
textsessions reindex  # walk every .jsonl and build the index
textsessions view     # open the TUI

The two-command switch

Align your textserve bundle names with your textaccounts profile names. Then changing context is two commands:

textaccounts switch work
textserve bundle use work

The first sets CLAUDE_CONFIG_DIR for the shell: auth, memory, and settings all shift. The second converges the MCP surface to the servers for that profile. Open textsessions view and the session list reflects the active profile automatically.

If you want a single command, a fish function handles it:

# in config.fish
function ctx
  textaccounts switch $argv[1]
  textserve bundle use $argv[1]
end
ctx work

The building

I use the three together now. A new session in a fresh context starts like this:

textaccounts switch work
textserve bundle use work
textsessions view

Auth, tools, sessions. Each command handles one layer, knows nothing about the other two, and leaves the environment in exactly the right state for the next command to follow from.

Something I noticed from using these in agent loops: the interface that's fast for a human at 9am is the same interface an AI tool calls well. textaccounts switch work is one operation, one input, one observable effect. No flags to negotiate, no schema to infer. When you're three levels deep in a Claude session and need to change context, that matters: a small, sharp command is unambiguous in a way that a config file or a multi-field API call isn't. The tool that's intuitive to type is also the tool an agent can reason about reliably. That convergence surprised me. I built these for myself, not for agents. They ended up being good for both for the same reason: they do one thing.

The thing I didn't expect is that the composition feels more stable than a platform would. Each brick was made to do one thing correctly. The architecture of the whole is just: same seam, right shape.

Part of Paperworlds. Pairs with: The fleet that plays war · Two Claude accounts, one config directory · Sessions pile up