CLI Entity Creator
When to use this skill
When you need to add CLI commands for a new entity in ores.cli. This skill
guides you through creating the options structs, entity parser, and application
logic following established patterns.
Prerequisites:
- The domain type must already exist
- The repository for CRUD operations must be implemented
- Table I/O and JSON I/O must be available for output formatting
These are all implemented by domain-type-creator skill.
How to use this skill
- Gather entity requirements (name, operations needed, parameters).
- Follow the detailed instructions to create CLI support in phases.
- Each phase ends with a PR checkpoint - raise PR, wait for review, merge.
- Create a fresh branch from main for the next phase (see feature-branch-manager).
- Build and test after each step.
PR Strategy
This skill is structured into three phases, each resulting in a separate PR.
| Phase | Steps | PR Title Template |
|---|---|---|
| 1 | Steps 1-4 | [cli] Add <entity> list and export commands |
| 2 | Steps 5-7 | [cli] Add <entity> add and delete commands |
| 3 | Step 8 | [doc] Add <entity> CLI command recipes |
After each PR is merged, use feature-branch-manager to transition to the next phase.
Detailed instructions
The following sections describe the step-by-step process for creating CLI commands for an entity.
Gather Requirements
Before starting, gather the following information:
- Entity name: The name of the entity (e.g.,
currency,account,feature_flags). - Component location: Which domain the entity belongs to (e.g.,
ores.refdata,ores.iam). - Command name: The CLI command name (typically plural with hyphens, e.g.,
currencies,accounts,feature-flags). - Operations needed:
[ ]Import from external files (ORE XML, etc.)[ ]Export to external formats (XML, CSV)[ ]List in internal formats (JSON, table)[ ]Delete by key[ ]Add new entity from command-line parameters
- Key field: The primary identifier for filtering (e.g.,
iso_codefor currencies). - Required add parameters: What fields are required when adding a new entity.
- Optional add parameters: What fields have defaults.
Phase 1: List and Export Commands
This phase creates the entity parser with basic list/export operations. After completing Steps 1-4, raise a PR.
Suggested PR title: [cli] Add <entity> list and export commands
Step 1: Add Entity Enum Value
Add the entity to the enumeration of available entities in projects/ores.cli/include/ores.cli/config/entity.hpp as follows:
enum class entity { currencies, accounts, feature_flags, login_info, <entity> // Add new entity here };
Step 2: Create Entity Parser
Phase 1 Checkpoint: Raise PR
At this point:
- Build and verify:
cmake --build --preset linux-clang-debug - Test manually:
./ores.cli <entities> --help - Test list:
./ores.cli <entities> list --format table - Commit all changes.
- Push branch and raise PR.
PR Title: [cli] Add <entity> list and export commands
PR Description:
## Summary - Add <entities> entity enum value - Create <entities>_parser for command handling - Implement list and export operations with JSON/table formats - Register in main parser ## Test Plan - [ ] Build succeeds - [ ] `<entities> --help` shows available operations - [ ] `<entities> list --format json` outputs JSON - [ ] `<entities> list --format table` outputs table
Wait for review feedback and merge before continuing to Phase 2.
Phase 2: Add and Delete Commands
After Phase 1 PR is merged, use feature-branch-manager to transition to Phase 2.
Suggested PR title: [cli] Add <entity> add and delete commands
Step 5: Create Add Options Struct
Create the options struct for add command parameters.
Header file location
projects/ores.cli/include/ores.cli/config/add_<entity>_options.hpp
Header structure
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2025 Marco Craveiro <marco.craveiro@gmail.com> * * licensing text... */ #ifndef ORES_CLI_CONFIG_ADD_<ENTITY>_OPTIONS_HPP #define ORES_CLI_CONFIG_ADD_<ENTITY>_OPTIONS_HPP #include <iosfwd> #include <string> #include <optional> namespace ores::cli::config { /** * @brief Configuration for adding a <entity> entity via command-line arguments. */ struct add_<entity>_options final { // Required fields std::string <key_field>; std::string modified_by; // Optional fields with defaults std::optional<std::string> optional_field1; std::optional<int> optional_field2; }; std::ostream& operator<<(std::ostream& s, const add_<entity>_options& v); } #endif
Implementation file location
projects/ores.cli/src/config/add_<entity>_options.cpp
Implementation structure
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright (C) 2025 Marco Craveiro <marco.craveiro@gmail.com> * * licensing text... */ #include "ores.cli/config/add_<entity>_options.hpp" #include <ostream> namespace ores::cli::config { std::ostream& operator<<(std::ostream& s, const add_<entity>_options& v) { s << "{ <key_field>: " << v.<key_field> << ", modified_by: " << v.modified_by; if (v.optional_field1) s << ", optional_field1: " << *v.optional_field1; if (v.optional_field2) s << ", optional_field2: " << *v.optional_field2; s << " }"; return s; } }
Commit message
[cli] Add add_<entity>_options struct
Step 6: Update Add Options Variant
Add the new options type to the add_options variant.
File to modify
projects/ores.cli/include/ores.cli/config/add_options.hpp
Changes required
Add include:
#include "ores.cli/config/add_<entity>_options.hpp"
Add to variant:
using add_options = std::variant< add_currency_options, add_account_options, add_feature_flag_options, add_login_info_options, add_<entity>_options // Add new type >;
Commit message
[cli] Add add_<entity>_options to variant
Step 7: Extend Parser and Application for Add/Delete
Add the add and delete operations to the parser and application.
Parser changes
Update projects/ores.cli/src/config/entity_parsers/<entities>_parser.cpp:
- Add include:
#include "ores.cli/config/add_<entity>_options.hpp"
- Add command names:
const std::string delete_command_name("delete"); const std::string add_command_name("add"); const std::vector<std::string> allowed_operations{ export_command_name, list_command_name, delete_command_name, add_command_name };
- Add options description function:
/** * @brief Creates the options related to adding <entities>. */ options_description make_add_<entity>_options_description() { options_description r("Add <Entity> Options"); r.add_options() ("<key-field>", value<std::string>(), "<Key field> (required)") ("optional-field1", value<std::string>()->default_value(""), "Optional field 1") ("optional-field2", value<int>()->default_value(0), "Optional field 2") ("modified-by", value<std::string>(), "Username of modifier (required)"); return r; }
- Add read function:
/** * @brief Reads the add configuration from the variables map for <entities>. */ ores::cli::config::add_<entity>_options read_add_<entity>_options(const variables_map& vm) { ores::cli::config::add_<entity>_options r; if (vm.count("modified-by") == 0) { BOOST_THROW_EXCEPTION( parser_exception("Must supply --modified-by for add command.")); } r.modified_by = vm["modified-by"].as<std::string>(); // Required fields if (vm.count("<key-field>") == 0) { BOOST_THROW_EXCEPTION( parser_exception("Must supply --<key-field> for add command.")); } r.<key_field> = vm["<key-field>"].as<std::string>(); // Optional fields if (vm.count("optional-field1") != 0) r.optional_field1 = vm["optional-field1"].as<std::string>(); if (vm.count("optional-field2") != 0) r.optional_field2 = vm["optional-field2"].as<int>(); return r; }
- Update help operations list:
const std::vector<std::pair<std::string, std::string>> operations = { {"export", "Export <entities> to external formats"}, {"list", "List <entities> as JSON or table"}, {"delete", "Delete a <entity> by <key>"}, {"add", "Add a new <entity>"} };
- Add operation handlers in
handle_<entities>_command():
} else if (operation == delete_command_name) { auto d = add_common_options(make_delete_options_description()); if (has_help) { print_help_command("<entities> delete", d, info); return {}; } store(command_line_parser(o).options(d).run(), vm); store(parse_environment(d, name_mapper), vm); r.deleting = read_delete_options(vm, entity::<entities>); } else if (operation == add_command_name) { auto d = add_common_options(make_add_<entity>_options_description()); if (has_help) { print_help_command("<entities> add", d, info); return {}; } store(command_line_parser(o).options(d).run(), vm); store(parse_environment(d, name_mapper), vm); r.adding = read_add_<entity>_options(vm); }
Application changes
Update projects/ores.cli/src/app/application.cpp:
- Add include:
#include "ores.cli/config/add_<entity>_options.hpp"
- Add delete method:
void application:: delete_<entity>(const config::delete_options& cfg) const { BOOST_LOG_SEV(lg(), debug) << "Deleting <entity>: " << cfg.key; <component>::repository::<entity>_repository repo(context_); repo.remove(cfg.key); output_stream_ << "<Entity> deleted successfully: " << cfg.key << std::endl; BOOST_LOG_SEV(lg(), info) << "Deleted <entity>: " << cfg.key; }
- Add to
delete_data()switch:
case config::entity::<entities>: delete_<entity>(cfg); break;
- Add add method:
void application:: add_<entity>(const config::add_<entity>_options& cfg) const { BOOST_LOG_SEV(lg(), info) << "Adding <entity>: " << cfg.<key_field>; // Construct <entity> from command-line arguments <component>::domain::<entity> item; item.<key_field> = cfg.<key_field>; item.optional_field1 = cfg.optional_field1.value_or(""); item.optional_field2 = cfg.optional_field2.value_or(0); item.recorded_by = cfg.modified_by; // Write to database <component>::repository::<entity>_repository repo(context_); repo.write(item); output_stream_ << "Successfully added <entity>: " << item.<key_field> << std::endl; BOOST_LOG_SEV(lg(), info) << "Added <entity>: " << item.<key_field>; }
- Add to
add_data()visitor:
} else if constexpr (std::is_same_v<T, config::add_<entity>_options>) { add_<entity>(opts); }
- Update application header with new method declarations.
Update parser command description
In parser.cpp, update the command description:
const std::string <entities>_command_desc("Manage <entities> (list, export, delete, add).");
Commit message
[cli] Add <entity> add and delete operations Implement add command with entity-specific options and delete command for removing <entities> by key.
Phase 2 Checkpoint: Raise PR
At this point:
- Build and verify:
cmake --build --preset linux-clang-debug - Test add:
./ores.cli <entities> add --<key-field> test --modified-by admin - Test delete:
./ores.cli <entities> delete --key test - Commit all changes.
- Push branch and raise PR.
PR Title: [cli] Add <entity> add and delete commands
PR Description:
## Summary - Add add_<entity>_options struct for add parameters - Implement add command for creating new <entities> - Implement delete command for removing <entities> by key - Update parser to support all four operations ## Test Plan - [ ] Build succeeds - [ ] `<entities> add --help` shows required parameters - [ ] `<entities> add` creates new <entity> - [ ] `<entities> delete --key <key>` removes <entity>
Wait for review feedback and merge before continuing to Phase 3.
Phase 3: Recipe Documentation
After Phase 2 PR is merged, use feature-branch-manager to transition to Phase 3.
Suggested PR title: [doc] Add <entity> CLI command recipes
Step 8: Add CLI Recipes
Add all new commands to CLI recipes following the ores-cli-recipes skill. This ensures every command is documented with runnable examples.
File to modify
doc/recipes/cli_recipes.org
Recipe requirements
Create recipes that test every command added for the entity:
- Help recipe: Show the entity command help output
- List recipe: Demonstrate listing all entities (JSON and table formats)
- Add recipe: Demonstrate creating a new entity
- Delete recipe: Demonstrate deleting an entity
Recipe section structure
Add a new section for the entity following the existing pattern in cli_recipes.org:
#+begin_src fundamental
<Entities>
<Entity> management operations from the ORE Studio <Component> Component.
Help
Show available operations for <entities>.
./ores.cli <entities> --help
List
List all <entities> in table format.
export ORES_CLI_DB_PASSWORD ./ores.cli <entities> list ${db_args} ${log_args} --format table
List as JSON
List all <entities> in JSON format.
export ORES_CLI_DB_PASSWORD ./ores.cli <entities> list ${db_args} ${log_args} --format json
Add
Create a new <entity>.
export ORES_CLI_DB_PASSWORD ./ores.cli <entities> add ${db_args} ${log_args} \ --<key-field> test_value \ --modified-by admin
Delete
Delete a <entity> by key.
export ORES_CLI_DB_PASSWORD ./ores.cli <entities> delete ${db_args} ${log_args} --key test_value
#+end_src
Recipe conventions
- Set
:header-args+: :wrap src jsonat section level for default JSON output - Use
:wrap src textoverride for help and table output - Include
export ORES_CLI_DB_PASSWORDbefore commands needing DB access - Use
${db_args}and${log_args}variables (defined in file header) - Add descriptive paragraph before each recipe explaining what it demonstrates
- Link to the component's org-mode documentation using
[[id:...][...]]
Updating ores-cli-recipes entity table
After adding recipes, update the entity operations table in the ores-cli-recipes
skill (doc/skills/ores-cli-recipes/skill.org) to include the new entity:
| <entities> | list, delete, add |
Commit message
[doc] Add <entity> CLI command recipes Document all <entity> CLI commands with runnable examples: - Help menu - List all <entities> - Add new <entity> - Delete <entity>
Phase 3 Checkpoint: Raise PR
At this point:
- Verify recipes execute correctly.
- Check output formatting is correct.
- Commit all changes.
- Push branch and raise PR.
PR Title: [doc] Add <entity> CLI command recipes
PR Description:
## Summary - Add CLI recipes section for <entity> commands - Document help, list, add, and delete operations - Include runnable examples with expected output ## Test Plan - [ ] All recipes execute without errors - [ ] Output matches expected format - [ ] Documentation renders correctly
Key conventions reference
Output formatting
| Outcome | Format |
|---|---|
| Success | output_stream_ << "Successfully..." << std::endl; |
| Error | BOOST_THROW_EXCEPTION(application_exception(...)); |
| List | output_stream_ << items << std::endl; |
Logging levels
| Level | Use Case |
|---|---|
| debug | Operation start, parameter values |
| info | Successful completion with counts/IDs |
| warn | Operation failed but handled |
| error | Invalid input, unexpected state |
Option patterns
| Type | Pattern |
|---|---|
| Required | Check vm.count(), throw if missing |
| Optional | Use ->default_value() or check before reading |
| String | value<std::string>() |
| Integer | value<int>() |
File locations summary
| Component | Location |
|---|---|
| Entity enum | include/ores.cli/config/entity.hpp |
| Parser header | include/ores.cli/config/entity_parsers/<entities>_parser.hpp |
| Parser implementation | src/config/entity_parsers/<entities>_parser.cpp |
| Add options header | include/ores.cli/config/add_<entity>_options.hpp |
| Add options impl | src/config/add_<entity>_options.cpp |
| Add options variant | include/ores.cli/config/add_options.hpp |
| Main parser | src/config/parser.cpp |
| Application header | include/ores.cli/app/application.hpp |
| Application impl | src/app/application.cpp |
| CLI recipes | doc/recipes/cli_recipes.org |
Related skills
- domain-type-creator - For creating the underlying domain type
- feature-branch-manager - For transitioning between phases
- ores-cli-recipes - For updating CLI documentation
- shell-entity-creator - Similar skill for shell commands