Skip to content
Development
Skill

/writing-user-outputs

CLI output formatting standards for worktrunk. Load before editing any code that calls warning_message, hint_message, error_message, info_message, eprintln, or println, or that produces strings the user will see (CLI help, progress UI, snapshot text). Documents ANSI color

From plugin
worktrunk
7.6k5 skills3 hooks
Install
$ npx -y skills add max-sixty/worktrunk --skill writing-user-outputs --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/writing-user-outputs

Context preview

The summary Claude sees to decide when to auto-load this skill.

CLI output formatting standards for worktrunk. Load before editing any code that calls warning_message, hint_message, error_message, info_message, eprintln, or println, or that produces strings the user will see (CLI help, progress UI, snapshot text). Documents ANSI color

SKILL.md

writing-user-outputs.SKILL.md
name: writing-user-outputs
description: CLI output formatting standards for worktrunk. Load before editing any code that calls warning_message, hint_message, error_message, info_message, eprintln, or println, or that produces strings the user will see (CLI help, progress UI, snapshot text). Documents ANSI color nesting rules, message patterns, and output system architecture.
metadata:
  internal: true

Output System Architecture

Shell Integration

Worktrunk uses one file-based directive for shell integration:

1. Shell wrapper creates a temp file via `mktemp` 2. Shell wrapper sets `WORKTRUNK_DIRECTIVE_CD_FILE` 3. wt writes a raw path to the file 4. Shell wrapper changes directory to that path after wt exits

`--execute` always launches its external program directly from wt, with the selected worktree as its working directory. It does not use a directive.

Output Functions

The output system handles shell integration automatically. Just call output functions — they do the right thing regardless of whether shell integration is active.

// NEVER DO THIS - don't check mode in command code
if is_shell_integration_active() {
    // different behavior
}

// ALWAYS DO THIS - just call output functions
eprintln!("{}", success_message("Created worktree"));
output::change_directory(&path)?;  // Writes to directive file if set, else no-op

**Printing output:**

Use `eprintln!` and `println!` from `worktrunk::styling` (re-exported from `anstream` for automatic color support and TTY detection):

use worktrunk::styling::{eprintln, println, stderr};

// Status messages to stderr
eprintln!("{}", success_message("Created worktree"));

// Primary output to stdout (tables, shell code, pipeable)
println!("{}", table_output);

// Flush before interactive prompts
stderr().flush()?;

`src/` holds two crates, and the path differs between them: `worktrunk::styling` from the binary's modules — the ones `src/main.rs` declares (`commands`, `cli`, `display`, …), which the examples throughout this skill are written for — and `crate::styling` from the library's, the ones `src/lib.rs` declares (`git`, `config`, `shell_exec`, …), where `worktrunk::` does not resolve at all. The guard tests accept either, so the compiler is the only thing that tells you the path is wrong for the file.

Which `println!` is in scope decides whether a closed pipe panics: std's panics on the `BrokenPipe` write error, anstream's drops it. `wt … | head` closes the pipe, so command code imports the `worktrunk::styling` one and no `std::println!` is left in `src/`.

The stderr macros carry the same rule for a different consequence: anstream's `eprint!` / `eprintln!` strip ANSI when stderr isn't a terminal, std's keep it, so a file importing one but not the other writes escapes on one line of a message block and not the next under `wt … 2>log`. `eprint!` is the half that slips — it has no newline, so it gets reached for mid-block in a file that imported only `eprintln`. Every bare `eprint!` / `eprintln!` under `src/` must resolve to anstream's: import it, or qualify the call as `styling::eprintln!(…)`. `check_stderr_macros_come_from_styling` in `tests/integration_tests/output_system_guard.rs` holds that statically, since no snapshot can — the suite forces `CLICOLOR_FORCE=1`, so both printers emit color and a snapshot agrees whichever macro is in scope. Its `STD_STDERR_ALLOWED_PATHS` exempts whole files, not calls, so an entry is only right where std's macro is right throughout.

A write that names no macro misses that scan entirely, since the scan looks for the name: `writeln!(std::io::stderr(), …)`, `std::io::stderr().write_all(…)`, a locked handle, a bound one. `check_raw_stderr_writes_go_through_anstream` refuses those shapes too, with its own `RAW_STDERR_ALLOWED_PATHS` — currently just `progress.rs`, whose spinner holds one lock across a frame (anstream's `stderr()` has none) and is tty-gated, so nothing of its output ever reaches a redirected stderr. The shape to watch for is a message rendered in one place and printed several layers below, where the printer has no idea it is handling narration: `Cmd::delayed_stream`'s progress line is the example, and a raw handle there made it the only colored line in a redirected `wt switch` log.

**Output whose ANSI is already decided** declares that once at the top of the command with `worktrunk::styling::ColorChoice::Always.write_global()` and then prints through the same anstream macros — the statusline a shell prompt or Claude Code renders, and the `--help-page` document whose escapes the docs pipeline turns into HTML (`--plain` and `--help-md` declare `Never` the same way). Neither consumer is ever a tty, so without the declaration anstream would strip their color every time — and the test suite would not catch it, because it forces color with `CLICOLOR_FORCE=1`; `test_color_follows_the_consumer` pins the unforced behavior. Declare `Always` only when the pipe is a courier rather than the destination; anything a person reads directly stays on plain anstream, which is what strips color on a pipe and honors `NO_COLOR`.

**`--format=json` answers** go through `crate::output::print_json`, never a hand-rolled `println!("{}", serde_json::to_string_pretty(&v)?)`. It serializes pretty with one trailing newline and prints through anstream, so no `--format=json` surface panics when its consumer stops reading. Before that, thirty call sites had open-coded those two lines, and whether any one of them panicked under `| head -3` came down to which `println!` its module happened to import. `wt switch --format=json` is the one non-caller: it emits its single result as one compact line (still through anstream's `println!`), because that is what a shell loop reads.

**Shell integration functions** (`src/output/global.rs`):

| Function | Purpose | |----------|---------| | `change_directory(path)` | Shell cd after wt exits (writes to directive file if set) | | `execute(argv)` | Ru

Read more
Ships withworktrunk

Worktrunk is a CLI for Git worktree management, designed for parallel AI agent workflows

Get the whole plugin
Stats
7,621
Stars
272
Forks
Active
Maintenance
Rust
Language
15h ago
Last commit
11mo ago
Created

Repo: max-sixty/worktrunk

Other skills on worktrunk.