cli

CLI tool for declarative capability management

main


def main(
    ctx:Context, cjm_config:Annotated=None, data_dir:Annotated=None, conda_prefix:Annotated=None,
    conda_type:Annotated=None
)->None:

cjm-substrate CLI for managing isolated capability environments.

Setup Runtime

The setup-runtime command downloads and installs micromamba for project-local mode. This is required before running install-all when using conda_type: micromamba in the configuration.


setup_runtime


def setup_runtime(
    force:bool=<typer.models.OptionInfo object at 0x7fcac100dd60>
)->None:

Download and setup micromamba runtime for project-local mode.


run_cmd


def run_cmd(
    cmd:str, # Shell command to execute
    check:bool=True, # Whether to raise on non-zero exit
)->None:

Run a shell command and stream output.

Uses the platform’s default shell (no hardcoded /bin/bash).

Regenerate Manifest

The regenerate-manifest command re-runs the introspection pipeline for an already-installed capability and rewrites its manifest in place. Picks up new substrate-side manifest fields (resources, install-time metadata) without forcing a full install-all --force.

Package-source recovery order: 1. --package <spec> operator override (always wins when provided) 2. The manifest’s own package_source field (post-Phase-5a manifests) 3. --capabilities capabilities.yaml lookup by capability name (legacy manifests without package_source) 4. Error with actionable message if none of the above resolve

The ecosystem-wide cascade_manifests.py (loops this command over every capability) is deferred to CR-8’s nested-format migration.


regenerate_manifest


def regenerate_manifest(
    capability_name:str=<typer.models.ArgumentInfo object at 0x7fcac0ed2600>,
    capabilities_path:Optional=<typer.models.OptionInfo object at 0x7fcac0ed2630>,
    package:Optional=<typer.models.OptionInfo object at 0x7fcac0ed2660>
)->None:

Re-run introspection for an installed capability and rewrite its manifest.

Reads the existing manifest via load_manifest, recovers env_name + package_source from the install section, runs _generate_manifest to refresh the code section, then post-writes to preserve the original installed_at so the regenerate only updates regenerated_at semantically. Always emits v2.0 layout.


generate_adapter_manifest


def generate_adapter_manifest(
    env_name:str=<typer.models.ArgumentInfo object at 0x7fcac10f7890>,
    target:str=<typer.models.ArgumentInfo object at 0x7fcac10f5d60>
):

CR-17 pt 2 (stage 4): introspect a task-adapter impl in-env and write its adapter manifest.

The adapter manifest is the REGISTRATION unit (pass-2 Thread 3): task_name + required_tool_protocol members (names + parameter lists + signatures) recorded IN-ENV where the protocol is importable, so host-side compatibility matching works against UNLOADED capabilities with zero protocol imports host-side. Written to the same manifests dir capability manifests live in; discover_manifests() routes by the unit key.

Thin typer wrapper over _generate_adapter_manifest (stage 6 J10: install-all runs the same core for per-capability adapters: entries).


install_all


def install_all(
    capabilities_path:Optional=<typer.models.OptionInfo object at 0x7fcac100e690>,
    substrate_source:str=<typer.models.OptionInfo object at 0x7fcac100fb60>,
    force:bool=<typer.models.OptionInfo object at 0x7fcac100e630>
)->None:

Install and register all capabilities defined in capabilities.yaml.

Per-capability adapters: entries ride the same pipeline (stage 6 J10; closes the I6/J8 manual-step gap): each entry’s lib is pip-installed into the worker env alongside the interface libs, and each impl (‘module:ClassName’) gets its adapter manifest generated right after the capability manifest – INSTALL puts code in envs, REGISTRATION is per-unit manifests (pass-2 Thread 3), one command does both.

Setup Host Environment

The setup-host command prepares the host application’s Python environment by installing all unique interface libraries referenced in capabilities.yaml. This is separate from install-all which sets up isolated capability environments.


setup_host


def setup_host(
    capabilities_path:str=<typer.models.OptionInfo object at 0x7fcac0eb2de0>,
    yes:bool=<typer.models.OptionInfo object at 0x7fcac100c830>
)->None:

Install interface libraries in the current Python environment.

Estimate Disk Space

The estimate-size command estimates the disk space required for capability environments before installation. It uses conda’s dry-run feature for accurate conda package sizes and queries PyPI for pip package sizes.

List Capabilities

The list command shows installed capabilities by scanning manifest files in the configured manifests directory (defaults to ~/.cjm/manifests/). It can optionally cross-reference with a capabilities.yaml file and check conda environment status.


list_capabilities


