AI-Friendly CLI Mode Server
Starting with Babel Licensing 11.8, the lic command line can emit a structured, machine-readable output stream suited to CI pipelines, order systems and AI agents, matching the mode the obfuscator gained in 11.7.
The default text output of lic is unchanged and remains fully backward compatible: existing scripts require no changes.
The AI-friendly mode is enabled by three global flags:
--format=text|json|ndjsonselects the output channel format--quiet(alias-q) suppresses the logo banner and fails fast on interactive prompts--strict-exitopts into semantic exit codes
When --format=json or --format=ndjson is selected, stdout carries the structured stream and the human-readable messages are routed to stderr, so the two channels never interleave.
--format is also the option that selects the license output format (xml, serial, base32, ascii). The two value sets are disjoint, so both meanings can appear in one command line: lic MyApp.dll --format base32 --format json.
Flags
—format <format>
Selects the output format used on stdout. Allowed values:
| Value | Description |
|---|---|
text | Default. Legacy human-readable console output. |
json | Buffered single JSON document written when the run completes (or on --help / --version). Best when stdout is captured to a file or piped into a JSON consumer. |
ndjson | Newline-delimited JSON. One event per line, flushed as it is produced. Best for streaming consumers, log shippers and live agents. |
lic MyApp.dll --keyfile keys.pem --sign --expiredate 365 --format json > result.json
lic MyApp.dll --keyfile keys.pem --sign --format ndjson | jq -c 'select(.level=="error")'With json or ndjson the logo banner is suppressed regardless of --logo, so that the first byte on stdout is valid JSON.
—quiet (-q)
Suppresses the copyright banner and every interactive prompt. The only prompt lic has is the password of a PFX key file: with --quiet, a missing --keypwd fails with an explicit error instead of blocking on input, which makes the flag safe for unattended runs on CI runners, in containers and from agents.
lic MyApp.dll --quiet --keyfile signing.pfx --keypwd "$KEY_PWD" --sign--quiet is independent from --format: it can be combined with the text output to keep human-readable logs without the banner.
—strict-exit
Opts the process into semantic exit codes. Without this flag, lic preserves the legacy contract where 0 means success and 1 means any failure.
| Exit code | Constant | Meaning |
|---|---|---|
0 | success | The command completed without errors. |
10 | invalidArguments | The command line could not be parsed or contained an invalid value (for example an unknown option). |
20 | inputNotFound | The assembly, license file or another required input file was not found. |
30 | processingFailure | License processing failed: generation, update, or a signature that did not verify. |
40 | licensingFailure | The tool’s own Babel Licensing license is missing, invalid or expired. |
50 | keyOrSigningFailure | A key file or container could not be loaded, or a cryptographic operation failed. |
The numbers are the same as the obfuscator’s --strict-exit table, so one caller can drive babel and lic from a single exit-code table; the only difference is that code 30 means license processing here and obfuscation there. The classification is also surfaced as exitReason in the JSON result, even when --strict-exit is not active.
lic MyApp.licenses --verify --keyfile keys.pem --strict-exit --quiet
echo "exit=$?"Schema: lic.cli.v1
All structured payloads carry "schema": "lic.cli.v1" and a kind that tells what the document is: event, result, help or version. The schema mirrors the obfuscator’s babel.cli.v1 but is versioned separately, because the two tools expose different option sets. Within a major schema version, additive changes are allowed; breaking changes will bump the schema name.
Event record
Every message emitted while a run is in progress is mapped to an event:
{
"schema": "lic.cli.v1",
"kind": "event",
"ts": "2026-09-03T06:31:54.1686900Z",
"level": "info",
"message": "License saved to 'MyApp.licenses'"
}| Field | Type | Notes |
|---|---|---|
ts | string (ISO 8601, UTC) | Timestamp at which the event was produced. |
level | debug | info | warning | error | Severity. |
code | string | Diagnostic code, present only when the message has one. |
message | string | Human-readable message. |
data | object | Optional structured payload, present only when attached. |
Result envelope
—format=json
A single JSON object is written to stdout when the run terminates. It contains the run-level summary and all events buffered during the run:
{
"schema": "lic.cli.v1",
"kind": "result",
"exitCode": 0,
"exitReason": "success",
"elapsedMs": 133,
"warnings": 0,
"errors": 0,
"events": [
{ "schema": "lic.cli.v1", "kind": "event", "ts": "2026-09-03T06:31:54.1686900Z", "level": "info", "message": "License saved to 'MyApp.licenses'" }
]
}A failed run reports the failure class in exitReason and the error among the events. Here the input assembly did not exist:
{
"schema": "lic.cli.v1",
"kind": "result",
"exitCode": 20,
"exitReason": "inputNotFound",
"elapsedMs": 34,
"warnings": 0,
"errors": 1,
"events": [
{ "schema": "lic.cli.v1", "kind": "event", "ts": "2026-09-03T06:30:27.4366510Z", "level": "error", "message": "Error: The specified file '/build/missing.dll' was not found." }
]
}—format=ndjson
Each event is flushed as soon as it is produced, on its own line. After the last event, a final result line is appended, without an events array since the events have already been streamed:
{"schema":"lic.cli.v1","kind":"event","ts":"2026-09-03T06:31:54.3357790Z","level":"info","message":"License saved to 'MyApp.licenses'"}
{"schema":"lic.cli.v1","kind":"result","exitCode":0,"exitReason":"success","elapsedMs":141,"warnings":0,"errors":0}| Field | Type | Notes |
|---|---|---|
exitCode | integer | Numeric exit code actually returned by the process (legacy or strict, depending on --strict-exit). |
exitReason | string | Stable classification token. Always reflects the semantic class, even without --strict-exit. |
elapsedMs | integer | Total wall-clock duration in milliseconds. |
warnings | integer | Count of warning-level events. |
errors | integer | Count of error-level events. |
events | array | Present in json mode; omitted in ndjson. |
Machine-readable —help
lic --help --format=json emits a complete description of every public option, suitable for tooling that needs to introspect the CLI surface: shell completions, AI agents, documentation generators.
{
"schema": "lic.cli.v1",
"kind": "help",
"version": "12.0.0.0",
"usage": "lic.exe <assembly|licensefile> [<other assemblies>...] [options]",
"options": [
{
"name": "trial",
"aliases": [],
"description": "Add or update trial license restriction",
"args": "<key=value>",
"argRequired": true,
"argOptional": false,
"negatable": false,
"incremental": false,
"container": "hash",
"type": "string",
"group": "- License Restrictions -",
"details": "Set trial restriction properties entering key-value pairs:\n\nid=<id> Restriction id\nexpire=<date> Set expiration date\ndays=<n> Set number of trial days\n..."
}
]
}| Field | Meaning |
|---|---|
name, aliases | The option name and its alternative spellings, including negated forms and single-letter aliases. |
args | The argument placeholder as shown in the usage, or empty for a switch. |
argRequired, argOptional | Whether the option takes a mandatory or an optional argument. |
negatable | Whether the option accepts the no prefix. |
container | scalar for a single value, array for a repeatable option, hash for key=value pairs. |
type | string or integer. |
group | The section of the help output the option belongs to. |
details | The extended description, the same text lic --help <option> prints. |
A scoped form is also supported: lic --help trial --format=json emits the same envelope containing only the trial option. Hidden and internal options are never returned.
Machine-readable —version
lic --version --format=json emits product, version, runtime and platform information:
{
"schema": "lic.cli.v1",
"kind": "version",
"product": "Babel Licensing",
"version": "12.0.0.0",
"fileVersion": "12.0.0.1",
"runtime": ".NET 10.0.0",
"platform": "osx-arm64"
}The platform field uses the .NET runtime identifier (RID) on .NET 5 and later, and a synthetic <os>-<arch> string on legacy targets.
Driving lic From an Agent
An agent that has never seen lic can operate it from the JSON help alone: read the option schema, build the command line from the args and container hints, run with --format json --quiet --strict-exit, and branch on exitReason. A typical loop for issuing a license looks like this:
lic --help --format json > lic-options.json
lic MyApp.dll --keyfile keys.pem --sign --licensee name="Contoso" --expiredate 365 --output MyApp.licenses --format json --quiet --strict-exit > result.json
jq -r '.exitReason' result.jsonThe same contract, exit codes and flag names apply to the obfuscator command line, documented in AI-Friendly Mode of the obfuscator manual, so one integration covers both tools.
Backward compatibility
- The default value of
--formatistext; the legacy textual output, exit codes and behavior are unchanged when no new flag is specified. - Without
--strict-exit, the process keeps returning0on success and1on any failure, so existing gates that branch on$?keep working. --quietis independent and can be opted into without enabling structured output.