All notable changes to apcore-cli (Python SDK) will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- Dependency bump: requires
apcore >= 0.18.0(was>= 0.17.1). Aligns with upstreamapcore 0.18.0andapcore-toolkit 0.4.2breaking changes. MAX_MODULE_ID_LENGTH128 → 192:validate_module_id()and all references updated to the new 192-character limit introduced inapcore 0.18.0(apcore.registry.registry.MAX_MODULE_ID_LENGTH).describe-pipelinerendersStrategyInfo:executor.describe_pipeline(strategy)now returns aStrategyInfodataclass (name,step_count,step_names,description).strategy.pyupdated to useStrategyInfofields; header line isPipeline: {info.name} ({info.step_count} steps). Falls back gracefully to the legacy_resolve_strategy_namepath whendescribe_pipelineis unavailable.
create_cli(app=...)parameter:create_cli()accepts an optionalapp: APCoreunified client (introduced inapcore 0.18.0).appis mutually exclusive withregistry/executor(raisesValueError). Whenappis provided,registryandexecutorare extracted fromapp.registryandapp.executor. Filesystem discovery is skipped ifapp.registryalready contains registered modules; otherwise normal discovery proceeds intoapp.registry.- FE-12: Module Exposure Filtering — Declarative control over which discovered modules are exposed as CLI commands.
ExposureFilterclass inexposure.pywithis_exposed(module_id)andfilter_modules(ids)methods.- Three modes:
all(default),include(whitelist),exclude(blacklist) with glob-pattern matching. ExposureFilter.from_config(dict)classmethod for loading fromapcore.yamlexposesection.create_cli(expose=...)parameter acceptingdictorExposureFilterinstance.list --exposure {exposed,hidden,all}filter flag in discovery commands.GroupedModuleGroup._build_group_map()integration: callsExposureFilter.is_exposed()to filter command registration.ConfigResolvergainsexpose.*config keys.- 4-tier config precedence:
CliConfig.expose>--expose-modeCLI flag > env var >apcore.yaml. - Hidden modules remain invocable via
exec <module_id>.
- New file:
exposure.py.
- Dependency bump: requires
apcore >= 0.17.1(was>= 0.15.1). Adds Execution Pipeline Strategy, Config Bus enhancements, Pipeline v2 declarative step metadata,minimalstrategy 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 acceptstimeoutparameter.
- FE-11: Usability Enhancements — 11 new capabilities:
--dry-runpreflight mode viaExecutor.validate(). Standalonevalidatecommand.- 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. --tracepipeline visualization viacall_with_trace().CliApprovalHandlerclass implementing apcoreApprovalHandlerprotocol, wired toExecutor.set_approval_handler().--approval-timeout,--approval-tokenflags.--streamJSONL output viaExecutor.stream().- Enhanced
listcommand:--search,--status,--annotation,--sort,--reverse,--deprecated,--deps. --strategyselection:standard,internal,testing,performance,minimal.describe-pipelinecommand.- Output format extensions:
--format csv|yaml|jsonl,--fieldsdot-path field selection. - Multi-level grouping:
cli.group_depthconfig 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.
- Pre-populated registry support —
create_cli()accepts optionalregistryandexecutorparameters. When a pre-populatedRegistryis provided, filesystem discovery is skipped entirely. This enables frameworks that register modules at runtime (e.g. apflow's bridge) to generate CLI commands from their existing registry without requiring an extensions directory. - Passing
registryalone auto-builds anExecutor; passingexecutorwithoutregistryraisesValueError.
- prevent click parameter mismatch by setting expose_value=False for the --man option
- Verbose help mode — Built-in apcore options (
--input,--yes,--large-input,--format,--sandbox) are now hidden from--helpoutput by default. Pass--help --verboseto display the full option list including built-in options. - Universal man page generation —
build_program_man_page()generates a complete roff man page covering all registered commands.configure_man_help()adds--help --mansupport to any Click CLI, enabling downstream projects to get man pages for free. - Documentation URL support —
set_docs_url()sets a base URL for online docs. Per-command help showsDocs: {url}/commands/{name}, man page SEE ALSO includesFull documentation at {url}. No default — disabled when not set.
build_module_command()respects the global verbose help flag to control built-in option visibility.--sandboxis 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.
- DisplayResolver integration —
__main__.pyintegratesDisplayResolverfromapcore-toolkit(optional) when--bindingoption is provided; gracefully skipped when not installed. inittoBUILTIN_COMMANDS—initsubcommand is now registered in the builtin commands set.APCORE_AUTH_API_KEYto 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_GRPenvironment variable (shell.py). - Path traversal validation for
--dirininitcommand — rejects paths containing..segments to prevent directory escape (init_cmd.py).
RegistryWriterAPI call — constructor now called without parameters; fixesTypeErrorintroduced by upstream API change.
apcoredependency bumped to>=0.14.0.
- Display overlay routing (§5.13) —
LazyModuleGroupnow readsmetadata["display"]["cli"]for alias and description when building the command list and routingget_command(). Commands are exposed under their CLI alias instead of raw module_id._alias_map: built frommetadata["display"]["cli"]["alias"](with module_id fallback), enablingapcore-cli alias-nameinvocation._descriptor_cache: populated during alias map build to avoid doubleregistry.get_definition()calls inget_command()._alias_map_builtflag only set on successful build, allowing retry after transient registry errors.
- Display overlay in JSON output —
format_module_list(..., "json")now readsmetadata["display"]["cli"]forid,description, andtags, consistent with the table output branch.
_ERROR_CODE_MAP.get(error_code, 1): guarded withisinstance(error_code, str)to preventNone-key lookup.- Runtime companion:
apcore-toolkit >= 0.4.0enablesDisplayResolverandConventionScanner(graceful fallback when not installed).
TestDisplayOverlayAliasRouting(6 tests):list_commandsuses CLI alias,get_commandby alias, cache hit path, module_id fallback,build_module_commandalias 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.
GroupedModuleGroup(LazyModuleGroup)— organizes modules into nestedclick.Groupsubcommands based on namespace prefixes. Auto-groups by first.segment, withdisplay.cli.groupoverride from binding.yaml._resolve_group()— 3-tier group resolution: explicitdisplay.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 --flatflag — opt-in flat display mode forlistcommand; 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.
create_cli()now usesGroupedModuleGroupinstead ofLazyModuleGroup.
- 48 new tests:
TestResolveGroup(8+),TestBuildGroupMap(5+),TestGroupedModuleGroupRouting(7),TestLazyGroupInner(4),TestGroupedHelpDisplay(5),TestCreateCliGrouped(1),TestGroupedE2E(5),TestGroupedDiscovery(7+),TestGroupedCompletion(6).
apcore-cli init module <id>— scaffolding command with--style(decorator, convention, binding) and--descriptionoptions. Generates module templates in the appropriate directory.--commands-dirCLI option — path to a convention commands directory. When set,ConventionScannerfromapcore-toolkitscans for plain functions and registers them as modules.
- 6 new tests in
tests/test_init_cmd.pycovering all three styles and options.
- Rebrand: aipartnerup → aiperceivable
- Help text truncation limit increased from 200 to 1000 characters (configurable via
cli.help_text_max_lengthconfig key) _extract_help: addedmax_length: int = 1000parameter (schema_parser.py)schema_to_click_options: addedmax_help_length: int = 1000parameter (schema_parser.py)build_module_command: addedhelp_text_max_length: int = 1000parameter, threaded through to schema parser (cli.py)LazyModuleGroup: constructor acceptshelp_text_max_length: int = 1000, passes tobuild_module_command(cli.py)create_cli: resolvescli.help_text_max_lengthfromConfigResolverand passes toLazyModuleGroup(__main__.py)format_exec_result: nested dict/list values in table mode now rendered withjson.dumpsinstead ofstr()(output.py)
cli.help_text_max_lengthconfig key (default: 1000) inConfigResolver.DEFAULTS(config.py)APCORE_CLI_HELP_TEXT_MAX_LENGTHenvironment variable support for configuring help text max lengthtest_help_truncation_default: tests default 1000-char truncationtest_help_no_truncation_within_limit: tests no truncation at 999 charstest_help_truncation_custom_max: tests custom max_length parameter- 263 tests (up from 261)
APCORE_CLI_LOGGING_LEVELenv var — CLI-specific log level that takes priority overAPCORE_LOGGING_LEVEL; 3-tier precedence:--log-levelflag >APCORE_CLI_LOGGING_LEVEL>APCORE_LOGGING_LEVEL>WARNING(__main__.py)test_cli_logging_level_takes_priority_over_global— verifiesAPCORE_CLI_LOGGING_LEVEL=DEBUGwins overAPCORE_LOGGING_LEVEL=ERRORtest_cli_logging_level_fallback_to_global— verifies fallback when CLI-specific var is unsettest_builtin_name_collision_exits_2— schema property namedformat(or other reserved names) causesbuild_module_commandto exit 2test_exec_result_table_format—--format tablerenders Rich Key/Value table to stdouttest_bash_completion_quotes_prog_name_in_directive— verifiesshlex.quote()applied tocomplete -Fdirective, not just embedded subshelltest_zsh_completion_quotes_prog_name_in_directives— verifiescompdefline uses quoted prog_nametest_fish_completion_quotes_prog_name_in_directives— verifiescomplete -clines use quoted prog_name- 17 new tests (244 → 261 total)
--log-levelaccepted choices:WARN→WARNING(__main__.py)schema_to_click_options: schema-derived options now always haverequired=False; required fields marked[required]in help text instead of Click enforcement — allows--input -STDIN to supply required values without Click rejecting first (schema_parser.py)format_exec_result: now routes throughresolve_format()and renders Rich table when--format tableis specified; previously ignored itsformatparameter (output.py)_generate_bash_completion,_generate_zsh_completion,_generate_fish_completion:shlex.quote()applied to ALL prog_name positions in generated scripts (complete directives, compdef, complete -c), not only embedded subshell commands (shell.py)check_approval: removed unusedctx: click.Contextparameter (approval.py)set_audit_logger: broadened type annotation fromAuditLoggertoAuditLogger | None(cli.py)collect_input: simplified redundant conditionif not raw or raw_size == 0:→if not raw:(cli.py)- Example
Inputmodels: all 7 modules updated withField(description=...)on every field so CLI--helpshows descriptive text for each flag
--input -STDIN blocked by Click required enforcement:schema_to_click_optionswas generatingrequired=TrueClick options; Click validated before the callback ran, rejecting STDIN-only invocations. Resolved by always usingrequired=Falseand delegating required validation tojsonschema.validate()after input collection. Fixes all 6TestRealStdinPipingfailures.--log-levelhad no effect:logging.basicConfig()is a no-op after the first call; subsequentcreate_cli()calls in tests retained the prior handler's level. Fixed by callinglogging.getLogger().setLevel()explicitly afterbasicConfig().test_log_level_flag_takes_effectfalse pass:--helpis an eager flag that exits before the group callback, so--log-level DEBUG --helpnever applied the log level. Test updated to usecompletion bashsubcommand instead.- Shell completion directives not shell-safe: prog names with spaces or special characters were unquoted in
complete -F,compdef, andcomplete -clines. Fixed by assigningquoted = shlex.quote(prog_name)and using it in all directive positions. - Audit
set_audit_logger(None)type error: type annotation rejectedNone; broadened toAuditLogger | None. - Test logger level leakage: tests modifying root logger level affected subsequent tests; fixed with
try/finallythat restores the original level.
AuditLogger._hash_input: now usessecrets.token_bytes(16)per-invocation salt before hashing, preventing cross-invocation input correlation via SHA-256 rainbow tablesbuild_module_command: added reserved-name collision guard — exits 2 if a schema property (input,yes,large_input,format,sandbox) conflicts with a built-in CLI option name_prompt_with_timeout(SIGALRM path): wrapped intry/finallyto guarantee signal handler restoration regardless of exit path
--sandboxflag for subprocess-isolated module execution (FE-05)ModuleExecutionErrorexception class for sandbox failures- Windows approval timeout support via
threading.Timer+ctypes(FE-03) - Approval timeout clamping to 1..3600 seconds range (FE-03)
- Tag format validation (
^[a-z][a-z0-9_-]*$) inlist --tag(FE-04) cli.auto_approveconfig key withFalsedefault (FE-07)- Extensions directory readability check with exit code 47 (FE-01)
- Missing required property warning in schema parser (FE-02)
- DEBUG log
"Loading extensions from {path}"before registry discovery (FE-01) TYPE_CHECKINGimports for proper type annotations (Registry,Executor,ModuleDescriptor,ConfigResolver,AuditLogger)_get_module_id()helper forcanonical_id/module_idresolutionAPCORE_AUTH_API_KEYandAPCORE_CLI_SANDBOXto README environment variables table--sandboxto README module execution options table- CHANGELOG.md
- Core Dispatcher (FE-01):
LazyModuleGroup,build_module_command,collect_input,validate_module_id - Schema Parser (FE-02):
schema_to_click_options,_map_type,_extract_help,reconvert_enum_values - Ref Resolver (FE-02):
resolve_refs,_resolve_nodewith$ref,allOf,anyOf,oneOfsupport - Config Resolver (FE-07):
ConfigResolverwith 4-tier precedence (CLI > Env > File > Default) - Approval Gate (FE-03):
check_approval,_prompt_with_timeoutwith TTY detection and Unix SIGALRM - Discovery (FE-04):
listanddescribecommands with tag filtering and TTY-adaptive output - Output Formatter (FE-08):
format_module_list,format_module_detail,format_exec_resultwith Rich rendering - Security Manager (FE-05):
AuthProvider,ConfigEncryptor(keyring + AES-256-GCM),AuditLogger(JSON Lines),Sandbox(subprocess isolation) - Shell Integration (FE-06): bash/zsh/fish completion generators, roff man page generator
- 8 example modules:
math.add,math.multiply,text.upper,text.reverse,text.wordcount,sysutil.info,sysutil.env,sysutil.disk - 244 tests (unit, integration, end-to-end)
- CI workflow with pytest and coverage
- Pre-commit hooks configuration