osc-progress is a TypeScript library for emitting and removing OSC 9;4 terminal progress
sequences. It is intended for Node.js CLIs running in Ghostty, WezTerm, Canario, or Windows
Terminal and becomes a no-op outside supported TTYs.
pnpm add osc-progressNode.js 24 or newer is required.
import { setTimeout as delay } from "node:timers/promises";
import { startOscProgress } from "osc-progress";
const stop = startOscProgress({ label: "Indexing", indeterminate: true });
try {
await delay(1_000);
} finally {
stop();
}startOscProgress() writes to stderr by default. In a supported terminal it starts progress and
returns an idempotent function that clears it; elsewhere both operations do nothing.
Use a controller when the work already exposes a percentage or moves between states:
import { createOscProgressController } from "osc-progress";
const progress = createOscProgressController({ stallAfterMs: 10_000 });
progress.setIndeterminate("Connecting");
progress.setPercent("Downloading", 42);
progress.done();Updates are deduplicated and throttled to about one every 150 ms. Percentages are rounded and
clamped to 0..100; the latest percentage is emitted when the throttle window ends, even if no
more updates arrive. done() and fail() emit their final state before clearing it.
Progress is enabled only for a TTY recognized as Ghostty, WezTerm, Canario, or Windows Terminal.
The same detection applies to both startOscProgress() and createOscProgressController().
import { supportsOscProgress } from "osc-progress";
const supported = supportsOscProgress(process.env, process.stderr.isTTY === true, {
forceEnvVar: "MY_CLI_FORCE_PROGRESS",
disableEnvVar: "MY_CLI_NO_PROGRESS",
});The named environment variables take effect when their value is "1". Direct force and
disabled options are also available; a non-TTY stream always remains disabled.
Remove progress control sequences before saving captured terminal output:
import { sanitizeOscProgress } from "osc-progress";
function prepareForStorage(output: string): string {
return sanitizeOscProgress(output, process.stdout.isTTY === true);
}The parser recognizes sequences terminated by BEL, ST (ESC \\), or C1 ST (0x9c).
The public API includes the timer-based helper, a stateful controller, support detection, label sanitization, sequence discovery, and stripping helpers. See the API reference for signatures, options, exported constants, and OSC 9;4 portability notes.
OSC 9;4 state 4 is interpreted as paused by some terminals and warning by others. The library
emits the numeric state without trying to normalize that terminal-specific behavior. Labels are
an extra payload outside the canonical OSC 9;4 fields, so terminals may ignore them.
pnpm install
pnpm build
pnpm test
pnpm checkpnpm check runs formatting, linting, typechecking, tests, and coverage thresholds.
After building, node scripts/smoke.mjs checks the compiled package through ESM and CommonJS
entrypoints with real timers. CI runs tests and this smoke check on Node 24 and 26;
formatting, typechecking, and coverage run once on Node 24. The Vitest 5 development toolchain
excludes the odd, non-LTS Node 25 line; the package still requires Node.js 24 or newer.
MIT. See LICENSE.