Skip to content

CLI Reference

The callora command-line interface scaffolds, validates, and signs plugins for the Callora platform. This page catalogues every command, its flags, defaults, behaviour, exit codes, and failure codes — extracted from the CLI source in src/Host/Cli/.

The entry facade is CalloraCliApplication.RunAsync; each command is parsed by a dedicated parser and executed by a dedicated worker (PluginScaffolder, PluginContractTester, PluginSigner).

Invocation

The CLI ships as a .NET global tool (PackageId Callora.Cli, command callora):

bash
dotnet tool install -g Callora.Cli
callora <command> [options]

Inside this repository, where the tool would be the released version rather than your working copy, run it through the project instead:

bash
dotnet run --project src/Host/Cli/Callora.Host.Cli.csproj -- <command> [options]

Everything after -- is passed to the CLI as its argument vector. In the examples below, the leading callora stands in for that invocation.

Status: No published dotnet tool / global callora executable was found in the repository. The dotnet run --project … form above is the verified way to run the CLI in-repo.

Running with no arguments, or with --help, -h, or help (exactly one argument), prints usage and exits 0.

text
Usage:
  callora plugin new [name] [--name <display-name>] [--id <plugin-id>] [--output <directory>] [--force]
  callora plugin test-contract --assembly <path-to-dll> [--registry <path-to-registry.json>] [--entry-type <full-type-name>]
  callora plugin sign --plugin <plugin-directory> --key <private-key.pem> [--out <plugin.signature.json>]

Commands at a glance

CommandPurposeRequired inputExit 0Exit 1
plugin newScaffold a new plugin project (csproj, entry class, registry.json).A plugin name (positional or --name).Scaffold created.Parse error or scaffold failure.
plugin test-contractValidate a built plugin assembly + registry.json against the v1 plugin contract.--assembly.All contract checks passed.Any validation issue (each printed with its code).
plugin signProduce a signed content manifest (plugin.signature.json) over the whole plugin directory.--plugin and --key.Signature written.Parse error or signing failure.

Every command exits 0 on success and 1 on any parse or execution failure. Failures are written to stderr; on a parse failure the usage block is re-printed.


plugin new

Scaffolds a fresh, host-managed plugin project into a target directory: a .csproj, an Application/<Name>Plugin.cs entry class implementing IHostManagedPlugin, and a registry.json manifest.

Flags

FlagAlias / formRequiredDefaultMeaning
[name]positionalOne of positional or --nameDisplay name of the plugin. Only one positional is allowed.
--name <display-name>optionOne of positional or --nameDisplay name. Takes precedence over the positional when both are given.
--id <plugin-id>optionNoDerived from the name (lowercased, non-alphanumeric runs joined by -).Stable plugin id written to registry.json and the entry class.
--output <directory>optionNo<cwd>/custom/plugins/<SafeNameSegment>.Target directory for the scaffold.
--forceflagNofalseAllow scaffolding into a non-empty directory (otherwise it is refused).

Behaviour

  • Name resolution. The effective name is --name if present, otherwise the positional. If neither is given the command fails with Plugin name is required.

  • Plugin id. If --id is omitted, the id is derived from the name via PluginScaffoldNaming.ToPluginId (split on non-alphanumeric characters, lowercased, joined with -; e.g. Acme Voiceacme-voice). The id must match [a-zA-Z0-9._-], be ≤ 128 characters, and must not start or end with -, ., or _; otherwise scaffolding fails with Invalid plugin id. Allowed: a-z, A-Z, 0-9, '.', '-', '_'.

  • Output directory. Default output is custom/plugins/<segment> under the current directory, where <segment> strips the name down to letters, digits, -, and _. Without --force, scaffolding refuses a directory that exists and is non-empty (Output directory is not empty: …).

  • Contract reference. Outside the repository the generated .csproj carries a single PackageReference to Callora.Plugin.Sdk, at the CLI's own version — so a tool never scaffolds against an SDK release that does not exist. The SDK brings the contract surface, the governance analyzers, and the build rule that keeps platform assemblies out of the output folder; that rule used to be a hand-written ExcludeAssets="runtime", which a plugin author could remove while restructuring with nothing failing until load time.

    Inside the repository (a Callora.Host.sln is found by walking up from the current directory) there are no packages to reference, so it emits ProjectReferences to the same pieces with Private="false". Either way the platform is compiled against but not shipped — the host provides it, and the plugin's load context shares its type identity (REV2 §10.1A).

