Skip to content

Releases: aiperceivable/apcore-cli-python

Release 0.9.0

13 May 03:20

Choose a tag to compare

Added

  • tests/conformance/test_snake_case_kwargs.py — runs the cross-language Algorithm C-SNAKE fixture (apcore-cli/conformance/fixtures/snake-case-kwargs/cases.json) against build_module_command via click.testing.CliRunner. Five cases verify that schema property names with underscores (has_solution, sort_by, sort_order) survive the round trip from CLI parse to the input dict received by executor.call. No source change required — click natively maps --has-solution to has_solution; the Python SDK is the parity reference for the parallel TypeScript fix. Surfaced as part of the cross-SDK regression coverage gap audit.

Fixed (2026-05-13 — cross-SDK audit D10/D11/D1)

  • ConfigEncryptor LOGNAME key-derivation chain (D10-001 / D11-003) — PBKDF2 username fallback was USER → USERNAME → "unknown" (3-tier); now USER → LOGNAME → USERNAME → "unknown" (4-tier) matching the spec and Rust. On hosts where USER is unset but LOGNAME is set (cron, sudo -i, container init), ciphertext written by the Python SDK now round-trips correctly with the Rust SDK. src/apcore_cli/security/config_encryptor.py:96, 165.
  • ConfigEncryptor.store keyring write-failure not wrapped (D11-004) — raw keyring.set_password exceptions now caught and re-raised as ConfigDecryptionError, matching TypeScript and Rust. src/apcore_cli/security/config_encryptor.py:31.
  • ref_resolver only descended into properties (D11-001) — recursive schema walk now visits every dict-valued child (items, additionalProperties, patternProperties, if/then/else, not, contains, propertyNames), matching TypeScript and Rust. $ref under array schemas and conditional schemas is now resolved. src/apcore_cli/ref_resolver.py:142.
  • ref_resolver copy-on-write visited-set (D11-002) — now uses a single mutable set with remove-on-unwind, allowing diamond $ref patterns (two sibling schemas referencing the same $def) to resolve correctly. src/apcore_cli/ref_resolver.py:71.
  • AuditLogger._get_user uses real UID instead of effective UID (D11-010) — switched from os.getuid() to os.geteuid() so audit records reflect the privileges the process actually runs with under sudo / setuid binaries. Matches Rust (geteuid) and TypeScript (os.userInfo). src/apcore_cli/security/audit.py:77.
  • check_approval ignores APCORE_CLI_APPROVAL_TIMEOUT env var (D11-012) — CliApprovalHandler and the legacy check_approval() wrapper now honor the env var when no explicit timeout is passed (precedence: constructor arg > env var > 60 s default). Matches TypeScript. src/apcore_cli/approval.py.
  • exec --dry-run crashes with AttributeError when executor lacks validate (D11-013) — guarded with hasattr(executor, "validate"); falls back to synthetic {"valid": True} matching TypeScript. src/apcore_cli/discovery.py:378.
  • CliApprovalHandler.request_approval missing requires_approval=False short-circuit (D11-014) — now returns approved/not_required when the request explicitly carries requires_approval=False, matching Rust. src/apcore_cli/approval.py:66.
  • CLI brand string in auth error messages (D11-006) — remediation strings now say apcli config set auth.api_key (canonical FE-13 name) instead of apcore-cli config set auth.api_key. src/apcore_cli/security/auth.py.
  • reconvert_enum_values missing from public re-export (D1-W2) — added to __init__.py import block and __all__. Embedders can now from apcore_cli import reconvert_enum_values for parity with TypeScript and Rust.
  • Ref-resolver error hierarchy missing from public re-export (D1-W3) — CircularRefError, MaxDepthExceededError, UnresolvableRefError, RefResolverError added to __init__.py import block and __all__. Parity with TypeScript index.ts:82-84.
  • DEFAULT_BUILTIN_GROUP_NAME missing from public re-export (D1 re-audit) — added to __init__.py. Parity with Rust lib.rs:190.
  • Dead exit-code constants removed (D9-W1) — EXIT_CONFIG_ENV_PREFIX_CONFLICT and EXIT_CONFIG_ENV_MAP_CONFLICT (both = 78, zero callers) deleted from src/apcore_cli/exit_codes.py.
  • Unused pytest-asyncio dev dependency removed (D6) — the package was declared but never exercised; no async tests exist. Removed from [project.optional-dependencies].dev.

