@cosyte/cli
Public programmatic entry point for @cosyte/cli (the . subpath export).
@cosyte/cli is a bin package: its primary artifact is the cosyte command on your PATH
(npx @cosyte/cli parse …), not an import surface. This module is the small, honest programmatic
API over the same core the CLI uses: the format autodetector, the exit-code contract, the
value-free diagnostic types, and the dispatcher, so another adapter (the MCP server, or a web
playground) can drive the same logic without shelling out.
The CLI is a thin, PHI-safe skin over the wrapped @cosyte/* parsers: it routes, reads, shapes
output, and owns the exit-code + value-free-diagnostic disciplines; it re-implements no wire-format
parsing.
Classes
CliError
A typed, PHI-safe CLI error. Its CliError.message | message is value-free by construction: callers must build it from codes, indices, offsets, format names, and file paths, never from an input value. It carries the ExitCode the invocation resolves to.
Example
import { CliError, CLI_CODES, EXIT } from "@cosyte/cli";
throw new CliError(CLI_CODES.CLI_NO_INPUT, EXIT.NOINPUT, "cannot read the named file");
Extends
Error
Constructors
Constructor
new CliError(
code,exit,message):CliError
Parameters
code
A stable CliCode.
exit
The ExitCode to resolve to.
message
string
A value-free explanation (positional/structural context only, never PHI).
Returns
Overrides
Error.constructor
Properties
code
readonlycode:CliCode
The stable diagnostic code.
exit
readonlyexit:ExitCode
The exit code this error resolves the invocation to.
Interfaces
DeidAvailability
Whether de-identification is available, and (while it is not) the value-free reason why. The one
function that answers "is the ground layer here?", consulted before any input is read so a
redact invocation never touches PHI it cannot strip.
Properties
available
readonlyavailable:boolean
true when @cosyte/deid resolves in this install.
reason
readonlyreason:string
A value-free explanation: the delegate's own label and version, or why it is unavailable.
DeidDelegate
The delegate, reduced to what the command needs: its own published honesty label and version, and one call that de-identifies an input. Deliberately CLI-owned shapes, so the delegate's types never reach this package's published surface.
Properties
label
readonlylabel:string
The delegate's own output label, quoted verbatim; the CLI asserts no standard of its own.
redact
readonlyredact: (format,bytes) =>Promise<RedactOutcome>
De-identify one input under the delegate's default policy.
Parameters
format
A covered format.
bytes
Uint8Array
The input bytes.
Returns
Promise<RedactOutcome>
The serialized de-identified document and the value-free manifest.
version
readonlyversion:string
The delegate's own version string.
DeidManifestRecord
One entry of the delegate's value-free manifest, narrowed to the fields the CLI renders. The
locus is a path, never a value, by the delegate's own contract; nothing here carries an input
value, which is what lets the manifest go on the diagnostic channel at all.
Properties
category
readonlycategory:string
The identifier category acted on.
code
readonlycode:string
The delegate's stable disposition code.
count
readonlycount:number
How many values at this locus were acted on.
disposition
readonlydisposition:string
What happened: transformed, removed, or the fail-closed blocked.
locus
readonlylocus:string
The structural path acted on: never a value.
transform
readonlytransform:string
The transform applied.
DetectResult
How confident autodetection is:
certain: exactly one signature matched (formatnames it);ambiguous: more than one matched (formatisnull;candidatesnames them);none: nothing matched (formatisnull;candidatesis empty).
format is null unless confidence is certain, so a non-certain result can never be mistaken
for a routable format.
Properties
candidates
readonlycandidates: readonlyCosyteFormat[]
The matching candidate formats (0 for none, 1 for certain, ≥2 for ambiguous). Value-free.
confidence
readonlyconfidence:"certain"|"ambiguous"|"none"
The detection confidence.
format
readonlyformat:CosyteFormat|null
The detected format, or null when confidence is not certain.
Finding
A single value-free finding: a stable code, a severity, and a positional locator. Every field is safe to print, log, or serialize. None carries a name, DOB, MRN, or field value.
Properties
code
readonlycode:string
The stable diagnostic/warning code (the wrapped library's or the CLI's).
location
readonlylocation:string
A value-free positional locator: a FHIRPath expression, or an HL7 segment/field index path.
severity
readonlyseverity:string
The R4-style severity (fatal | error | warning | information), or warning for HL7.
FmtResult
The result of a canonical re-serialization (fmt): the spec-clean text + a value-free warning count.
Properties
output
readonlyoutput:string
The wrapped serializer's output text.
warningCount
readonlywarningCount:number
How many parse warnings the round-trip surfaced (value-free count only).
ParseResult
The result of wrapping a parser's parse: the library's parsed model + its value-free warnings.
Properties
model
readonlymodel:unknown
The wrapped library's parsed model: emitted verbatim as JSON on the data channel.
warnings
readonlywarnings: readonlyParseWarning[]
The parser's value-free warnings (code + position).
PhiPosture
The resolved PHI display posture for one invocation. showValues is true only when the user
passed --unsafe-show-values; every value-echoing decision reads this one flag.
Properties
showValues
readonlyshowValues:boolean
true iff --unsafe-show-values was given: a value may appear on a secondary surface.
RedactOutcome
A completed de-identification pass: the serialized document plus the delegate's manifest.
Properties
manifest
readonlymanifest: readonlyDeidManifestRecord[]
The delegate's value-free manifest, in locus order.
output
readonlyoutput:string
The de-identified document, serialized exactly as this format's wrapped serializer emits it.
ResolvedInput
A successfully-resolved input: the format (guaranteed to support the requested op) and the bytes.
Properties
bytes
readonlybytes:Uint8Array
The input bytes (a whole file or a drained stdin buffer); guaranteed non-empty.
format
readonlyformat:CosyteFormat
The resolved format: guaranteed to satisfy supportsOp(format, op) for the requested op.
ResolvedInputStream
A successfully-resolved streaming input: the format, plus the input as a stream of chunks with the leading bytes detection consumed put back in front, so the consumer still sees the whole input in order.
Properties
chunks
readonlychunks:ByteChunks
The whole input as chunks, size-limited: reading past the limit raises CLI_INPUT_TOO_LARGE.
format
readonlyformat:CosyteFormat
The resolved format: guaranteed to satisfy supportsOp(format, op) for the requested op.
RunDeps
The injectable I/O side-effects the dispatcher needs. Kept tiny and pure-ish so tests drive the CLI end to end with in-memory fakes and no real filesystem, stdin or stdout.
The two whole-input readers are required; the chunk readers and the output sink are optional,
and a caller that omits them still gets identical output. They are the difference between reading
a stream as it arrives and being handed one buffer: with them, a multi-record parse emits each
record as it is produced and refuses an over-limit input while it is still arriving; without them,
the same code path runs over a single chunk and returns its output on the result's stdout as
before.
Properties
openFile?
readonlyoptionalopenFile?: (path) =>AsyncIterable<Uint8Array<ArrayBufferLike>>
Optional: open a file as a stream of byte chunks (see fileChunks).
Parameters
path
string
Returns
AsyncIterable<Uint8Array<ArrayBufferLike>>
openStdin?
readonlyoptionalopenStdin?: () =>AsyncIterable<Uint8Array<ArrayBufferLike>>
Optional: open stdin as a stream of byte chunks (see streamChunks).
Returns
AsyncIterable<Uint8Array<ArrayBufferLike>>
readFile
readonlyreadFile: (path) =>Promise<Uint8Array<ArrayBufferLike>>
Read a file's bytes, or raise a CLI_NO_INPUT CliError if it cannot be read.
Parameters
path
string
Returns
Promise<Uint8Array<ArrayBufferLike>>
readStdin
readonlyreadStdin: () =>Promise<Uint8Array<ArrayBufferLike>>
Read all of stdin's bytes.
Returns
Promise<Uint8Array<ArrayBufferLike>>
writeStdout?
readonlyoptionalwriteStdout?: (chunk) =>void
Optional: write one piece of the data channel as it is produced. When present, a multi-record
parse writes each record's NDJSON line through here instead of accumulating them, and the
returned result's stdout is empty because the output has already been delivered. May throw (a
consumer that closed the pipe); the caller turns that into a value-free
CLI_OUTPUT_WRITE_FAILED.
Parameters
chunk
string
Returns
void
RunResult
A fully-resolved CLI invocation.
Example
import { EXIT, type RunResult } from "@cosyte/cli";
const r: RunResult = { stdout: "{}\n", stderr: "", exit: EXIT.OK };
r.exit; // => 0
Properties
exit
readonlyexit:ExitCode
The exit code the invocation resolves to.
stderr
readonlystderr:string
The diagnostic channel is value-free: codes, positions, file paths, format names only.
stdout
readonlystdout:string
The data channel: the parsed model or requested output. May contain the user's data by design.
Verdict
The verdict of running a format's validation surface: value-free findings + the validity boolean.
Properties
findings
readonlyfindings: readonlyFinding[]
The value-free findings (code + severity + positional locator).
valid
readonlyvalid:boolean
true iff the input is valid (no error/fatal-severity finding).
Type Aliases
ByteChunks
ByteChunks =
AsyncIterable<Uint8Array>
A stream of input byte chunks, in arrival order: what every function here consumes.
CliCode
A value from CLI_CODES: the stable code carried by a CliError.
CosyteFormat
CosyteFormat =
"hl7"|"fhir"|"dicom"|"x12"|"ccda"|"ncpdp"|"astm"|"mllp"
The set of formats the cosyte command names.
DeidCoverage
DeidCoverage =
"covered"|"no-adapter"|"unserializable"
What the delegate can do for one format, from this CLI's point of view:
covered: an adapter for the standard this CLI resolves, serializable onto the text channel.no-adapter: the delegate does not de-identify this format (CLI_NOT_IMPLEMENTED, exit69).unserializable: the delegate covers it, but the result cannot ride a text stdout (CLI_FORMAT_UNSUPPORTED, exit65: the CLI's own limit, not the delegate's).
DeidCoveredFormat
DeidCoveredFormat =
"hl7"|"fhir"|"x12"|"ccda"
A format the delegate de-identifies and the CLI can serialize.
ExitCode
A value from EXIT: the exit code a CLI invocation resolves to.
InputResolution
InputResolution = {
input:ResolvedInput;ok:true; } | {ok:false;result:RunResult; }
The outcome of resolveInput: either the resolved input, or a ready-to-return value-free
RunResult carrying the diagnostic + exit code for whatever went wrong. A discriminated union
so a command reads if (!r.ok) return r.result; and then works with r.input.
InputStreamResolution
InputStreamResolution = {
input:ResolvedInputStream;ok:true; } | {ok:false;result:RunResult; }
The outcome of resolveInputStream: the resolved streaming input, or a ready-to-return value-free RunResult. The same discriminated-union shape as InputResolution.
InspectSummary
InspectSummary =
Hl7Summary|FhirSummary|X12Summary|AstmSummary|CcdaSummary|DicomSummary|NcpdpSummary|MllpSummary
The discriminated value-free structural summary inspect renders.
Op
Op =
"parse"|"inspect"|"fmt"|"validate"
The four wrapping operations a command can ask a format's adapter to perform.
ParseWarning
ParseWarning =
object&Readonly<Record<string,unknown>>
A value-free parse warning: a stable code plus the parser's positional (index-only) context.
Type Declaration
code
readonlycode:string
Variables
CLI_CODES
constCLI_CODES:object
Stable CLI diagnostic code registry: errors the CLI owns (routing, I/O, argument handling),
distinct from the wrapped library's own warning/issue codes which are passed through unchanged.
Each code is its own value (key === value) so the set survives Object.values(...); renaming or
removing one is a breaking change because scripts branch on the stderr text.
Type Declaration
CLI_DEID_INCOMPLETE
readonlyCLI_DEID_INCOMPLETE:"CLI_DEID_INCOMPLETE"="CLI_DEID_INCOMPLETE"
The delegated de-identifier reported that it could not handle every element of the input (a
fail-closed blocked locus, or its own fatal). The run is not a de-identified copy, so no
output is emitted: an operation-level failure, never a bug. Names loci and codes, never a
value. Exit 1.
CLI_EMPTY_INPUT
readonlyCLI_EMPTY_INPUT:"CLI_EMPTY_INPUT"="CLI_EMPTY_INPUT"
The input was empty, no bytes to detect or parse. Exit 65.
CLI_FORMAT_AMBIGUOUS
readonlyCLI_FORMAT_AMBIGUOUS:"CLI_FORMAT_AMBIGUOUS"="CLI_FORMAT_AMBIGUOUS"
More than one format signature matched; the CLI will not guess. Names the candidates, never the bytes. Exit 65.
CLI_FORMAT_UNDETECTED
readonlyCLI_FORMAT_UNDETECTED:"CLI_FORMAT_UNDETECTED"="CLI_FORMAT_UNDETECTED"
No format signature matched; the CLI will not guess. Names the candidates, never the bytes. Exit 65.
CLI_FORMAT_UNSUPPORTED
readonlyCLI_FORMAT_UNSUPPORTED:"CLI_FORMAT_UNSUPPORTED"="CLI_FORMAT_UNSUPPORTED"
A recognised format whose parser does not support the requested operation (e.g. parse on a
format wired only for inspect). Names the format + operation, never the bytes. Exit 65.
CLI_INPUT_TOO_LARGE
readonlyCLI_INPUT_TOO_LARGE:"CLI_INPUT_TOO_LARGE"="CLI_INPUT_TOO_LARGE"
The input is larger than the documented size limit the CLI reads in one invocation. Raised
against the running byte count while the input is read, so the refusal always arrives before an
allocation ceiling could. Names the limit, never the input. Exit 65, never 70.
CLI_INTERNAL
readonlyCLI_INTERNAL:"CLI_INTERNAL"="CLI_INTERNAL"
An unexpected internal error (a bug). Exit 70.
CLI_MAP_INVALID
readonlyCLI_MAP_INVALID:"CLI_MAP_INVALID"="CLI_MAP_INVALID"
The BYO ConceptMap supplied to map-codes is not valid JSON or not a loadable FHIR ConceptMap.
Names the stable terminology-loader code, never the map's bytes. Exit 65.
CLI_NO_INPUT
readonlyCLI_NO_INPUT:"CLI_NO_INPUT"="CLI_NO_INPUT"
The named file does not exist or could not be read. Exit 66.
CLI_NOT_IMPLEMENTED
readonlyCLI_NOT_IMPLEMENTED:"CLI_NOT_IMPLEMENTED"="CLI_NOT_IMPLEMENTED"
A command whose ground-layer library does not cover the resolved format (e.g. redact on a
format @cosyte/deid ships no adapter for). Never a fake success: a distinct, value-free
"unavailable" signal. Exit 69.
CLI_OUTPUT_WRITE_FAILED
readonlyCLI_OUTPUT_WRITE_FAILED:"CLI_OUTPUT_WRITE_FAILED"="CLI_OUTPUT_WRITE_FAILED"
The output stream could not be written: a consumer that closes the pipe ends the invocation
here rather than as an unhandled platform error, whether it went away part way through a record
stream or before a single-result write was delivered. Names no input, and never the platform's
own error text. Exit 74.
CLI_PARSE_FAILED
readonlyCLI_PARSE_FAILED:"CLI_PARSE_FAILED"="CLI_PARSE_FAILED"
The wrapped parser rejected the input. Positional context only, never the offending bytes. Exit 65.
CLI_PARSER_UNAVAILABLE
readonlyCLI_PARSER_UNAVAILABLE:"CLI_PARSER_UNAVAILABLE"="CLI_PARSER_UNAVAILABLE"
The optional parser package for a recognised format is not installed. The six breadth parsers
(dicom/x12/ccda/ncpdp/astm/mllp) are optionalDependencies: normally installed, but if
one is absent the CLI degrades to this value-free signal rather than crashing. Exit 69.
CLI_USAGE
readonlyCLI_USAGE:"CLI_USAGE"="CLI_USAGE"
A required argument was missing or a flag was invalid. Exit 2.
Example
import { CLI_CODES } from "@cosyte/cli";
if (diagnostic.code === CLI_CODES.CLI_FORMAT_UNDETECTED) {
// ask the user for --format
}
DEID_BLOCKED
constDEID_BLOCKED:"blocked"="blocked"
The fail-closed disposition: the delegate reports it could not handle the element.
DEID_COVERAGE
constDEID_COVERAGE:Readonly<Record<CosyteFormat,DeidCoverage>>
The honest per-format de-identification matrix, the redact analogue of the parser registry's
operation matrix. Every one of the eight formats has an entry, so a new format cannot be added
without answering for it here.
Example
import { DEID_COVERAGE } from "@cosyte/cli";
DEID_COVERAGE.hl7; // => "covered"
DEID_COVERAGE.dicom; // => "unserializable"
DEID_PACKAGE
constDEID_PACKAGE:"@cosyte/deid"="@cosyte/deid"
The npm package the whole de-identification capability is delegated to.
DEID_UNAVAILABLE_REASON
constDEID_UNAVAILABLE_REASON:string
The value-free reason redact/deid reports when the de-identification library is absent from the
install. It is an optionalDependency, so a copy without it degrades rather than crashing.
DETECT_PREFIX_BYTES
constDETECT_PREFIX_BYTES:512=512
How many leading bytes detection reads. Small: every signature lives in the first line (the deepest is DICOM's magic at byte 128). Exported because a streaming caller has to know how much of the input to hold back before it can detect the format, and reading a different amount than detection uses would make the two disagree.
Example
import { DETECT_PREFIX_BYTES } from "@cosyte/cli";
DETECT_PREFIX_BYTES; // => 512
DETECTABLE_FORMATS
constDETECTABLE_FORMATS: readonlyCosyteFormat[]
The formats content-autodetection can recognise (every format now carries a signature).
EXIT
constEXIT:object
The stable exit-code map. Adding a code is a documented, tested change to the CLI's contract; renaming or repurposing one is a breaking change.
Type Declaration
DATAERR
readonlyDATAERR:65=65
Data error: input could not be parsed or its format could not be detected (EX_DATAERR).
INVALID
readonlyINVALID:1=1
Operation-level failure: validate found the input invalid (parseable but non-conformant),
or redact could not de-identify every locus: a real, expected CI signal that the tool worked
and the operation did not complete. Never emitted for unparseable input (that is DATAERR).
IOERR
readonlyIOERR:74=74
Output error: the stream the CLI was writing to closed before the output had been delivered, so
the consumer holds nothing or holds only part of the answer. The shape a downstream consumer
that exits early produces (a pager quit, a head that has seen enough). A handled condition the
consumer owns, never a defect in the CLI, so it is deliberately distinct from SOFTWARE
(EX_IOERR).
NOINPUT
readonlyNOINPUT:66=66
No input: the named file does not exist or is unreadable (EX_NOINPUT).
OK
readonlyOK:0=0
Success: the operation completed; validate found the input valid.
SOFTWARE
readonlySOFTWARE:70=70
Internal error: an unexpected exception (a bug), distinct from a handled bad input (EX_SOFTWARE).
UNAVAILABLE
readonlyUNAVAILABLE:69=69
Unavailable: a required capability is not wired for this input (e.g. redact on a format
@cosyte/deid has no adapter for, or with that optional library absent), a distinct non-zero
signal that is never a fake success (EX_UNAVAILABLE).
USAGE
readonlyUSAGE:2=2
Usage error: unknown command, bad flag, missing argument. A value specific to this CLI, not a
sysexits.h constant.
Example
import { EXIT } from "@cosyte/cli";
process.exitCode = EXIT.OK; // => 0
KNOWN_FORMATS
constKNOWN_FORMATS: readonlyCosyteFormat[]
The full set of format names --format accepts as syntactically valid (a bad value is a usage error).
MAX_INPUT_BYTES
constMAX_INPUT_BYTES:number
The maximum number of input bytes cosyte parse reads in one invocation: 67108864 bytes
(64 MiB).
Chosen to sit far below the smaller of Node's two allocation ceilings
(buffer.constants.MAX_STRING_LENGTH, 536870888 characters), because the bytes read are not the
peak: a parsed model rendered as JSON is routinely several times the size of the input that
produced it, so the limit has to leave room for the output as well as the input. A power of two, so
the byte count and its mebibyte rendering are both exact.
Example
import { MAX_INPUT_BYTES } from "@cosyte/cli";
MAX_INPUT_BYTES; // => 67108864
OP_SUPPORT
constOP_SUPPORT:Readonly<Record<CosyteFormat,ReadonlySet<Op>>>
The honest per-format operation matrix. A (format, op) pair absent here is a value-free
CLI_FORMAT_UNSUPPORTED, never a faked result. The unsupported cells and why:
- dicom
parse/fmt. The model is binary (no faithful JSON view;serializeDicomemits a Part-10 byte stream, not text).inspect/validateare supported. - ccda
parse: the canonical form is XML; there is no library-blessed JSON model, sofmt(XML re-serialize) is the faithful surface.inspect/fmt/validateare supported. - mllp
fmt/validate: MLLP is a transport container the CLI de-frames to HL7;parseyields the enclosed HL7 message(s) andinspectreports the frame count.
Example
import { OP_SUPPORT } from "@cosyte/cli";
OP_SUPPORT.dicom.has("parse"); // => false
OP_SUPPORT.hl7.has("validate"); // => true
SHOW_VALUES
constSHOW_VALUES:PhiPosture
The opted-in, PHI-exposing posture selected by --unsafe-show-values.
UNSAFE_EXCERPT_MAX
constUNSAFE_EXCERPT_MAX:200=200
The maximum number of leading input bytes an --unsafe-show-values diagnostic may echo. Bounded so
an unsafe excerpt stays a single, readable diagnostic line rather than dumping a whole message.
UNSAFE_SHOW_VALUES_FLAG
constUNSAFE_SHOW_VALUES_FLAG:"--unsafe-show-values"="--unsafe-show-values"
The single global flag token that opts into showing values on secondary surfaces.
VALUE_FREE
constVALUE_FREE:PhiPosture
The default, safe posture: no value ever reaches a secondary surface.
VERSION
constVERSION:string="0.1.5"
The @cosyte/cli version. On the uniform v0.0.x-until-first-alpha ladder.
Read by cosyte --version and by the MCP server's advertised SERVER_INFO, so it is a
user-visible surface on both front doors, not merely an export.
Example
import { VERSION } from "@cosyte/cli";
console.log(`cosyte ${VERSION}`);
Functions
asCosyteFormat()
asCosyteFormat(
value):CosyteFormat|null
Narrow an arbitrary --format string to a CosyteFormat, or null if it is not a known
format name (which the caller turns into a usage error, exit 2).
Parameters
value
string
The raw --format argument.
Returns
CosyteFormat | null
The narrowed format, or null.
Example
import { asCosyteFormat } from "@cosyte/cli";
asCosyteFormat("hl7"); // => "hl7"
asCosyteFormat("nope"); // => null
classifyCandidates()
classifyCandidates(
candidates):DetectResult
Classify a list of matched candidate formats into a DetectResult: exactly one → certain,
zero → none, two-or-more → ambiguous (all with format null unless certain). Split out and
exported so the ambiguity contract is directly testable. It is otherwise unreachable while
every signature is disjoint, and it must stay a detected ambiguity, never a mis-route.
Parameters
candidates
readonly CosyteFormat[]
The formats whose signatures matched the input.
Returns
The classified DetectResult.
Example
import { classifyCandidates } from "@cosyte/cli";
classifyCandidates(["hl7"]).confidence; // => "certain"
classifyCandidates([]).confidence; // => "none"
classifyCandidates(["hl7", "fhir"]).confidence; // => "ambiguous"
collectChunks()
collectChunks(
chunks):Promise<Uint8Array<ArrayBufferLike>>
Drain a chunk stream into one buffer: the single-message path, where the parser needs the whole message anyway. The size limit is already applied by withinLimit upstream, so this cannot accumulate more than the limit allows.
Parameters
chunks
The input chunk stream.
Returns
Promise<Uint8Array<ArrayBufferLike>>
The concatenated bytes.
Example
import { collectChunks, oneChunk } from "@cosyte/cli";
(await collectChunks(oneChunk(new TextEncoder().encode("MSH|")))).length; // => 4
completionCommand()
completionCommand(
args):RunResult
Run the completion command.
Parameters
args
string[]
The arguments after the completion subcommand token; the first is the shell name.
Returns
A RunResult: the completion script on stdout (exit 0), or a value-free usage
error (exit 2) when the shell is missing or unrecognised.
Example
import { completionCommand } from "@cosyte/cli";
completionCommand(["bash"]).exit; // => 0
completionCommand(["powershell"]).exit; // => 2
convertCommand()
convertCommand(
args,deps,posture?):Promise<RunResult>
Run the convert command.
Parameters
args
string[]
The arguments after the convert subcommand token.
deps
Injected input readers (RunDeps).
posture?
PhiPosture = VALUE_FREE
The resolved PhiPosture (governs only the opt-in unsafe excerpt on an HL7 parse-failure diagnostic; the converted bundle and the value-free findings are unaffected).
Returns
Promise<RunResult>
A RunResult: the converted FHIR Bundle on stdout (the data channel), value-free
findings on stderr (or JSON under --json), and an exit code that carries the outcome: 0
clean · 1 an error-severity conversion issue · 65 the HL7 input could not be parsed or is not
an HL7 v2 source · 66 no input · 2 usage.
Throws
Never CliError; may propagate a truly unexpected error for the dispatcher to map to
CLI_INTERNAL.
Example
import { convertCommand } from "@cosyte/cli";
const deps = {
readFile: async () => new TextEncoder().encode("MSH|^~\\&|A|B|C|D|20240101||ADT^A01|1|P|2.5\r"),
readStdin: async () => new Uint8Array(),
};
(await convertCommand(["adt.hl7", "--to", "fhir"], deps)).exit; // => 0
convertOutcome()
convertOutcome(
findings):object
Decide the conversion outcome from the library's value-free findings: whether any is
error-severity (which drives a non-zero exit: the load-bearing "a conversion error is never
exit 0" rule) and the value-free human report. The severity classification is
the library's: @cosyte/transform fixes each issue's severity; the CLI only reads it. Exported so
the exit-verdict and report logic is unit-testable with synthetic findings, independent of which HL7
message happens to produce an error.
Parameters
findings
readonly Finding[]
The value-free Findings the conversion produced.
Returns
object
hasError (true iff any finding is error/fatal severity) and the rendered value-free
report (one line per finding + a summary line).
hasError
readonlyhasError:boolean
report
readonlyreport:string
Example
import { convertOutcome } from "@cosyte/cli";
convertOutcome([{ code: "TRANSFORM_RESOURCE_INVALID", severity: "error", location: "PID" }])
.hasError; // => true
convertOutcome([{ code: "TRANSFORM_ELEMENT_DROPPED", severity: "information", location: "PID.13" }])
.hasError; // => false
deframeMllp()
deframeMllp(
bytes):Promise<{payloads:Buffer<ArrayBufferLike>[];warningCount:number; }>
De-frame an MLLP byte stream into its enclosed HL7 payloads via @cosyte/mllp's FrameReader. MLLP
is a transport container, not a document format: each frame's payload is an HL7 v2 message the CLI
then parses/inspects with the hl7 adapter. Multi-frame streams are the CLI's multi-message surface.
Parameters
bytes
Uint8Array
The MLLP-framed input.
Returns
Promise<{ payloads: Buffer<ArrayBufferLike>[]; warningCount: number; }>
The de-framed HL7 payloads (in stream order) + a value-free framing-warning count.
Truncation is a data error, never a silent drop. The FrameReader is a streaming decoder: an
unterminated trailing frame (a VT opened with no closing FS/CR) is left buffered and delivered by
no callback, so feeding a whole file and reading only onFrame would silently lose the partial
message with a green exit. We therefore detect an open trailing frame at the byte level (the last
VT sits after the last FS: MLLP payloads are HL7 v2 text and never carry the 0x0B/0x1C framing
bytes) and reject the whole stream as a value-free CLI_PARSE_FAILED data error.
Throws
CLI_PARSER_UNAVAILABLE if @cosyte/mllp is absent; CLI_PARSE_FAILED (exit
65) on a truncated stream; a hard framing error propagates for the caller's value-free boundary.
Example
import { deframeMllp } from "@cosyte/cli";
// a VT-framed "MSH|..." message → one payload
(await deframeMllp(new Uint8Array([0x0b, 0x4d, 0x53, 0x48, 0x1c, 0x0d]))).payloads.length; // => 1
deidCoverage()
deidCoverage(
format):DeidCoverage
What the delegate can do for format.
Parameters
format
The resolved format.
Returns
That format's DeidCoverage.
Example
import { deidCoverage } from "@cosyte/cli";
deidCoverage("ncpdp"); // => "no-adapter"
deidCoveredFormats()
deidCoveredFormats(): readonly
CosyteFormat[]
The formats redact produces output for, sorted: the single source the help text, the published
documentation and the tests all read, so the three cannot drift.
Returns
readonly CosyteFormat[]
The covered format names, sorted.
Example
import { deidCoveredFormats } from "@cosyte/cli";
deidCoveredFormats(); // => ["ccda", "fhir", "hl7", "x12"]
deidStatus()
deidStatus(
load?):Promise<DeidAvailability>
Report the current de-identification availability. This is the single documented flip point: it resolves the delegated library rather than asserting anything about it.
Parameters
load?
() => Promise<DeidDelegate>
The delegate loader; injectable so a test can drive the unavailable branch.
Returns
Promise<DeidAvailability>
{ available: true } with the delegate's own label and version, else { available: false }
with the value-free DEID_UNAVAILABLE_REASON.
Example
import { deidStatus } from "@cosyte/cli";
(await deidStatus()).available; // => true when @cosyte/deid is installed
describeByteLimit()
describeByteLimit(
limit?):string
Render a byte limit as a concrete number with an explicit byte-based unit: "67108864 bytes (64 MiB)". Every surface that states the limit (the refusal diagnostic, cosyte --help, the
command reference) renders it through here, so the number a user reads is always the number the
code enforces, and never a vague description of one.
Parameters
limit?
number = MAX_INPUT_BYTES
The limit in bytes. Defaults to MAX_INPUT_BYTES.
Returns
string
The limit as bytes, plus a mebibyte rendering when it is a whole number of mebibytes.
Example
import { describeByteLimit } from "@cosyte/cli";
describeByteLimit(67108864); // => "67108864 bytes (64 MiB)"
describeByteLimit(1000); // => "1000 bytes"
detectFormat()
detectFormat(
bytes):DetectResult
Detect the healthcare format of bytes by content, conservatively.
Parameters
bytes
Uint8Array
The input bytes (a whole file or a stdin buffer). Only the leading prefix is sniffed.
Returns
A DetectResult: certain + a format on a single match, else ambiguous/none
with format: null and the value-free candidates list. Never guesses.
Example
import { detectFormat } from "@cosyte/cli";
const enc = new TextEncoder();
detectFormat(enc.encode("MSH|^~\\&|A|B\r")).format; // => "hl7"
detectFormat(enc.encode('{"resourceType":"Patient"}')).format; // => "fhir"
detectFormat(enc.encode("hello")).confidence; // => "none"
detectionError()
detectionError(
detected):CliError
Build the value-free CliError for a non-certain detection: the data error the caller
returns instead of guessing a parser. ambiguous names the matching candidates (a value-free code
list); none asks for --format. Both map to the data-error exit (65). Never echoes input bytes.
Parameters
detected
A DetectResult whose format is null (i.e. not certain).
Returns
A value-free CLI_FORMAT_AMBIGUOUS or CLI_FORMAT_UNDETECTED error.
Example
import { classifyCandidates, detectionError } from "@cosyte/cli";
detectionError(classifyCandidates([])).code; // => "CLI_FORMAT_UNDETECTED"
detectionError(classifyCandidates(["hl7", "fhir"])).code; // => "CLI_FORMAT_AMBIGUOUS"
errorResult()
errorResult(
err):RunResult
Resolve a CliError into a value-free RunResult: empty stdout (nothing reaches the
data channel on an error), the rendered diagnostic on stderr, and the error's exit code. The
single place the CLI turns an owned error into a result, so every command and the dispatcher render
errors identically.
Parameters
err
The CLI error to resolve.
Returns
The value-free RunResult.
Example
import { CliError, CLI_CODES, EXIT, errorResult } from "@cosyte/cli";
errorResult(new CliError(CLI_CODES.CLI_USAGE, EXIT.USAGE, "missing <file>")).exit; // => 2
extractPhiPosture()
extractPhiPosture(
argv):object
Resolve the global --unsafe-show-values flag out of an argument vector, order-independently
(it may appear before or after the subcommand), and return the posture plus the argv with every
occurrence of the flag removed, so each command's own parseArgs never sees it and cannot reject
it as unknown. This is the one place the flag is recognised.
Parameters
argv
readonly string[]
The raw arguments (after the program name).
Returns
object
The resolved PhiPosture and the flag-stripped argv.
argv
argv:
string[]
posture
posture:
PhiPosture
Example
import { extractPhiPosture } from "@cosyte/cli";
extractPhiPosture(["parse", "x.hl7"]).posture.showValues; // => false
extractPhiPosture(["--unsafe-show-values", "parse", "x.hl7"]).posture.showValues; // => true
extractPhiPosture(["parse", "x.hl7", "--unsafe-show-values"]).argv; // => ["parse", "x.hl7"]
extractStableCode()
extractStableCode(
e):string|null
Pull a code off a thrown value only when it is a PHI-free constant token
(^[A-Z][A-Z0-9_]*$: a letter-led UPPER_SNAKE code like MALFORMED_JSON), e.g. a wrapped
parser's stable fatal code. Anything else (no code, a non-string, a non-token, or a pure-digit
value that could be a raw identifier) yields null, so a parser exception that embedded input bytes
in a code-shaped field can never reach a diagnostic.
Parameters
e
unknown
A caught value.
Returns
string | null
The stable code token, or null.
Example
import { extractStableCode } from "@cosyte/cli";
extractStableCode({ code: "MALFORMED_JSON" }); // => "MALFORMED_JSON"
extractStableCode(new Error("boom")); // => null
fileChunks()
fileChunks(
path):AsyncGenerator<Uint8Array<ArrayBufferLike>>
Open a file as a stream of byte chunks, with the same value-free failure mode as
readFileBytes: a missing, unreadable or non-file path is a CLI_NO_INPUT error, never a
raw filesystem exception.
Parameters
path
string
The file path to read.
Returns
AsyncGenerator<Uint8Array<ArrayBufferLike>>
An async iterable of the file's chunks.
Throws
CLI_NO_INPUT (exit 66) when the file cannot be opened or read.
Example
import { fileChunks } from "@cosyte/cli";
for await (const chunk of fileChunks("/no/such/file")) chunk.length; // throws CLI_NO_INPUT
fmtCommand()
fmtCommand(
args,deps,posture?):Promise<RunResult>
Run the fmt command.
Parameters
args
string[]
The arguments after the fmt subcommand token.
deps
Injected input readers (RunDeps).
posture?
PhiPosture = VALUE_FREE
The resolved PhiPosture (governs only the opt-in unsafe excerpt on a parse-failure diagnostic).
Returns
Promise<RunResult>
A RunResult: the canonical re-serialization on stdout (exit 0), a value-free
warning-count note on stderr unless --quiet; an unparseable input is a data error (65) with
no partial emit, an unreadable file 66, a bad flag 2.
Throws
Never CliError; may propagate a truly unexpected error for the dispatcher to map.
Example
import { fmtCommand } from "@cosyte/cli";
const deps = {
readFile: async () => new TextEncoder().encode('{ "resourceType":"Patient" , "id":"x" }'),
readStdin: async () => new Uint8Array(),
};
(await fmtCommand(["patient.json"], deps)).exit; // => 0
fmtFormat()
fmtFormat(
format,bytes):Promise<FmtResult>
Canonically re-serialize one input of format via the wrapped library's spec-clean serializer. The
CLI never re-canonicalizes on its own; the output is exactly the serializer's.
Parameters
format
A format for which supportsOp(format, "fmt") is true.
bytes
Uint8Array
The input bytes.
Returns
Promise<FmtResult>
The serialized text + a value-free warning count.
Throws
CLI_PARSER_UNAVAILABLE if the optional parser is absent; the wrapped parser's
rejection propagates for the command's value-free failure boundary (no partial emit).
Example
import { fmtFormat } from "@cosyte/cli";
const bytes = new TextEncoder().encode('{ "resourceType":"Patient" , "id":"x" }');
(await fmtFormat("fhir", bytes)).output.startsWith("{"); // => true
formatDiagnostic()
formatDiagnostic(
err):string
Render a CliError as a single value-free stderr line: cosyte: <CODE>: <message>. The
message is already value-free by the CliError contract; this only prefixes the tool name
and the code so scripts and humans can branch on a stable token.
Parameters
err
The CLI error to render.
Returns
string
The stderr line (no trailing newline).
Example
import { CliError, CLI_CODES, EXIT, formatDiagnostic } from "@cosyte/cli";
formatDiagnostic(new CliError(CLI_CODES.CLI_USAGE, EXIT.USAGE, "missing <file> argument"));
// => "cosyte: CLI_USAGE: missing <file> argument"
formatHl7Position()
formatHl7Position(
pos):string
Render an HL7 warning position as a value-free locator string built only from its numeric
indices: e.g. seg[3].field[5].comp[1]. No segment content, no field value, ever appears; only the
structural coordinates the parser reported.
Parameters
pos
Hl7PositionLike
The HL7 position (segment/field/repetition/component/subcomponent indices).
Returns
string
A value-free locator, e.g. "seg[3].field[5]".
Example
import { formatHl7Position } from "@cosyte/cli";
formatHl7Position({ segmentIndex: 3, fieldIndex: 5 }); // => "seg[3].field[5]"
formatHl7Position({ segmentIndex: 0 }); // => "seg[0]"
formatsSupporting()
formatsSupporting(
op): readonlyCosyteFormat[]
The formats that support op, as a sorted, value-free list: used to build the "does not support"
diagnostic so the user is told which formats do support the operation they asked for.
Parameters
op
The wrapping operation.
Returns
readonly CosyteFormat[]
The supporting format names, sorted.
Example
import { formatsSupporting } from "@cosyte/cli";
formatsSupporting("fmt"); // => ["astm", "ccda", "fhir", "hl7", "ncpdp", "x12"]
helpText()
helpText(
limitBytes?):string
Build the value-free --help text. Names commands, flags, the input-size limit, and exit codes,
never any input.
The limit is rendered from the constant the code enforces, not typed in: the help output and the published command reference state one number because there is only one number to state.
Parameters
limitBytes?
number = MAX_INPUT_BYTES
The input-size limit to state. Defaults to MAX_INPUT_BYTES.
Returns
string
The full help text.
Example
import { helpText } from "@cosyte/cli";
helpText(1024).includes("1024 bytes"); // => true
inputTooLargeError()
inputTooLargeError(
limit?):CliError
Build the value-free over-limit refusal: a stable CLI_INPUT_TOO_LARGE code, the data-error
exit (65, never the internal-error 70), and a message that names the limit and nothing else.
The offending input's own size is deliberately not reported either: the limit is the actionable
fact, and a size read off the input is a property of the input.
Parameters
limit?
number = MAX_INPUT_BYTES
The limit that was exceeded, in bytes. Defaults to MAX_INPUT_BYTES.
Returns
The value-free CliError to resolve the invocation with.
Example
import { inputTooLargeError } from "@cosyte/cli";
inputTooLargeError().exit; // => 65
inputTooLargeError().code; // => "CLI_INPUT_TOO_LARGE"
inspectCommand()
inspectCommand(
args,deps,posture?):Promise<RunResult>
Run the inspect command.
Parameters
args
string[]
The arguments after the inspect subcommand token.
deps
Injected input readers (RunDeps).
posture?
PhiPosture = VALUE_FREE
The resolved PhiPosture (governs only the opt-in unsafe excerpt on a parse-failure diagnostic; the summary itself is always value-free).
Returns
Promise<RunResult>
A RunResult: the value-free structural summary on stdout (human, or JSON under
--json), exit 0; a parse failure is a data error (65), an unreadable file 66, a bad flag 2.
Throws
Never CliError; may propagate a truly unexpected error for the dispatcher to map.
Example
import { inspectCommand } from "@cosyte/cli";
const deps = {
readFile: async () => new TextEncoder().encode('{"resourceType":"Patient"}'),
readStdin: async () => new Uint8Array(),
};
(await inspectCommand(["patient.json", "--json"], deps)).exit; // => 0
inspectFormat()
inspectFormat(
format,bytes):Promise<InspectSummary>
Build the value-free structural summary of one input of format: counts and structural type codes
only, never a field value.
Parameters
format
A format for which supportsOp(format, "inspect") is true.
bytes
Uint8Array
The input bytes.
Returns
Promise<InspectSummary>
A InspectSummary variant for the format.
Throws
CLI_PARSER_UNAVAILABLE if the optional parser is absent; the wrapped parser's
rejection propagates for the command's value-free failure boundary.
Example
import { inspectFormat } from "@cosyte/cli";
const bytes = new TextEncoder().encode('{"resourceType":"Patient"}');
(await inspectFormat("fhir", bytes)).format; // => "fhir"
isDeidCovered()
isDeidCovered(
format):format is DeidCoveredFormat
Whether format is one the delegate de-identifies and the CLI can serialize.
Parameters
format
The resolved format.
Returns
format is DeidCoveredFormat
true iff redact produces output for it.
Example
import { isDeidCovered } from "@cosyte/cli";
isDeidCovered("x12"); // => true
loadOptional()
loadOptional<
T>(format,load):Promise<T>
Lazy-import an optional parser package, mapping an absent package to a value-free
CLI_PARSER_UNAVAILABLE (exit 69): the graceful-degradation path for the optionalDependencies
breadth parsers. A genuine import that succeeds passes straight through; any error whose
shape is "module not found" becomes the value-free CLI error. Other errors propagate unchanged.
Type Parameters
T
T
The imported module's type.
Parameters
format
The format whose optional parser is being loaded (named in the diagnostic).
load
() => Promise<T>
A thunk performing the dynamic import.
Returns
Promise<T>
The imported module.
Throws
CLI_PARSER_UNAVAILABLE (exit 69) when the package is absent; any other error
propagates unchanged.
Example
import { loadOptional } from "@cosyte/cli";
// a present parser resolves; an absent one throws CLI_PARSER_UNAVAILABLE
await loadOptional("x12", () => import("@cosyte/x12"));
loadOptionalPackage()
loadOptionalPackage<
T>(detail,load):Promise<T>
The same graceful-degradation path as loadOptional, keyed on a package name rather than a CosyteFormat, with the diagnostic supplied by the caller.
Two of the packages the CLI lazy-loads cannot go through loadOptional: @cosyte/transform
(a conversion library, not one of the eight formats, so calling it a "parser" in the diagnostic
would be wrong) and @cosyte/fhir (a format, but not a declared dependency of this package: it
arrives as the peer dependency of @cosyte/transform, so "it is an optional dependency" would be
wrong). An install can lack either, so both must degrade to a value-free CLI_PARSER_UNAVAILABLE
(exit 69) instead of surfacing a raw resolver error and a stack frame.
Type Parameters
T
T
The imported module's type.
Parameters
detail
string
The value-free diagnostic detail shown when the package is absent.
load
() => Promise<T>
A thunk performing the dynamic import.
Returns
Promise<T>
The imported module.
Throws
CLI_PARSER_UNAVAILABLE (exit 69) when the package is absent; any other error
propagates unchanged.
Example
import { loadOptionalPackage } from "@cosyte/cli";
await loadOptionalPackage("the conversion library is not installed", () =>
import("@cosyte/transform"),
);
mapCodesCommand()
mapCodesCommand(
args,deps):Promise<RunResult>
Run the map-codes command.
Parameters
args
string[]
The arguments after the map-codes subcommand token.
deps
Injected input readers (RunDeps); the positional argument (or -) is the BYO
ConceptMap document.
Returns
Promise<RunResult>
A RunResult: the translation result on stdout (matched target coding(s) or the
value-free unmapped signal), a value-free note on stderr (unless --quiet), and an exit code
carrying the outcome: 0 mapped · 1 unmapped · 65 unloadable ConceptMap · 66 no input ·
2 usage.
Throws
Never CliError; may propagate a truly unexpected error for the dispatcher to map to
CLI_INTERNAL.
Example
import { mapCodesCommand } from "@cosyte/cli";
const cm = JSON.stringify({
resourceType: "ConceptMap",
group: [
{
source: "http://hl7.org/fhir/administrative-gender",
target: "http://terminology.hl7.org/CodeSystem/v2-0001",
element: [{ code: "male", target: [{ code: "M", equivalence: "equivalent" }] }],
},
],
});
const deps = {
readFile: async () => new TextEncoder().encode(cm),
readStdin: async () => new Uint8Array(),
};
const r = await mapCodesCommand(
["gender.json", "--system", "http://hl7.org/fhir/administrative-gender", "--code", "male"],
deps,
);
r.exit; // => 0
mllpFrames()
mllpFrames(
chunks):AsyncGenerator<Uint8Array<ArrayBufferLike>>
De-frame a chunk stream into MLLP frame payloads (each an enclosed HL7 v2 message), yielded as
each frame completes. The framing is @cosyte/mllp's own streaming FrameReader, fed chunk by
chunk; the CLI only tracks where the frame bytes fell so it can tell a truncated stream from a
complete one.
Truncation is a data error, never a silent drop. An unterminated trailing frame (a start-of-block byte after the last end-of-block byte) is delivered by no callback, so it would otherwise vanish with a green exit. It is detected at end of stream and raised, after the frames that did complete have already been yielded: their output stands, and the invocation still resolves to the data error.
Parameters
chunks
The input chunk stream.
Returns
AsyncGenerator<Uint8Array<ArrayBufferLike>>
A stream of enclosed HL7 payloads, in frame order.
Throws
CLI_PARSER_UNAVAILABLE (exit 69) if @cosyte/mllp is absent;
CLI_PARSE_FAILED (exit 65) at end of stream on a truncated final frame.
Example
import { mllpFrames, oneChunk } from "@cosyte/cli";
const framed = new Uint8Array([0x0b, 0x4d, 0x53, 0x48, 0x1c, 0x0d]);
const seen: number[] = [];
for await (const p of mllpFrames(oneChunk(framed))) seen.push(p.length);
seen.length; // => 1
ndjsonRecords()
ndjsonRecords(
chunks):AsyncGenerator<Uint8Array<ArrayBufferLike>>
Split a chunk stream into NDJSON records: each non-empty, newline-terminated line, yielded as soon as its terminator arrives. A record split across two chunks is carried over; a final record with no trailing newline is yielded at end of stream; a blank line carries no record and is skipped.
Parameters
chunks
The input chunk stream.
Returns
AsyncGenerator<Uint8Array<ArrayBufferLike>>
A stream of record bytes, in input order.
Example
import { ndjsonRecords, oneChunk } from "@cosyte/cli";
const src = oneChunk(new TextEncoder().encode('{"a":1}\n\n{"b":2}\n'));
const out: string[] = [];
for await (const r of ndjsonRecords(src)) out.push(new TextDecoder().decode(r));
out.length; // => 2
oneChunk()
oneChunk(
bytes):AsyncGenerator<Uint8Array<ArrayBufferLike>>
Wrap bytes as a one-chunk stream, so the streaming record path can also serve a caller that
hands the CLI a whole buffer (an injected reader, or the programmatic API). The path is the same;
only the granularity the input arrives at differs.
Parameters
bytes
Uint8Array
The whole input.
Returns
AsyncGenerator<Uint8Array<ArrayBufferLike>>
A stream yielding exactly one chunk (or none, when bytes is empty).
Example
import { oneChunk } from "@cosyte/cli";
let n = 0;
for await (const c of oneChunk(new Uint8Array([1, 2, 3]))) n += c.length;
n; // => 3
parseCommand()
parseCommand(
args,deps,posture?):Promise<RunResult>
Run the parse command.
Parameters
args
string[]
The arguments after the parse subcommand token.
deps
Injected I/O (RunDeps). When it carries the chunk readers and the output
sink, a multi-record input is read and emitted incrementally; when it does not, the same path
runs over one chunk and the output comes back on stdout instead.
posture?
PhiPosture = VALUE_FREE
The resolved PhiPosture. Defaults to VALUE_FREE; under
--unsafe-show-values a bounded excerpt of the offending input is appended to a CLI_PARSE_FAILED
diagnostic (the single, opt-in value-echoing surface): single-record mode only.
Returns
Promise<RunResult>
A RunResult: the typed-JSON model (or NDJSON records) on stdout, a value-free note
(or nothing) on stderr, and the resolved exit code. An input past the documented size limit is a
value-free data error naming the limit. Never throws a CliError: it resolves it to a
result; unexpected exceptions are caught by the dispatcher and mapped to CLI_INTERNAL.
Throws
Never CliError; may propagate a truly unexpected error for the dispatcher to map.
Example
import { parseCommand } from "@cosyte/cli";
const enc = new TextEncoder();
const deps = {
readFile: async () => enc.encode('{"resourceType":"Patient","id":"x"}'),
readStdin: async () => new Uint8Array(),
};
(await parseCommand(["patient.json"], deps)).exit; // => 0
parseFailureResult()
parseFailureResult(
format,bytes,posture,e):RunResult
Build the value-free RunResult for a wrapped-parser rejection: a CLI_PARSE_FAILED
data error (exit 65). The stderr line names the format and, when the thrown value carried one, a
stable code token in parentheses; it never echoes the parser's message or the input bytes. Under
--unsafe-show-values (and only then) a bounded excerpt of the offending input is appended via the
single unsafeInputSuffix chokepoint.
Parameters
format
The format whose parser rejected the input.
bytes
Uint8Array
The offending input (only consulted for the opt-in unsafe excerpt).
posture
The resolved PhiPosture; the excerpt is appended only when it opts in.
e
unknown
The caught exception (its code, if a stable token, is surfaced: nothing else).
Returns
A value-free CLI_PARSE_FAILED / exit-65 RunResult.
Example
import { parseFailureResult, VALUE_FREE } from "@cosyte/cli";
const bytes = new TextEncoder().encode("not hl7");
parseFailureResult("hl7", bytes, VALUE_FREE, { code: "MISSING_MSH" }).exit; // => 65
parseFormat()
parseFormat(
format,bytes):Promise<ParseResult>
Parse one record of format into the wrapped library's model + its value-free warnings. The model
is emitted verbatim on the data channel; the CLI adds no parsing of its own.
Parameters
format
A format for which supportsOp(format, "parse") is true.
bytes
Uint8Array
The single record's bytes (already de-framed for MLLP: callers pass "hl7" there).
Returns
Promise<ParseResult>
The parsed model + value-free warnings.
Throws
CLI_PARSER_UNAVAILABLE if the optional parser is absent; the wrapped parser's
own rejection propagates for the command's value-free failure boundary to catch.
Example
import { parseFormat } from "@cosyte/cli";
const bytes = new TextEncoder().encode('{"resourceType":"Patient","id":"x"}');
(await parseFormat("fhir", bytes)).warnings.length; // => 0
readFileBytes()
readFileBytes(
path,limit?):Promise<Uint8Array<ArrayBufferLike>>
Read a file into bytes, mapping any read failure to a value-free CLI_NO_INPUT /
EXIT.NOINPUT error. The path is structural context (the user supplied it), so it may appear
in the message; the file contents never do.
A file whose size already exceeds limit is refused before it is read, with the value-free
over-limit data error, so an oversized file is never allocated in the first place. The size is read
from the open descriptor rather than from the path, so what is measured and what is read are the
same file even if the path is replaced in between.
Parameters
path
string
The file path to read.
limit?
number = MAX_INPUT_BYTES
The maximum number of bytes to accept. Defaults to MAX_INPUT_BYTES.
Returns
Promise<Uint8Array<ArrayBufferLike>>
The file bytes.
Throws
CLI_NO_INPUT (exit 66) when the file is missing, a directory, or unreadable;
CLI_INPUT_TOO_LARGE (exit 65) when it is larger than limit.
Example
import { readFileBytes } from "@cosyte/cli";
await readFileBytes("/no/such/file"); // throws CliError CLI_NO_INPUT
readStreamBytes()
readStreamBytes(
stream,limit?):Promise<Uint8Array<ArrayBufferLike>>
Drain a readable stream (e.g. process.stdin) into a single byte buffer, refusing an over-limit
stream against the running total: the read is abandoned the moment the accumulated size passes
limit, so the bytes past it are never allocated and no platform allocation ceiling is ever
reached.
Parameters
stream
Readable
The readable stream to drain.
limit?
number = MAX_INPUT_BYTES
The maximum number of bytes to accept. Defaults to MAX_INPUT_BYTES.
Returns
Promise<Uint8Array<ArrayBufferLike>>
The concatenated bytes.
Throws
CLI_INPUT_TOO_LARGE (exit 65) as soon as the running total passes limit.
Example
import { Readable } from "node:stream";
import { readStreamBytes } from "@cosyte/cli";
const bytes = await readStreamBytes(Readable.from([Buffer.from("MSH|")]));
bytes.length; // => 4
redactCommand()
redactCommand(
args,deps,loadDelegate?):Promise<RunResult>
Run the redact / deid command.
Parameters
args
string[]
The arguments after the redact/deid subcommand token.
deps
Injected input readers (RunDeps).
loadDelegate?
() => Promise<DeidDelegate>
The de-identification delegate loader; injectable so a test can drive the library-absent path without uninstalling it.
Returns
Promise<RunResult>
A RunResult. A clean pass over a covered format: the de-identified document on
stdout, the delegate's attribution and value-free manifest on stderr, exit 0. Otherwise
empty stdout and a typed non-zero exit: 1 when the delegate could not handle an element, 69
when it has no adapter for the format or is absent, 65 when the format cannot ride a text
stdout or the input did not parse, 66 for an unreadable file, 2 for a bad flag.
Throws
Never CliError; may propagate a truly unexpected error for the dispatcher to map.
Example
import { redactCommand } from "@cosyte/cli";
const deps = {
readFile: async () => new TextEncoder().encode('{"resourceType":"Patient","id":"x"}'),
readStdin: async () => new Uint8Array(),
};
(await redactCommand(["patient.json"], deps)).exit; // => 0
resolveInput()
resolveInput(
source,formatOverride,deps,op):Promise<InputResolution>
Resolve and read the input, and resolve its format, for a file-consuming command.
Parameters
source
string | undefined
The positional <file|-> argument (or undefined when it was omitted).
formatOverride
string | undefined
The raw --format value, or undefined to autodetect by content.
deps
Injected input readers (RunDeps).
op
Op | null
The wrapping operation the caller will run; the resolved format is confirmed to support
it (else a value-free CLI_FORMAT_UNSUPPORTED naming the supporting formats). Pass null when
the caller answers for capability itself: redact delegates to @cosyte/deid, whose per-format
coverage is a different matrix with its own two distinct refusals, so applying the parser
registry's matrix first would answer with the wrong code.
Returns
Promise<InputResolution>
{ ok: true, input } when the bytes read and the format resolved to a parser supporting
op; else { ok: false, result } with a value-free usage/no-input/data-error RunResult.
Throws
Propagates a non-CliError read failure unchanged, so the dispatcher maps it to
CLI_INTERNAL (a CliError read failure, e.g. a missing file, is caught and returned).
Example
import { resolveInput } from "@cosyte/cli";
const deps = {
readFile: async () => new TextEncoder().encode('{"resourceType":"Patient"}'),
readStdin: async () => new Uint8Array(),
};
const r = await resolveInput("patient.json", undefined, deps, "parse");
if (r.ok) r.input.format; // => "fhir"
resolveInputStream()
resolveInputStream(
source,formatOverride,deps,op,limit?):Promise<InputStreamResolution>
Resolve the input as a stream of chunks rather than one buffer, for a command that can act on a record before the whole input has arrived.
It reads only as far as the detection window (DETECT_PREFIX_BYTES) before deciding the format, then hands back those bytes followed by the rest of the stream, unread. The size limit is applied to the running total from the first chunk, so an over-limit input is refused while it is still arriving and never assembled.
When the caller's RunDeps carry no chunk readers, the whole-input readers are wrapped as a single chunk: the same code path, the same output, only the granularity differs.
Parameters
source
string | undefined
The positional <file|-> argument (or undefined when it was omitted).
formatOverride
string | undefined
The raw --format value, or undefined to autodetect by content.
deps
Injected readers (RunDeps); the chunk readers are used when present.
op
The wrapping operation the caller will run; the resolved format is confirmed to support it.
limit?
number = MAX_INPUT_BYTES
The maximum number of input bytes to accept. Defaults to MAX_INPUT_BYTES.
Returns
Promise<InputStreamResolution>
{ ok: true, input } with the format and the chunk stream, else { ok: false, result }
carrying the value-free usage / no-input / data-error RunResult.
Throws
Propagates a non-CliError read failure unchanged, so the dispatcher maps it to
CLI_INTERNAL.
Example
import { resolveInputStream } from "@cosyte/cli";
const deps = {
readFile: async () => new TextEncoder().encode('{"resourceType":"Patient"}'),
readStdin: async () => new Uint8Array(),
};
const r = await resolveInputStream("patient.json", undefined, deps, "parse");
if (r.ok) r.input.format; // => "fhir"
run()
run(
argv,deps):Promise<RunResult>
Run a full cosyte invocation as data.
Parameters
argv
string[]
The arguments after the program name (i.e. process.argv.slice(2)).
deps
Injected input readers (RunDeps).
Returns
Promise<RunResult>
The RunResult to write to the process streams and exit with.
Example
import { run } from "@cosyte/cli";
const deps = { readFile: async () => new Uint8Array(), readStdin: async () => new Uint8Array() };
const { exit } = await run(["--version"], deps);
exit; // => 0
streamChunks()
streamChunks(
stream):AsyncGenerator<Uint8Array<ArrayBufferLike>>
Open a readable stream (e.g. process.stdin) as a stream of byte chunks, in arrival order.
Unlike readStreamBytes this never concatenates: the consumer decides what to keep, which is
what lets a multi-record command emit a record before the rest of the input has been read.
Parameters
stream
Readable
The readable stream to iterate.
Returns
AsyncGenerator<Uint8Array<ArrayBufferLike>>
An async iterable of the stream's chunks, string chunks encoded as UTF-8.
Example
import { Readable } from "node:stream";
import { streamChunks } from "@cosyte/cli";
const seen: number[] = [];
for await (const c of streamChunks(Readable.from([Buffer.from("MS"), Buffer.from("H|")]))) {
seen.push(c.length);
}
seen.length; // => 2
supportsOp()
supportsOp(
format,op):boolean
True iff format's wrapped parser supports op in this build (see OP_SUPPORT).
Parameters
format
The resolved format.
op
The wrapping operation.
Returns
boolean
Whether the (format, op) pair is wired.
Example
import { supportsOp } from "@cosyte/cli";
supportsOp("x12", "fmt"); // => true
supportsOp("dicom", "parse"); // => false
toCliError()
toCliError(
err):CliError
Coerce an unknown thrown value into a CliError. A CliError passes through; anything
else becomes a CLI_INTERNAL / EXIT.SOFTWARE error whose message is a fixed, value-free
string: the original message is deliberately discarded so a parser exception that embedded input
bytes can never reach stderr.
Parameters
err
unknown
The caught value.
Returns
A CliError safe to render to stderr.
Example
import { toCliError } from "@cosyte/cli";
toCliError(new Error("boom")).code; // => "CLI_INTERNAL"
truncatedMllpError()
truncatedMllpError():
CliError
The value-free data error for a truncated MLLP stream: a frame opened and never closed, which
the streaming reader buffers and delivers to no callback. Built here so the whole-buffer de-framer
above and the chunk-by-chunk one in core/records.ts refuse the same input with the same code and
the same words.
Returns
A CLI_PARSE_FAILED (exit 65) CliError.
Example
import { truncatedMllpError } from "@cosyte/cli";
truncatedMllpError().exit; // => 65
unsafeInputSuffix()
unsafeInputSuffix(
bytes,posture):string
Build the only value-bearing addition the CLI ever appends to a secondary (stderr) surface: a
bounded, single-line excerpt of the offending input, shown iff --unsafe-show-values is set.
Under the default value-free posture it returns the empty string, so a diagnostic stays value-free
unless the user explicitly opted in. This is the single chokepoint the PHI-leak matrix pins.
Parameters
bytes
Uint8Array
The input the CLI is operating on (only the leading UNSAFE_EXCERPT_MAX bytes are considered).
posture
The resolved PhiPosture.
Returns
string
"" under the value-free default; otherwise a [unsafe-show-values] … suffix carrying a
bounded, newline-flattened prefix of bytes.
Example
import { unsafeInputSuffix, VALUE_FREE, SHOW_VALUES } from "@cosyte/cli";
const bytes = new TextEncoder().encode("bad message");
unsafeInputSuffix(bytes, VALUE_FREE); // => ""
unsafeInputSuffix(bytes, SHOW_VALUES); // => " [unsafe-show-values] offending input …: bad message"
validateCommand()
validateCommand(
args,deps,posture?):Promise<RunResult>
Run the validate command.
Parameters
args
string[]
The arguments after the validate subcommand token.
deps
Injected input readers (RunDeps).
posture?
PhiPosture = VALUE_FREE
The resolved PhiPosture (governs only the opt-in unsafe excerpt on a parse-failure diagnostic; a validation verdict is always value-free).
Returns
Promise<RunResult>
A RunResult whose exit code carries the verdict (0 valid / 1 invalid /
65 unparseable / 66 no input / 2 usage). Value-free findings on stderr, or value-free
JSON on stdout under --json.
Throws
Never CliError; may propagate a truly unexpected error for the dispatcher to map.
Example
import { validateCommand } from "@cosyte/cli";
const deps = {
readFile: async () => new TextEncoder().encode('{"resourceType":"Patient","gender":"male"}'),
readStdin: async () => new Uint8Array(),
};
(await validateCommand(["patient.json"], deps)).exit; // => 0
validateFormat()
validateFormat(
format,bytes):Promise<Verdict>
Run the format's validation surface and return a value-free Verdict. For the Postel's-Law
lenient parsers, a parseable input is valid: recovered deviations are non-fatal warnings the
library surfaces, never a stricter verdict the CLI invents. FHIR additionally runs validateResource
and is invalid on any error/fatal-severity issue.
Parameters
format
A format for which supportsOp(format, "validate") is true.
bytes
Uint8Array
The input bytes.
Returns
Promise<Verdict>
The value-free verdict (findings + validity).
Throws
CLI_PARSER_UNAVAILABLE if the optional parser is absent; an unparseable input
propagates as the wrapped parser's throw for the command to map to a data error (65).
Example
import { validateFormat } from "@cosyte/cli";
const bytes = new TextEncoder().encode('{"resourceType":"Patient","gender":"male"}');
(await validateFormat("fhir", bytes)).valid; // => true
valueFreeLocator()
valueFreeLocator(
pos):string
Render an arbitrary parser position object as a value-free locator string, keeping only
number-valued own properties (the parsers' positions are index-only): e.g. { segmentIndex: 3, elementIndex: 2 } → segmentIndex[3].elementIndex[2]. A non-number field (which a position should
never carry) is dropped, so a stray value can never reach a diagnostic. Falls back to "?" when no
numeric index is present.
Parameters
pos
unknown
A parser position object (index-only by the parser's contract).
Returns
string
A value-free locator built solely from numeric indices.
Example
import { valueFreeLocator } from "@cosyte/cli";
valueFreeLocator({ segmentIndex: 3, elementIndex: 2 }); // => "segmentIndex[3].elementIndex[2]"
valueFreeLocator({}); // => "?"
withinLimit()
withinLimit(
chunks,limit?):AsyncGenerator<Uint8Array<ArrayBufferLike>>
Pass chunks through while counting them against the CLI's own size limit, and refuse the moment the running total passes it: the chunk that crosses the limit is never yielded, the source is never pulled again, and nothing downstream ever allocates memory proportional to an oversized input. This is why the refusal a caller sees is the CLI's own data error rather than a platform allocation failure reported as an internal error.
Parameters
chunks
The input chunk stream.
limit?
number = MAX_INPUT_BYTES
The maximum number of bytes to accept. Defaults to MAX_INPUT_BYTES.
Returns
AsyncGenerator<Uint8Array<ArrayBufferLike>>
The same chunks, up to the limit.
Throws
CLI_INPUT_TOO_LARGE (exit 65) as soon as the running total passes limit.
Example
import { oneChunk, withinLimit } from "@cosyte/cli";
const src = withinLimit(oneChunk(new Uint8Array(10)), 4);
await src.next().catch((e: unknown) => (e as { code: string }).code); // => "CLI_INPUT_TOO_LARGE"