Loading...
Loading...
Use this skill when editing or creating CLI output, logging, warnings, error messages, progress indicators, or diagnostic summaries in the APM codebase. Activate whenever code touches console helpers (_rich_success, _rich_warning, _rich_error, _rich_info, _rich_echo), DiagnosticCollector, STATUS_SYMBOLS, CommandLogger, or any user-facing terminal output — even if the user doesn't mention "logging" or "UX" explicitly.
npx skill4agent add microsoft/apm cli-logging-ux# Fails — not actionable, user can't do anything
Sub-skill 'my-skill' from 'my-package' overwrites existing skill
# Passes — tells the user exactly what to do
Skipping my-skill — local file exists (not managed by APM). Use 'apm install --force' to overwrite.--verbose| Color | Helper | Meaning | When to use |
|---|---|---|---|
| Green | | Success / completed | Operation finished as expected |
| Yellow | | User action needed | Something requires user decision |
| Red | | Error / failure | Operation failed, cannot continue |
| Blue | | Informational | Status updates, progress, summaries |
| Dim | | Secondary detail | Verbose-mode details, grouping headers |
# Bad — warnings break the visual flow between status and summary
[checkmark] package-name
[warning] something happened
[warning] something else happened
[tree] 3 skill(s) integrated
# Good — clean tree, diagnostics at the end
[checkmark] package-name
[tree] 3 skill(s) integrated
── Diagnostics ──
[warning] 2 skills replaced by a different package (last installed wins)
Run with --verbose to see details_rich_success_rich_info└─_rich_error# Bad — inline warning repeated per file, clutters output
for file in files:
if collision:
_rich_warning(f"Skipping {file}...")
# Good — collect during loop, render grouped summary at the end
for file in files:
if collision:
diagnostics.skip(file, package=pkg_name)
# Later, after the loop:
if diagnostics.has_diagnostics:
diagnostics.render_summary()skip()overwrite()warn()error()apm_cli.utils.consoleprint()click.echo()STATUS_SYMBOLSfrom apm_cli.utils.console import (
_rich_success, _rich_error, _rich_warning, _rich_info, _rich_echo
)
_rich_success("Installed 3 APM dependencies") # green, bold
_rich_info(" └─ 2 prompts integrated → .github/prompts/") # blue
_rich_warning("Config drift detected — re-run apm install") # yellow
_rich_error("Failed to download package") # red
_rich_echo(" [pkg-name]", color="dim") # dim, for verbose detailsSTATUS_SYMBOLSsymbol=_rich_info("Starting operation...", symbol="gear") # renders as "[*] Starting operation..."[checkmark] package-name-1 # _rich_success — download/copy ok
[tree] 2 prompts integrated → .github/prompts/ # _rich_info — indented summary
[tree] 1 skill(s) integrated → .github/skills/
[checkmark] package-name-2
[tree] 1 instruction(s) integrated → .github/instructions/
── Diagnostics ── # Only if diagnostics.has_diagnostics
[warning] N files skipped — ... # Grouped by category
Run with --verbose to see details
Installed 2 APM dependencies # _rich_success — final summary# Bad — always copies and reports, even when content is identical
shutil.rmtree(target)
shutil.copytree(source, target)
_rich_info(f" └─ Skill updated")
# Good — skip when content matches
if SkillIntegrator._dirs_equal(source, target):
continue # Nothing changed, nothing to report┌─────────────────────────────────────────────────────┐
│ Command layer (install.py, pack.py, audit.py …) │
│ Calls: logger.success(), logger.tree_item(), … │
│ NEVER calls: _rich_*, click.echo(), print() │
├─────────────────────────────────────────────────────┤
│ Logger layer (command_logger.py) │
│ CommandLogger ← InstallLogger, future subclasses │
│ Owns: verbose gating, symbol choice, indentation │
│ Delegates to: _rich_* helpers │
├─────────────────────────────────────────────────────┤
│ Rendering layer (console.py) │
│ _rich_echo, _rich_success, _rich_error, … │
│ Owns: Rich/colorama fallback, color, STATUS_SYMBOLS │
└─────────────────────────────────────────────────────┘CommandLoggersrc/apm_cli/core/command_logger.py| Method | Purpose | When to use |
|---|---|---|
| Operation start | Beginning of a command |
| Status update with | Mid-operation phase changes |
| Green success | Operation completed |
| Yellow warning | User action needed |
| Red error | Operation failed |
| Dim text, verbose-only | Internal details (paths, hashes) |
| Green text, no symbol prefix | |
| Yellow text, verbose-only | Per-package diagnostic hints |
| | Dry-run explanation |
| Auth resolution step | Verbose auth tracing |
| Render DiagnosticCollector | End of command |
InstallLogger(CommandLogger)CommandLogger| Method | Purpose | Output |
|---|---|---|
| Start validation | |
| Package OK | |
| Package bad | |
| Start resolution | Context-aware install/update message |
| Package installed | |
| Download error | |
| Lockfile verbose line | |
| Auth source verbose | |
| Package type verbose | |
| Final summary | |
_rich_echo(f" Something: {value}", color="dim")if logger.verbose:_rich_echo_rich_*_rich_info()_rich_error()logger.progress()logger.error()_rich_*console.print()CommandLoggerCommandLogger@cli.command()
@click.option("--verbose", "-v", is_flag=True)
@click.option("--dry-run", is_flag=True)
def my_command(verbose, dry_run):
logger = CommandLogger("my-command", verbose=verbose, dry_run=dry_run)
logger.start("Starting operation...")
_do_work(logger=logger)
logger.render_summary()if verbose:# Bad — manual verbose check in command
if verbose:
_rich_echo(f" Auth: {source}", color="dim")
# Good — logger handles the gate
logger.package_auth(source, token_type) # No-ops when not verbose
logger.verbose_detail(f" Path: {path}") # No-ops when not verboselogger.diagnostics# During operation — collect
diagnostics.skip(file, package=pkg_name) # Collision
diagnostics.overwrite(file, package=pkg_name) # Cross-package replacement
diagnostics.error(msg, package=pkg_name) # Failure
diagnostics.auth(msg, package=pkg_name) # Auth issue
# Query during operation (e.g., for inline verbose hints)
count = diagnostics.count_for_package(pkg_name, category="collision")
if count > 0:
logger.package_inline_warning(f" [!] {count} files skipped")
# After operation — render grouped summary
logger.render_summary() # Delegates to diagnostics.render_summary() [+] package-name #v1.0 @b0cbd3df # download_complete
Auth: git-credential-fill (oauth) # package_auth (verbose)
Package type: Skill (SKILL.md detected) # package_type_info (verbose)
└─ 3 skill(s) integrated -> .github/skills/ # tree_item
└─ 1 prompt integrated -> .github/prompts/ # tree_item
[!] 2 files skipped (local files exist) # package_inline_warning (verbose)
[+] another-package (cached) # download_complete
── Diagnostics ── # render_summary
[!] 2 files skipped -- local files exist # Grouped by category
Use 'apm install --force' to overwrite
[*] Installed 2 APM dependencies. # install_summary[+]└─CommandLoggerInstallLoggerCommandLoggerdiagnostics=console.pyCommandLogger_rich_info--verbose_rich_warningDiagnosticCollectordiagnosticsdiagnostics=diagnosticsSTATUS_SYMBOLSSTATUS_SYMBOLSsymbol=└─_rich_*logger.start()logger.progress()logger.tree_item()_rich_*_rich_echoif verbose:logger.verbose_detail()logger.package_auth()if dry_run:logger.should_executelogger.dry_run_notice()f" Auth: {source}"logger.package_auth(source)AuthResolverlogger.progress()progress()[i]└─logger.tree_item()