Fixed

  • CSV --format csv Python-repr bugcsv.DictWriter was called with {k: str(v) for k, v in row.items()} which emitted Python repr {'k': 'v'} (single quotes) for nested dict/list values. The output was not valid JSON and any downstream JSON parser would fail. Now delegates to apcore_toolkit.format_csv(rows) which emits canonical compact JSON. src/apcore_cli/output.py:149, 378.
  • CSV heterogeneous-keys data loss — header is now the union of keys across all rows (was first-row only via list(rows[0].keys())).
  • CSV line terminator — now \r\n per RFC 4180.
  • JSONL canonical form — now compact (no spaces between separators), matching the cross-SDK contract. Tests updated.

Changed

  • User-visible help/man/completion text no longer leaks the apcore framework name to end users of downstream CLIs built on apcore-cli. Affected strings: init group description (Scaffold new apcore modulesScaffold new modules, init_cmd.py:45), --extensions-dir option help (Path to apcore extensions directory.Path to extensions directory., factory.py:460), zsh/fish completion descriptions for exec (Execute an apcore moduleExecute a module, shell.py:130, 211), and man-page ENVIRONMENT section text (shell.py:299, 314, 319, 458) — drops apcore from the descriptive copy (Path to the apcore extensions directoryPath to the extensions directory, Global apcore logging verbosityGlobal logging verbosity, API key for authenticating with the apcore registryAPI key for authenticating with the registry). Logger names, source comments, module docstrings, and environment-variable identifiers (APCORE_*) are unchanged — only descriptive copy that appears in --help, shell completion, and man output. Cross-SDK parity with TypeScript 0.8.2 and Rust 0.8.1.

Changed (breaking CLI surface)

  • Global --verbose flag renamed to --all-options — The help-display flag is now --all-options; use apcore-cli module --help --all-options to reveal hidden built-in options. verbose is removed from the reserved schema property names set — module schemas may now freely define verbose: boolean for runtime output control. Internal API: set_verbose_help() renamed to set_all_options_help(); module-level global _verbose_help renamed to _all_options_help. Tracked in apcore-cli#21.

Changed (breaking dependency semantics)

  • apcore-toolkit promoted from optional extra to REQUIRED runtime dependency (>=0.7.0). The previous pip install 'apcore-cli[toolkit]' extras pattern is retained as a no-op for backward compat with install scripts, but the toolkit is now always installed alongside apcore-cli. All --format operations route through the toolkit's reference implementation for csv/jsonl/markdown/skill.

Why

See ADR-09 in apcore-cli/docs/tech-design.md for the byte-equivalent toolkit-delegated tier rationale.

Release 0.8.0

08 May 10:15

Choose a tag to compare

Removed

  • D9-001 — FE-13 §11.2 deprecation shims removed. The 13 hidden root-level
    shims (list, describe, exec, init, validate, health, usage,
    enable, disable, reload, config, completion, describe-pipeline)
    installed by _register_deprecation_shims and the __is_deprecation_shim__
    collision-handling path in extra_commands wiring have been deleted along
    with the _DEPRECATED_ROOT_COMMANDS table. Use the canonical
    apcli <command> paths instead. Calls like apcore-cli list now exit
    non-zero with Click's "No such command" message — the warning window
    documented as "removed in v0.8" is closed.

Deprecated

  • CliModuleNotFoundError alias — the symbol still resolves to
    ModuleNotFoundError (see D1-002 in Changed) but is scheduled for
    removal in v0.10.0. Update imports to
    from apcore_cli import ModuleNotFoundError.

