Icod.Terminal
0.9.0
See the version list below for details.
dotnet add package Icod.Terminal --version 0.9.0
NuGet\Install-Package Icod.Terminal -Version 0.9.0
<PackageReference Include="Icod.Terminal" Version="0.9.0" />
<PackageVersion Include="Icod.Terminal" Version="0.9.0" />
<PackageReference Include="Icod.Terminal" />
paket add Icod.Terminal --version 0.9.0
#r "nuget: Icod.Terminal, 0.9.0"
#:package Icod.Terminal@0.9.0
#addin nuget:?package=Icod.Terminal&version=0.9.0
#tool nuget:?package=Icod.Terminal&version=0.9.0
Icod.Terminal

Icod.Terminal is the managed, cross-platform live-terminal layer for the Icod library family. It sits between Icod.TermInfo and higher-level consumers such as Icod.DCurses, terminal-aware command-line tools, monitors, editors, pagers, and REPLs.
Status
0.9.0 is the current stable release. It adds scoped synchronized-output ownership using DEC private mode 2026, identity-aware first-owner/last-owner nesting, lifecycle-safe leave/re-entry, retryable cleanup after output failures, and downstream Icod.DCurses refresh acceptance.
The release preserves the existing live-session, rich-input, active-query, OSC 0/1/2 title, OSC 7 current-location, OSC 8 hyperlink, OSC 52 clipboard, and 0.8 cursor-style contracts.
Installation
dotnet add package Icod.Terminal --version 0.9.0
The package targets net8.0, net9.0, and net10.0 and depends on Icod.TermInfo 1.10.0 and Icod.Timing 1.0.0.
Architecture
Icod.TermInfo
^
|
Icod.Terminal
^
|
Icod.DCurses
^
|
watch / slabtop / top
Icod.TermInfo remains the immutable terminal-capability authority. Icod.Terminal owns live endpoint observation, terminal modes, input, dimensions, lifecycle, terminal identity, output setup, reversible presentation state, active terminal-query routing, and semantic terminal-output operations. Icod.DCurses owns cells, windows, virtual-screen state, and refresh/diff policy.
Icod.Timing supplies monotonic elapsed-time and cancellable-delay primitives used by input ambiguity windows and active query transactions.
Quick start
using Icod.Terminal;
await using TerminalSession session = await TerminalSession.OpenAsync(
new TerminalSessionOptions {
InputMode = TerminalInputMode.CBreak,
EchoInput = false
}
);
await using ( TerminalSynchronizedOutputLease synchronized =
await session.AcquireSynchronizedOutputAsync() ) {
await session.WriteTextAsync( "updating terminal state\r\n" );
await session.SetWindowTitleAsync( "my terminal app" );
await session.PublishCurrentLocationAsync(
"/usr/local/src",
TerminalLocationPathStyle.Posix
);
}
The session borrows process-standard endpoints, owns only terminal state transitions it applies, and restores captured state during DisposeAsync().
0.9 synchronized output
Semantic lease
Synchronized output is exposed only through a scoped semantic lease:
await using TerminalSynchronizedOutputLease synchronized =
await session.AcquireSynchronizedOutputAsync();
The first logical owner emits canonical seven-bit DEC private mode 2026 enable:
ESC [ ? 2 0 2 6 h
The final logical owner emits:
ESC [ ? 2 0 2 6 l
followed by one output flush.
Nested acquisitions share the same physical terminal mode request. Because every owner requests the same boolean synchronized-output state, nested leases are identity-aware rather than strict-LIFO and may be disposed out of order. Non-final releases emit nothing and do not flush.
Truthful support posture
Successful acquisition proves only that any required begin frame was emitted and logical ownership was established. It does not prove that the attached terminal implements or continues honoring private mode 2026.
Icod.Terminal does not infer synchronized-output support from TERM, operating system, terminal-emulator identity, or environment variables, and ordinary acquisition does not perform an automatic DECRQM query.
The terminal may independently stop deferring presentation because of its own timeout or implementation limits. The lease therefore guarantees protocol ownership and cleanup, not an unlimited terminal-side atomic transaction.
Composition and lifecycle
Synchronized output is a terminal-side presentation-timing bracket, not an application-side byte buffer. Existing operations retain their normal framing and flush behavior inside the lease, including text, OSC title/location/hyperlink/clipboard operations, cursor style, presentation state, and explicit active terminal queries.
Managed suspension physically leaves synchronized output and flushes before suspension while retaining logical ownership. Resume re-enters mode 2026 only if logical owners remain. Releasing all owners while suspended is logical-only and prevents re-entry.
Final-release write or flush failures retain cleanup ownership so the same lease can retry. Session disposal remains authoritative best-effort cleanup.
The reviewed 0.9 API is frozen in docs/Public-API-Baseline-0.9.md. T96 downstream acceptance is recorded in docs/T96-Synchronized-Output-Integration-Compatibility-and-DCurses-Acceptance.md.
0.8 cursor style
Semantic styles
TerminalCursorStyle exposes exactly six semantic styles:
TerminalCursorStyle.BlinkingBlock
TerminalCursorStyle.SteadyBlock
TerminalCursorStyle.BlinkingUnderline
TerminalCursorStyle.SteadyUnderline
TerminalCursorStyle.BlinkingBar
TerminalCursorStyle.SteadyBar
They map to DECSCUSR parameters 1 through 6. Outbound frames use canonical seven-bit CSI:
ESC [ Ps SP q
Bar styles use the xterm-compatible DECSCUSR extension. Successful write completion proves only that the full frame was emitted; it does not prove that the terminal recognized or applied the style.
Explicit setter
await session.SetCursorStyleAsync(
TerminalCursorStyle.SteadyUnderline
);
The setter participates in session-owned output ordering, validates before emission, rejects known redirected output, and does not implicitly flush.
Explicit observation
TerminalCursorStyleObservation observation =
await session.QueryCursorStyleAsync(
TimeSpan.FromMilliseconds( 750 )
);
if ( observation.IsSupported ) {
Console.WriteLine( observation.Style );
}
Observation reuses the existing DECRQSS SP q query path. An explicit negative DECRQSS response returns IsSupported == false. Timeout remains TimeoutException; malformed or unknown positive state remains FormatException. A timeout is not treated as proof of unsupported behavior.
Inbound omitted, 0, and 1 state normalize to BlinkingBlock; recognized values 2 through 6 map directly. xterm parameter 7 is not exposed as a generic semantic style or restoration primitive.
Truthful scoped restoration
await using TerminalCursorStyleLease lease =
await session.AcquireCursorStyleAsync(
TerminalCursorStyle.SteadyBar,
TimeSpan.FromMilliseconds( 750 )
);
await session.WriteTextAsync( "work while the leased style is active" );
The outermost lease first observes the actual current semantic cursor style. No mutation occurs unless that observation succeeds. Nested leases use strict LIFO ownership and restore the immediately preceding session-owned style. The outermost release restores the actually observed pre-lease style.
Exact restoration never means guessing a reset. Icod.Terminal does not use DECSCUSR parameter 0, hard-code a block cursor, or emit xterm parameter 7 as a substitute for observed prior state.
Active cursor-style leases also participate in managed suspend/resume. Before suspension, the observed baseline is restored. After successful re-entry, the innermost active logical style is re-applied. Releasing a lease while suspended updates logical ownership without emitting extra cursor-style bytes.
Cursor style is not cursor visibility
Cursor shape/blink policy and cursor visibility remain separate public concepts. TerminalCursorStyle does not hide or show the cursor. TerminalCursorVisibility remains part of reversible TerminalPresentationLease state.
The reviewed 0.8 API is frozen in docs/Public-API-Baseline-0.8.md. T86 integration acceptance is recorded in docs/T86-Cursor-Style-Integration-Compatibility-and-Regression-Acceptance.md.
Earlier semantic terminal operations
0.7 OSC 52 clipboard and selections
await session.WriteClipboardAsync(
TerminalClipboardSelection.Clipboard,
"copied text"
);
byte[] payload = await session.ReadClipboardAsync(
TerminalClipboardSelection.Clipboard,
TimeSpan.FromMilliseconds( 750 )
);
Reads are always explicit. Opening, probing, suspending, resuming, or disposing a session never initiates a clipboard read. The decoded payload ceiling is 65,536 bytes and text writes use strict UTF-8 without BOM.
See docs/Public-API-Baseline-0.7.md.
0.6 OSC 8 hyperlinks
await session.WriteHyperlinkAsync(
"example",
"https://example.com/"
);
await using TerminalHyperlinkLease hyperlink =
await session.AcquireHyperlinkAsync(
"https://example.com/"
);
Hyperlink scopes are strict LIFO and participate in managed suspend/resume cleanup.
See docs/Public-API-Baseline-0.6.md.
0.5 OSC 7 current-location publication
await session.PublishCurrentLocationAsync(
"/usr/local/src",
TerminalLocationPathStyle.Posix
);
Path grammar is explicit; the library does not automatically publish Environment.CurrentDirectory.
See docs/Public-API-Baseline-0.5.md.
0.4 OSC title operations
await session.SetTitleAsync( "both" );
await session.SetIconNameAsync( "icon" );
await session.SetWindowTitleAsync( "window" );
See docs/Public-API-Baseline-0.4.md.
Active terminal queries
Opening a session does not interrogate the terminal. Queries are explicit and bounded:
TimeSpan timeout = TimeSpan.FromMilliseconds( 750 );
TerminalPrimaryDeviceAttributes primary =
await session.QueryPrimaryDeviceAttributesAsync( timeout );
TerminalCursorPosition cursor =
await session.QueryCursorPositionAsync( timeout );
TerminalStatusStringResponse sgr =
await session.QueryStatusStringAsync(
TerminalStatusStringKind.SelectGraphicRendition,
timeout
);
Responses are routed through the same session-owned input path used by ordinary text, keys, mouse, focus, paste, and lifecycle events. There is no second public response reader.
Rich input and reversible presentation
Rich input remains on TerminalSession.ReadEventAsync. Reporting protocols are enabled only through reversible session-owned leases.
TerminalControlResult<TerminalInputProtocolLease> protocols =
await session.AcquireInputProtocolsAsync(
new TerminalInputProtocolOptions {
BracketedPaste = true,
FocusReporting = true,
MouseTrackingMode = TerminalMouseTrackingMode.ButtonEvents
}
);
Presentation state such as alternate screen, keypad mode, and cursor visibility is separately owned by TerminalPresentationLease.
Samples
The repository contains focused samples for each major public family:
Icod.Terminal.SynchronizedOutput.Sample— 0.9 scoped synchronized output;Icod.Terminal.CursorStyle.Sample— 0.8 cursor-style observation and truthful scoped restoration;Icod.Terminal.Clipboard.Sample— 0.7 OSC 52 clipboard writes and reads;Icod.Terminal.Hyperlink.Sample— 0.6 OSC 8 bounded and scoped hyperlinks;Icod.Terminal.Location.Sample— 0.5 OSC 7 location publication;Icod.Terminal.Title.Sample— 0.4 OSC 0/1/2 title operations;Icod.Terminal.Query.Sample— active query families;Icod.Terminal.RichInput.Sample— rich input and protocol leases;Icod.Terminal.Sample— minimal live session.
See samples/README.md for run instructions.
Build and validation
On Windows:
build.cmd
On POSIX hosts:
sh build.sh
Both scripts support clean, restore, build, test, pack, and validate. Distribution validation builds/tests the solution—including the focused 0.9 sample—runs real downstream Icod.DCurses synchronized-refresh acceptance, packs the NuGet artifacts, verifies package structure and XML documentation, and runs fresh package-only consumers.
The 0.8 cursor-style and 0.9 synchronized-output package consumers are both required to restore and run from the freshly produced NuGet artifact on net8.0, net9.0, and net10.0.
Release process
Stable release readiness requires:
- PR validation green on Windows, Linux, and macOS;
- exact Staging package verification green;
- synchronized-output source sample and downstream
Icod.DCursesacceptance green; - retained 0.8 cursor-style and new 0.9 synchronized-output XML documentation/package-only smoke gates green on all supported TFMs;
- merge to
main; - Release distribution validation green on the
mainarchitecture matrix; - only then create tag
v0.9.0.
The tag workflow rebuilds and retests the tagged solution, reruns downstream DCurses acceptance, selects the exact package matching the tag, reruns package verification including both the 0.8 and 0.9 public contracts, and only then publishes to NuGet.org and GitHub Packages.
Development roadmap
The 0.9 milestone is documented in Icod.Terminal-0.9.0-Development-Roadmap.md, with tranche records T90–T97 under docs/.
The broader protocol-closure sequence is documented in Icod.Terminal-0.4.0-to-0.9.0-Protocol-Closure-Roadmap.md.
License
Icod.Terminal is licensed under LGPL-3.0-or-later. See LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Icod.TermInfo (>= 1.10.0)
- Icod.Timing (>= 1.0.0)
-
net8.0
- Icod.TermInfo (>= 1.10.0)
- Icod.Timing (>= 1.0.0)
-
net9.0
- Icod.TermInfo (>= 1.10.0)
- Icod.Timing (>= 1.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Icod.Terminal:
| Package | Downloads |
|---|---|
|
Icod.DCurses
Managed, cross-platform curses-like terminal UI library for .NET, built on Icod.TermInfo and Icod.Terminal. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.13.0 | 0 | 9/6/2026 |
| 0.12.0 | 0 | 9/5/2026 |
| 0.11.0 | 0 | 9/5/2026 |
| 0.10.0 | 0 | 9/5/2026 |
| 0.9.0 | 24 | 9/5/2026 |
| 0.8.0 | 43 | 9/5/2026 |
| 0.7.0 | 36 | 9/5/2026 |
| 0.6.1 | 36 | 9/4/2026 |
| 0.6.0 | 34 | 9/4/2026 |
| 0.5.0 | 37 | 9/4/2026 |
| 0.4.0 | 39 | 9/4/2026 |
| 0.3.0 | 2,885 | 8/29/2026 |
| 0.3.0-alpha.8 | 67 | 8/28/2026 |
| 0.2.0 | 87 | 8/28/2026 |
| 0.2.0-alpha.6 | 86 | 8/27/2026 |
| 0.1.0 | 85 | 8/27/2026 |
| 0.1.0-alpha.13 | 56 | 8/27/2026 |
| 0.1.0-alpha.11 | 1,755 | 8/25/2026 |
| 0.1.0-alpha.10 | 70 | 8/25/2026 |
| 0.1.0-alpha.9 | 58 | 8/25/2026 |
0.9.0 adds scoped synchronized-output ownership using DEC private mode 2026, first-owner/last-owner nesting, lifecycle-aware cleanup and recovery, composition with existing terminal operations and queries, and downstream Icod.DCurses refresh acceptance.