Skip to content

Commit ab09c73

Browse files
committed
feat: implement --verbose help flag to toggle visibility of built-in CLI options and add roff man page generation support
1 parent 6122c93 commit ab09c73

8 files changed

Lines changed: 433 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ All notable changes to apcore-cli (Python SDK) will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.4.0] - 2026-03-29
9+
10+
### Added
11+
- **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.
12+
- **Universal man page generation**`build_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.
13+
- **Documentation URL support**`set_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.
14+
15+
### Changed
16+
- `build_module_command()` respects the global verbose help flag to control built-in option visibility.
17+
- `--sandbox` is now always hidden from help (not yet implemented). Only four built-in options (`--input`, `--yes`, `--large-input`, `--format`) toggle with `--verbose`.
18+
- Improved built-in option descriptions for clarity.
19+
20+
---
21+
822
## [0.3.1] - 2026-03-27
923

1024
### Added

README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@ apcore-cli [OPTIONS] COMMAND [ARGS]
204204
| `--log-level` | `WARNING` | Logging: `DEBUG`, `INFO`, `WARNING`, `ERROR` |
205205
| `--version` | | Show version and exit |
206206
| `--help` | | Show help and exit |
207+
| `--verbose` | | Show hidden built-in options in `--help` output |
208+
| `--man` | | Print man page to stdout (use with `--help`) |
207209

208210
### Built-in Commands
209211

@@ -216,15 +218,15 @@ apcore-cli [OPTIONS] COMMAND [ARGS]
216218

217219
### Module Execution Options
218220

219-
When executing a module (e.g. `apcore-cli math.add`), these built-in options are always available:
221+
When executing a module (e.g. `apcore-cli math.add`), these built-in options are available (hidden by default; pass `--help --verbose` to display them):
220222

221223
| Option | Description |
222224
|--------|-------------|
223225
| `--input -` | Read JSON input from STDIN |
224226
| `--yes` / `-y` | Bypass approval prompts |
225227
| `--large-input` | Allow STDIN input larger than 10MB |
226228
| `--format` | Output format: `json` or `table` |
227-
| `--sandbox` | Run module in subprocess sandbox |
229+
| `--sandbox` | Run module in subprocess sandbox *(not yet implemented)* |
228230

229231
Schema-generated flags (e.g. `--a`, `--b`) are added automatically from the module's `input_schema`.
230232

@@ -291,7 +293,8 @@ cli:
291293
- **Schema validation** -- inputs validated against JSON Schema before execution, with `$ref`/`allOf`/`anyOf`/`oneOf` resolution
292294
- **Security** -- API key auth (keyring + AES-256-GCM), append-only audit logging, subprocess sandboxing
293295
- **Shell completions** -- `apcore-cli completion bash|zsh|fish` generates completion scripts with dynamic module ID completion
294-
- **Man pages** -- `apcore-cli man <command>` generates roff-formatted man pages
296+
- **Man pages** -- `apcore-cli man <command>` generates per-command man pages; `--help --man` prints a full-program man page via `configure_man_help()`
297+
- **Documentation URL** -- `set_docs_url()` sets a base URL; per-command help shows `Docs: {url}/commands/{name}`, man page SEE ALSO links to the full docs site
295298
- **Audit logging** -- all executions logged to `~/.apcore-cli/audit.jsonl` with SHA-256 input hashing
296299

297300
## How It Works
@@ -316,6 +319,10 @@ apcore-cli (the adapter)
316319
|
317320
+-- ConfigResolver 4-tier config precedence
318321
+-- LazyModuleGroup Dynamic Click command generation
322+
+-- set_verbose_help Toggle built-in option visibility
323+
+-- set_docs_url Set base URL for online docs
324+
+-- build_program_man_page Full-program roff man page
325+
+-- configure_man_help Add --help --man support to any CLI
319326
+-- schema_parser JSON Schema -> Click options
320327
+-- ref_resolver $ref / allOf / anyOf / oneOf
321328
+-- approval TTY-aware HITL approval

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "apcore-cli"
7-
version = "0.3.1"
7+
version = "0.4.0"
88
description = "Terminal adapter for apcore — execute AI-Perceivable modules from the command line"
99
readme = "README.md"
1010
license = "Apache-2.0"

