Skip to main content

Adding Command Families Safely

Part of the docs handbook: see docs index.

This guide explains how to add new command families and capability packs to OTerminus without weakening safety guarantees.

OTerminus is intentionally curated. It should not become a giant shell encyclopedia that tries to support every Unix command and every possible flag combination. The preferred path is to support high-value workflows with deterministic structured rendering.

Core concepts

What is a command family?

A command family is a curated base command entry in the registry (CommandSpec), such as ls, grep, cp, or rm.

Each family defines: - safety and policy metadata (risk_level, maturity_level) - validation shape (operands and allowed flags) - capability mapping (capability_id, label, description) - direct-command detection behavior

What is a capability pack?

A capability pack is a module-level tuple of command specs (for example filesystem, text, archive, process, system, network, macos, dangerous) that is merged into the global command registry.

Capability packs group commands by workflow intent (for example filesystem inspection vs mutation), not by “all flags from man pages.”

Design principles (read before adding anything)

  1. Curate, don’t mirror shells. Only add commands that support clear user workflows.
  2. Structured-first is the default. Prefer deterministic command-family renderers over free-form command execution.
  3. Experimental mode is a constrained fallback. It is not a shortcut for skipping structured design.
  4. Small allowlists beat broad compatibility. Keep flags/operands intentionally minimal.
  5. Safety metadata is mandatory. Every command must have explicit risk and maturity policy.
  6. Network access is explicit. Commands that contact external hosts must be marked network_touching=True and reviewed as crossing the local-first boundary.

Step-by-step: add a new command family

1) Choose capability placement and define metadata

  • Put the new command in the correct capability pack under src/oterminus/commands/.
  • Reuse an existing capability when it matches the workflow; introduce a new capability only when needed.
  • Ensure capability_id is non-empty and stable.

Recommended: start by copying the style of nearby command specs in the same pack.

2) Choose maturity level correctly

Set maturity_level intentionally:

  • structured: command participates in deterministic structured mode.
  • direct_only: command can be accepted only as direct user command, but no structured renderer exists yet.
  • experimental_only: command is allowed only through constrained experimental path (higher friction) when direct_supported=true; with direct_supported=false, it is planned/metadata-only and must not be advertised as normal executable support.
  • blocked: explicitly tracked but blocked from execution.

Use these rules: - Pick structured when command behavior can be represented with a stable schema and renderer. - Pick direct_only when direct invocation is needed now, but structured schema is not yet safe/clear. - Pick experimental_only only when there is a justified temporary gap and strong constraints remain. - Pick blocked for privileged/high-impact commands that should never execute in curated policy.

If uncertain, start stricter (experimental_only or blocked) and relax later with tests. When a planned/metadata-only capability graduates to executable support, update maturity_level, direct_supported if direct command input is supported, examples, prompt coverage, discovery/completion tests, generated references, and docs in the same PR.

3) Assign risk_level with justification

Use least privilege:

  • safe: read-only inspection or metadata queries.
  • write: local mutations that do not require elevation and have bounded blast radius.
  • dangerous: destructive, privileged, or broad-impact operations.

Document your reasoning in code review/PR notes. Risk should align with: - data-loss potential - privilege implications - breadth of target scope - reversibility

4) Define minimal allowed flags

In CommandSpec, explicitly model supported flags: - allowed_flags - flags_with_values - path_valued_flags - leading_flags* for commands like find

Guidelines: - Start with the smallest useful subset. - Do not bulk-copy man-page flags. - Add flags only when backed by workflow need + tests. - Reject unsupported flags by default.

direct_flag_policy should usually stay at its default, explicit. A broader direct-only policy is reserved for safe inspection commands with a documented need, trusted direct-origin validation, and focused tests proving structured rendering remains typed. Do not enable safe_inspection_passthrough for write, network, archive, project-health, or dangerous commands without a separate design review.

5) Handle dangerous flags explicitly

If specific flags increase blast radius (example recursive deletion), mark them in dangerous_flags.

Expected behavior: - validator may escalate risk/warnings when these flags appear - policy gating should still apply

Also model dangerous literals when needed (dangerous_target_literals) and forbidden operand prefixes (forbidden_operand_prefixes) for unsafe targets like URLs or broad system paths.

6) Define path operand behavior explicitly

If command accepts paths, set or validate path behavior deliberately:

  • Set path_operand_mode when non-default parsing is needed (CD, FIND).
  • Ensure value-taking flags that point to paths are listed in path_valued_flags.
  • Confirm allowed-roots policy checks cover both operands and path-valued flags.

Never assume path handling is implicit. Make it explicit in spec + tests.

7) Mark and constrain network-touching commands

Network diagnostics are useful, but they are not local-only operations. A read-only network command can still reveal IP address, DNS query, target host, or other network metadata.

When adding any command that contacts external hosts:

  • set network_touching=True in CommandSpec
  • keep the initial surface read-only and narrowly scoped
  • validate hosts and URLs conservatively; reject ambiguous, broad, or shell-expanded targets
  • do not allow POST, PUT, DELETE, or other remote-state-changing methods
  • do not allow arbitrary headers, bearer tokens, cookies, API keys, or other secret-bearing inputs
  • do not add network commands through experimental_only as a shortcut around structured design
  • add tests and eval cases for accepted safe requests and rejected unsafe requests
  • update user docs and reference docs so the network boundary is visible

The validator remains authoritative. Network metadata provides warning and discovery context; it does not bypass command-shape validation, policy checks, preview, confirmation, or audit behavior.

