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 ~/Development/OreStudio/OreStudio.local1/ 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