agent-cli/SKILL.md
Add agent-friendly --json NDJSON output to Python CLI scripts, or scaffold a complete cli_utils package for a project. Use this skill when the user wants to make scripts machine-readable for AI agents, add --json flags, convert print statements to structured JSON, build a CLI helper library, create an open-source CLI-for-agents package, add structured logging, or make CLI output machine-readable. Also use when the user mentions NDJSON, structured CLI output, agent-friendly CLI, non-interactive scripts, or JSON I/O for automation.
npx skillsauth add glebis/claude-skills agent-cliInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Convert Python CLI scripts from human-only output to agent-consumable NDJSON, or scaffold a complete cli_utils package ready for open-source distribution.
When the user points at a script and says "make this agent-friendly" or "add --json":
cli_utils.py if the project doesn't have oneWhen the user says "create a cli_utils package" or wants an open-source library:
json_log, json_error, die, log, add_json_flag, enable_json, is_jsonThe fundamental pattern: every script gets a --json flag. When active, all stdout becomes newline-delimited JSON (NDJSON). Each line is a self-contained JSON object with a standard envelope.
Every JSON line has at minimum:
{"event": "ready", "ts": "2026-04-30T14:00:00+00:00", "pid": 1234, "port": 8765}
event — what happened (snake_case string)ts — ISO 8601 UTC timestamp--json opt-in over default: preserves human DX, doesn't break existing scripts or habitsdie() over repeated if/else: the pattern if is_json(): json_error(); sys.exit(1) else: print(); sys.exit() appears constantly — die() collapses it to one lineWhen generating cli_utils.py, produce exactly this (adapt only if the project has specific needs):
"""Shared helpers for JSON CLI output."""
import json
import os
import sys
from datetime import datetime, timezone
_json_mode = False
def enable_json():
global _json_mode
_json_mode = True
def is_json():
return _json_mode
def json_log(event: str, **kwargs):
"""Emit one NDJSON line to stdout."""
obj = {"event": event, "ts": datetime.now(timezone.utc).isoformat(), **kwargs}
print(json.dumps(obj, default=str), flush=True)
def json_error(message: str, **kwargs):
"""Emit a structured error event."""
json_log("error", message=message, **kwargs)
def die(message: str, code: int = 1, **kwargs):
"""Print error and exit — JSON or human depending on mode."""
if _json_mode:
json_error(message, **kwargs)
else:
print(message, file=sys.stderr)
sys.exit(code)
def add_json_flag(parser):
"""Add --json flag to an argparse parser."""
parser.add_argument("--json", action="store_true",
help="NDJSON output for agent consumption")
def log(message: str, **json_kwargs):
"""Print human message normally, or emit JSON event if --json is active."""
if _json_mode:
json_log(json_kwargs.pop("event", "info"), message=message, **json_kwargs)
else:
print(message)
def json_ready(**kwargs):
"""Emit the readiness signal — only in JSON mode. Call early in daemon startup."""
if _json_mode:
json_log("ready", pid=os.getpid(), **kwargs)
Search the target script for all places that produce output or exit:
grep -n 'print(\|sys\.exit\|exit(\|input(\|os\.system.*say' TARGET.py
Categorize each hit:
log(message, event="descriptive_name")die(message)if is_json(): json_log("status", ...) else: print("\r...", end="", flush=True)if not is_json(): or add --no-interactive flagif not is_json():sys.exit("message") (writes to stderr)At the top of the script, after existing imports:
from cli_utils import add_json_flag, enable_json, is_json, json_log, log, die
In the if __name__ == "__main__" block, add to argparse:
add_json_flag(parser)
args = parser.parse_args()
if args.json:
enable_json()
Apply the categorization from Step 1. Key patterns:
Simple informational:
# Before
print(f"Connected to {device}")
# After
log(f"Connected to {device}", event="connected", device=device)
Error + exit:
# Before
print("Device not found")
sys.exit(1)
# After
die("Device not found")
Daemon readiness (first output after initialization):
# Before
print(f"Server running on port {port}")
# After — json_ready() only emits in JSON mode, so always call it + human fallback
json_ready(port=port)
log(f"Server running on port {port}", event="ready", port=port)
Status lines (\r overwrite):
# Before
print(f"\r HR {hr} RMSSD {rmssd:.1f}", end="", flush=True)
# After
if is_json():
json_log("status", hr=hr, rmssd=rmssd)
else:
print(f"\r HR {hr} RMSSD {rmssd:.1f}", end="", flush=True)
Human-only output (banners, usage examples):
if not is_json():
print("Usage: send {\"type\": \"join\", \"name\": \"Alice\"}")
python script.py --help — confirm --json flag appearspython script.py --json — confirm first line is valid JSONprint( calls — ensure each is guarded or intentionalUse snake_case, be descriptive, keep them grep-friendly:
| Category | Events |
|----------|--------|
| Lifecycle | ready, shutdown, connected, disconnected |
| Data | hr, status, metric, heartbeat |
| Errors | error, retry |
| Actions | recording_started, recording_stopped, preset_change |
| Progress | scanning, connecting, downloading, importing |
When the user wants a distributable package, scaffold this structure:
cli-utils-agent/
├── pyproject.toml
├── LICENSE # MIT by default, ask user
├── README.md
├── src/
│ └── cli_utils_agent/
│ ├── __init__.py # re-exports all public API
│ └── core.py # the implementation
├── tests/
│ ├── __init__.py
│ ├── test_json_log.py
│ ├── test_die.py
│ ├── test_log.py
│ └── test_add_json_flag.py
└── .github/
└── workflows/
└── test.yml # CI with pytest
__init__.py — re-export public API# src/cli_utils_agent/__init__.py
from .core import (
enable_json, is_json, json_log, json_error, die,
add_json_flag, log, json_ready,
)
__all__ = [
"enable_json", "is_json", "json_log", "json_error", "die",
"add_json_flag", "log", "json_ready",
]
Generate a README with: project name, one-line description, install instructions (pip install cli-utils-agent), quick usage example showing add_json_flag + enable_json + log(), API reference table listing all exports with one-line descriptions, and a link to the research background.
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "cli-utils-agent"
version = "0.1.0"
description = "Add agent-friendly --json NDJSON output to any Python CLI"
readme = "README.md"
license = "MIT"
requires-python = ">=3.10"
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Topic :: Software Development :: Libraries",
]
[project.urls]
Homepage = "https://github.com/USER/cli-utils-agent"
[tool.hatch.build.targets.wheel]
packages = ["src/cli_utils_agent"]
Use capsys for stdout capture, pytest.raises(SystemExit) for die(). Example:
# tests/test_json_log.py
import json
from cli_utils_agent import json_log, enable_json, is_json
def test_json_log_writes_ndjson(capsys):
json_log("ready", port=8765, pid=42)
line = capsys.readouterr().out.strip()
obj = json.loads(line)
assert obj["event"] == "ready"
assert obj["port"] == 8765
assert "ts" in obj
# tests/test_die.py
import json
import pytest
from cli_utils_agent import die, enable_json
from cli_utils_agent import core as _core
def test_die_human_mode(capsys):
_core._json_mode = False
with pytest.raises(SystemExit) as exc:
die("something broke")
assert exc.value.code == 1
assert "something broke" in capsys.readouterr().err
def test_die_json_mode(capsys):
_core._json_mode = True
try:
with pytest.raises(SystemExit):
die("something broke", code=10)
obj = json.loads(capsys.readouterr().out.strip())
assert obj["event"] == "error"
assert obj["message"] == "something broke"
finally:
_core._json_mode = False
Cover: json_log, json_error, die, log, add_json_flag, enable_json/is_json, json_ready.
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install -e ".[dev]"
- run: pytest -v
Add dev dependencies to pyproject.toml:
[project.optional-dependencies]
dev = ["pytest>=8.0"]
After converting a script or creating a package:
--help shows --json flag--json produces valid NDJSON (every line is parseable JSON)"event": "ready""event": "error" with non-zero exit codeprint() can fire when --json is activesys.exit("message") not print()--json is NOT passedpython -m build (if package mode)Read references/best-practices.md when you need:
--schema (section 2.5)development
This skill should be used when designing, running, validating, or auditing statistical experiments on personal or observational time-series data (health metrics, speech/text corpora, behavioral logs, diaries, n-of-1 self-tracking). It enforces pre-registration, exact permutation tests, FDR discipline, data-validation gates, adversarial code review, and cross-validation with external models. Triggers on "design an experiment", "test this hypothesis on my data", "is this correlation real", "audit these findings", "pre-register", "validate this dataset", or any n-of-1 / quantified-self analysis request.
development
Create Tufte-inspired data reports and infographic dashboards as standalone HTML files. Uses EB Garamond for text, Monaspace Argon for numbers, Chart.js for interactive charts, and inline SVG sparklines. Produces publication-quality reports with 2-column narrative+data layouts, status dashboards, scroll animations, and responsive mobile support. Use this skill whenever the user wants to create a data report, activity dashboard, infographic, personal analytics page, health tracker visualization, or any document that combines narrative text with interactive charts and tables. Also triggers for "make a report like Tufte", "create an infographic", "build a dashboard", "visualize my data", or requests for beautiful data-driven documents.
documentation
Cut a software release and maintain a tiered compatibility policy. Use when the user wants to release, ship a version, bump the version, tag a release, write a changelog, or update COMPATIBILITY. Config-driven via release.config.json; bumps version files, runs a readiness gate, updates COMPATIBILITY.md tiers and deprecations, tags (→ release workflow), and reports closed issues. Teaches the underlying standards as it runs.
development
Sync and manage bilingual (EN/RU) library content for agency-docs. Use when adding, updating, or reviewing library articles. Handles translation, sync checks, and Russian stylistic review.