Security

  • D10-001 — Sandbox per-stream output cap (sandbox.py:155). The previous
    implementation summed stdout + stderr against a single max_output_bytes
    budget — a runaway child writing only to stderr could starve the stdout
    budget and vice versa, and the diagnostic on overflow did not name the
    offending stream. Each stream now has an independent byte budget matching
    Rust and TypeScript; the overflow error names the stream that tripped the
    cap.
  • D11-W2 — Sandbox switched from subprocess.run to subprocess.Popen
    with threaded chunked reads
    (sandbox.py:155). capture_output=True
    buffered the entire child stdio into parent memory before the cap was
    checked, so a child producing GBs of output could OOM the parent before
    the limit was enforced. The new implementation streams stdout/stderr
    through reader threads with bounded buffers and kills the child as soon
    as either stream exceeds its cap. Memory consumption is now bounded by
    2 × max_output_bytes regardless of child output volume.
  • D11-003 — ConfigEncryptor v1 decryption honours
    APCORE_CLI_CONFIG_PASSPHRASE
    (config_encryptor.py:128). _aes_decrypt_v1
    hard-coded the host:user material, so v1 ciphertext encrypted by the Rust
    or TypeScript SDKs under a passphrase failed to decrypt on Python.
    Decryption now tries the passphrase-derived key first when the env var is
    set, falling back to host:user material — matching TypeScript
    aesDecryptV1. Cross-SDK config bundles are now portable.
  • D11-008 — AuditLogger._get_user fallback chain now includes LOGNAME
    (audit.py:66). The canonical chain per security.md (D11-W1) is
    getlogin → pwd.getpwuid → USER → LOGNAME → USERNAME → unknown. Python
    previously skipped LOGNAME, so audit-log user fields diverged from
    Rust/TS on hosts where only LOGNAME is set (some container runtimes,
    cron jobs).

Added

  • builtin_group_name="apcli" kwarg on create_cli — downstream branded CLIs that embed apcore-cli can now expose the built-in commands under a custom namespace (e.g. mycorp-cli admin health instead of mycorp-cli apcli health). ApcliGroup gains a name parameter (with property accessor) threaded through from_cli_config / from_yaml / _build. Default "apcli" is unchanged. Validated against /^[a-z][a-z0-9_-]*$/; invalid values exit 2. RESERVED_GROUP_NAMES collision check now consults GroupedModuleGroup._reserved_group_names (instance attribute, defaults to the static frozenset; factory replaces with the resolved name). Env var APCORE_CLI_APCLI and config keys apcli.* deliberately do NOT rename — they are apcore-cli-internal toggles, not user-facing. Cross-SDK parity with TypeScript createCli({ builtinGroupName }). New DEFAULT_BUILTIN_GROUP_NAME constant exported from apcore_cli.builtin_group.
  • _exit_on_system_error(e) helper in system_cmd.py — centralizes the canonical error→exit-code mapping for system-management subcommands, replacing 7 sites that previously used bare sys.exit(1) (audit D11-B-002, see Fixed).
  • 5 new tests in tests/test_builtin_group.pyTestBuiltinGroupRename class covers default name, custom name via both factories, validation of valid/invalid name shapes (5 valid + 6 invalid forms each).
  • D1-001 — 13 register_*_command factories + configure_man_help
    re-exported from apcore_cli package root
    . Embedders that compose
    their own root command tree no longer need to reach into private
    submodules (apcore_cli.commands.list_cmd, etc.). All TS/Rust
    register_* counterparts now have a Python public-API equivalent.
  • D1-003 — apcore_cli.exit_codes module with 24 EXIT_* integer
    constants, an EXIT_CODES mapping dict, and an exit_code_for_error()
    helper. Mirrors TS errors.ts EXIT_CODES + exitCodeForError and
    Rust src/lib.rs EXIT_* constants. Embedders can now map exceptions
    to documented exit codes without re-implementing the table.
  • D1-007 — format_module_list, format_module_detail,
    resolve_format re-exported from package root
    . The
    output-formatter feature spec declares these as Contracts; previously
    only format_exec_result was public.
  • D1-W1 — APCLI_SUBCOMMAND_NAMES re-exported from apcore_cli.
    Matches Rust lib.rs and is now in __all__ for static-analysis
    tooling.
  • D1-W2 — ApcliConfig TypedDict added to the public surface,
    mirroring the TypeScript type alias and Rust struct so embedders have
    a static contract for the apcli.* config block.
  • D1-W3 — register_config_namespace() helper + module-level
    DEFAULTS constant
    in config.py. The package still registers the
    namespace at import time, but embedders can now invoke the helper
    explicitly (parity with apcore-cli-typescript).
  • D1-W5 — Core dispatcher embedder API re-exported from package
    root
    : build_module_command, collect_input, validate_module_id,
    set_audit_logger, set_verbose_help, set_docs_url. Embedders no
    longer have to import from apcore_cli.cli directly. Matches Rust
    lib.rs:186-190 and TS index.ts:18. New tests/test_public_api.py
    pins the surface against future drift.
  • D1-info-1 — typed ApcliGroupError exception
    (builtin_group.py:107). Cross-SDK parity with Rust ApcliGroupError;
    embedders previously had no stable error class to match on for
    built-in-group config validation. ApcliGroupError(ValueError)
    preserves backwards compat — existing except ValueError callers
    still catch it. The invalid-name regex check in __init__ now raises
    ApcliGroupError. Re-exported from apcore_cli.

Fixed

  • D11-B-006 — discovery.py:208 sort direction inverted. apcli list --sort calls|errors|latency now defaults to DESCENDING (highest call count first) per spec T-LST-04, matching Rust discovery.rs:209 and TypeScript discovery.ts:186. Previously the user's raw --reverse flag (default False) was passed directly to sort_modules_by_usage(..., reverse=...), producing ASCENDING output by default — the inverse of the spec. Fix passes reverse=not reverse for the data path AND adds a re-sort at the call site for the audit-log-empty fallback so id-fallback continues to default ASCENDING per spec.
  • D11-B-002 — system_cmd.py collapsed every error to exit 1. The 7 except Exception as e: sys.exit(1) sites bypassed Python's own _ERROR_CODE_MAP (canonical 44/46/47/77) — scripted operators could not distinguish "module not found" from "ACL denied" from generic failure. All 7 sites now route through the new _exit_on_system_error(e) helper which calls exit_code_for_error(e) from apcore_cli.exit_codes. The 4 audit-log entries previously hardcoding exit_code=1 now log the resolved code.
  • D11-NEW-005 — RESERVED_PROPERTY_NAMES no longer raises generic ValueError. schema_to_click_options previously raised ValueError when a schema property collided with a built-in CLI option — opaque to scripted callers and inconsistent with the neighbour flag-collision branch (which already exited 48). Now writes a user-facing Error: line to stderr and calls sys.exit(48) per spec, matching TS process.exit(EXIT_CODES.SCHEMA_CIRCULAR_REF) and Rust CliError::SchemaParserFailure → EXIT_SCHEMA_CIRCULAR_REF. Tests tightened from pytest.raises((ValueError, Exception)) to pytest.raises(SystemExit) with code == 48 assertion.
  • D9-NEW-002 — ref_resolver.py allOf required not deduplicated. _resolve_node's allOf branch concatenated parent required + each branch's required without dedup, producing duplicate entries in the merged schema's required array. JSON Schema validators ignore duplicates so observable validation behaviour was unchanged, but cross-SDK byte-comparison tooling (and the anyOf/oneOf paths, which already deduped) flagged the divergence. Fix: explicit seen-set dedup preserving first-seen order, matching TS [...new Set(...)] and Rust merge_allof.
  • D10-003 — build_module_command leaked RefResolverError
    tracebacks
    (cli.py:538). The resolve_refs catch clause re-raised
    unchanged, so callers saw a Python traceback instead of a clean
    documented exit code. Now translates CircularRefError /
    MaxDepthExceededError to sys.exit(48) and UnresolvableRefError
    (plus generic RefResolverError) to sys.exit(45), mirroring
    schema_parser.py:111 and the Rust/TS contracts.
  • D11-NEW-003 — ref_resolver max_depth over-counted plain nested
    properties
    (ref_resolver.py). _resolve_node previously
    incremented depth + 1 when recursing into nested properties
    values, so a schema with >32 levels of nested objects (no $ref at
    all) was rejected with MaxDepthExceededError. The spec wording is
    "Maximum $ref resolution recursion depth" — $ref hops along a
    single chain, not total stack depth. depth is now only incremented
    on $ref traversal, aligning with Rust ref_resolver.rs:297. Also
    adds 4 regression tests for anyOf/oneOf sibling-required
    preservation and anyOf overlap dedup.
  • D10-info-1 — APCORE_CLI_APCLI env var not trimmed on read
    (builtin_group.py:414). Spec invariant 2 requires the parser to be...
