diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 45538ba..4b542cf 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -17,6 +17,7 @@ on:
- ".github/workflows/dependency-matrix.yml"
- ".github/workflows/compatibility.yml"
- "scripts/generate_api_reference.py"
+ - "scripts/generate_core_surface.py"
- "scripts/generate_compatibility_dashboard.py"
- ".github/workflows/tests.yml"
- ".github/workflows/dependency-matrix.yml"
@@ -74,6 +75,8 @@ jobs:
- name: Validate generated public API reference
run: python scripts/generate_api_reference.py --check
+ - name: Validate generated core surface
+ run: python scripts/generate_core_surface.py --check
- name: Build documentation with strict warnings
run: python -m mkdocs build --strict --site-dir site
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d3482a1..8b320cd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,8 @@ and versions are tracked in the repo-root `VERSION` file.
### Changed
- Include `uv.lock` freshness in the authoritative local validation aggregate (#424).
+- Classify the compatibility facade in a reviewed API manifest and add the
+ smaller `base_cli.core` starting surface for new applications (#242).
## [0.5.1] - 2026-10-10
diff --git a/README.md b/README.md
index 617b52c..e4eed8e 100644
--- a/README.md
+++ b/README.md
@@ -88,6 +88,11 @@ and independently released.
an application's project model. Base is one consumer of the library, not part
of its generic contract.
+New applications can start with the smaller `base_cli.core` surface. The
+compatibility facade remains available for existing 0.x consumers; the
+[API manifest](https://basefoundry.github.io/base-cli/api-stability/#public-surface)
+classifies advanced and optional integration APIs.
+
It is intentionally thin. Click still owns argument parsing and command
execution, while `base_cli` provides reusable lifecycle behavior:
diff --git a/docs/api-manifest.json b/docs/api-manifest.json
new file mode 100644
index 0000000..2224afc
--- /dev/null
+++ b/docs/api-manifest.json
@@ -0,0 +1,138 @@
+{
+ "schema_version": 1,
+ "facade": "base_cli",
+ "tiers": {
+ "core": [
+ "App",
+ "ApplicationStateT",
+ "BaseCliDeprecationWarning",
+ "ConfigT",
+ "ConfigurationError",
+ "Context",
+ "ExitCode",
+ "LIFECYCLE_META_KEY",
+ "LifecycleOption",
+ "LifecycleOptions",
+ "LifecycleValues",
+ "ServicesT",
+ "__version__",
+ "argument",
+ "attach",
+ "command",
+ "configure_logger",
+ "deprecated",
+ "get_current_context",
+ "get_lifecycle_values",
+ "log_critical",
+ "log_debug",
+ "log_error",
+ "log_info",
+ "log_warning",
+ "option",
+ "run_app",
+ "testing"
+ ],
+ "integration": [
+ "TelemetryOptions",
+ "TelemetrySession",
+ "TyperAdapter",
+ "asyncio_adapter",
+ "attach_typer",
+ "get_typer_command",
+ "integrations",
+ "run_async",
+ "try_render_rich_table"
+ ],
+ "advanced": [
+ "AttachmentAdapter",
+ "AttachmentContextFactory",
+ "AttachmentContract",
+ "AttachmentServiceFactory",
+ "BOOLEAN",
+ "BatteriesIncludedConfigLoader",
+ "COMMAND_ENTRY_POINT_GROUP",
+ "CliProfile",
+ "CommandCodec",
+ "CommandExtension",
+ "CommandFilterNormalizer",
+ "CommandProtocolError",
+ "CommandSchemaRegistry",
+ "ConfigLoader",
+ "ConfigSnapshot",
+ "DEFAULT_SCHEMA_REGISTRY",
+ "DisplayCommandResolver",
+ "ENTRY_POINT_GROUPS",
+ "EXTENSION_API_VERSION",
+ "EnvironmentConfigLoader",
+ "ExtensionCollisionError",
+ "ExtensionCompatibilityError",
+ "ExtensionDescriptor",
+ "ExtensionDiscovery",
+ "ExtensionDiscoveryError",
+ "ExtensionLoadError",
+ "ExtensionLoadResult",
+ "ExtensionsDisabledError",
+ "FieldSpec",
+ "FrameworkConfig",
+ "HistoryDisplayResolver",
+ "HistoryWriter",
+ "JSON_CONTRACT_VERSION",
+ "JSON_ERROR_SCHEMA",
+ "JSON_LOG_SCHEMA",
+ "JSON_OUTPUT_SCHEMA",
+ "JsonLogFormatter",
+ "MAX_JSON_LOG_MESSAGE_LENGTH",
+ "NDJSON_SCHEMA",
+ "NDJSON_SCHEMA_VERSION",
+ "NULLABLE_STRING",
+ "NdjsonWriter",
+ "OutputFormatError",
+ "PLUGIN_ENTRY_POINT_GROUP",
+ "PROFILE_ENTRY_POINT_GROUP",
+ "PUBLIC_OUTPUT_FORMATS",
+ "PluginExtension",
+ "ProfileExtension",
+ "ProjectDiscovery",
+ "ProjectInfo",
+ "RECORD_SCHEMAS",
+ "RetentionPolicy",
+ "RuntimeBinding",
+ "RuntimeLayout",
+ "RuntimeResolver",
+ "STRING",
+ "StructuredRecord",
+ "StructuredResultWriter",
+ "UserConfigLoader",
+ "WorkspaceRootResolver",
+ "command_filters",
+ "command_matches",
+ "command_protocol",
+ "delegated_display_command",
+ "deprecations",
+ "dumps_envelope",
+ "dumps_record",
+ "dumps_records",
+ "error_envelope",
+ "extensions",
+ "get_command_app",
+ "history",
+ "is_terminal",
+ "json_contracts",
+ "loads_records",
+ "normalize_command_filter",
+ "normalize_command_filters",
+ "output_format_choices",
+ "redact_json_value",
+ "register_record_schema",
+ "render_document",
+ "render_records",
+ "resolve_output_format",
+ "success_envelope"
+ ],
+ "provisional": [
+ "experimental",
+ "inspection_envelope",
+ "render_inspection_json"
+ ]
+ }
+}
diff --git a/docs/api-reference.md b/docs/api-reference.md
index e1390ec..62dd2bc 100644
--- a/docs/api-reference.md
+++ b/docs/api-reference.md
@@ -1,7 +1,9 @@
# Public API reference
-This page is generated from `base_cli.__all__`. It is the complete stable
-facade: private modules and names are deliberately excluded. Each entry
+This page is generated from `base_cli.__all__` and checked against
+`api-manifest.json`. It is the compatibility facade: private modules and
+names are deliberately excluded. New applications should start with the
+smaller `base_cli.core` surface described in `api-stability.md`. Each entry
includes the runtime signature when Python can inspect one, the public
docstring summary, compatibility guidance, and a minimal usage shape.
For behavior that is shared by several symbols, follow the linked
@@ -19,7 +21,7 @@ The facade currently documents **124** public symbols.
## Classes and protocols
### `App`
-**Kind:** class
+**Kind:** class
**Signature:** `App(name: 'str | None' = None, version: 'str | None' = None, help: 'str | None' = None, log_to_file: 'bool' = True, max_log_files: 'int | None' = None, profile: 'CliProfile | None' = None, lifecycle_options: 'LifecycleOptions | None' = None, retention: 'RetentionPolicy | None' = None, max_run_bundles: 'int | None' = None, max_run_age_seconds: 'float | None' = None, max_run_total_bytes: 'int | None' = None, rich: 'bool' = False, telemetry: 'TelemetryOptions | None' = None) -> 'None'`
**Behavior:** Define a Click-backed command with a shared runtime lifecycle.
@@ -35,7 +37,7 @@ base_cli.App(...)
```
### `AttachmentAdapter`
-**Kind:** class
+**Kind:** class
**Signature:** `AttachmentAdapter(*args, **kwargs)`
**Behavior:** Lifecycle adapter contract implemented by :class:`base_cli.App`.
@@ -51,7 +53,7 @@ base_cli.AttachmentAdapter(...)
```
### `AttachmentContextFactory`
-**Kind:** class
+**Kind:** class
**Signature:** `AttachmentContextFactory(*args, **kwargs)`
**Behavior:** Create consumer-owned application state for an active Context.
@@ -67,7 +69,7 @@ base_cli.AttachmentContextFactory(...)
```
### `AttachmentContract`
-**Kind:** class
+**Kind:** class
**Signature:** `AttachmentContract(app: 'Any', command: 'CommandT', context_factory: 'Callable[[Context[Any, Any, Any]], Any] | None', service_factory: 'Callable[[Context[Any, Any, Any]], Any] | None', sensitive_parameters: 'frozenset[str]', lifecycle_options: 'Any', standard_bindings: 'dict[str, Any]') -> None`
**Behavior:** Immutable attachment state shared by the private Click adapter.
@@ -83,7 +85,7 @@ base_cli.AttachmentContract(...)
```
### `AttachmentServiceFactory`
-**Kind:** class
+**Kind:** class
**Signature:** `AttachmentServiceFactory(*args, **kwargs)`
**Behavior:** Create consumer-owned services for an active Context.
@@ -99,7 +101,7 @@ base_cli.AttachmentServiceFactory(...)
```
### `TyperAdapter`
-**Kind:** class
+**Kind:** class
**Signature:** `TyperAdapter(typer_app: '_TyperApp') -> 'None'`
**Behavior:** Attach one Typer application to a base-cli lifecycle.
@@ -115,7 +117,7 @@ base_cli.TyperAdapter(...)
```
### `BatteriesIncludedConfigLoader`
-**Kind:** class
+**Kind:** class
**Signature:** `BatteriesIncludedConfigLoader(cli_name: 'str | None' = None, *, user_config_dir: 'Path | None' = None, user_config_name: 'str' = 'config.yaml', project_config_name: 'str' = '.base-cli.yaml', environment_dir_name: 'str' = 'environments', verify_project_config: 'bool' = False) -> 'None'`
**Behavior:** Load conventional user, project, environment, and explicit layers.
@@ -131,7 +133,7 @@ base_cli.BatteriesIncludedConfigLoader(...)
```
### `BaseCliDeprecationWarning`
-**Kind:** class
+**Kind:** class
**Signature:** (value is not callable)
**Behavior:** Warning emitted when a supported base-cli API is being retired.
@@ -147,7 +149,7 @@ base_cli.BaseCliDeprecationWarning(...)
```
### `CliProfile`
-**Kind:** class
+**Kind:** class
**Signature:** `CliProfile(discover_project: 'ProjectDiscovery', load_user_config: 'UserConfigLoader', load_config: 'ConfigLoader', resolve_runtime: 'RuntimeResolver', history_writer: 'HistoryWriter | None' = None, display_command: 'DisplayCommandResolver' = , history_display_command: 'HistoryDisplayResolver' = , resolve_workspace_root: 'WorkspaceRootResolver' = , load_config_for_environment: 'EnvironmentConfigLoader | None' = None) -> None`
**Behavior:** Policy boundary between the generic CLI lifecycle and its consumer.
@@ -163,7 +165,7 @@ base_cli.CliProfile(...)
```
### `ConfigLoader`
-**Kind:** class
+**Kind:** class
**Signature:** `ConfigLoader(*args, **kwargs)`
**Behavior:** Load validated framework configuration and opaque consumer settings.
@@ -179,7 +181,7 @@ base_cli.ConfigLoader(...)
```
### `ConfigSnapshot`
-**Kind:** class
+**Kind:** class
**Signature:** `ConfigSnapshot(config: 'dict[str, Any]', framework: 'FrameworkConfig', provenance: 'Mapping[str, str]') -> None`
**Behavior:** One deterministic layered configuration result.
@@ -195,7 +197,7 @@ base_cli.ConfigSnapshot(...)
```
### `CommandCodec`
-**Kind:** class
+**Kind:** class
**Signature:** `CommandCodec(registry: 'CommandSchemaRegistry | None' = None) -> 'None'`
**Behavior:** Encode and decode records using one isolated schema registry.
@@ -211,7 +213,7 @@ base_cli.CommandCodec(...)
```
### `CommandProtocolError`
-**Kind:** class
+**Kind:** class
**Signature:** (value is not callable)
**Behavior:** Raised when a command-protocol schema or payload violates its contract.
@@ -227,7 +229,7 @@ base_cli.CommandProtocolError(...)
```
### `CommandSchemaRegistry`
-**Kind:** class
+**Kind:** class
**Signature:** `CommandSchemaRegistry() -> 'None'`
**Behavior:** Own an isolated set of command record schemas.
@@ -243,7 +245,7 @@ base_cli.CommandSchemaRegistry(...)
```
### `ConfigurationError`
-**Kind:** class
+**Kind:** class
**Signature:** (value is not callable)
**Behavior:** A user-correctable configuration error that is safe to show.
@@ -259,7 +261,7 @@ base_cli.ConfigurationError(...)
```
### `CommandExtension`
-**Kind:** class
+**Kind:** class
**Signature:** `CommandExtension(*args, **kwargs)`
**Behavior:** Callable contract for ``base_cli.commands`` entry points.
@@ -275,7 +277,7 @@ base_cli.CommandExtension(...)
```
### `Context`
-**Kind:** class
+**Kind:** class
**Signature:** `Context(cli_name: 'str', run_id: 'str', state_dir: 'Path', log_dir: 'Path', cache_dir: 'Path', temp_dir: 'Path', log_file: 'Path | None', config: 'ConfigT', environment: 'str', debug: 'bool', keep_temp: 'bool', log: 'logging.Logger', dry_run: 'bool' = False, application_home: 'Path | None' = None, project_root: 'Path | None' = None, manifest_path: 'Path | None' = None, project_name: 'str | None' = None, history_scope: 'str' = 'primary', history_parent_run_id: 'str | None' = None, user_config: 'object | None' = None, history_display_command: 'Callable[[str, list[str]], str]' = , cleanup_hooks: 'list[Callable[[], None]]' = , workspace_root: 'Path | None' = None, quiet: 'bool' = False, runtime_owner: 'str' = 'default', owner_root: 'Path | None' = None, run_root: 'Path | None' = None, application_context: 'ApplicationStateT | None' = None, services: 'ServicesT | None' = None, framework_config: 'FrameworkConfig | None' = None, config_provenance: 'Mapping[str, str]' = , json_output: 'bool' = False, rich: 'bool' = False) -> None`
**Behavior:** Runtime state and cleanup hooks available to an active CLI command.
@@ -291,7 +293,7 @@ base_cli.Context(...)
```
### `ExtensionCompatibilityError`
-**Kind:** class
+**Kind:** class
**Signature:** `ExtensionCompatibilityError(descriptor: 'ExtensionDescriptor', supported: 'Sequence[str]') -> 'None'`
**Behavior:** Raised when an extension declares an unsupported SDK version.
@@ -307,7 +309,7 @@ base_cli.ExtensionCompatibilityError(...)
```
### `ExtensionCollisionError`
-**Kind:** class
+**Kind:** class
**Signature:** `ExtensionCollisionError(group: 'str', name: 'str', descriptors: 'Sequence[ExtensionDescriptor]') -> 'None'`
**Behavior:** Raised when more than one distribution claims the same extension name.
@@ -323,7 +325,7 @@ base_cli.ExtensionCollisionError(...)
```
### `ExtensionDescriptor`
-**Kind:** class
+**Kind:** class
**Signature:** `ExtensionDescriptor(group: 'str', name: 'str', value: 'str', distribution: 'str | None', version: 'str | None', extras: 'tuple[str, ...]' = (), api_version: 'str' = '1', capabilities: 'tuple[str, ...]' = ()) -> None`
**Behavior:** Stable metadata for one entry point, before its object is loaded.
@@ -339,7 +341,7 @@ base_cli.ExtensionDescriptor(...)
```
### `ExtensionDiscovery`
-**Kind:** class
+**Kind:** class
**Signature:** `ExtensionDiscovery(*, disabled: 'bool' = False, allowlist: 'Iterable[str] | None' = None, entry_points: 'Iterable[Any] | EntryPointProvider | None' = None, paths: 'Iterable[Path] | None' = None, supported_api_versions: 'Iterable[str]' = ('1',)) -> 'None'`
**Behavior:** Discover and lazily load command, profile, and plugin entry points.
@@ -355,7 +357,7 @@ base_cli.ExtensionDiscovery(...)
```
### `ExtensionDiscoveryError`
-**Kind:** class
+**Kind:** class
**Signature:** (value is not callable)
**Behavior:** Base class for actionable extension discovery failures.
@@ -371,7 +373,7 @@ base_cli.ExtensionDiscoveryError(...)
```
### `ExtensionLoadError`
-**Kind:** class
+**Kind:** class
**Signature:** `ExtensionLoadError(descriptor: 'ExtensionDescriptor', cause: 'BaseException') -> 'None'`
**Behavior:** Wrap an extension import failure without hiding its source metadata.
@@ -387,7 +389,7 @@ base_cli.ExtensionLoadError(...)
```
### `ExtensionLoadResult`
-**Kind:** class
+**Kind:** class
**Signature:** `ExtensionLoadResult(descriptor: 'ExtensionDescriptor', value: 'Any | None' = None, error: 'ExtensionLoadError | None' = None) -> None`
**Behavior:** Result of an isolated bulk extension load.
@@ -403,7 +405,7 @@ base_cli.ExtensionLoadResult(...)
```
### `ExtensionsDisabledError`
-**Kind:** class
+**Kind:** class
**Signature:** (value is not callable)
**Behavior:** Raised when a caller attempts to load an extension while disabled.
@@ -419,7 +421,7 @@ base_cli.ExtensionsDisabledError(...)
```
### `DisplayCommandResolver`
-**Kind:** class
+**Kind:** class
**Signature:** `DisplayCommandResolver(*args, **kwargs)`
**Behavior:** Resolve the process-facing command label used in diagnostics.
@@ -435,7 +437,7 @@ base_cli.DisplayCommandResolver(...)
```
### `EnvironmentConfigLoader`
-**Kind:** class
+**Kind:** class
**Signature:** `EnvironmentConfigLoader(*args, **kwargs)`
**Behavior:** Load configuration with an explicitly selected environment.
@@ -451,7 +453,7 @@ base_cli.EnvironmentConfigLoader(...)
```
### `ExitCode`
-**Kind:** class
+**Kind:** class
**Signature:** `ExitCode()`
**Behavior:** Standard command exit code constants.
@@ -467,7 +469,7 @@ base_cli.ExitCode(...)
```
### `FieldSpec`
-**Kind:** class
+**Kind:** class
**Signature:** `FieldSpec(value_type: 'str', nullable: 'bool' = False) -> None`
**Behavior:** Describe the wire type and nullability of one command-record field.
@@ -483,7 +485,7 @@ base_cli.FieldSpec(...)
```
### `FrameworkConfig`
-**Kind:** class
+**Kind:** class
**Signature:** `FrameworkConfig(environment: 'str' = 'dev', log_level: 'str | None' = None, keep_temp: 'bool' = False) -> None`
**Behavior:** Validated lifecycle settings separated from consumer configuration.
@@ -499,7 +501,7 @@ base_cli.FrameworkConfig(...)
```
### `LifecycleOption`
-**Kind:** class
+**Kind:** class
**Signature:** `LifecycleOption(*param_decls: 'str', name: 'str | None' = None, help: 'str | None' = None, metavar: 'str | None' = None, envvar: 'str | tuple[str, ...] | list[str] | None' = None, show_envvar: 'bool' = False, show_default: 'bool | str | None' = None, hidden: 'bool' = False, default: 'Any' = None) -> 'None'`
**Behavior:** Immutable public configuration for one lifecycle-owned Click option.
@@ -515,7 +517,7 @@ base_cli.LifecycleOption(...)
```
### `LifecycleOptions`
-**Kind:** class
+**Kind:** class
**Signature:** `LifecycleOptions(debug: 'LifecycleOption | None' = , quiet: 'LifecycleOption | None' = , environment: 'LifecycleOption | None' = , config: 'LifecycleOption | None' = , keep_temp: 'LifecycleOption | None' = , log_file: 'LifecycleOption | None' = , version: 'LifecycleOption | None' = , dry_run: 'LifecycleOption | None' = None, json: 'LifecycleOption | None' = None) -> None`
**Behavior:** Composable option policy used by native and attached applications.
@@ -531,7 +533,7 @@ base_cli.LifecycleOptions(...)
```
### `LifecycleValues`
-**Kind:** class
+**Kind:** class
**Signature:** `LifecycleValues(debug: 'bool' = False, quiet: 'bool' = False, environment: 'str | None' = None, config: 'Path | None' = None, keep_temp: 'bool' = False, log_file: 'Path | None' = None, dry_run: 'bool' = False, json: 'bool' = False) -> None`
**Behavior:** Normalized lifecycle values resolved for the active Click context.
@@ -547,7 +549,7 @@ base_cli.LifecycleValues(...)
```
### `JsonLogFormatter`
-**Kind:** class
+**Kind:** class
**Signature:** `JsonLogFormatter(run_id: 'str | None' = None) -> 'None'`
**Behavior:** Format one ``LogRecord`` as a bounded, redacted JSON object.
@@ -563,7 +565,7 @@ base_cli.JsonLogFormatter(...)
```
### `NdjsonWriter`
-**Kind:** class
+**Kind:** class
**Signature:** `NdjsonWriter(stream: 'TextIO', schema: 'str' = 'base-cli.record', schema_version: 'int' = 1) -> None`
**Behavior:** Write versioned structured records as newline-delimited JSON.
@@ -579,7 +581,7 @@ base_cli.NdjsonWriter(...)
```
### `OutputFormatError`
-**Kind:** class
+**Kind:** class
**Signature:** (value is not callable)
**Behavior:** Raised when a public output format is not supported.
@@ -595,7 +597,7 @@ base_cli.OutputFormatError(...)
```
### `StructuredResultWriter`
-**Kind:** class
+**Kind:** class
**Signature:** `StructuredResultWriter(*args, **kwargs)`
**Behavior:** Typed sink for one structured result at a time.
@@ -611,7 +613,7 @@ base_cli.StructuredResultWriter(...)
```
### `ProjectInfo`
-**Kind:** class
+**Kind:** class
**Signature:** `ProjectInfo(root: 'Path | None' = None, manifest: 'Path | None' = None, name: 'str | None' = None) -> None`
**Behavior:** Consumer-neutral project information discovered for an invocation.
@@ -627,7 +629,7 @@ base_cli.ProjectInfo(...)
```
### `ProjectDiscovery`
-**Kind:** class
+**Kind:** class
**Signature:** `ProjectDiscovery(*args, **kwargs)`
**Behavior:** Discover consumer-owned project information for the current directory.
@@ -643,7 +645,7 @@ base_cli.ProjectDiscovery(...)
```
### `RuntimeLayout`
-**Kind:** class
+**Kind:** class
**Signature:** `RuntimeLayout(owner_root: 'Path', run_root: 'Path', state_dir: 'Path', log_dir: 'Path', cache_dir: 'Path', temp_dir: 'Path') -> None`
**Behavior:** Filesystem locations owned by one base-cli runtime binding.
@@ -659,7 +661,7 @@ base_cli.RuntimeLayout(...)
```
### `PluginExtension`
-**Kind:** class
+**Kind:** class
**Signature:** `PluginExtension(*args, **kwargs)`
**Behavior:** Callable contract for ``base_cli.plugins`` entry points.
@@ -675,7 +677,7 @@ base_cli.PluginExtension(...)
```
### `ProfileExtension`
-**Kind:** class
+**Kind:** class
**Signature:** `ProfileExtension(*args, **kwargs)`
**Behavior:** Callable contract for ``base_cli.profiles`` entry points.
@@ -691,7 +693,7 @@ base_cli.ProfileExtension(...)
```
### `RetentionPolicy`
-**Kind:** class
+**Kind:** class
**Signature:** `RetentionPolicy(max_bundles: 'int | None' = None, max_age_seconds: 'float | None' = None, max_total_bytes: 'int | None' = None) -> None`
**Behavior:** Bounds for complete invocation bundles.
@@ -707,7 +709,7 @@ base_cli.RetentionPolicy(...)
```
### `RuntimeResolver`
-**Kind:** class
+**Kind:** class
**Signature:** `RuntimeResolver(*args, **kwargs)`
**Behavior:** Resolve the runtime directories and ownership for one invocation.
@@ -723,7 +725,7 @@ base_cli.RuntimeResolver(...)
```
### `RuntimeBinding`
-**Kind:** class
+**Kind:** class
**Signature:** `RuntimeBinding(cache_root: 'Path', layout: 'RuntimeLayout', application_home: 'Path | None', runtime_owner: 'str', project_root: 'Path | None', project_name: 'str | None', inherited_path: 'Path | None', history_parent_run_id: 'str | None', run_id: 'str', primary_log_file: 'Path | None' = None, history_scope: 'str' = 'primary', write_identity: 'bool' = False) -> None`
**Behavior:** Runtime decisions supplied by a consumer profile.
@@ -739,7 +741,7 @@ base_cli.RuntimeBinding(...)
```
### `TelemetryOptions`
-**Kind:** class
+**Kind:** class
**Signature:** `TelemetryOptions(enabled: 'bool' = True, tracer: 'Any | None' = None, tracer_provider: 'Any | None' = None, tracer_name: 'str' = 'base_cli') -> None`
**Behavior:** Opt-in OpenTelemetry configuration for one :class:`base_cli.App`.
@@ -755,7 +757,7 @@ base_cli.TelemetryOptions(...)
```
### `TelemetrySession`
-**Kind:** class
+**Kind:** class
**Signature:** `TelemetrySession(span: 'Any', started_monotonic_ns: 'int') -> None`
**Behavior:** Best-effort state for a single lifecycle span.
@@ -771,7 +773,7 @@ base_cli.TelemetrySession(...)
```
### `HistoryWriter`
-**Kind:** class
+**Kind:** class
**Signature:** `HistoryWriter(*args, **kwargs)`
**Behavior:** Persist one completed invocation using the active typed Context.
@@ -787,7 +789,7 @@ base_cli.HistoryWriter(...)
```
### `HistoryDisplayResolver`
-**Kind:** class
+**Kind:** class
**Signature:** `HistoryDisplayResolver(*args, **kwargs)`
**Behavior:** Resolve the command label persisted in consumer history.
@@ -803,7 +805,7 @@ base_cli.HistoryDisplayResolver(...)
```
### `UserConfigLoader`
-**Kind:** class
+**Kind:** class
**Signature:** `UserConfigLoader(*args, **kwargs)`
**Behavior:** Load opaque consumer-owned user configuration.
@@ -819,7 +821,7 @@ base_cli.UserConfigLoader(...)
```
### `WorkspaceRootResolver`
-**Kind:** class
+**Kind:** class
**Signature:** `WorkspaceRootResolver(*args, **kwargs)`
**Behavior:** Project a consumer-owned user configuration into a workspace root.
@@ -837,7 +839,7 @@ base_cli.WorkspaceRootResolver(...)
## Functions
### `command_matches`
-**Kind:** function
+**Kind:** function
**Signature:** `command_matches(value: 'str', command_filters: 'tuple[str, ...]', *, normalizer: 'CommandFilterNormalizer | None' = None) -> 'bool'`
**Behavior:** Return whether a command value matches one of the normalized filters.
@@ -853,7 +855,7 @@ base_cli.command_matches(...)
```
### `dumps_envelope`
-**Kind:** function
+**Kind:** function
**Signature:** `dumps_envelope(envelope: 'Mapping[str, Any]') -> 'str'`
**Behavior:** Serialize an envelope as one compact, newline-terminated JSON record.
@@ -869,7 +871,7 @@ base_cli.dumps_envelope(...)
```
### `dumps_record`
-**Kind:** function
+**Kind:** function
**Signature:** `dumps_record(record_type: 'str', record: 'Record', *, protocol_header: 'str' = 'COMMAND_PROTOCOL_V1', registry: 'CommandSchemaRegistry | None' = None) -> 'str'`
**Behavior:** Serialize one typed command record using the protocol framing.
@@ -885,7 +887,7 @@ base_cli.dumps_record(...)
```
### `dumps_records`
-**Kind:** function
+**Kind:** function
**Signature:** `dumps_records(record_type: 'str', records: 'tuple[Record, ...] | list[Record]', *, protocol_header: 'str' = 'COMMAND_PROTOCOL_V1', registry: 'CommandSchemaRegistry | None' = None) -> 'str'`
**Behavior:** Serialize a sequence of typed command records using the protocol framing.
@@ -901,7 +903,7 @@ base_cli.dumps_records(...)
```
### `error_envelope`
-**Kind:** function
+**Kind:** function
**Signature:** `error_envelope(*, run_id: 'str | None', code: 'str', message: 'str', details: 'Mapping[str, Any] | None' = None) -> 'dict[str, Any]'`
**Behavior:** Return the stable v1 machine-readable error envelope.
@@ -917,7 +919,7 @@ base_cli.error_envelope(...)
```
### `inspection_envelope`
-**Kind:** function
+**Kind:** function
**Signature:** `inspection_envelope(*, command: 'str', status: 'InspectionStatus', data: 'Mapping[str, Any]', error: 'Mapping[str, Any] | None' = None) -> 'dict[str, Any]'`
**Behavior:** Build the stable v1 envelope for read-only inspection commands.
@@ -933,7 +935,7 @@ base_cli.inspection_envelope(...)
```
### `render_inspection_json`
-**Kind:** function
+**Kind:** function
**Signature:** `render_inspection_json(*, command: 'str', status: 'InspectionStatus', data: 'Mapping[str, Any]', error: 'Mapping[str, Any] | None' = None) -> 'str'`
**Behavior:** Serialize the stable inspection envelope with Python's JSON encoder.
@@ -949,7 +951,7 @@ base_cli.render_inspection_json(...)
```
### `argument`
-**Kind:** function
+**Kind:** function
**Signature:** `argument(*param_decls: 'str', sensitive: 'bool' = False, **attrs: 'Any') -> 'Callable[[Callable[_P, _R]], Callable[_P, _R]]'`
**Behavior:** Public facade symbol; see the linked contract and source annotations for details.
@@ -965,7 +967,7 @@ base_cli.argument(...)
```
### `run_async`
-**Kind:** function
+**Kind:** function
**Signature:** `run_async(awaitable: 'Awaitable[_T]') -> '_T'`
**Behavior:** Run one awaitable with an adapter-owned event loop.
@@ -981,7 +983,7 @@ base_cli.run_async(...)
```
### `attach`
-**Kind:** function
+**Kind:** function
**Signature:** `attach(command: '_ClickCommandT', *, app: 'App | None' = None, context_factory: 'Callable[[Context[Any, Any, Any]], Any] | None' = None, service_factory: 'Callable[[Context[Any, Any, Any]], Any] | None' = None, sensitive_parameters: 'Iterable[str]' = (), **app_kwargs: 'Any') -> '_ClickCommandT'`
**Behavior:** Attach lifecycle middleware and return the same Click command object.
@@ -997,7 +999,7 @@ base_cli.attach(...)
```
### `attach_typer`
-**Kind:** function
+**Kind:** function
**Signature:** `attach_typer(typer_app: '_TyperApp', *, app: 'App | None' = None, context_factory: 'Callable[[Context[Any, Any, Any]], Any] | None' = None, service_factory: 'Callable[[Context[Any, Any, Any]], Any] | None' = None, sensitive_parameters: 'Iterable[str]' = (), name: 'str | None' = None, **app_kwargs: 'Any') -> '_ClickCommand'`
**Behavior:** Attach a Typer application and return its native Click command.
@@ -1013,7 +1015,7 @@ base_cli.attach_typer(...)
```
### `command`
-**Kind:** function
+**Kind:** function
**Signature:** `command(*args: 'Any', **kwargs: 'Any') -> 'Callable[[Callable[_P, _R]], Callable[_P, _R]]'`
**Behavior:** Public facade symbol; see the linked contract and source annotations for details.
@@ -1029,7 +1031,7 @@ base_cli.command(...)
```
### `configure_logger`
-**Kind:** function
+**Kind:** function
**Signature:** `configure_logger(cli_name: 'str', log_file: 'Path | None', debug: 'bool', *, quiet: 'bool' = False, stream: 'TextIO | None' = None, formatter: 'logging.Formatter | None' = None, json_logs: 'bool' = False, run_id: 'str | None' = None, log_level: 'str | None' = None, propagate: 'bool | None' = None) -> 'logging.Logger'`
**Behavior:** Configure user-facing and persistent handlers for a CLI logger.
@@ -1045,7 +1047,7 @@ base_cli.configure_logger(...)
```
### `delegated_display_command`
-**Kind:** function
+**Kind:** function
**Signature:** `delegated_display_command(default: 'str | None' = None) -> 'str | None'`
**Behavior:** Return the wrapper display label or ``default`` when none is set.
@@ -1061,7 +1063,7 @@ base_cli.delegated_display_command(...)
```
### `deprecated`
-**Kind:** function
+**Kind:** function
**Signature:** `deprecated(since: 'str', *, remove: 'str', alternative: 'str | None' = None) -> 'Callable[[Callable[P, R]], Callable[P, R]]'`
**Behavior:** Mark a callable as deprecated while preserving its normal behavior.
@@ -1077,7 +1079,7 @@ base_cli.deprecated(...)
```
### `get_command_app`
-**Kind:** function
+**Kind:** function
**Signature:** `get_command_app(command_func: 'Any') -> 'App'`
**Behavior:** Return the :class:`App` owning a registered function or attached tree.
@@ -1093,7 +1095,7 @@ base_cli.get_command_app(...)
```
### `get_current_context`
-**Kind:** function
+**Kind:** function
**Signature:** `get_current_context() -> 'Context[Any, Any, Any]'`
**Behavior:** Return the active command context or raise when called outside a command.
@@ -1109,7 +1111,7 @@ base_cli.get_current_context(...)
```
### `get_typer_command`
-**Kind:** function
+**Kind:** function
**Signature:** `get_typer_command(typer_app: '_TyperApp') -> '_ClickCommand'`
**Behavior:** Materialize Typer's native Click command tree.
@@ -1125,7 +1127,7 @@ base_cli.get_typer_command(...)
```
### `get_lifecycle_values`
-**Kind:** function
+**Kind:** function
**Signature:** `get_lifecycle_values(click_context: 'Any | None' = None) -> 'LifecycleValues'`
**Behavior:** Return normalized lifecycle values stored on an active Click context.
@@ -1141,7 +1143,7 @@ base_cli.get_lifecycle_values(...)
```
### `log_critical`
-**Kind:** function
+**Kind:** function
**Signature:** `log_critical(message: 'str', *args: 'object') -> 'None'`
**Behavior:** Public facade symbol; see the linked contract and source annotations for details.
@@ -1157,7 +1159,7 @@ base_cli.log_critical(...)
```
### `log_debug`
-**Kind:** function
+**Kind:** function
**Signature:** `log_debug(message: 'str', *args: 'object') -> 'None'`
**Behavior:** Public facade symbol; see the linked contract and source annotations for details.
@@ -1173,7 +1175,7 @@ base_cli.log_debug(...)
```
### `log_error`
-**Kind:** function
+**Kind:** function
**Signature:** `log_error(message: 'str', *args: 'object') -> 'None'`
**Behavior:** Public facade symbol; see the linked contract and source annotations for details.
@@ -1189,7 +1191,7 @@ base_cli.log_error(...)
```
### `log_info`
-**Kind:** function
+**Kind:** function
**Signature:** `log_info(message: 'str', *args: 'object') -> 'None'`
**Behavior:** Public facade symbol; see the linked contract and source annotations for details.
@@ -1205,7 +1207,7 @@ base_cli.log_info(...)
```
### `log_warning`
-**Kind:** function
+**Kind:** function
**Signature:** `log_warning(message: 'str', *args: 'object') -> 'None'`
**Behavior:** Public facade symbol; see the linked contract and source annotations for details.
@@ -1221,7 +1223,7 @@ base_cli.log_warning(...)
```
### `loads_records`
-**Kind:** function
+**Kind:** function
**Signature:** `loads_records(payload: 'str', expected_record_type: 'str | None' = None, *, protocol_header: 'str' = 'COMMAND_PROTOCOL_V1', registry: 'CommandSchemaRegistry | None' = None) -> 'tuple[str, tuple[dict[str, RecordValue], ...]]'`
**Behavior:** Validate and decode protocol-framed command records.
@@ -1237,7 +1239,7 @@ base_cli.loads_records(...)
```
### `normalize_command_filter`
-**Kind:** function
+**Kind:** function
**Signature:** `normalize_command_filter(value: 'str', *, normalizer: 'CommandFilterNormalizer | None' = None) -> 'str'`
**Behavior:** Normalize one command name for matching.
@@ -1253,7 +1255,7 @@ base_cli.normalize_command_filter(...)
```
### `normalize_command_filters`
-**Kind:** function
+**Kind:** function
**Signature:** `normalize_command_filters(value: 'str | None', *, normalizer: 'CommandFilterNormalizer | None' = None) -> 'tuple[str, ...]'`
**Behavior:** Normalize a comma-separated command filter and reject empty entries.
@@ -1269,7 +1271,7 @@ base_cli.normalize_command_filters(...)
```
### `is_terminal`
-**Kind:** function
+**Kind:** function
**Signature:** `is_terminal(stream: 'TextIO | None' = None) -> 'bool'`
**Behavior:** Return whether *stream* is an interactive terminal.
@@ -1285,7 +1287,7 @@ base_cli.is_terminal(...)
```
### `output_format_choices`
-**Kind:** function
+**Kind:** function
**Signature:** `output_format_choices() -> 'str'`
**Behavior:** Return the public choices in help/error-message order.
@@ -1301,7 +1303,7 @@ base_cli.output_format_choices(...)
```
### `option`
-**Kind:** function
+**Kind:** function
**Signature:** `option(*param_decls: 'str', sensitive: 'bool' = False, dry_run: 'bool' = False, **attrs: 'Any') -> 'Callable[[Callable[_P, _R]], Callable[_P, _R]]'`
**Behavior:** Public facade symbol; see the linked contract and source annotations for details.
@@ -1317,7 +1319,7 @@ base_cli.option(...)
```
### `render_document`
-**Kind:** function
+**Kind:** function
**Signature:** `render_document(document: 'Mapping[str, Any]', *, requested_format: 'str | None', records_key: 'str | None' = None, columns: 'Sequence[tuple[str, str]] | None' = None, stream: 'TextIO | None' = None, formula_guard: 'bool' = True) -> 'str'`
**Behavior:** Render a structured report or leave terminal text to its existing renderer.
@@ -1333,7 +1335,7 @@ base_cli.render_document(...)
```
### `render_records`
-**Kind:** function
+**Kind:** function
**Signature:** `render_records(records: 'Iterable[Mapping[str, Any]]', *, requested_format: 'str | None', columns: 'Sequence[tuple[str, str]]', stream: 'TextIO | None' = None, footer: 'str | None' = None, minimum_widths: 'Sequence[int] | None' = None, terminal_width: 'int | None' = None, max_cell_width: 'int | None' = 80, rich: 'bool' = False, formula_guard: 'bool' = True) -> 'str'`
**Behavior:** Render records according to the shared public output contract.
@@ -1349,7 +1351,7 @@ base_cli.render_records(...)
```
### `register_record_schema`
-**Kind:** function
+**Kind:** function
**Signature:** `register_record_schema(record_type: 'str', fields: 'Mapping[str, FieldSpec]') -> 'None'`
**Behavior:** Register an application-specific record schema for the wire protocol.
@@ -1365,7 +1367,7 @@ base_cli.register_record_schema(...)
```
### `redact_json_value`
-**Kind:** function
+**Kind:** function
**Signature:** `redact_json_value(value: 'Any', *, _key: 'str | None' = None, _depth: 'int' = 0, _seen: 'set[int] | None' = None) -> 'Any'`
**Behavior:** Recursively redact JSON values with bounded depth and cycle handling.
@@ -1381,7 +1383,7 @@ base_cli.redact_json_value(...)
```
### `resolve_output_format`
-**Kind:** function
+**Kind:** function
**Signature:** `resolve_output_format(requested: 'str | None', *, stream: 'TextIO | None' = None) -> 'str'`
**Behavior:** Resolve a requested format, making text TTY-aware.
@@ -1397,7 +1399,7 @@ base_cli.resolve_output_format(...)
```
### `run_app`
-**Kind:** function
+**Kind:** function
**Signature:** `run_app(app: 'App | Callable[..., Any]', argv: 'list[str] | None' = None, *, reraise_unexpected: 'bool' = False) -> 'int'`
**Behavior:** Run an App, registered command, or attached Click tree and return its status.
@@ -1413,7 +1415,7 @@ base_cli.run_app(...)
```
### `success_envelope`
-**Kind:** function
+**Kind:** function
**Signature:** `success_envelope(*, run_id: 'str | None', details: 'Mapping[str, Any] | None' = None, message: 'str' = 'Success', code: 'str' = 'ok') -> 'dict[str, Any]'`
**Behavior:** Return the stable v1 machine-readable success envelope.
@@ -1429,7 +1431,7 @@ base_cli.success_envelope(...)
```
### `try_render_rich_table`
-**Kind:** function
+**Kind:** function
**Signature:** `try_render_rich_table(stream: 'TextIO', headers: 'Sequence[str]', rows: 'Sequence[Sequence[str]]', footer: 'str | None', *, terminal_width: 'int | None' = None) -> 'bool'`
**Behavior:** Render a human table with Rich when it is installed and healthy.
@@ -1447,7 +1449,7 @@ base_cli.try_render_rich_table(...)
## Modules
### `asyncio_adapter`
-**Kind:** module
+**Kind:** module
**Signature:** (value is not callable)
**Behavior:** Explicit asyncio support for commands that opt into an async boundary.
@@ -1462,7 +1464,7 @@ base_cli.asyncio_adapter
```
### `command_filters`
-**Kind:** module
+**Kind:** module
**Signature:** (value is not callable)
**Behavior:** Shared command-name filter normalization for CLI reports.
@@ -1477,7 +1479,7 @@ base_cli.command_filters
```
### `command_protocol`
-**Kind:** module
+**Kind:** module
**Signature:** (value is not callable)
**Behavior:** Public facade symbol; see the linked contract and source annotations for details.
@@ -1492,7 +1494,7 @@ base_cli.command_protocol
```
### `deprecations`
-**Kind:** module
+**Kind:** module
**Signature:** (value is not callable)
**Behavior:** Small, consistent deprecation primitives for public base-cli APIs.
@@ -1507,7 +1509,7 @@ base_cli.deprecations
```
### `experimental`
-**Kind:** module
+**Kind:** module
**Signature:** (value is not callable)
**Behavior:** Preview APIs under active development.
@@ -1522,7 +1524,7 @@ base_cli.experimental
```
### `json_contracts`
-**Kind:** module
+**Kind:** module
**Signature:** (value is not callable)
**Behavior:** Versioned JSON contracts for machine-facing CLI consumers.
@@ -1537,7 +1539,7 @@ base_cli.json_contracts
```
### `extensions`
-**Kind:** module
+**Kind:** module
**Signature:** (value is not callable)
**Behavior:** Lazy, deterministic discovery of optional Python entry-point extensions.
@@ -1552,7 +1554,7 @@ base_cli.extensions
```
### `history`
-**Kind:** module
+**Kind:** module
**Signature:** (value is not callable)
**Behavior:** Public facade symbol; see the linked contract and source annotations for details.
@@ -1567,7 +1569,7 @@ base_cli.history
```
### `integrations`
-**Kind:** module
+**Kind:** module
**Signature:** (value is not callable)
**Behavior:** Optional Rich and OpenTelemetry integrations.
@@ -1582,7 +1584,7 @@ base_cli.integrations
```
### `testing`
-**Kind:** module
+**Kind:** module
**Signature:** (value is not callable)
**Behavior:** Public facade symbol; see the linked contract and source annotations for details.
@@ -1599,7 +1601,7 @@ base_cli.testing
## Constants and typing helpers
### `__version__`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Installed base-cli distribution version exposed by the public facade.
@@ -1614,7 +1616,7 @@ value = base_cli.__version__
```
### `BOOLEAN`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Schema field descriptor for a required boolean command-record value.
@@ -1629,7 +1631,7 @@ value = base_cli.BOOLEAN
```
### `ApplicationStateT`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Consumer-owned application-state type parameter carried by Context.
@@ -1644,7 +1646,7 @@ value = base_cli.ApplicationStateT
```
### `CommandFilterNormalizer`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Callable type for canonicalizing command filter names.
@@ -1659,7 +1661,7 @@ value = base_cli.CommandFilterNormalizer
```
### `COMMAND_ENTRY_POINT_GROUP`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Entry-point group name used to discover command extensions.
@@ -1674,7 +1676,7 @@ value = base_cli.COMMAND_ENTRY_POINT_GROUP
```
### `ConfigT`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Consumer-owned configuration type parameter carried by Context and App.
@@ -1689,7 +1691,7 @@ value = base_cli.ConfigT
```
### `ENTRY_POINT_GROUPS`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Supported base-cli extension entry-point groups in discovery order.
@@ -1704,7 +1706,7 @@ value = base_cli.ENTRY_POINT_GROUPS
```
### `EXTENSION_API_VERSION`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Extension API version negotiated through entry-point metadata.
@@ -1719,7 +1721,7 @@ value = base_cli.EXTENSION_API_VERSION
```
### `DEFAULT_SCHEMA_REGISTRY`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Process-local registry containing built-in and consumer record schemas.
@@ -1734,7 +1736,7 @@ value = base_cli.DEFAULT_SCHEMA_REGISTRY
```
### `LIFECYCLE_META_KEY`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Metadata key used to mark lifecycle-owned Click parameters.
@@ -1749,7 +1751,7 @@ value = base_cli.LIFECYCLE_META_KEY
```
### `NULLABLE_STRING`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Schema field descriptor for a nullable string command-record value.
@@ -1764,7 +1766,7 @@ value = base_cli.NULLABLE_STRING
```
### `STRING`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Schema field descriptor for a required string command-record value.
@@ -1779,7 +1781,7 @@ value = base_cli.STRING
```
### `JSON_CONTRACT_VERSION`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Version number for the stable JSON output, error, and log contracts.
@@ -1794,7 +1796,7 @@ value = base_cli.JSON_CONTRACT_VERSION
```
### `JSON_ERROR_SCHEMA`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Schema identifier emitted by machine-readable error envelopes.
@@ -1809,7 +1811,7 @@ value = base_cli.JSON_ERROR_SCHEMA
```
### `JSON_LOG_SCHEMA`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Schema identifier emitted by structured JSON log records.
@@ -1824,7 +1826,7 @@ value = base_cli.JSON_LOG_SCHEMA
```
### `JSON_OUTPUT_SCHEMA`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Schema identifier emitted by successful JSON output envelopes.
@@ -1839,7 +1841,7 @@ value = base_cli.JSON_OUTPUT_SCHEMA
```
### `MAX_JSON_LOG_MESSAGE_LENGTH`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Maximum retained message length for one structured JSON log record.
@@ -1854,7 +1856,7 @@ value = base_cli.MAX_JSON_LOG_MESSAGE_LENGTH
```
### `NDJSON_SCHEMA`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Schema identifier emitted by versioned newline-delimited record output.
@@ -1869,7 +1871,7 @@ value = base_cli.NDJSON_SCHEMA
```
### `NDJSON_SCHEMA_VERSION`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Version number for the newline-delimited record contract.
@@ -1884,7 +1886,7 @@ value = base_cli.NDJSON_SCHEMA_VERSION
```
### `PUBLIC_OUTPUT_FORMATS`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Supported public output format names and their resolution order.
@@ -1899,7 +1901,7 @@ value = base_cli.PUBLIC_OUTPUT_FORMATS
```
### `StructuredRecord`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Mapping shape accepted by the versioned command-record serializers.
@@ -1914,7 +1916,7 @@ value = base_cli.StructuredRecord
```
### `RECORD_SCHEMAS`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Mutable registry mapping consumer record names to FieldSpec definitions.
@@ -1929,7 +1931,7 @@ value = base_cli.RECORD_SCHEMAS
```
### `PLUGIN_ENTRY_POINT_GROUP`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Entry-point group name used to discover optional plugin extensions.
@@ -1944,7 +1946,7 @@ value = base_cli.PLUGIN_ENTRY_POINT_GROUP
```
### `PROFILE_ENTRY_POINT_GROUP`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Entry-point group name used to discover profile extensions.
@@ -1959,7 +1961,7 @@ value = base_cli.PROFILE_ENTRY_POINT_GROUP
```
### `ServicesT`
-**Kind:** constant/type
+**Kind:** constant/type
**Signature:** (value is not callable)
**Behavior:** Consumer-owned service-container type parameter carried by Context.
diff --git a/docs/api-stability.md b/docs/api-stability.md
index 67bf451..e5c6744 100644
--- a/docs/api-stability.md
+++ b/docs/api-stability.md
@@ -14,6 +14,21 @@ the other modules listed in their module-level `__all__`) are public as well.
Names beginning with `_`, modules not documented here, and implementation
details are private and may change without notice.
+The reviewed facade is classified in [`api-manifest.json`](api-manifest.json).
+Every facade symbol belongs to exactly one of four tiers:
+
+- **core** — the small lifecycle surface recommended for new applications;
+- **integration** — optional parser, telemetry, and presentation adapters;
+- **advanced** — explicit configuration, contract, extension, and runtime APIs;
+- **provisional** — public for compatibility, but not a 1.0 default.
+
+New applications should begin with `import base_cli.core as cli`. The broad
+`base_cli` facade remains available during the 0.x compatibility window so
+existing consumers can migrate deliberately. The manifest and its validator
+prevent an export from appearing without a reviewed tier classification.
+Optional integration packages remain lazy: importing `base_cli.core` does not
+require Typer, Rich, or OpenTelemetry.
+
The following machine-facing contracts are public and versioned:
- command framing defaults to `COMMAND_PROTOCOL_V1`; record schemas are owned
diff --git a/docs/index.md b/docs/index.md
index 35ef02d..0dd2c18 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -64,6 +64,8 @@ application.
for dated CI results and the permissioned-adopter program.
- Read [API stability](api-stability.md) and the [migration guide](migrations.md)
before upgrading across a compatibility boundary.
+- Start new applications with the small `base_cli.core` surface and consult the
+ [API manifest](api-manifest.json) for advanced and integration tiers.
- Browse the [complete public API reference](api-reference.md) for exported
symbols, signatures, and usage shapes.
- Follow [consumer profiles](consumer-profiles.md) when your application owns
diff --git a/lib/python/base_cli/core.py b/lib/python/base_cli/core.py
new file mode 100644
index 0000000..34becdb
--- /dev/null
+++ b/lib/python/base_cli/core.py
@@ -0,0 +1,83 @@
+"""Small, stable starting surface for production CLI applications.
+
+The compatibility facade at :mod:`base_cli` remains broad during the 0.x
+series. New applications should prefer this module for the lifecycle core;
+integration and advanced APIs stay behind their explicit modules.
+"""
+
+from __future__ import annotations
+
+from . import __version__, testing
+from .app import (
+ App,
+ argument,
+ attach,
+ command,
+ option,
+ run_app,
+)
+from .context import (
+ ApplicationStateT,
+ ConfigT,
+ Context,
+ ServicesT,
+ get_current_context,
+)
+from .deprecations import (
+ BaseCliDeprecationWarning,
+ deprecated,
+)
+from .errors import (
+ ConfigurationError,
+)
+from .exit_codes import (
+ ExitCode,
+)
+from .lifecycle_options import (
+ LIFECYCLE_META_KEY,
+ LifecycleOption,
+ LifecycleOptions,
+ LifecycleValues,
+ get_lifecycle_values,
+)
+from .logging import (
+ configure_logger,
+ log_critical,
+ log_debug,
+ log_error,
+ log_info,
+ log_warning,
+)
+
+__all__ = [
+ "App",
+ "ApplicationStateT",
+ "BaseCliDeprecationWarning",
+ "ConfigT",
+ "ConfigurationError",
+ "Context",
+ "ExitCode",
+ "LIFECYCLE_META_KEY",
+ "LifecycleOption",
+ "LifecycleOptions",
+ "LifecycleValues",
+ "ServicesT",
+ "__version__",
+ "argument",
+ "attach",
+ "command",
+ "configure_logger",
+ "deprecated",
+ "get_current_context",
+ "get_lifecycle_values",
+ "log_critical",
+ "log_debug",
+ "log_error",
+ "log_info",
+ "log_warning",
+ "option",
+ "run_app",
+ "testing",
+]
+
+# generated by scripts/generate_core_surface.py; do not edit
diff --git a/scripts/api_manifest.py b/scripts/api_manifest.py
new file mode 100644
index 0000000..ec52683
--- /dev/null
+++ b/scripts/api_manifest.py
@@ -0,0 +1,21 @@
+"""Shared loading helpers for the reviewed public API manifest."""
+
+from __future__ import annotations
+
+import json
+from pathlib import Path
+from typing import Any
+
+
+def load_manifest(path: Path) -> dict[str, Any]:
+ """Load one API manifest and turn malformed input into a short error."""
+
+ try:
+ value = json.loads(path.read_text(encoding="utf-8"))
+ except OSError as exc:
+ raise ValueError(f"cannot read {path}: {exc}") from exc
+ except json.JSONDecodeError as exc:
+ raise ValueError(f"invalid JSON in {path}: {exc.msg} at line {exc.lineno}, column {exc.colno}") from exc
+ if not isinstance(value, dict):
+ raise ValueError("API manifest must contain a JSON object")
+ return value
diff --git a/scripts/generate_api_reference.py b/scripts/generate_api_reference.py
index 381051c..9f21763 100644
--- a/scripts/generate_api_reference.py
+++ b/scripts/generate_api_reference.py
@@ -12,12 +12,16 @@
import inspect
import re
import sys
+import typing
from pathlib import Path
from typing import Any
+from api_manifest import load_manifest
+
ROOT = Path(__file__).resolve().parents[1]
PACKAGE_ROOT = ROOT / "lib" / "python"
REFERENCE = ROOT / "docs" / "api-reference.md"
+MANIFEST = ROOT / "docs" / "api-manifest.json"
MARKER = ""
HEADING_PATTERN = re.compile(r"^### `([^`]+)`$", re.MULTILINE)
@@ -59,6 +63,11 @@ def _load_package() -> Any:
def _kind(value: Any) -> str:
+ # Generic aliases changed from non-classes to class-like objects across
+ # supported Python versions. Treat typing aliases as data first so the
+ # generated reference is byte-identical on Python 3.10 through 3.14.
+ if typing.get_origin(value) is not None:
+ return "constant/type"
if inspect.ismodule(value):
return "module"
if inspect.isclass(value):
@@ -72,6 +81,8 @@ def _kind(value: Any) -> str:
def _signature(name: str, value: Any) -> str:
try:
+ if typing.get_origin(value) is not None:
+ return "(value is not callable)"
if inspect.isclass(value) or inspect.isfunction(value) or inspect.ismethod(value):
signature = str(inspect.signature(value))
# Default callback reprs contain process-specific memory addresses.
@@ -110,7 +121,7 @@ def render(base_cli: Any) -> str:
"\n".join(
(
f"### `{name}`",
- f"**Kind:** {kind} ",
+ f"**Kind:** {kind}
",
f"**Signature:** {_signature(name, value)}",
"",
f"**Behavior:** {_description(name, value)}",
@@ -128,8 +139,10 @@ def render(base_cli: Any) -> str:
lines = [
"# Public API reference",
"",
- "This page is generated from `base_cli.__all__`. It is the complete stable",
- "facade: private modules and names are deliberately excluded. Each entry",
+ "This page is generated from `base_cli.__all__` and checked against",
+ "`api-manifest.json`. It is the compatibility facade: private modules and",
+ "names are deliberately excluded. New applications should start with the",
+ "smaller `base_cli.core` surface described in `api-stability.md`. Each entry",
"includes the runtime signature when Python can inspect one, the public",
"docstring summary, compatibility guidance, and a minimal usage shape.",
"For behavior that is shared by several symbols, follow the linked",
@@ -160,12 +173,22 @@ def main() -> None:
base_cli = _load_package()
rendered = render(base_cli)
if check:
+ manifest = load_manifest(MANIFEST)
current = REFERENCE.read_text(encoding="utf-8") if REFERENCE.exists() else ""
if current != rendered:
print(f"{REFERENCE.relative_to(ROOT)} is stale; run scripts/generate_api_reference.py", file=sys.stderr)
raise SystemExit(1)
documented = set(HEADING_PATTERN.findall(current))
exported = set(base_cli.__all__)
+ tiers = manifest.get("tiers")
+ manifest_exports = (
+ {name for names in tiers.values() if isinstance(names, list) for name in names if isinstance(name, str)}
+ if isinstance(tiers, dict)
+ else set()
+ )
+ if manifest_exports != exported:
+ print("generated API manifest does not match base_cli.__all__", file=sys.stderr)
+ raise SystemExit(1)
if documented != exported:
print("generated API headings do not match base_cli.__all__", file=sys.stderr)
raise SystemExit(1)
diff --git a/scripts/generate_core_surface.py b/scripts/generate_core_surface.py
new file mode 100644
index 0000000..8f86410
--- /dev/null
+++ b/scripts/generate_core_surface.py
@@ -0,0 +1,127 @@
+#!/usr/bin/env python3
+"""Generate the small ``base_cli.core`` facade from the API manifest."""
+
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from typing import Any
+
+from api_manifest import load_manifest
+
+ROOT = Path(__file__).resolve().parents[1]
+MANIFEST = ROOT / "docs" / "api-manifest.json"
+CORE = ROOT / "lib" / "python" / "base_cli" / "core.py"
+MARKER = "# generated by scripts/generate_core_surface.py; do not edit"
+
+# These are the package modules that own the stable core names. The manifest
+# remains the source of truth for which names belong to the core tier; this
+# mapping only records how Python resolves those names for the generated file.
+CORE_SOURCE_MODULES = {
+ "App": ".app",
+ "argument": ".app",
+ "attach": ".app",
+ "command": ".app",
+ "option": ".app",
+ "run_app": ".app",
+ "ApplicationStateT": ".context",
+ "ConfigT": ".context",
+ "Context": ".context",
+ "ServicesT": ".context",
+ "get_current_context": ".context",
+ "BaseCliDeprecationWarning": ".deprecations",
+ "deprecated": ".deprecations",
+ "ConfigurationError": ".errors",
+ "ExitCode": ".exit_codes",
+ "LIFECYCLE_META_KEY": ".lifecycle_options",
+ "LifecycleOption": ".lifecycle_options",
+ "LifecycleOptions": ".lifecycle_options",
+ "LifecycleValues": ".lifecycle_options",
+ "get_lifecycle_values": ".lifecycle_options",
+ "configure_logger": ".logging",
+ "log_critical": ".logging",
+ "log_debug": ".logging",
+ "log_error": ".logging",
+ "log_info": ".logging",
+ "log_warning": ".logging",
+ "__version__": ".",
+ "testing": ".",
+}
+
+
+def _core_names(manifest: dict[str, Any]) -> list[str]:
+ tiers = manifest.get("tiers")
+ if not isinstance(tiers, dict) or not isinstance(tiers.get("core"), list):
+ raise ValueError("API manifest tiers.core must be a list")
+ names = tiers["core"]
+ if any(not isinstance(name, str) for name in names):
+ raise ValueError("API manifest tiers.core must contain only strings")
+ missing = sorted(set(names) - set(CORE_SOURCE_MODULES))
+ if missing:
+ raise ValueError(f"no core import mapping exists for: {missing}")
+ return names
+
+
+def render(manifest: dict[str, Any]) -> str:
+ names = _core_names(manifest)
+ grouped: dict[str, list[str]] = {}
+ for name in names:
+ grouped.setdefault(CORE_SOURCE_MODULES[name], []).append(name)
+
+ lines = [
+ '"""Small, stable starting surface for production CLI applications.',
+ "",
+ "The compatibility facade at :mod:`base_cli` remains broad during the 0.x",
+ "series. New applications should prefer this module for the lifecycle core;",
+ "integration and advanced APIs stay behind their explicit modules.",
+ '"""',
+ "",
+ "from __future__ import annotations",
+ "",
+ ]
+ for module in (
+ ".",
+ ".app",
+ ".context",
+ ".deprecations",
+ ".errors",
+ ".exit_codes",
+ ".lifecycle_options",
+ ".logging",
+ ):
+ module_names = grouped.get(module)
+ if not module_names:
+ continue
+ prefix = "from . import" if module == "." else f"from {module} import"
+ if module == ".":
+ lines.append(f"{prefix} {', '.join(module_names)}")
+ else:
+ lines.append(f"{prefix} (")
+ lines.extend(f" {name}," for name in module_names)
+ lines.append(")")
+ lines.extend(["", "__all__ = ["])
+ lines.extend(f' "{name}",' for name in names)
+ lines.extend(["]", "", MARKER, ""])
+ return "\n".join(lines)
+
+
+def main() -> None:
+ check = "--check" in sys.argv[1:]
+ try:
+ rendered = render(load_manifest(MANIFEST))
+ except ValueError as exc:
+ print(f"core surface generation failed: {exc}", file=sys.stderr)
+ raise SystemExit(1) from exc
+ if check:
+ current = CORE.read_text(encoding="utf-8") if CORE.exists() else ""
+ if current != rendered:
+ print(f"{CORE.relative_to(ROOT)} is stale; run scripts/generate_core_surface.py", file=sys.stderr)
+ raise SystemExit(1)
+ print(f"Validated {CORE.relative_to(ROOT)}.")
+ return
+ CORE.write_text(rendered, encoding="utf-8")
+ print(f"Generated {CORE.relative_to(ROOT)}.")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/validate_api_manifest.py b/scripts/validate_api_manifest.py
new file mode 100644
index 0000000..bf40ebb
--- /dev/null
+++ b/scripts/validate_api_manifest.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+"""Validate the reviewed public API tier manifest against the package facade."""
+
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+
+from api_manifest import load_manifest
+from generate_api_reference import _load_package
+
+ROOT = Path(__file__).resolve().parents[1]
+MANIFEST = ROOT / "docs" / "api-manifest.json"
+PACKAGE_ROOT = ROOT / "lib" / "python"
+
+
+def _validate() -> None:
+ base_cli = _load_package()
+ import base_cli.core as core
+
+ manifest = load_manifest(MANIFEST)
+ if manifest.get("schema_version") != 1:
+ raise ValueError("unsupported API manifest schema version")
+ if manifest.get("facade") != "base_cli":
+ raise ValueError("API manifest facade must be base_cli")
+
+ tiers = manifest.get("tiers")
+ if not isinstance(tiers, dict):
+ raise ValueError("API manifest tiers must be an object")
+ expected_tiers = {"core", "integration", "advanced", "provisional"}
+ if set(tiers) != expected_tiers:
+ raise ValueError(f"API manifest tiers must be exactly {sorted(expected_tiers)}")
+
+ tier_names: list[str] = []
+ for tier in sorted(expected_tiers):
+ names = tiers[tier]
+ if not isinstance(names, list) or any(not isinstance(name, str) for name in names):
+ raise ValueError(f"API manifest tier {tier!r} must be a list of strings")
+ if names != sorted(names):
+ raise ValueError(f"API manifest tier {tier!r} must be sorted")
+ tier_names.extend(names)
+
+ duplicates = sorted(name for name in set(tier_names) if tier_names.count(name) > 1)
+ if duplicates:
+ raise ValueError(f"API manifest contains duplicate symbols: {duplicates}")
+
+ exported = set(base_cli.__all__)
+ manifest_exports = set(tier_names)
+ if manifest_exports != exported:
+ missing = sorted(exported - manifest_exports)
+ extra = sorted(manifest_exports - exported)
+ raise ValueError(f"API manifest mismatch; missing={missing}, extra={extra}")
+ if any(name.startswith("_") and name != "__version__" for name in manifest_exports):
+ raise ValueError("private names other than __version__ cannot be public API")
+ for name in manifest_exports:
+ if not hasattr(base_cli, name):
+ raise ValueError(f"facade export is not resolvable: {name}")
+
+ core_names = tiers["core"]
+ if set(core.__all__) != set(core_names):
+ raise ValueError("base_cli.core.__all__ must match the manifest core lifecycle symbols")
+ if any(not hasattr(core, name) for name in core.__all__):
+ raise ValueError("base_cli.core contains an unresolved export")
+ for name in core_names:
+ if getattr(core, name) is not getattr(base_cli, name):
+ raise ValueError(f"base_cli.core.{name} is not identical to base_cli.{name}")
+
+ print(f"Validated API manifest: {len(exported)} facade symbols, {len(core.__all__)} core symbols.")
+
+
+def main() -> None:
+ try:
+ _validate()
+ except (OSError, ValueError, TypeError) as exc:
+ print(f"API manifest validation failed: {exc}", file=sys.stderr)
+ raise SystemExit(1) from exc
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/full_validate.sh b/tests/full_validate.sh
index 9dec3d8..49a8dca 100755
--- a/tests/full_validate.sh
+++ b/tests/full_validate.sh
@@ -60,6 +60,8 @@ run_contracts() {
require_commands python
python scripts/validate_docs.py
python scripts/validate_changelog.py
+ python scripts/validate_api_manifest.py
+ python scripts/generate_core_surface.py --check
python scripts/validate_schemas.py
python scripts/validate_contract_fixtures.py
if command -v node >/dev/null 2>&1; then
diff --git a/tests/test_api_manifest.py b/tests/test_api_manifest.py
new file mode 100644
index 0000000..a6eec87
--- /dev/null
+++ b/tests/test_api_manifest.py
@@ -0,0 +1,50 @@
+from __future__ import annotations
+
+import json
+import os
+import subprocess
+import sys
+import unittest
+from pathlib import Path
+
+import base_cli
+import base_cli.core as core
+
+
+class ApiManifestTests(unittest.TestCase):
+ def test_manifest_partitions_the_facade(self) -> None:
+ manifest_path = Path(__file__).resolve().parents[1] / "docs" / "api-manifest.json"
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
+ tiers = manifest["tiers"]
+ names = [name for tier in tiers.values() for name in tier]
+
+ self.assertEqual(len(names), len(set(names)))
+ self.assertEqual(set(names), set(base_cli.__all__))
+ self.assertEqual(set(core.__all__), set(tiers["core"]))
+ self.assertTrue(all(hasattr(base_cli, name) for name in names))
+ self.assertTrue(all(getattr(core, name) is getattr(base_cli, name) for name in tiers["core"]))
+
+ def test_core_surface_has_no_optional_integration_imports(self) -> None:
+ package_root = Path(__file__).resolve().parents[1] / "lib" / "python"
+ environment = os.environ.copy()
+ environment["PYTHONPATH"] = str(package_root)
+ probe = subprocess.run(
+ [
+ sys.executable,
+ "-c",
+ "import sys; import base_cli.core; "
+ "assert all(name not in sys.modules for name in ('typer', 'rich', 'opentelemetry'))",
+ ],
+ check=False,
+ capture_output=True,
+ text=True,
+ env=environment,
+ )
+ self.assertEqual(probe.returncode, 0, probe.stderr)
+
+ self.assertIn("App", core.__all__)
+ self.assertIn("run_app", core.__all__)
+
+
+if __name__ == "__main__":
+ unittest.main()