Shell facet
Table of Contents
An entity's shell commands are codegen output. The ores.cpp.shell-command
facet emits one command unit per entity: a submenu that carries the get,
add, delete and history verbs. The entity opts in from its own model,
and the unit lands in the adapter part named for the entity's component.
Return to Knowledge.
1. The facet
| Field | Value |
|---|---|
| Facet | ores.cpp.shell-command |
| Model type | domain_entity |
| Level | cross |
| Default | disabled |
| Archetypes | command_header, command_implementation, command_tests |
The facet is disabled by default, so an entity receives a unit only when its model asks for one. The three archetypes are all per-entity, and each entity owns its own three files.
2. Opting an entity in
Add one property to the top-level properties drawer of the entity model,
beside :ID::
:PROPERTIES: :ID: <uuid> :ores.cpp.shell-command.enabled: true :END:
Then generate the unit:
./compass.sh codegen generate --model projects/ores.<component>/modeling/ores.<component>.<entity>.org --address ores.cpp.shell-command
3. File locations
The unit belongs to the adapter part named for its component, not to
ores.shell.application.
| File | Path |
|---|---|
| Header | projects/ores.shell/{component}/include/ores.shell/app/commands/{component}/{entity}_commands.hpp |
| Implementation | projects/ores.shell/{component}/src/app/commands/{component}/{entity}_commands.cpp |
| Tests | projects/ores.shell/{component}/tests/{entity}_commands_tests.cpp |
Every consumer includes the header as
ores.shell/app/commands/{component}/{entity}_commands.hpp, which is its path
under the part's own include/ directory. The part declares
#+component_kind: adapter, so codegen writes only its two file lists beside
the hand-authored CMakeLists.
4. Command class layout
The commands class is all-static. It carries an
inline static std::string_view logger_name with the fully-qualified logger
path and a static lg() accessor.
register_commands(cli::Menu& root_menu, nats_client& session, pagination_context& pagination)
inserts one submenu named for the entity's plural, registers one lambda per
verb, and calls pagination.register_list_callback for the list verb. Each
verb is a separate static process_* method.
5. The two shapes
The template branches on has_audit_group, which is true when the entity
model declares #+domain_audit_group:. Both shapes carry the same four verbs
under the same submenu name, so the two shapes never change the command a
user types. They differ in the history verb.
| Shape | History verb |
|---|---|
| With an audit group | {entities} history <key> |
| Without an audit group | {entities} history <key> [--diff] [--version <n>] |
A no-audit entity holds change_reason_code, change_commentary and
recorded_at as flat members in place of v.audit. The add verb fills them
from the last two positionals and from the clock, so its arity stays the same
in both shapes.
The --diff branch renders a unified diff through render_history_diff and
returns early. A --version without --diff is rejected.
The add verb takes free-form tokens rather than one typed parameter per column. The cli library expands a typed handler into one template instantiation per argument, and the Windows clang toolchain abandons the mangler on the deeper chains. A token vector holds the argument list at a fixed shape.
6. Who supplies a column's value
The add verb fills one domain member per positional argument. The
:supplied_by: annotation on a column says who supplies the value.
| Value | Meaning |
|---|---|
user |
The default. The verb reads the value from the next positional. |
minted |
The verb mints a fresh uuid. The default for a uuid primary key. |
session_party |
The verb reads the acting party from the session, and fails when the account has no default party. |
unset |
The verb leaves the member at its default and asks for nothing. |
The tenant_id member takes no annotation. It is not a declared column, so
the template names it directly and fills it from the session.
A malformed token names the field it belongs to, and the block that builds
the entity sits inside one try. The handler reports the failure through
fail() and returns.
7. Registration
One aggregator per component wires the units to the shell host. It is
hand-written and lives in the application part at
projects/ores.shell/application/src/app/commands/{component}/{component}_commands.cpp.
It includes each unit's header and calls register_commands once per unit.
repl::setup_menus calls the aggregator once at startup.
The facet has no component-scoped archetype for that aggregator today, so
adding an entity means one include and one call in the aggregator, plus a
refresh of the part's file lists. A generated aggregator needs a render-time
list of the component's opted-in entities, the same pattern the generator
already uses for _COMPONENT_FILES_TEMPLATES.
The units replace, they do not join. Delete the hand-written unit for an entity in the same change that generates its replacement, so the shell never carries two units for one submenu.
8. See also
- ores.cpp.shell-command — the facet, its archetypes and its routing table.
- compass-codegen-add-surface-entity — skill that drives this workflow.
- How do I create shell commands for a new entity? — recipe walkthrough.
- Entity lifecycle — layer ordering overview.