def list_capabilities(
    capabilities_path:Optional=<typer.models.OptionInfo object at 0x7fcac0ed3da0>,
    show_envs:bool=<typer.models.OptionInfo object at 0x7fcac10f5eb0>
)->None:

List installed capabilities from manifest directory.

Observability commands (CR-14 follow-up)

cjm-ctl logs is the operator projection over the two stores (the replacement for the retired tail .cjm/logs/<capability>.log habit): the default view is structured diagnostics records (worker logger.* output, job-stamped); --chunks shows the raw stream pump (the death-rattle floor); --journal shows the account-of-action. --follow polls the seq cursor — the same exact-cursor mechanism that replaced LOG_APPENDED.

cjm-ctl retention applies the diagnostics retention policy on demand (the startup sweep in CapabilityManager is the automatic invocation). The journal has no retention surface by design.


logs_command


def logs_command(
    job:Annotated=None, run:Annotated=None, session:Annotated=None, level:Annotated=None, journal:Annotated=False,
    chunks:Annotated=False, limit:Annotated=50, follow:Annotated=False
)->None:

Tail / follow the observability stores (CR-14).

Default view: structured diagnostics records (worker logger output, EXACTLY job-stamped via the call envelope). --chunks: the raw stream pump. --journal: the durable account-of-action (job lifecycle, worker spawn/death, admission, config, runs, worker-reported accounts). --follow polls the store’s seq cursor — exact, no byte offsets.


retention_command


def retention_command(
    max_age_days:Annotated=None, max_total_mb:Annotated=None
)->None:

Apply the diagnostics retention policy now (CR-14).

The explicit half of the invocation policy (CapabilityManager’s startup sweep is the automatic half). Defaults come from cjm.yaml’s substrate.diagnostics_retention_days / diagnostics_retention_max_mb. The JOURNAL is never touched — it has no retention surface by design.

Remove Capability

The remove command removes a capability’s manifest and optionally its conda environment. It can look up the environment name from the manifest or a config file.


remove_capability


def remove_capability(
    capability_name:str=<typer.models.ArgumentInfo object at 0x7fcac10f7ef0>,
    capabilities_path:Optional=<typer.models.OptionInfo object at 0x7fcac1092b40>,
    keep_env:bool=<typer.models.OptionInfo object at 0x7fcac10f5ca0>,
    yes:bool=<typer.models.OptionInfo object at 0x7fcac10f7620>
)->None:

Remove a capability’s manifest and conda environment.

Validate Command (SG-6)

The validate command checks that a manifest JSON file or a capabilities.yaml file conforms to the substrate’s expected structure. Auto-detects format from the file extension (.json → manifest, .yaml/.yml → capabilities.yaml).

Composes with CR-8 (manifest schema authority): the validator checks the nested v2.0 manifest layout. (The legacy flat v1.0 shape + its validator were removed at SG-48.)


validate_file


def validate_file(
    path:Path=<typer.models.ArgumentInfo object at 0x7fcac100dbb0>,
    format:Optional=<typer.models.OptionInfo object at 0x7fcac100df40>
)->None:

SG-6 + T23: validate a manifest / capabilities.yaml / capability source.

Auto-detects format from the path (.json → manifest, .yaml/.yml → capabilities.yaml, .py or a directory → source lint). The source lint is the CR-14 logging.basicConfig gate: force=True is an ERROR (it destroys the substrate diagnostics handler), a plain call is a WARNING. Exits non-zero with a list of validation errors if any check fails.


list_secrets


def list_secrets(
    capability_name:str=<typer.models.ArgumentInfo object at 0x7fcac0cba9f0>,
    scope:Optional=<typer.models.OptionInfo object at 0x7fcac0cbad50>
):

List the secret KEY NAMES stored for a capability — never the values (CR-12).


set_secret


def set_secret(
    capability_name:str=<typer.models.ArgumentInfo object at 0x7fcac0cb9a00>,
    key:str=<typer.models.ArgumentInfo object at 0x7fcac0cb9bb0>,
    value:Optional=<typer.models.OptionInfo object at 0x7fcac0cb9dc0>,
    scope:Optional=<typer.models.OptionInfo object at 0x7fcac0cba8d0>
):

Store a capability secret in the project-local SecretStore (CR-12).

The value is written to /secrets/secrets.json (0600) — never to capabilities.yaml, manifests, or the config store. Capabilities read it from their worker env at spawn. Omit –value to be prompted (hidden input) so the secret stays out of shell history. After setting, reload the capability (or restart the host) so its worker respawns with the new env — the GUI / CapabilityManager.set_capability_secret do this automatically.