ScriptTheme colors (function refs not strings), gradient hex tuple, CollapseLevel resolution (stage>flag>theme), HeaderStyle, StageStyle/StepStyle TokenFormatString, TasukuReporter.printHeader, ctx.setTaskTitle/setTaskOutput/setTaskStatus/setTaskError/setTaskWarning fallback behavior, Bold/GradientText/ANSI utilities, DEFAULT_THEME
Configure terminal appearance, collapse behavior, and in-flight step feedback for a Stagehand CLI application.
Stagehand is a TypeScript CLI framework (runtime: Bun) for orchestrating multi-stage, multi-step command processors with typed flags, error registries, compensation (rollback), and tasuku v3-powered terminal task rendering.
Theme configuration happens on the ScriptApp instance via .theme().
The method shallow-merges your overrides into DEFAULT_THEME — you only
supply the properties you want to change.
import { ScriptApp } from './modules/classes/appScript';
import { blue, red, green, yellow, darkGray, white } from './modules/textFormatting';
const app = new ScriptApp('my-cli')
.meta({ version: '2.1.0', author: 'eng-team' })
.theme({
collapseLevel: 'stage',
headerStyle: 'fancy',
colors: {
primary: blue,
error: red,
success: green,
gradient: ['#00FFFF', '#FF00FF'],
},
})
.command({ /* ... */ });
await app.parseAsync();
The merge logic in appScript.ts is:
this.__theme = {
...this.__theme,
...theme,
colors: { ...this.__theme.colors, ...(theme.colors ?? {}) },
stageStyle: { ...this.__theme.stageStyle, ...(theme.stageStyle ?? {}) },
stepStyle: { ...this.__theme.stepStyle, ...(theme.stepStyle ?? {}) },
};
Any color you omit keeps its DEFAULT_THEME value.
When you need full control over every color, header, and format string:
import { ScriptApp } from './modules/classes/appScript';
import {
blue, red, green, yellow, white, darkGray,
blueBackground, grayBackground, greenBackground,
Bold, GradientText,
} from './modules/textFormatting';
const app = new ScriptApp('deploy-tool')
.meta({ version: '3.0.0', environment: 'production' })
.theme({
collapseLevel: 'tasks',
headerStyle: 'fancy',
colors: {
// Every color property is a FUNCTION: (text: string) => string
primary: blue,
secondary: white,
accent: green,
warning: yellow,
error: red,
info: blue,
debug: darkGray,
success: green,
dimmed: darkGray,
primaryBackground: blueBackground,
secondaryBackground: grayBackground,
accentBackground: greenBackground,
// gradient is the ONLY non-function — it is a [string, string] tuple
gradient: ['#FF6B6B', '#4ECDC4'],
},
stageStyle: {
formatString: '$stage: $message',
color: green,
},
stepStyle: {
formatString: '$step: $message',
},
});
Key facts:
primary through dimmed) are functions (text: string) => string.gradient is the sole exception — a [string, string] tuple of hex color codes.stageStyle.color is also a function (text: string) => string.formatString values must start with a FormatToken ($stage, $step, $message, $progress, $total, $elapsed, $remaining, $percent). The type is `${FormatToken}${string}`.Collapse controls what stays visible in the terminal after a stage completes. Three levels exist:
| Level | Behavior |
|---|---|
'none' | Everything stays visible — all stages and steps remain after completion |
'tasks' | Individual step TaskPromises are .clear()-ed after completion; stage titles remain |
'stage' | The entire stage TaskPromise is .clear()-ed after completion, hiding all children |
Resolution priority (first defined wins):
stage.collapseLevel → runtime.flags.collapseLevel → theme.collapseLevel
Set it at the theme level (global default):
.theme({ collapseLevel: 'tasks' })
Override per-stage in a StageBuilder:
.stage('deploy', 'Deploy to production', (s) =>
s
.collapse('stage') // this stage collapses entirely
.step({ /* ... */ })
)
Override at runtime via CLI flags (if your command exposes a collapseLevel flag):
// In the command definition's build callback: