ADR-031: CLI Usability - Gateway Commands and Interactive Mode
Status
Proposed
Date
2025-12-07
Deciders
- Norbert Bede
- Claude (AI Assistant)
Research Summary
ERP CLI Comparison (Odoo, Frappe, SAP, Dynamics 365)
| Feature | Odoo | Frappe | SAP BTP | D365 | iDempiere |
|---|---|---|---|---|---|
| Command Pattern | Verb-Noun | Noun-Verb | Verb-Noun-Noun | Verb-Noun | Noun-Verb |
| Scaffolding | scaffold |
new-app |
❌ | ❌ | dev init |
| Code Generation | ❌ | ❌ | ❌ | ❌ | ✅ unique |
| Hot Reload | --dev reload |
serve |
❌ | ❌ | ❌ |
| Shell/REPL | shell |
console |
❌ | ❌ | shell |
| IDE Setup | ❌ | ❌ | ❌ | ❌ | ✅ unique |
| AI Features | ❌ | ❌ | ❌ | ❌ | ✅ unique |
| Plugin System | ❌ | ❌ | ❌ | ❌ | ✅ unique |
| JSON Output | ❌ | ❌ | ❌ | --output json |
--json |
| Dry Run | ❌ | ❌ | ❌ | --whatif |
--dry-run |
Headless CMS & E-commerce CLI Comparison
| Platform | Type | Key CLI Commands | Schema Approach |
|---|---|---|---|
| Strapi | Headless CMS | generate, transfer, export |
Code-first |
| Contentful | Headless CMS | migration, Merge App CLI |
Migration scripts |
| Sanity | Headless CMS | schema validate, migration create |
TypeScript-first |
| Directus | Headless CMS | schema snapshot/apply |
Database-first |
| Shopify | E-commerce | app dev, theme dev, AI code gen |
Platform-managed |
| Magento | E-commerce | module:enable, setup:upgrade |
Module lifecycle |
| Medusa | E-commerce | seed, create-medusa-app |
Seed data focus |
| Saleor | E-commerce | Configurator CLI | GraphQL-native |
Key Sources
| Source | URL | Insight |
|---|---|---|
| Odoo CLI | https://www.odoo.com/documentation/19.0/developer/reference/cli.html | Hot reload, scaffolding |
| Frappe Bench | https://docs.frappe.io/framework/user/en/bench/bench-commands | Comprehensive workflow |
| SAP BTP CLI | https://help.sap.com/docs/btp/sap-business-technology-platform/commands-in-btp-cli | Enterprise patterns |
| Strapi CLI | https://docs.strapi.io/cms/cli | Transfer tools, generators |
| Directus Migrations | https://directus.io/docs/configuration/migrations | Snapshot/apply pattern |
| Shopify CLI | https://github.com/Shopify/cli | AI code gen, MCP support |
| Magento CLI | https://experienceleague.adobe.com/en/docs/commerce-operations/tools/cli-reference | Module lifecycle |
| clig.dev | https://clig.dev/ | Modern CLI guidelines |
| 12-Factor CLI | https://medium.com/@jdxcode/12-factor-cli-apps-dd3c227a0e46 | Best practices |
iDempiere CLI Unique Strengths (No Other ERP Has)
- Code Generation:
gen model,gen process,gen callout,gen event - IDE Setup Automation:
ide eclipse,ide intellij,ide vscode - AI Integration: MCP server, RAG knowledge base, LangChain4j
- Plugin System: SPI-based extensibility
- Application Dictionary Query:
dict registrywith live database access
Context and Problem Statement
The iDempiere CLI has grown to 60+ commands across 11 groups. While powerful, this creates a discovery and usability problem for new users. Analysis shows:
| Interface | Time to create table | Effort |
|---|---|---|
| Web UI | 20-25 min | ~50 clicks |
| CLI (current) | 3-5 min | 5 commands (must know them) |
| AI (MCP) | 30-60 sec | 1 prompt |
The CLI is 4-5x faster than Web UI, but requires knowing which commands to use. New users face a steep learning curve.
Decision Drivers
- Discoverability: Users should find commands without reading documentation
- Progressive disclosure: Simple tasks should be simple; complexity available when needed
- Consistency: Follow patterns from successful CLIs (gh, npm, docker)
- Backward compatibility: Existing commands must continue to work
Considered Options
- Gateway commands only: Add
new,add,deployas simplified entry points - Interactive mode only: Launch wizard when no command given
- Both gateway commands + interactive mode: Comprehensive solution
- AI-first: Route everything through
ai ask
Decision Outcome
Chosen option: "Both gateway commands + interactive mode", because it serves both users who know what they want (new plugin) and users who are exploring (just run idempiere-cli).
Implementation
1. Gateway Commands
Three task-oriented commands that wrap existing functionality:
idempiere-cli new - Create something new
# Interactive - shows menu
$ idempiere-cli new
? What do you want to create?
> plugin - New iDempiere plugin project
table - New table with window
process - New process class
callout - New callout class
form - New ZK form
# Direct - skips menu
$ idempiere-cli new plugin org.mycompany.hr
$ idempiere-cli new table XX_Employee
$ idempiere-cli new process MyProcess
Mapping:
new subcommand |
Underlying command |
|---|---|
new plugin |
dev init |
new table |
dict add table --wizard |
new process |
gen process |
new callout |
gen callout |
new form |
gen form |
idempiere-cli add - Add to existing
# Interactive
$ idempiere-cli add
? What do you want to add?
> column - Add column to existing table
field - Add field to existing window
menu - Add menu entry
process - Add process to plugin
# Direct
$ idempiere-cli add column XX_Employee.Email
$ idempiere-cli add menu "My Menu" --window XX_Employee
Mapping:
add subcommand |
Underlying command |
|---|---|
add column |
dict add column |
add field |
dict add field |
add menu |
dict add menu |
add process |
dict add process |
idempiere-cli deploy - Ship your work
# Interactive
$ idempiere-cli deploy
? What do you want to deploy?
> packout - Export changes as 2Pack
packin - Import 2Pack to server
sync - Sync table to database
# Direct
$ idempiere-cli deploy packout -o MyPlugin.zip
$ idempiere-cli deploy packin MyPlugin.zip
Mapping:
deploy subcommand |
Underlying command |
|---|---|
deploy packout |
pack out |
deploy packin |
pack in |
deploy sync |
dict sync |
2. Interactive Mode
When user runs idempiere-cli with no arguments OR with --interactive:
$ idempiere-cli
▗▄▄▄▖▗▄▄▄ ▗▄▄▄▖▗▖ ▗▖▗▄▄▖▗▄▄▄▖▗▄▄▄▖▗▄▄▖ ▗▄▄▄▖ ▗▄▄▖▗▖ ▗▄▄▄▖
...
iDempiere Development, AI & DevOps CLI v1.56.0
? What would you like to do?
> Create something new (new)
Add to existing (add)
Deploy / export (deploy)
Check environment (doctor)
Ask AI (ai)
Show all commands (help)
Exit
3. Command Structure
idempiere-cli
├── new # Gateway: create new things
│ ├── plugin # → dev init
│ ├── table # → dict add table --wizard
│ ├── process # → gen process
│ ├── callout # → gen callout
│ └── form # → gen form
├── add # Gateway: add to existing
│ ├── column # → dict add column
│ ├── field # → dict add field
│ ├── menu # → dict add menu
│ └── process # → dict add process
├── deploy # Gateway: deployment
│ ├── packout # → pack out
│ ├── packin # → pack in
│ └── sync # → dict sync
├── doctor # Keep at top level (common)
├── ai # Keep at top level (common)
│ └── ask # Natural language
├── dev/dict/gen/pack/... # Expert commands (unchanged)
└── shell # Interactive REPL
4. Implementation Details
NewCommand.java
@Command(name = "new",
description = "Create new iDempiere artifacts",
subcommands = {
NewPluginCommand.class,
NewTableCommand.class,
NewProcessCommand.class,
NewCalloutCommand.class,
NewFormCommand.class
})
public class NewCommand implements Callable<Integer> {
@Inject JLinePrompt prompt;
@Override
public Integer call() {
// No subcommand given - show interactive menu
List<String> options = List.of(
"plugin - New iDempiere plugin project",
"table - New table with window",
"process - New process class",
"callout - New callout class",
"form - New ZK form"
);
int selected = prompt.promptSelect("What do you want to create?", options, 0);
// Delegate to appropriate subcommand
return switch (selected) {
case 0 -> new NewPluginCommand().call();
case 1 -> new NewTableCommand().call();
// ...
default -> ExitCodes.SUCCESS;
};
}
}
Interactive Mode in IdempiereCli.java
@Override
public Integer call() {
// No subcommand - launch interactive mode
if (System.console() != null && isInteractive()) {
return runInteractiveMode();
}
// Non-interactive - show help
printBanner();
printGroupedHelp();
return 0;
}
private int runInteractiveMode() {
printBanner();
List<String> options = List.of(
"Create something new (new)",
"Add to existing (add)",
"Deploy / export (deploy)",
"Check environment (doctor)",
"Ask AI (ai)",
"Show all commands (help)",
"Exit"
);
int selected = jlinePrompt.promptSelect("What would you like to do?", options, 0);
return switch (selected) {
case 0 -> runNew();
case 1 -> runAdd();
case 2 -> runDeploy();
case 3 -> runDoctor();
case 4 -> runAi();
case 5 -> { printGroupedHelp(); yield 0; }
default -> 0;
};
}
Pros and Cons
Pros
- Discoverability: New users can explore via menus
- Speed: Experienced users can use direct commands
- Educational: Shows underlying commands being run
- Backward compatible: All existing commands work
- Consistent: Follows
gh,npm initpatterns
Cons
- More code: New command classes needed
- Duplication risk: Gateway commands wrap existing ones
- Maintenance: Must keep mappings up to date
Migration Path
- v1.57.0: Add gateway commands (
new,add,deploy) - v1.58.0: Add interactive mode when no args
- Future: Consider deprecating some nested commands if gateway proves popular
References
- DEVELOPER_EXPERIENCE_COMPARISON.md
- ADR-030: CLI Design Guidelines
- GitHub CLI -
gh repo createpattern - npm init - interactive scaffolding
- clig.dev - CLI best practices