# JSON, NDJSON, and semantic color

Source: https://libtmux.org/en/cxx/latest/workspace/reference/output/

> Python output and the native CLI stream and color contract.

**tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../compatibility/) describes this port's implemented coverage.

**Native CLI development contract.** The local `workspace-cli` worktrees provide JSON and NDJSON output. The schema below is the shared target; implemented command coverage remains port-specific. Python tmuxp provides JSON and NDJSON on `ls` and `search`, and JSON on `debug-info`.

## Format selection

The shared contract accepts `--json` and `--ndjson` before or after every leaf command.
NDJSON wins when both are present. A saved workspace's encoding is separate:
`freeze -f yaml` describes the file, while `--json` describes the CLI result
stream. Machine output has no ANSI styling, prompts, spinner frames, or raw
child output.

## Native C++ load output

Human load publishes its summary and flushes stdout and stderr before terminal
attachment or client switching. A handoff failure preserves loaded changes.
If final publication fails, a writable diagnostic stream reports the completed
summary under `retained_state`; human stderr labels the same observed state.
A previous nonzero load status survives a later output failure.

Closed event output while building retains completed inputs and borrowed-session
effects in the failure summary. A known script failure keeps its status and
captured output when its completion event cannot be delivered. Scripts can make
additional changes outside the builder's retained-window records. NDJSON can
emit its terminal result only while its output stream remains writable. See
[loading and attachment](../../cli/load/#native-c-loading) for terminal and
client requirements.

## Native C++ logging

`load --log-file PATH` appends one JSON diagnostic record per line. The global
`--log-level` defaults to `warning`; `info` includes lifecycle events and `debug`
adds script-output chunks. Other records omit structured script captures.
Required errors and primary JSON/NDJSON results remain visible at every level.

The destination must be a regular file. New files receive owner-only
permissions; existing contents and permissions are preserved. Invalid paths,
symlinks and non-regular destinations fail before backend mutation. A later
write failure disables the file and reports one optional warning after primary
output checks. It preserves child status, cleanup and terminal handoff, but can
leave an incomplete final log record.

## Machine output

The document-to-stdout behaviors below are new machine-mode extensions. In the
pinned reference, freeze always saves a file and quiet only suppresses status
text. Separate the workspace file's encoding from the CLI stream's encoding.
`freeze -f json` selects the document format. `--json` selects structured CLI
output. Never append a status line to raw YAML or JSON document output.

| Command | JSON stdout | NDJSON stdout |
| --- | --- | --- |
| `ls` | Object containing `workspaces` and `global_workspace_dirs`, retaining tmuxp's existing record fields. Empty discovery yields the same object with an empty array. | One workspace record per line, matching tmuxp. Empty discovery emits zero records. Directory diagnostics stay on stderr. |
| `search` | Array of result records with `"name"`, `"path"`, `"session_name"`, `"source"`, `matched_fields`, `"matches"`. Empty results yield `[]`. | One result record per line. Empty results emit zero records. |
| `debug-info` | One diagnostics object. Retain home masking for named path fields, define redaction for raw tmux values, and add port/runtime details under named fields. | One compact diagnostics object plus newline. |
| `tmuxp load` | Versioned operation summary: command, status, results, errors and completed/failed stages. | Ordered operation events followed by exactly one terminal result. |
| `tmuxp freeze` | The workspace document as a JSON object when writing stdout; if saving to a file, a versioned save result containing destination, format and recoverability warnings. | One versioned capture/save result per line, with a `"workspace"` object when returning the document. |
| `convert` and importer leaves | The converted document as JSON when writing stdout; a versioned save result when an explicit destination is supplied. | One versioned conversion/save result with a nested document when returning it. |
| `edit` | One versioned result after the editor exits, including selected file and child status. | One terminal edit result; interactive editor display uses the terminal rather than machine stdout. |
| `"shell"` | For `-c`, one result containing captured Python stdout/stderr and child status. Interactive REPL requires a separate terminal; otherwise reject before execution. | For `-c`, stream captured Python output events and one terminal result. Interactive behavior has the same terminal requirement. |

Keep the established read-command JSON shapes rather than forcing a new
universal envelope around existing pipelines. New operation envelopes use
integer `schema_version: 1`. Search's empty array is a deliberate correction to
tmuxp 1.74.0's empty byte stream. No-pattern machine search must return usage
status 2, empty stdout and a structured stderr diagnostic. Explicit `--help`
remains a documented human-help request. Invalid patterns must return usage
status 2 and a diagnostic; tmuxp's current JSON search can silently return no
output for an invalid regex.

A new load summary has `schema_version`, `"command"`, `"status"` (`ok`, `partial` or
`"error"`), `results` and `errors`. Each result identifies its workspace input and
created/reused session; IDs are strings because tmux uses prefixes such as `$`,
`@` and `%`. Errors include a stable `code`, a readable `message`, the input
index and any completed/failed stage. Do not serialize native exception objects,
language-specific field capitalization or unserializable handles.

Every result names `input`, `input_index`, `session_id`, `session_name` and
`reused`; ports add their own fields around those. An input that failed keeps
its result record beside its `errors` entry, so a reader can tell which input
failed and what became of its session. `status` is `ok` when every input
completed, `partial` when some input completed or a failed one left effects
behind, such as a borrowed or appended session, and `error` when nothing
completed and nothing was retained.

NDJSON operation events have `schema_version`, `"command"`, `event`, a
monotonically increasing `"sequence"`, and event-specific data. The initial
vocabulary is `started`, `workspace-started`, `session-created`,
`window-created`, `pane-created`, `script-output`, `warning`,
`workspace-completed`, `"failed"`, `"completed"`. Emit `"completed"` or `"failed"` once
per invocation. Include operation/input identifiers where several files are
involved. `workspace-started` names the `input` and `input_index` it belongs to,
and `session-created` follows it directly, before any `window-created`, so a
reader knows the session a window belongs to as soon as the windows arrive.
Flush records as events arrive; buffering the whole run and splitting a JSON
array into lines is not streaming.

Drain child stdout and stderr concurrently to avoid pipe deadlocks. In machine
mode, script text belongs inside escaped JSON strings; it must never be written
directly to stdout. Apply backpressure, cap retained output and expose
truncation explicitly. Line breaks, tabs, ANSI bytes, Unicode and arbitrary
workspace names must remain valid encoded data. Binary output needs an explicit
byte encoding or a documented replacement policy.

Machine stderr contains one compact diagnostic JSON object per line.
Parse/validation failures before work leave stdout empty and return 2 or 1
respectively. A partial load emits a partial/failure result describing completed
work and returns 1; do not claim rollback unless it occurred. Human-mode
diagnostics remain readable text. Interruptions should stop scheduling new work,
drain or close owned streams, release owned process handles, and return 130
without killing unrelated tmux sessions.

## Prompts and file writes

Machine mode resolves choices from arguments and never treats missing input as
yes. Detached loading avoids mixing terminal attachment with JSON. Interactive
editor and REPL display require a separate controlling terminal; otherwise the
command rejects the request before execution.

The native contract defines explicit save, format, and overwrite controls for
conversion/import automation. With no destination, machine conversion returns
the document and writes no guessed file. Existing files require explicit
overwrite authorization, including explicit freeze destinations. These rules
differ from the Python overwrite behavior documented by
[freeze](../../cli/freeze/) and [convert](../../cli/convert/).

## Semantic color

Use roles at the point that a domain value is rendered. Formatting an entire
line with one success color loses the structure the requested style should
communicate. Compose a status, subject, identifiers, paths, counts and hints
separately, and reset styling after every token.

| Role | Default tmuxp-aligned style | Typical values |
| --- | --- | --- |
| Heading | Bold bright cyan | Command and section headings |
| Primary subject | Bold magenta | Workspace/session name, selected window |
| Information | Cyan | Paths, targets, useful values |
| Success | Green | Created, loaded, saved |
| Warning | Yellow | Partial support, lossy capture, retained objects |
| Error | Red | Failed operation, invalid field |
| Secondary text | Blue or dim text, verified against terminal contrast | Sizes, timestamps, source labels, hints |
| Command syntax | Distinct option/argument roles from the same theme | Flags, metavariables, examples |

Copy tmuxp's policy explicitly: nonempty `NO_COLOR` disables; explicit never
disables; explicit always enables; nonempty `FORCE_COLOR` enables auto;
otherwise use the destination stream's terminal capability. To support the
supplied reports' extra variables, add `CLICOLOR_FORCE` and `CLICOLOR` below
those explicit/reference choices. `CLICOLOR_FORCE=0` does not force. Machine
mode takes precedence over all color choices, including forced color.

Measure layout using visible terminal width, not byte length or ANSI-bearing
string length. Exercise narrow terminals, wrapped paths, Unicode and redirected
output. Keep status words and labels even when color is enabled, so meaning
survives monochrome output. Progress updates belong on stderr, animate only on a
terminal, and become discrete records in NDJSON mode. Honor the reference
progress presets, custom tokens and panel-line rules.

## Related reference

See [exit codes](../exit-codes/), [command flags](../../cli/), and
[environment](../../configuration/environment/).

[tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py).