src/apcore_cli/__main__.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import click
1010

1111
from apcore_cli import __version__
12-
from apcore_cli.cli import GroupedModuleGroup, set_audit_logger
12+
from apcore_cli.cli import GroupedModuleGroup, set_audit_logger, set_verbose_help
1313
from apcore_cli.config import ConfigResolver
1414
from apcore_cli.discovery import register_discovery_commands
1515
from apcore_cli.security.audit import AuditLogger
@@ -50,6 +50,12 @@ def _extract_binding_path(argv: list[str] | None = None) -> str | None:
5050
return _extract_argv_option(argv, "--binding")
5151

5252

53+
def _has_verbose_flag(argv: list[str] | None = None) -> bool:
54+
"""Check if --verbose is present in argv (pre-parse, before Click)."""
55+
args = argv if argv is not None else sys.argv[1:]
56+
return "--verbose" in args
57+
58+
5359
def create_cli(
5460
extensions_dir: str | None = None,
5561
prog_name: str | None = None,
@@ -75,6 +81,11 @@ def create_cli(
7581
if prog_name is None:
7682
prog_name = os.path.basename(sys.argv[0]) or "apcore-cli"
7783

84+
# Pre-parse --verbose before Click runs so build_module_command knows
85+
# whether to hide built-in options.
86+
verbose = _has_verbose_flag()
87+
set_verbose_help(verbose)
88+
7889
# Resolve CLI log level (3-tier precedence, evaluated before Click runs):
7990
# APCORE_CLI_LOGGING_LEVEL (CLI-specific) > APCORE_LOGGING_LEVEL (global) > WARNING
8091
# The --log-level flag (parsed later) can further override at runtime.
@@ -115,7 +126,7 @@ def create_cli(
115126

116127
if ext_dir_missing:
117128
click.echo(
118-
f"Error: Extensions directory not found: '{ext_dir}'. " "Set APCORE_EXTENSIONS_ROOT or verify the path.",
129+
f"Error: Extensions directory not found: '{ext_dir}'. Set APCORE_EXTENSIONS_ROOT or verify the path.",
119130
err=True,
120131
)
121132
sys.exit(EXIT_CONFIG_NOT_FOUND)
@@ -212,13 +223,21 @@ def create_cli(
212223
type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR"], case_sensitive=False),
213224
help="Log verbosity. Overrides APCORE_CLI_LOGGING_LEVEL and APCORE_LOGGING_LEVEL env vars.",
214225
)
226+
@click.option(
227+
"--verbose",
228+
"verbose_help",
229+
is_flag=True,
230+
default=False,
231+
help="Show all options in help output (including built-in apcore options).",
232+
)
215233
@click.pass_context
216234
def cli(
217235
ctx: click.Context,
218236
extensions_dir_opt: str | None = None,
219237
commands_dir_opt: str | None = None,
220238
binding_opt: str | None = None,
221239
log_level: str | None = None,
240+
verbose_help: bool = False,
222241
) -> None:
223242
if log_level is not None:
224243
# basicConfig() is a no-op once handlers exist; set level on the root logger directly.
@@ -229,6 +248,7 @@ def cli(
229248
logging.getLogger("apcore").setLevel(apcore_level)
230249
ctx.ensure_object(dict)
231250
ctx.obj["extensions_dir"] = ext_dir
251+
ctx.obj["verbose_help"] = verbose_help
232252

233253
# Register discovery commands
234254
register_discovery_commands(cli, registry)

src/apcore_cli/cli.py

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,33 @@
3232
# Module-level audit logger, set during CLI init
3333
_audit_logger: AuditLogger | None = None
3434

35+
# Module-level verbose help flag, set during CLI init
36+
_verbose_help: bool = False
37+
38+
39+
def set_verbose_help(verbose: bool) -> None:
40+
"""Set the verbose help flag. When False, built-in options are hidden."""
41+
global _verbose_help
42+
_verbose_help = verbose
43+
44+
45+
# Module-level docs URL, set by downstream projects
46+
_docs_url: str | None = None
47+
48+
49+
def set_docs_url(url: str | None) -> None:
50+
"""Set the base URL for online documentation links in help and man pages.
51+
52+
Pass None to disable. Command-level help appends ``/commands/{name}``
53+
automatically.
54+
55+
Example::
56+
57+
set_docs_url("https://docs.apcore.dev/cli")
58+
"""
59+
global _docs_url
60+
_docs_url = url
61+
3562

3663
def set_audit_logger(audit_logger: AuditLogger | None) -> None:
3764
"""Set the global audit logger instance. Pass None to clear."""
@@ -452,55 +479,69 @@ def callback(**kwargs: Any) -> None:
452479
sys.exit(exit_code)
453480

454481
# Build the command with schema-generated options + built-in options
482+
_epilog_parts: list[str] = []
483+
if not _verbose_help:
484+
_epilog_parts.append("Use --verbose to show all options (including built-in apcore options).")
485+
if _docs_url:
486+
_epilog_parts.append(f"Docs: {_docs_url}/commands/{effective_cmd_name}")
487+
_epilog = "\n".join(_epilog_parts) if _epilog_parts else None
455488
cmd = click.Command(
456489
name=effective_cmd_name,
457490
help=cmd_help,
458491
callback=callback,
492+
epilog=_epilog,
459493
)
460494

461-
# Add built-in options
495+
# Add built-in options (hidden unless --verbose is passed with --help)
496+
_hide = not _verbose_help
462497
cmd.params.append(
463498
click.Option(
464499
["--input"],
465500
default=None,
466-
help="Read input from file or STDIN ('-').",
501+
help="Read JSON input from a file path, or use '-' to read from stdin pipe.",
502+
hidden=_hide,
467503
)
468504
)
469505
cmd.params.append(
470506
click.Option(
471507
["--yes", "-y"],
472508
is_flag=True,
473509
default=False,
474-
help="Bypass approval prompts.",
510+
help="Skip interactive approval prompts (for scripts and CI).",
511+
hidden=_hide,
475512
)
476513
)
477514
cmd.params.append(
478515
click.Option(
479516
["--large-input"],
480517
is_flag=True,
481518
default=False,
482-
help="Allow STDIN input larger than 10MB.",
519+
help="Allow stdin input larger than 10MB (default limit protects against accidental pipes).",
520+
hidden=_hide,
483521
)
484522
)
485523
cmd.params.append(
486524
click.Option(
487525
["--format"],
488526
type=click.Choice(["json", "table"]),
489527
default=None,
490-
help="Output format.",
528+
help="Set output format: 'json' for machine-readable, 'table' for human-readable.",
529+
hidden=_hide,
491530
)
492531
)
532+
# --sandbox is always hidden (not yet implemented)
493533
cmd.params.append(
494534
click.Option(
495535
["--sandbox"],
496536
is_flag=True,
497537
default=False,
498-
help="Run module in subprocess sandbox.",
538+
help="Run module in an isolated subprocess with restricted filesystem and env access.",
539+
hidden=True,
499540
)
500541
)
501542

502543
# Guard: schema property names must not collide with built-in option names.
503-
_reserved = {"input", "yes", "large_input", "format", "sandbox"}
544+
_reserved = {"input", "yes", "large_input", "format", "sandbox", "verbose"}
504545
for opt in schema_options:
505546
if opt.name in _reserved:
506547
click.echo(

0 commit comments

Comments
 (0)