Command Line Interface
SSMD ships a command-line tool for creating, validating, converting, and formatting SSMD
files. The CLI uses Typer/Click for command registration and provides a root-level
--json option for machine-readable output with stable success/error envelopes.
After installing SSMD, the ssmd command is available:
pip install ssmd
ssmd --version
You can also run it as python -m ssmd.
Exit codes
Exit code |
Meaning |
|---|---|
|
Success. No lint errors (warnings allowed unless |
|
Lint found one or more errors, or |
|
CLI usage error, unreadable input, invalid output path, or invalid profile/preset. |
|
Fatal conversion or parse error. |
Machine-readable output
Use --json at the root level to get JSON output:
ssmd --json lint story.ssmd.md
ssmd --json profiles
ssmd --json inspect story.ssmd.md --spans
ssmd --json voices list
The JSON output uses a stable envelope format:
{
"schema": "ssmd.cli.v1",
"ok": true,
"command": "lint",
"result_type": "lint_report",
"result": { ... }
}
The schema value is ssmd.cli.v1 for both success and error envelopes. Consumers
should check the schema before interpreting the command-specific payload. Error
envelope:
{
"schema": "ssmd.cli.v1",
"ok": false,
"command": "convert",
"error": {
"code": "USAGE_ERROR",
"message": "...",
"exit_code": 2
}
}
For lint and format-check reports, result.passed or result.clean indicates whether
the document passed validation. The outer ok indicates whether the CLI operation
itself succeeded.
Agents must check both the process exit code and the command-specific result state:
Command |
Required success state |
|---|---|
|
exit |
|
exit |
|
exit |
conversion with output |
exit |
|
exit |
ok == true only means that a domain result was returned. A warning-blocked
create --fail-on-warn can return ok == true, result.created == false, and exit
1; it must not be treated as a successful shipment.
config and voices
The local authoring configuration is resolved in this order: root --config PATH,
SSMD_CONFIG, then Click’s platform application directory. On Linux the default is
~/.config/ssmd/config.yaml. Read-only commands do not create the file.
ssmd --json config path
ssmd config init
ssmd --json config show --effective
ssmd --json config validate
ssmd --json voices list --provider kokoro
ssmd voices bind kokoro moderator af_sarah
ssmd --json voices resolve moderator --provider kokoro
voices list is deterministic and excludes disabled entries unless --include-disabled
is supplied. Inventory entries are local authoring data and are not copied into portable
document headers.
lint / check
Validate SSMD syntax and profile compatibility:
ssmd lint story.ssmd.md
ssmd check story.ssmd.md # alias for lint
Options:
--profile NAME
: Lint profile to use (default ssmd-core). Available profiles: run ssmd profiles.
--dialect {auto,0.8,0.9}
: Select syntax dialect. auto uses ssmd_version when present and retains the legacy
behavior for unversioned documents; explicit 0.9 enables strict 0.9 parsing.
--loss-policy {error,warn,drop}
: Choose how unsupported rendering semantics are handled during compatibility checks.
error fails, warn reports a warning, and drop allows the conversion with an
informational diagnostic. --capabilities PRESET
: Validate conversion against a TTS capability preset.
--format {text,json}
: Output format (default text). JSON emits a machine-readable report.
--fail-on-warn
: Exit 1 when warnings are found (useful in CI).
--quiet
: Suppress the path: ok line on success.
--no-xml-check
: Skip checking generated SSML for XML well-formedness.
--roundtrip
: Additionally compare canonical SSMD semantics across SSMD→SSML→SSMD. Equivalent block-level and inline voice representations are treated as the same semantics.
The comparison tolerates formatting-only whitespace changes but preserves spoken text, voice boundaries, supported annotations, marks, breaks, paragraph structure, and front-matter meaning. Reconstructed nested markup is emitted in a safe block form rather than becoming literal prose.
--parse-yaml-header / --no-yaml-header
: Front matter is parsed by default. Use --no-yaml-header for literal leading ---
content; --parse-yaml-header remains a compatibility spelling.
--voice-provider PROVIDER
: Resolve voice references against one provider.
--no-config
: Perform portable structural lint without requiring local inventory entries.
Text output uses clean chars offsets (the clean-text coordinate system), not source
line/column positions:
story.ssmd: error: clean chars 0-7: Tag 'inline' is not supported by profile 'ssmd-core'.
story.ssmd: warn: say-as 'currency' not supported, dropping
Parser diagnostics with a remediation hint print a following <path>: hint: ... line.
Lint JSON issue objects include a hint field only when a hint is available.
create
Create a formatted and validated SSMD file with an atomic write:
ssmd create draft.ssmd.md -o episode.ssmd.md
cat draft.ssmd.md | ssmd create - -o episode.ssmd.md
ssmd create draft.ssmd.md -o episode.ssmd.md --fail-on-warn
ssmd create draft.ssmd.md -o episode.ssmd.md --force
create performs source formatting, syntax/profile validation, SSMD→SSML conversion,
XML well-formedness validation, and a semantic SSMD→SSML→SSMD round-trip check before
writing the output. If validation fails, the output file is not created or replaced. In
JSON mode, successful creation requires result.created == true, nonzero
result.bytes_written, and the requested output path to exist.
Options:
--profile NAME
: Lint profile to enforce (default ssmd-core).
--capabilities PRESET
: Validate against a target TTS capability preset.
--fail-on-warn
: Refuse to write when warnings are present.
--parse-yaml-header
: Compatibility spelling; YAML front matter is parsed by default.
--config PATH
: Select the local authoring configuration. SSMD_CONFIG is used when this option is
absent, followed by Click’s platform application directory (~/.config/ssmd/config.yaml
on Linux).
--voice-provider PROVIDER
: Select the active provider for voice binding materialization.
Create discovers canonical fenced directives and materializes required voice_bindings
as defaults. Empty mappings and missing nested provider or role entries may be filled,
while explicit document bindings remain authoritative. Raw <div> syntax is
compatibility-only.
--bind REFERENCE=VOICE_ID
: Add a repeatable explicit binding override for the selected provider.
--materialize-config/--no-materialize-config
: Enable or disable create-time config-derived header fields (enabled by default).
--materialize-voice-bindings/--no-materialize-voice-bindings and
--materialize-pause-defaults/--no-materialize-pause-defaults
: Override individual materialization categories.
--no-format
: Preserve source bytes instead of normalizing line endings.
--no-roundtrip
: Skip the semantic round-trip check.
--force
: Replace an existing output file. Replacement is atomic and preserves existing permissions.
convert
Convert between SSMD, SSML, and plain text:
ssmd convert story.ssmd.md --to ssml
ssmd convert story.ssml --from ssml --to ssmd -o story.ssmd.md
ssmd convert story.ssmd.md --to text
ssmd convert story.ssmd.md --to ssml -o story.ssml
Use .ssmd.md and .ssmd as unambiguous SSMD filename hints. SSML inputs with .ssml
or .xml are inferred as SSML. A plain .md file is inferred as SSMD only when its
front matter contains ssmd_version; otherwise specify --from explicitly. Explicit
--from always takes precedence.
cat story.ssmd.md | ssmd convert - --from ssmd --to ssml
For SSMD-to-SSML conversions, --target {generic,ssml-1.1,provider} selects the
serialization target. generic produces portable SSML, ssml-1.1 enforces SSML 1.1
requirements, and provider applies the selected capability profile.
--language and --fallback-language override the root language used for SSML output.
The ssml-1.1 target requires a root language and a <speak> document wrapper; the CLI
rejects --target ssml-1.1 combined with --no-speak-tag.
--loss-policy {error,warn,drop} controls unsupported semantics: error rejects lossy
output, warn returns warnings, and drop permits losses with informational
diagnostics. The optional --dialect {auto,0.8,0.9} selects the input syntax version;
auto honors the document version and preserves the unversioned compatibility default.
For SSML-to-SSMD conversion, from-ssml and convert --from ssml reject
unrepresentable semantics by default. warn and drop opt into reported losses.
Results are complete, versioned SSMD 0.9 documents by default; use --fragment only
when a body fragment is needed.
to-ssml / from-ssml / text
Convenience aliases for common conversions:
ssmd to-ssml story.ssmd.md -o story.ssml
ssmd from-ssml story.ssml -o story.ssmd.md
ssmd text story.ssmd.md
to-ssml accepts the same SSMD-to-SSML options as convert, including --target,
--loss-policy, --language, --fallback-language, and --no-speak-tag. The strict
SSML 1.1 target cannot be combined with --no-speak-tag.
Sentence detection options on convert and to-ssml are:
--sentence-spacy-model TEXTfor an exact package.--sentence-model-size sm|md|lg|trffor an exact tier.--sentence-use-spacyor--no-sentence-use-spacyto force the backend.
When neither model nor size is set, SSMD uses phrasplit’s highest installed compatible
model for the document language. JSON conversion results and inspect --header include
the selected model diagnostics when sentence detection runs.
text --capabilities PRESET applies strict capability filtering before plain-text
rendering. For example, unsupported substitutions remain as their source text.
fmt
fmt applies canonical formatting to explicitly versioned SSMD 0.9. Unversioned and 0.8
documents retain their dialect and receive source-preserving line-ending normalization
only; fmt never migrates input. Use ssmd migrate for an explicit
semantic-equivalence-checked upgrade.
ssmd fmt story.ssmd.md # formatted output to stdout
ssmd fmt story.ssmd.md -w # write normalized result in place, atomically
ssmd fmt story.ssmd.md --check # exit 1 if formatting would change
ssmd fmt a.ssmd.md b.ssmd.md -w # format multiple files
Without -w or --check, formatted SSMD is written to stdout. Multiple files require
either -w or --check; those two modes are mutually exclusive. Stdin cannot be
combined with -w, and - may appear only once.
migrate
Migrate a legacy document to canonical SSMD 0.9 only when semantic equivalence is verified:
ssmd migrate legacy.ssmd --to 0.9 # canonical content to stdout
ssmd migrate legacy.ssmd --to 0.9 -o story-09.ssmd.md
ssmd migrate legacy.ssmd --to 0.9 --write # replace in place atomically
ssmd --json migrate legacy.ssmd --to 0.9 # inspect migration result
Migration never modifies the source unless --write or an output path is explicit.
Existing output files are not replaced without --overwrite. Documents that require a
semantic decision are left unchanged, with diagnostics and manual actions returned
instead of an unsafe rewrite.
Migration preserves clean text, effective annotation coverage, structural events, and
portable header data. Block-aligned legacy <div> scopes are emitted as fenced
directives. When fencing would introduce paragraph boundaries absent from the legacy
structure, migration can use paragraph-local inline annotations with separators outside
the annotations. Invalid source syntax is reported as a source error. An invalid
generated 0.9 candidate is a migration error, not a request to edit syntax that was
absent from the source.
profiles
List available lint profiles and capability presets:
ssmd profiles
ssmd profiles --json
inspect (JSON)
Inspect parsed structure (useful for debugging and TTS integrations):
ssmd inspect story.ssmd.md --spans
ssmd inspect story.ssmd.md --sentences
ssmd inspect story.ssmd.md --paragraphs
Output is always JSON.
inspect --sentences reports both author declarations and resolved rendering values for
voice prosody. Each sentence includes declared_prosody, effective_prosody, and
sources; source values identify inline, directive, inherited directive, or
voice_default provenance. inspect --header exposes the parsed voice_defaults and
prosody_transitions metadata.
voice_defaults is keyed by logical SSMD voice names and does not contain provider
bindings. prosody_transitions is preserved as a renderer hint. It does not add a
non-standard transition element to generated SSML.
version
Print the installed SSMD version:
ssmd version
ssmd --version