一键导入
madsci-cli
Working with the MADSci CLI (the `madsci` command). Use when adding, modifying, or debugging CLI commands, or when using the CLI to operate a MADSci lab.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Working with the MADSci CLI (the `madsci` command). Use when adding, modifying, or debugging CLI commands, or when using the CLI to operate a MADSci lab.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Maintain `docs/CHANGELOG.md` in the Keep a Changelog 1.1.0 format. Use when adding a CHANGELOG entry for a new feature, bug fix, deprecation, or removal; preparing a release (cutting an `[Unreleased]` section into a versioned section); reviewing a PR for missing or malformed CHANGELOG entries; or whenever the user mentions "changelog", "release notes", "what's new", or "Unreleased" section.
Audit MADSci release artifacts for completeness and correctness. Use when preparing a PR, finishing a plan, completing a feature branch, or before any merge to main/unstable. Checks CHANGELOG, docs, guides, example lab, notebooks, templates, and skills for staleness, broken imports, and deprecated patterns.
Implement code quality ratchets to prevent proliferation of deprecated patterns. Use when (1) migrating away from legacy code patterns, (2) enforcing gradual codebase improvements, (3) preventing copy-paste proliferation of deprecated practices, or (4) setting up pre-commit hooks to count and limit specific code patterns. A ratchet fails if pattern count exceeds OR falls below expected—ensuring patterns never increase and prompting updates when they decrease.
Treat type errors, test failures, lint warnings, and coverage gaps as authoritative feedback rather than obstacles. Use when running tests, type checkers, linters, or coverage tools; when tempted to add `# type: ignore`, `# noqa`, `as any`, `@ts-ignore`, `# pragma: no cover`, or to skip/quarantine a test; when deciding how strict to be in an existing codebase; or when designing code that is hard to type or test.
Working with MADSci manager services (Event, Experiment, Resource, Data, Workcell, Location, Lab). Use when creating, modifying, debugging, or configuring manager servers.
Working with MADSci node modules for laboratory instrument integration. Use when creating, modifying, debugging, or testing node modules that interface with scientific instruments.
| name | madsci-cli |
| description | Working with the MADSci CLI (the `madsci` command). Use when adding, modifying, or debugging CLI commands, or when using the CLI to operate a MADSci lab. |
The madsci CLI is built on Click with lazy command loading, Rich output formatting, and a Textual TUI. It provides 26 commands for managing MADSci labs.
Detailed material is in reference/. Read the relevant file when the task touches that area:
| File | Purpose |
|---|---|
src/madsci_client/madsci/client/cli/__init__.py | Root command, AliasedGroup, _LAZY_COMMANDS |
src/madsci_client/madsci/client/cli/commands/ | 26 command modules |
src/madsci_client/madsci/client/cli/utils/output.py | Rich console output helpers |
src/madsci_client/madsci/client/cli/tui/app.py | Textual TUI application |
src/madsci_client/madsci/client/cli/tui/styles/theme.tcss | TUI CSS theme |
src/madsci_common/madsci/common/bundled_templates/ | 33 template manifests + Jinja2 files |
madsci (AliasedGroup)
├── Global options: --lab-url, -v, -q, --no-color, --json, --version
├── Context: MadsciContext (lazy), Console, verbosity flags
└── Commands (lazy-loaded via _LAZY_COMMANDS):
├── version, doctor (doc), status (s), logs (l)
├── tui (ui), registry, migrate, new (n)
├── start, stop, init, validate (val)
├── run, completion, backup, add
├── commands (cmd), config (cfg)
└── [aliases resolve via AliasedGroup]
Commands are loaded on first use to minimize CLI startup time:
# In cli/__init__.py
_LAZY_COMMANDS = {
"version": ("madsci.client.cli.commands.version", "version"),
"doctor": ("madsci.client.cli.commands.doctor", "doctor"),
"status": ("madsci.client.cli.commands.status", "status"),
"logs": ("madsci.client.cli.commands.logs", "logs"),
"tui": ("madsci.client.cli.commands.tui", "tui"),
"registry": ("madsci.client.cli.commands.registry", "registry"),
"migrate": ("madsci.client.cli.commands.migrate", "migrate"),
"new": ("madsci.client.cli.commands.new", "new"),
"start": ("madsci.client.cli.commands.start", "start"),
"stop": ("madsci.client.cli.commands.stop", "stop"),
"init": ("madsci.client.cli.commands.init", "init"),
"validate": ("madsci.client.cli.commands.validate", "validate"),
"run": ("madsci.client.cli.commands.run", "run"),
"completion": ("madsci.client.cli.commands.completion", "completion"),
"backup": ("madsci.client.cli.commands.backup", "backup"),
"commands": ("madsci.client.cli.commands.commands", "commands"),
"config": ("madsci.client.cli.commands.config", "config"),
"workflow": ("madsci.client.cli.commands.workflow", "workflow"),
"resource": ("madsci.client.cli.commands.resource", "resource"),
"location": ("madsci.client.cli.commands.location", "location"),
"node": ("madsci.client.cli.commands.node", "node"),
"experiment": ("madsci.client.cli.commands.experiment", "experiment"),
"campaign": ("madsci.client.cli.commands.campaign", "campaign"),
"data": ("madsci.client.cli.commands.data", "data"),
"events": ("madsci.client.cli.commands.events", "events"),
"add": ("madsci.client.cli.commands.add", "add"),
}
class AliasedGroup(click.Group):
_aliases: ClassVar[dict[str, str]] = {
"n": "new",
"s": "status",
"l": "logs",
"doc": "doctor",
"val": "validate",
"ui": "tui",
"cmd": "commands",
"cfg": "config",
"wf": "workflow",
"res": "resource",
"loc": "location",
"nd": "node",
"exp": "experiment",
"camp": "campaign",
"dt": "data",
"ev": "events",
}
Resolves aliases first, then tries eager commands, then lazy-imports.
# src/madsci_client/madsci/client/cli/commands/my_command.py
import click
@click.command()
@click.pass_context
def my_command(ctx: click.Context):
"""One-line description shown in --help."""
# Lazy imports for heavy dependencies
from madsci.client.cli.utils.output import get_console, success
console = get_console(ctx)
success(console, "Command executed successfully")
_LAZY_COMMANDS# In cli/__init__.py, add to _LAZY_COMMANDS dict:
_LAZY_COMMANDS = {
...
"my_command": ("madsci.client.cli.commands.my_command", "my_command"),
}
# In AliasedGroup._aliases:
_aliases = {
...
"mc": "my_command",
}
Critical rules:
@click.pass_context to access the CLI context (console, verbosity, etc.)--json via ctx.obj["json"]from madsci.client.cli.utils.output import (
get_console, # Get Rich Console from click context
output_result, # Handles json/yaml/text output
success, # Green checkmark message
error, # Red X message (with optional details)
warning, # Yellow warning message
info, # Blue info message
print_panel, # Rich Panel with border
format_url, # Rich link format
)
# Usage pattern:
console = get_console(ctx)
success(console, "Operation completed")
error(console, "Failed to connect", details="Connection refused on port 8001")
# JSON-aware output:
if ctx.obj.get("json"):
output_result(console, data, format="json")
else:
output_result(console, data, format="text", title="Results")
madsci start # docker compose up (foreground)
madsci start -d # detached + health polling
madsci start --build # rebuild images first
madsci start --services event resource # specific services
madsci stop # docker compose down
madsci stop --volumes # remove volumes (data loss!)
madsci start --mode=local # All 7 managers in-process, in-memory backends
madsci start --mode=local -d # Background with PID tracking
madsci start manager event -d # Start Event Manager in background
madsci start node ./my_node.py -d # Start node in background
madsci stop manager event # Stop background Event Manager
madsci stop node my_node # Stop background node
.madsci/pids/{name}.pid (JSON: {pid, exe, name}).madsci/logs/{name}_TIMESTAMP.log-d with --wait)After detached start, polls /health on each service every 2s (timeout: 60s). Displays live Rich table with status.
9 main screens + 5 detail/modal screens built with Textual:
| Screen | Key | Description |
|---|---|---|
| Dashboard | d | Service overview, quick actions, recent events |
| Status | s | Detailed health with infrastructure checks |
| Logs | l | Log viewer with filtering and follow mode |
| Nodes | n | Node registry with status and actions |
| Workflows | w | Workflow history and details |
| Experiments | e | Experiment listing with search/filter and detail panels |
| Resources | i | Resource inventory with search/filter and detail panels |
| Data Browser | b | Data capture browsing and querying |
| Locations | o | Location management with resource attachments |
tui/styles/theme.tcssCtrl+PUse Click's CliRunner:
from click.testing import CliRunner
from madsci.client.cli import madsci
def test_version_command():
runner = CliRunner()
result = runner.invoke(madsci, ["version"])
assert result.exit_code == 0
assert "madsci" in result.output
def test_version_json():
runner = CliRunner()
result = runner.invoke(madsci, ["version", "--json"])
assert result.exit_code == 0
import json
data = json.loads(result.output)
assert "packages" in data
def test_command_alias():
runner = CliRunner()
result = runner.invoke(madsci, ["s"]) # alias for "status"
# Will fail to connect but should parse correctly
assert result.exit_code in (0, 1)
Smoke test pattern: Verify all 26 commands parse without import errors:
@pytest.mark.parametrize("cmd", ["version", "doctor", "status", ...])
def test_command_help(cmd):
runner = CliRunner()
result = runner.invoke(madsci, [cmd, "--help"])
assert result.exit_code == 0
_LazyMadsciContext: The context object is lazy; it only initializes MadsciContext when attributes are first accessed. Don't force early initialization.yarn for Node.js package managementstart searches for compose.yaml, compose.yml, docker-compose.yaml, docker-compose.yml in current and parent directoriesnew_ulid_str() for any IDs--json output modestart and stop are Click groups with manager/node subcommandscommands command patches Trogon's _apply_default_value for Click Sentinel.UNSET compatibility