Binary Structure CLI Tool

A command-line interface for decoding and encoding binary data with any binstruct package. Binary arrives on stdin and JSON5 leaves on stdout, or the other way round, so the tool drops into a shell pipeline.

The argument list is a prefix chain, and every prefix of it is a valid invocation:

binstruct [<package> [<coder> [<command>]]] [options]

A prefix that stops short prints guidance for the missing word — what it means, the values it may take, and a paste-ready command one step further along — to stderr, and exits 1; --help prints the same material to stdout and exits 0. Stdout otherwise carries the payload and nothing else. A bare package name means the @binstruct scope on JSR, and a package exposing exactly one zero-argument coder may omit the <coder> word.

The package list shown for the missing <package> word is fetched from JSR's scope API and cached for a day, so it names what is published today rather than what was published when the CLI was released. That costs --allow-net=jsr.io; without it — or without a network — the list is omitted and everything else still works.

Examples

Decode with the full three-word form

deno run -A @binstruct/cli png pngFile decode < input.png > struct.json5

Encode it back

deno run -A @binstruct/cli png pngFile encode < struct.json5 > output.png

Omit the coder when the package has only one

deno run -A @binstruct/cli arp decode < arp.bin > arp.json5

A local module works the same way, relative to the working directory

deno run -A @binstruct/cli ./my-package/mod.ts myStruct decode < input.bin > output.json5

Programmatic usage: plan an invocation without performing it

import { assertEquals } from "@std/assert";
import { planCli } from "@binstruct/cli";

const plan = await planCli(["png", "pngFile", "decode"]);

assertEquals(plan.kind, "run");
if (plan.kind === "run") assertEquals(plan.specifier, "jsr:@binstruct/png");

Functions

f
diagnoseEmptyDiscovery(
specifier: string,
timeout?: number
): Promise<EmptyDiscoveryDiagnosis>

Explains why a package yielded no coders, by reading its module graph.

f
discoverCoders(
specifier: string,
timeout?: number
): Promise<DiscoveryOutcome>

Discovers the coder factories a package exposes, without importing it.

f
explainFailure(
packageInput: string,
coderName: string,
error: unknown
): Promise<string>

Explains a failure that only surfaced once the package was imported.

f
inspectLocalTarget(specifier: string): Promise<LocalTarget>

Reports what a resolved specifier points at, before anything is run on it.

f
isModulePath(name: string): boolean

Reports whether a name ends in an extension the runtime will load.

f
listScopePackages(options?: ScopeListingOptions): Promise<ScopeListing>

Lists the packages of the @binstruct scope, live from JSR.

f
nearestName(
input: string,
candidates: Iterable<string>
): string | undefined

Picks the candidate closest to a misspelling, for a "did you mean" line.

f
parseCliArgs(args: string[]): CliOptions

Parses command line arguments into the three positionals and the flags.

f
planCli(args: string[]): Promise<CliPlan>

Works out what an invocation amounts to, without performing it.

f
readDocSurface(doc: DenoDocJson): PackageSurface

Reads a package's discoverable surface out of parsed deno doc --json output.

f
readScopeListing(body: unknown): ScopePackage[] | undefined

Reads a package listing out of a parsed JSR scope-packages response.

f
readSymbolDocs(
specifier: string,
symbol: string,
timeout?: number
): Promise<SymbolDocsOutcome>

Returns deno doc's formatted documentation for one exported symbol.

f
renderGuide(guide: Guide): string

Renders a guidance screen.

f
resolveSpecifier(input: string): ResolvedSpecifier

Resolves a user-typed package argument to a module specifier.

f
shortenSpecifier(specifier: string): string

Renders the shortest form of a specifier that still resolves back to it.

Interfaces

I
CliOptions

CLI configuration options.

I
ResolvedSpecifier

A user-typed package argument and everything derived from it.

Type Aliases

T
CommandName = (COMMANDS)[number]["name"]

A command the CLI can carry out.

T
DenoDocDeclaration = { kind: string; location: { filename: string; }; jsDoc?: { doc?: string; }; def: { params?: DenoDocParam[]; returnType?: DenoDocType; }; }

A single declaration of an exported symbol in deno doc --json output.

T
DenoDocJson = { nodes: Record<string, DenoDocNode>; }

The deno doc --json document, narrowed to the parts discovery reads.

T
DenoDocNode = { module_doc?: { doc?: string; }; symbols: DenoDocSymbol[]; }

A module entry in deno doc --json output.

T
DenoDocParam = { kind: string; optional?: boolean; }

A parameter of a declaration in deno doc --json output.

T
DenoDocSymbol = { name: string; declarations: DenoDocDeclaration[]; }

An exported symbol in deno doc --json output.

T
DenoDocType = { repr?: string; value?: { typeParams?: DenoDocType[]; }; }

A type reference in deno doc --json output.

T
DiscoveredCoder = { name: string; decodedType?: string; summary?: string; requiredParams: number; }

A coder factory found in a package's public types.

T
DiscoverySuccess = PackageSurface & { ok: true; specifier: string; }

A package whose type declarations were read successfully.

T
Guide = { readonly header?: string; readonly diagnostic?: boolean; readonly notes?: readonly string[]; readonly next: GuideNext; readonly options: GuideOptions; readonly try?: readonly string[]; readonly footer?: readonly string[]; }

Everything one guidance screen says.

T
GuideNext = { readonly word: string; readonly meaning: string; }

The missing word and what it means.

T
GuideOption = { readonly name: string; readonly detail?: string; readonly summary?: string; }

One legal value of the missing word.

T
GuideOptions = { readonly heading: string; readonly items: readonly GuideOption[]; readonly empty?: string; }

The options block: every value the missing word may take.

T
PackageSurface = { version?: string; summary?: string; coders: DiscoveredCoder[]; }

The public surface of a package as read from its type declarations.

T
ScopeListing = { readonly packages: readonly ScopePackage[]; readonly source: ListingSource; readonly reason?: string; }

The result of listScopePackages.

T
ScopeListingOptions = { readonly cacheDir?: string | null; readonly now?: number; readonly ttl?: number; }

Options for listScopePackages.

  • cacheDir: string | null

    Directory holding the cache file. Absent means the OS cache directory; null disables the cache in both directions.

  • now: number

    Current time in milliseconds, for ageing the cache.

  • ttl: number

    How long a cached listing is served before a refetch, in milliseconds.

T
ScopePackage = { readonly name: string; readonly description: string; readonly latestVersion: string; }

One published package of the scope, as JSR describes it.

T
SpecifierForm = "scheme" | "path" | "scoped" | "bare"

Which resolution rule matched an input.

T
ToolFailure = { ok: false; reason: ToolFailureReason; specifier: string; command: string[]; code?: number; stderr: string; }

A deno subprocess that did not produce usable output.