Read more

Release 0.7.0

26 Apr 04:07

Choose a tag to compare

Changed

  • Dependency bump: requires apcore >= 0.18.0 (was >= 0.17.1). Aligns with upstream apcore 0.18.0 and apcore-toolkit 0.4.2 breaking changes.
  • MAX_MODULE_ID_LENGTH 128 → 192: validate_module_id() and all references updated to the new 192-character limit introduced in apcore 0.18.0 (apcore.registry.registry.MAX_MODULE_ID_LENGTH).
  • describe-pipeline renders StrategyInfo: executor.describe_pipeline(strategy) now returns a StrategyInfo dataclass (name, step_count, step_names, description). strategy.py updated to use StrategyInfo fields; header line is Pipeline: {info.name} ({info.step_count} steps). Falls back gracefully to the legacy _resolve_strategy_name path when describe_pipeline is unavailable.
  • CI — spec-repo checkout: .github/workflows/ci.yml now checks out aiperceivable/apcore-cli into .apcore-cli-spec/ and exposes it to pytest via APCORE_CLI_SPEC_REPO. Mirrors the pattern established in apcore-python / apcore-cli-typescript.

Added

  • create_cli(app=...) parameter: create_cli() accepts an optional app: APCore unified client (introduced in apcore 0.18.0). app is mutually exclusive with registry/executor (raises ValueError). When app is provided, registry and executor are extracted from app.registry and app.executor. Filesystem discovery is skipped if app.registry already contains registered modules; otherwise normal discovery proceeds into app.registry.
  • Cross-language conformance test harness (tests/conformance/) consuming the shared apcli-visibility fixtures from the aiperceivable/apcore-cli spec repo (conformance/fixtures/apcli-visibility/). Behavioral assertions (apcli group visibility, registered subcommand set for include/exclude modes, always-registered exec) run today across all five canonical scenarios (standalone-default, embedded-default, cli-override, env-override, yaml-include). Byte-matching against expected_help.txt is marked xfail until Click's HelpFormatter is replaced with a canonical clap v4 / GNU-style emitter, tracked for parity with apcore-cli-typescript/src/canonical-help.ts.
  • APCORE_CLI_SPEC_REPO env var — overrides the spec-repo lookup path for conformance fixtures. Defaults to a sibling checkout (../apcore-cli/). Tests are skipped (not failed) when the spec repo is absent.
  • FE-12: Module Exposure Filtering — Declarative control over which discovered modules are exposed as CLI commands.
  • ExposureFilter class in exposure.py with is_exposed(module_id) and filter_modules(ids) methods.
  • Three modes: all (default), include (whitelist), exclude (blacklist) with glob-pattern matching.
  • ExposureFilter.from_config(dict) classmethod for loading from apcore.yaml expose section.
  • create_cli(expose=...) parameter accepting dict or ExposureFilter instance.
  • list --exposure {exposed,hidden,all} filter flag in discovery commands.
  • GroupedModuleGroup._build_group_map() integration: calls ExposureFilter.is_exposed() to filter command registration.
  • ConfigResolver gains expose.* config keys.
  • 4-tier config precedence: CliConfig.expose > --expose-mode CLI flag > env var > apcore.yaml.
  • Hidden modules remain invocable via exec <module_id>.
  • New file: exposure.py.

Release 0.6.0

06 Apr 12:59

Choose a tag to compare

Changed

  • Dependency bump: requires apcore >= 0.17.1 (was >= 0.15.1). Adds Execution Pipeline Strategy, Config Bus enhancements, Pipeline v2 declarative step metadata, minimal strategy preset.
  • Schema parser: Required schema properties now correctly enforced at CLI option level (was silently optional).
  • Approval gate: Fixed inverted logic in annotation type guard; check_approval() now accepts timeout parameter.

Added

  • FE-11: Usability Enhancements — 11 new capabilities:
  • --dry-run preflight mode via Executor.validate(). Standalone validate command.
  • System management commands: health, usage, enable, disable, reload, config get/config set. Graceful no-op when system modules unavailable.
  • Enhanced error output: structured JSON with ai_guidance, suggestion, retryable, user_fixable, details. TTY hides machine-only fields.
  • --trace pipeline visualization via call_with_trace().
  • CliApprovalHandler class implementing apcore ApprovalHandler protocol, wired to Executor.set_approval_handler(). --approval-timeout, --approval-token flags.
  • --stream JSONL output via Executor.stream().
  • Enhanced list command: --search, --status, --annotation, --sort, --reverse, --deprecated, --deps.
  • --strategy selection: standard, internal, testing, performance, minimal. describe-pipeline command.
  • Output format extensions: --format csv|yaml|jsonl, --fields dot-path field selection.
  • Multi-level grouping: cli.group_depth config key.
  • Custom command extension: create_cli(extra_commands=[...]) with collision detection.
  • New error code: CONFIG_ENV_MAP_CONFLICT.
  • New config keys: cli.approval_timeout (60), cli.strategy ("standard"), cli.group_depth (1).
  • New environment variables: APCORE_CLI_APPROVAL_TIMEOUT, APCORE_CLI_STRATEGY, APCORE_CLI_GROUP_DEPTH.
  • New files: system_cmd.py, strategy.py.

Release 0.5.0

01 Apr 03:40

Choose a tag to compare

Release version 0.5.0

See CHANGELOG.md for details.

Release 0.4.1

30 Mar 08:15

Choose a tag to compare

Fixed

  • prevent click parameter mismatch by setting expose_value=False for the --man option

Release 0.4.0

29 Mar 04:20

Choose a tag to compare

Added

  • Verbose help mode — Built-in apcore options (--input, --yes, --large-input, --format, --sandbox) are now hidden from --help output by default. Pass --help --verbose to display the full option list including built-in options.
  • Universal man page generationbuild_program_man_page() generates a complete roff man page covering all registered commands. configure_man_help() adds --help --man support to any Click CLI, enabling downstream projects to get man pages for free.
  • Documentation URL supportset_docs_url() sets a base URL for online docs. Per-command help shows Docs: {url}/commands/{name}, man page SEE ALSO includes Full documentation at {url}. No default — disabled when not set.

Changed

  • build_module_command() respects the global verbose help flag to control built-in option visibility.
  • --sandbox is now always hidden from help (not yet implemented). Only four built-in options (--input, --yes, --large-input, --format) toggle with --verbose.
  • Improved built-in option descriptions for clarity.

Release 0.3.1

27 Mar 10:22

Choose a tag to compare