Generated files

Given a name Acme Voice, the scaffold produces (all names derived via PascalCase):

PathContents
Callora.Plugins.AcmeVoice.csprojnet10.0 SDK project, ImplicitUsings/Nullable enabled, GenerateDocumentationFile=true (with NoWarn=$(NoWarn);1591), EnableDefaultCompileItems=false (only src/**/*.cs is compiled, so a front-end bundle at the plugin root stays out of the .NET compilation), the SDK reference above, and registry.json copied to output (PreserveNewest).
src/AcmeVoicePlugin.cspublic sealed class AcmeVoicePlugin : IHostManagedPlugin with PluginId, DisplayName, and no-op StartAsync/StopAsync.
registry.jsoncontractVersion: v2, schemaVersion: 1.0, name, pluginId, version: 0.1.0, assemblyFileName, entryTypeName, capabilities: ["workspace.navigation"], one extensions entry (extensionPointId: workspace.navigation.main, surface: surface), and dependencies: { "Callora.Core": ">=0.1.0" }.

Example

bash
callora plugin new "Acme Voice" --id acme-voice --output custom/plugins/acme-voice
# → Plugin scaffold created: /abs/path/custom/plugins/acme-voice

See the how-to: Plugin CLI and Your first plugin.


plugin test-contract

Validates a built plugin against the v1 plugin contract: it reads the registry.json manifest, checks the required fields, then loads the assembly in an isolated, collectible load context (PluginInspectionLoadContext) and verifies the contract reference and the plugin lifecycle entrypoint. Every issue is printed to stderr as [CODE] <message> Fix: <remediation>; any issue fails the command with exit 1.

Flags

FlagRequiredDefaultMeaning
--assembly <path-to-dll>YesPath to the built plugin DLL to inspect.
--registry <path-to-registry.json>Noregistry.json next to the assembly.Explicit path to the manifest.
--entry-type <full-type-name>NoManifest entryTypeName, else auto-detected.Overrides which type is treated as the plugin entrypoint.

Any unknown option, or a missing value for a known option, fails parsing. --assembly is mandatory (Option --assembly is required.).

Validations performed

Manifest resolution and shape

Failure codeFires when
ASSEMBLY_NOT_FOUNDThe --assembly file does not exist.
MANIFEST_NOT_FOUNDNo registry.json at the resolved/explicit path.
MANIFEST_PARSE_ERRORregistry.json is empty or is not valid JSON.

Required manifest fields (each checked independently, so several may report at once)

Failure codeFires when
MANIFEST_CONTRACT_VERSION_MISSINGcontractVersion is absent/blank.
MANIFEST_CONTRACT_VERSION_UNSUPPORTEDcontractVersion is present but unknown to PluginContractVersionPolicy, or listed there as removed (currently v0). Case-insensitive.
MANIFEST_CONTRACT_VERSION_DEPRECATEDcontractVersion is a deprecated tier (currently v1). Reported as a warning: the run still exits 0, because the host installs such a plugin too.
MANIFEST_SCHEMA_VERSION_MISSINGschemaVersion is absent/blank.
MANIFEST_NAME_MISSINGname is absent/blank.
MANIFEST_PLUGIN_ID_MISSINGpluginId is absent/blank.
MANIFEST_VERSION_MISSINGversion is absent/blank.
MANIFEST_ASSEMBLY_FILE_NAME_MISSINGassemblyFileName is absent/blank.
MANIFEST_ASSEMBLY_FILE_NAME_MISMATCHassemblyFileName does not equal the actual --assembly file name (case-insensitive).
MANIFEST_ENTRY_TYPE_NAME_MISSINGentryTypeName is absent/blank.

Contract compatibility (assembly loaded and inspected)