8) Add/extend structured support when maturity is structured

When a command is part of structured mode: - add argument schema validation in structured_commands.py - add deterministic rendering logic - ensure ambiguous or unsafe forms are rejected

A command marked structured should have an end-to-end deterministic path.

9) Add validator and direct-command tests

At minimum, add tests for:

  • registry metadata (capability_id, flags, risk/maturity)
  • validator acceptance for valid forms
  • validator rejection for invalid flags/shape
  • dangerous-flag behavior and risk escalation (if applicable)
  • allowed-roots path checks (if paths involved)
  • network-touching metadata and warnings (if the command contacts external hosts)
  • direct-command detection behavior (direct_supported, heuristics)

Prefer focused tests in: - tests/test_command_registry.py - tests/test_validator.py - tests/test_direct_commands.py - tests/test_structured_commands.py (for structured renderers)

10) Add eval fixtures

Update regression eval fixtures under evals/cases/.

Include representative cases for: - expected mode (structured vs experimental) - command family routing - expected risk level - acceptance/rejection behavior - rendered command + argv when deterministic

Choose the fixture file by capability (network_diagnostics.json, git_inspection.json, project_health.json, archive/filesystem/text/process/system files) or by cross-cutting behavior (direct_commands.json, ambiguity.json, unsafe_and_blocked.json). Add both “happy path” and “should fail” fixtures for new family behavior. For natural-language cases that would otherwise need Ollama, include a planner_proposal fixture. For ambiguity gates, omit planner_proposal and assert expected_ambiguity_detected. For unsupported structured shapes, use expected_planner_error_contains; for validator/policy blocks, assert expected_acceptance: false.

For network-touching families, include safe read-only diagnostics and rejected requests for mutating methods, unsafe headers/secrets, unsupported URL forms, shell operators, and unsupported broad targets. Evals must not require live network access, current filesystem contents, a real Git repo state, local project tooling, subprocess execution, or a running Ollama service. If the coverage set would become a command manual or exceed a focused 30-60 case expansion, split archive, network, project-health, or unsafe coverage into follow-up PRs.

11) Update autocomplete and docs

If your change introduces a new command/capability visible to users:

  • verify completion behavior still works for first-token suggestions and capability hints
  • keep planned/metadata-only capabilities out of executable autocomplete and planner context until maturity metadata is updated
  • add/adjust completion tests if needed
  • update README and contributor docs when behavior/policy changes

Documentation should explain workflow intent, not just command syntax. See the contributor workflow for the shared formatting, docs, test, and eval checklist.

Generated reference docs workflow

The capability map and command-family reference pages are generated from the command registry:

  • website/docs/reference/capability-map.md
  • website/docs/reference/command-families.md

When you add or change command specs in src/oterminus/commands/, refresh and validate the reference docs:

poetry run python scripts/generate_command_reference.py --write
poetry run python scripts/generate_command_reference.py --check
cd website
npm ci
npm run build
npm run typecheck

Do not edit command tables in those reference pages by hand; update registry specs and regenerate. Generated reference tables must show risk, maturity/status, direct support, examples, and notes so planned/experimental capabilities are not mistaken for fully supported workflows.

Acceptance checklist

Before merging, confirm all of the following:

  • Command has a capability_id.
  • Risk level is explicitly justified.
  • Maturity/status accurately reflects whether the command is normal executable, direct-only, experimental fallback, planned/metadata-only, or blocked.
  • Allowed flags are minimal and intentional.
  • Dangerous flags are marked when applicable.
  • Network-touching commands set network_touching=True.
  • Path handling is explicit if paths are accepted.
  • Structured renderer exists if command is part of structured mode.
  • Validator tests exist.
  • Direct-command tests exist where applicable.
  • Eval fixtures exist.
  • README/docs are updated.
  • Command does not require sudo or broad system mutation unless explicitly blocked or dangerous.
  • Network commands do not mutate remote state or accept secret-bearing headers.

Practical scope reminder

A good command-family addition makes OTerminus more deterministic, auditable, and safe.

If a proposed command would require huge flag coverage, fragile parsing, or privileged mutations, prefer one of: - a smaller curated subset, - an explicit experimental-only constraint, - or an explicit blocked entry with rationale.

Command pack availability

Command-pack disabling is configured with OTERMINUS_COMMAND_PROFILE presets plus OTERMINUS_DISABLED_COMMAND_PACKS for additional explicit disables. Keep the canonical rules in one place and refer to the config reference: Command pack availability.

Declare platform support

When adding platform-specific commands, set supported_platforms on the command spec or command pack using normalized ids (darwin, linux, windows). Use pack-level metadata when every command in the pack has the same platform boundary, as the macOS desktop pack does for open; use spec-level metadata for individual exceptions.

Unsupported platform commands should be filtered out of autocomplete, planner context, and discovery views where practical, but that filtering is not a security boundary. Keep validator enforcement intact: unsupported platform commands must be rejected before execution, and platform filtering must not bypass normal validation, risk policy, or confirmation checks. Do not document native Windows support or add Windows classifiers unless the command behavior is implemented and tested.

Additional guidance for project-workflow capabilities

If a family can execute local project code through standard tooling (tests, docs, evals), treat it as write-risk metadata at minimum, document this explicitly in notes, and keep operation choices enumerated/curated. Do not introduce a generic shell escape hatch such as arbitrary poetry run .... If only structured natural-language support is intended, keep direct_supported=false and add tests showing exact direct command strings are not accepted as a broad project-tooling shortcut.