Added

  • DisplayResolver integration__main__.py integrates DisplayResolver from apcore-toolkit (optional) when --binding option is provided; gracefully skipped when not installed.
  • init to BUILTIN_COMMANDSinit subcommand is now registered in the builtin commands set.
  • APCORE_AUTH_API_KEY to man page — environment variable documented in generated roff man page.
  • Grouped shell completion with _APCORE_GRP — bash/zsh/fish completion scripts now support two-level group/command completion via the _APCORE_GRP environment variable (shell.py).
  • Path traversal validation for --dir in init command — rejects paths containing .. segments to prevent directory escape (init_cmd.py).

Fixed

  • RegistryWriter API call — constructor now called without parameters; fixes TypeError introduced by upstream API change.

Changed

  • apcore dependency bumped to >=0.14.0.

Release 0.3.0

24 Mar 03:42

Choose a tag to compare

Added

  • Display overlay routing (§5.13) — LazyModuleGroup now reads metadata["display"]["cli"] for alias and description when building the command list and routing get_command(). Commands are exposed under their CLI alias instead of raw module_id.
  • _alias_map: built from metadata["display"]["cli"]["alias"] (with module_id fallback), enabling apcore-cli alias-name invocation.
  • _descriptor_cache: populated during alias map build to avoid double registry.get_definition() calls in get_command().
  • _alias_map_built flag only set on successful build, allowing retry after transient registry errors.
  • Display overlay in JSON outputformat_module_list(..., "json") now reads metadata["display"]["cli"] for id, description, and tags, consistent with the table output branch.

Changed

  • _ERROR_CODE_MAP.get(error_code, 1): guarded with isinstance(error_code, str) to prevent None-key lookup.
  • Runtime companion: apcore-toolkit >= 0.4.0 enables DisplayResolver and ConventionScanner (graceful fallback when not installed).

Tests

  • TestDisplayOverlayAliasRouting (6 tests): list_commands uses CLI alias, get_command by alias, cache hit path, module_id fallback, build_module_command alias and description.
  • test_format_list_json_uses_display_overlay: JSON output uses display overlay alias/description/tags.
  • test_format_list_json_falls_back_to_scanner_when_no_overlay: JSON output falls back to scanner values.

Added (Grouped Commands — FE-09)

  • GroupedModuleGroup(LazyModuleGroup) — organizes modules into nested click.Group subcommands based on namespace prefixes. Auto-groups by first . segment, with display.cli.group override from binding.yaml.
  • _resolve_group() — 3-tier group resolution: explicit display.cli.group > first . segment of CLI alias > top-level.
  • _build_group_map() — lazy, idempotent group map builder with builtin collision detection and shell-safe group name validation.
  • format_help() — collapsed root help with Commands, Modules, and Groups sections (with command counts).
  • _LazyGroup(click.Group) — nested group that lazily builds subcommands from module descriptors.
  • list --flat flag — opt-in flat display mode for list command; default is now grouped display.
  • format_grouped_module_list() — Rich table output grouped by namespace.
  • Updated shell completions — bash/zsh/fish completion scripts handle two-level group/command structure.

Changed (Grouped Commands)

  • create_cli() now uses GroupedModuleGroup instead of LazyModuleGroup.

Tests (Grouped Commands)

  • 48 new tests: TestResolveGroup (8+), TestBuildGroupMap (5+), TestGroupedModuleGroupRouting (7), TestLazyGroupInner (4), TestGroupedHelpDisplay (5), TestCreateCliGrouped (1), TestGroupedE2E (5), TestGroupedDiscovery (7+), TestGroupedCompletion (6).

Added (Convention Module Discovery — §5.14)

  • apcore-cli init module <id> — scaffolding command with --style (decorator, convention, binding) and --description options. Generates module templates in the appropriate directory.
  • --commands-dir CLI option — path to a convention commands directory. When set, ConventionScanner from apcore-toolkit scans for plain functions and registers them as modules.

Tests (Convention Module Discovery)

  • 6 new tests in tests/test_init_cmd.py covering all three styles and options.

Release 0.2.2

22 Mar 13:14

Choose a tag to compare

Changed

  • Rebrand: aipartnerup → aiperceivable