@yohn-jp/cli-canon
v0.1.7
Published
Internal canonical TypeScript framework for yohn-jp CLI products.
Readme
CLI Canon
Canonical TypeScript CLI contracts for yohn-jp products
@yohn-jp/cli-canon is the shared CLI foundation used to define a product command surface once and derive typed handlers, routing, help, discovery, invocation metadata, Skill projections, Path Canon, output behavior, and package certification from the same authority.
It is intentionally not a general-purpose CLI framework. Product-domain authorization, lifecycle/state-machine decisions, provider behavior, filesystem safety, transport authority, and domain schemas remain owned by each consumer.
Install
Requires Node.js 24 or newer and Zod 4.
npm install @yohn-jp/cli-canon zodThe package is a library and does not install a standalone CLI binary.
Quick start
import * as z from "zod";
import {
bindHandlers,
compileProduct,
defineCommands,
flag,
option,
positional,
projectInvocation,
} from "@yohn-jp/cli-canon";
import { runNodeCli } from "@yohn-jp/cli-canon/node";
const commands = defineCommands({
"document.render": {
route: ["document", "render"],
summary: "Render a document.",
description: "Render a document to the selected output path.",
examples: ["example document render input.md --out=out.html"],
input: {
file: positional(z.string(), { description: "Input document." }),
target: positional(z.string(), {
required: false,
description: "Optional named target.",
}),
out: option("--out", z.string(), {
aliases: ["-o"],
required: true,
}),
format: option("--format", z.enum(["full", "json"]), {
valueArity: "optional",
}),
json: flag("--json"),
},
result: z.object({ writtenFile: z.string() }),
},
});
const handlers = bindHandlers(commands)({
"document.render": ({ out }) => ({ writtenFile: out }),
});
const product = compileProduct({
name: "example",
commands,
handlers,
schemaProjectionCompleteness: "complete",
});
const result = await runNodeCli(product, ["document", "render", "input.md", "--out", "out.html"]);
const invocation = projectInvocation(product, "document.render", {
file: "input.md",
out: "out.html",
json: true,
});
if (invocation.state === "ready") {
console.log(invocation.value);
// { executable: "example", argv: ["document", "render", ...] }
}One authority, multiple projections
typed authoring declarations
|
compileProduct
|
CompiledProduct
/ | | \
runtime help Skill discovery
|
invocation / Path / fixturesA CLI fact is authored once. Routing, usage, progressive help, JSON discovery, Skill command metadata, and invocation argv are derived rather than maintained as parallel tables.
Package surfaces
| Import | Purpose |
| ---------------------------- | --------------------------------------------------------- |
| @yohn-jp/cli-canon | Authoring, compiler, projections, Path/Skill/Output Canon |
| @yohn-jp/cli-canon/node | Node.js / Commander runtime adapter |
| @yohn-jp/cli-canon/testing | Reusable source/built/packed certification primitives |
The root import is side-effect free. Commander is private to the Node adapter.
Command and grammar contract
CLI Canon supports:
- nested command routes;
- required and optional positionals;
- flags and required/optional-value options;
- aliases and repeatable options;
--name=value;- options declared with
placement: "anywhere"; - trailing
rawArgs(); - progressive root/domain/leaf help;
--help=fulland--help=json;- deterministic JSON discovery.
Unsupported grammar fails closed rather than falling back to a second parser.
Current explicit limits:
orderedOptionGroupsare rejected asUNSUPPORTED_GRAMMAR;optionLookingValuePolicy: "reject"is rejected asUNSUPPORTED_GRAMMAR.
The default option-looking-value behavior remains Commander's consume semantics.
Invocation Canon
Executable invocation is a structured value, not a shell string:
{ executable: string, argv: string[] }projectInvocation(product, commandId, bindings) derives argv from Command Canon. Missing required bindings produce a requires-input projection and do not expose executable argv.
Skill command bindings use the same projection through skillCommand(...); usage text is display-only and is never reparsed for execution.
Help, discovery, and public schemas
Text help and JSON discovery derive from the same compiled command graph.
For incremental migrations that must preserve an established terminal format, the Node adapter exposes a typed help-presentation hook. Canon still parses the help mode and resolves the target route; the adapter receives that resolved request plus scoped discovery metadata and may only render the product-specific terminal shape:
import { textOutput } from "@yohn-jp/cli-canon";
import { runNodeCli } from "@yohn-jp/cli-canon/node";
await runNodeCli(product, argv, {
legacyRoutes,
terminalAdapter: {
help: ({ mode, request, discovery }) => textOutput(renderProductHelp({ mode, request, discovery })),
},
});This keeps help-mode parsing, route ownership, discovery composition, and command metadata in CLI Canon while allowing compatibility presentation without parsing Canon-rendered text.
Pass package metadata at composition time when package identity should be projected:
import packageMetadata from "./package.json" with { type: "json" };
const product = compileProduct({
name: "document-cli",
packageMetadata,
schemaProjectionCompleteness: "complete",
commands,
handlers,
});projectProductSchemas(product, "complete") projects framework-owned value contracts to JSON Schema. Unsupported complete projections fail explicitly. Use "structural-only" when a schema cannot claim equivalent complete validation.
Output contract
CLI Canon keeps exit status, stream, bytes, and failure classification together.
Machine JSON:
- is complete or fails;
- is never byte-truncated;
- counts UTF-8 bytes including the final newline;
- rejects non-finite numbers and unsupported JSON values instead of silently converting or omitting them.
Products may supply a typed domain-error adapter without moving domain error semantics into CLI Canon.
Skill Canon
Skills are intent-oriented projections over Command Canon and product-owned results. They may contain prose, command references, delegation, invariants, and opaque domain-result references.
Public Skill projections reject unknown commands, private commands, unknown delegated Skills, and delegation cycles at construction.
Skill text and JSON use the shared output policy. The default budget is 4096 UTF-8 bytes; over-budget output fails explicitly and is never truncated.
Path Canon
Path Canon models addresses, not permissions.
It supports derived Path IDs, parameters, file/directory kinds, parent references, explicit cwd/home/env/platform context, and ordered root strategies. Resolution is lexical and side-effect free; it does not create files, resolve authorization, or grant filesystem ownership.
Certification
@yohn-jp/cli-canon/testing provides reusable certification scenarios with:
- stable scenario identity;
- target command identity;
- explicit input;
- independent expected output;
- required source/built/packed lanes;
- optional setup.
The repository package gate builds one tarball, installs that exact artifact into an isolated consumer, and verifies exports, public types, root-import purity, and runtime behavior.
The current architecture-conformance baseline is recorded in test/architecture-conformance.md.
Architecture
The README is an entry point, not a second architecture authority.
| Authority | Scope | | ------------------------------------------------------- | --------------------------------------------------------------- | | Canonical architecture | Normative ownership, boundaries, and implementation rules | | Architecture design | Cross-product rationale, admission corpus, and migration design |
Releases
Release notes live under docs/releases/.
- 0.1.7 — typed help-presentation compatibility hook for incremental migrations.
- 0.1.6 — Canon-owned mixed-migration help/discovery and Node presentation surfaces.
- 0.1.0 — initial public package release.
- Releasing CLI Canon — bootstrap manual publish and subsequent OIDC release workflow.
Security
Report suspected vulnerabilities privately as described in SECURITY.md. Do not file sensitive vulnerability details in a public Issue.
Contributing
See CONTRIBUTING.md for the repository development, architecture, branch, verification, and pull request contracts. Participation is governed by the Code of Conduct.
Development
pnpm install --frozen-lockfile
pnpm run verifypnpm run verify is the authoritative local verification entry point.
License
MIT — see LICENSE.