Failure codeFires when
COMPATIBILITY_CONTRACTS_REFERENCE_MISSINGThe assembly does not reference Callora.Core.
COMPATIBILITY_CONTRACTS_MAJOR_MISMATCHThe referenced Callora.Core major version differs from the host's IHostManagedPlugin assembly major.

Lifecycle entrypoint (resolved from --entry-type, else manifest entryTypeName, else the first concrete type implementing IHostManagedPlugin)

Failure codeFires when
LIFECYCLE_ENTRYPOINT_NOT_FOUNDNo entrypoint type could be located.
LIFECYCLE_ENTRYPOINT_INVALIDThe resolved type is abstract/an interface or does not implement IHostManagedPlugin.
LIFECYCLE_ENTRYPOINT_INSTANTIATION_FAILEDNo public parameterless constructor, or the constructor throws when invoked.
LIFECYCLE_PLUGIN_ID_MISSINGThe instantiated entrypoint's PluginId property is empty.
LIFECYCLE_DISPLAY_NAME_MISSINGThe instantiated entrypoint's DisplayName property is empty.

On success, the CLI prints All contract checks passed. and exits 0.

Example

bash
callora plugin test-contract \
  --assembly custom/plugins/acme-voice/bin/Release/net10.0/Callora.Plugins.AcmeVoice.dll
# On failure, e.g.:
# [MANIFEST_ASSEMBLY_FILE_NAME_MISMATCH] registry.json assemblyFileName '…' does not match assembly '…'. Fix: Set assemblyFileName to the actual built DLL file name.

The manifest fields validated here are documented in Registry manifest and Extension manifests. The [CalloraInternal] contract boundary these checks enforce is described under .NET contracts and Architecture.


plugin sign

Produces a signed content manifest (plugin.signature.json) over an entire plugin directory. Every file in the directory (except the signature file itself) is hashed; the hashes, the signer's public-key fingerprint, and a detached signature over the canonical serialization form the manifest. Because registry.json is among the hashed files, plugin metadata — capabilities, entry type — is tamper-evident too, not just the assembly.

Flags

FlagRequiredDefaultMeaning
--plugin <plugin-directory>YesDirectory of the built plugin (must contain registry.json).
--key <private-key.pem>YesECDSA P-256 private key in PEM. Must not live inside the plugin directory.
--out <plugin.signature.json>No<plugin-directory>/plugin.signature.json.Output path for the signature manifest.

Relative paths are resolved against the current directory. Any unknown option, or a missing value, fails parsing. Both --plugin and --key are mandatory.

Cryptography

PropertyValue
Algorithm idECDSA-P256-SHA256 (PluginSignatureAlgorithms.EcdsaP256Sha256)
SignatureECDSA over SHA-256 of the canonical manifest bytes, Base64-encoded
Per-file hashSHA-256 (PluginContentHasher)
Signer fingerprintUppercase hex SHA-256 of the key's SubjectPublicKeyInfo — the trust unit stored in the host trust store
SchemaPluginSignatureManifest: schemaVersion (1.0), pluginId, version, algorithm, signerFingerprint, files[] (relativePath + hash), signature

Authenticode is not used (it is broken on Linux); this cross-platform ECDSA manifest is the signing path.

Behaviour and failure messages

The command fails (exit 1, message to stderr) when:

  • The plugin directory is missing — Plugin directory not found: …
  • No registry.json in the directory — registry.json was not found in the plugin directory.
  • The key file is missing — Signing key not found: …
  • registry.json cannot be parsed — registry.json could not be parsed: …
  • registry.json lacks pluginId or assemblyFileNameregistry.json is missing pluginId or assemblyFileName.
  • The declared assemblyFileName is not present among the package files — Declared assembly '…' was not found in the plugin directory.
  • The PEM key cannot be loaded — Could not load the signing key: …

On success it writes the manifest and prints Plugin signature written: <path>.

Example

bash
callora plugin sign \
  --plugin custom/plugins/acme-voice/bin/Release/net10.0 \
  --key ./keys/publisher.pem
# → Plugin signature written: …/bin/Release/net10.0/plugin.signature.json

The resulting plugin.signature.json structure is catalogued in Extension manifests. The host verifies it at install time against configured trusted signers (see the plugin-security routes in the REST API).