ADR-026: CLI Execution Modes
Status
Proposed
Date
2025-12-07
Context
The idempiere-cli needs to support multiple execution modes for different deployment and usage scenarios:
- Local development - Developer runs commands on same machine
- CI/CD pipelines - Automated one-shot execution
- Container deployment - Long-running process in K8s/Docker
- Remote execution - Send commands from Computer A to Server B
- AI assistants - Claude Code/Desktop integration via MCP
Currently, only the MCP server mode (server mcp) stays running. All other commands exit after execution, which is problematic for container deployments and remote execution scenarios.
Research Summary
Key References
| Resource | URL | Key Insight |
|---|---|---|
| CLI Guidelines | https://clig.dev/ | Comprehensive CLI design best practices |
| 12 Factor CLI Apps | https://medium.com/@jdxcode/12-factor-cli-apps-dd3c227a0e46 | Modern CLI principles |
| Unix Interface Design Patterns | https://homepage.cs.uri.edu/~thenry/resources/unix_art/ch11s06.html | Classic daemon/filter patterns |
| Quarkus Picocli + HTTP | https://github.com/quarkusio/quarkus/discussions/43925 | How to combine CLI with HTTP server |
| Docker Daemon Best Practices | https://stackoverflow.com/questions/41494592 | Don't daemonize inside container |
| Remote CLI via REST | https://ganeshvelrajan.medium.com/execute-remote-commands-using-rest-apis-without-ssh-daba4a113608 | REST API as SSH alternative |
| n8n CLI Architecture | https://docs.n8n.io/hosting/cli-commands/ | Multiple execution modes pattern |
CLI Execution Mode Patterns
From Unix Interface Design Patterns:
| Pattern | Description | Stays Running |
|---|---|---|
| Filter | stdin → transform → stdout | No |
| Cantrip | Do something, exit | No |
| Source | Generate data → stdout | No |
| Sink | stdin → process | No |
| Compiler | file → transform → file | No |
| Daemon/Server | Background service, shared resource access | Yes |
| Spooler | Queue jobs for daemon processing | Yes (daemon) |
| Interactive/REPL | User prompts, shell environment | Yes |
Key Insight from Unix Art
> "A daemon is designed to mediate access to some sort of shared resource — a database, or a transaction stream. Another reason for such a daemon may be to avoid performing expensive startup actions each time the program is invoked."
This is exactly our use case: database connections, embedding model loading, and Quarkus startup are expensive operations.
Container Deployment Best Practice
From Docker best practices: > "Don't daemonize a process inside a container. Run the process in foreground as PID 1. The container will exit when PID 1 exits."
This means our CLI must have a mode that runs in foreground and stays alive.
Remote Execution Pattern
From REST API remote execution research: > "SSH is OK for one-off accesses but not suited for repeated tasks/jobs and not easy to automate. REST API provides secure access via HTTPS/TLS and ability to automate using scripts."
Decision
Implement three execution modes for idempiere-cli:
1. One-Shot Mode (Default) - EXISTS
Standard CLI behavior - execute command and exit.
idempiere-cli dict registry list --pattern "C_Order%"
idempiere-cli dev doctor
idempiere-cli gen model XX_MyTable
Use cases: Local development, CI/CD pipelines, shell scripts
2. Server Mode - PARTIAL (MCP only)
Long-running HTTP server exposing CLI functionality.
# MCP Server (EXISTS)
idempiere-cli server mcp
# Listens on http://localhost:8765/mcp
# REST API Server (PROPOSED)
idempiere-cli server api
# Listens on http://localhost:8765/api
# POST /api/dict/registry/list
# POST /api/dev/doctor
Use cases: Container deployment, remote execution, n8n integration
3. Interactive Shell Mode - PROPOSED
REPL for interactive sessions.
idempiere-cli shell
idempiere> dict registry list --pattern "C_Order%"
┌────────────┬─────────────────────┬────────────┐
│ Table Name │ Description │ Entity Type│
├────────────┼─────────────────────┼────────────┤
│ C_Order │ Sales Order │ D │
│ C_OrderLine│ Sales Order Line │ D │
└────────────┴─────────────────────┴────────────┘
idempiere> dev doctor
✓ Java 21.0.1
✓ Maven 3.9.6
✓ Database connected
idempiere> exit
Use cases: Interactive debugging, docker exec -it, SSH sessions
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ idempiere-cli │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ One-Shot │ │ Server │ │ Shell │ │
│ │ Commands │ │ Modes │ │ (REPL) │ │
│ │ │ │ │ │ │ │
│ │ dict, gen, │ │ server mcp │ │ shell │ │
│ │ pack, dev, │ │ server api │ │ │ │
│ │ env, ide │ │ │ │ │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └─────────────────┼─────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────┐ │
│ │ Shared Tool Logic Layer │ │
│ │ (org.idempiere.cli.ai.shared) │
│ └────────────────────────────┘ │
│ │ │
│ ┌────────────┼────────────┐ │
│ ▼ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ iDempiere │ │ PostgreSQL│ │ Local │ │
│ │ REST API │ │ Database │ │ Files │ │
│ └───────────┘ └───────────┘ └───────────┘ │
└─────────────────────────────────────────────────────────────────┘
Implementation
Server API Mode
Use Quarkus JAX-RS to expose CLI tools as REST endpoints:
@Path("/api")
@ApplicationScoped
public class CliApiResource {
@Inject
RegistryToolLogic registryLogic;
@POST
@Path("/dict/registry/list")
@Produces(MediaType.APPLICATION_JSON)
public Response listTables(
@QueryParam("pattern") String pattern,
@QueryParam("limit") Integer limit) {
ToolResult result = registryLogic.listTables(pattern, false, limit, "en_US");
return Response.ok(result.toJson()).build();
}
}
Command implementation:
@Command(name = "api", description = "Start REST API server")
public class ApiServerCommand implements Callable<Integer> {
@Override
public Integer call() {
System.out.println("REST API Server starting...");
System.out.println("Endpoints: http://localhost:8765/api");
System.out.println("Press Ctrl+C to stop");
Quarkus.waitForExit();
return 0;
}
}
Shell Mode
Use JLine3 for interactive REPL:
@Command(name = "shell", description = "Start interactive shell")
public class ShellCommand implements Callable<Integer> {
@Inject
CommandLine.IFactory factory;
@Override
public Integer call() {
Terminal terminal = TerminalBuilder.builder().build();
LineReader reader = LineReaderBuilder.builder()
.terminal(terminal)
.completer(new PicocliCompleter())
.build();
while (true) {
String line = reader.readLine("idempiere> ");
if ("exit".equals(line.trim())) break;
// Parse and execute command
String[] args = line.split("\\s+");
new CommandLine(IdempiereCli.class, factory).execute(args);
}
return 0;
}
}
Usage Matrix
| Scenario | Mode | Command |
|---|---|---|
| Local development | One-shot | idempiere-cli dict registry list |
| CI/CD pipeline | One-shot | idempiere-cli pack out --table XX_MyTable |
| Docker container | Server | idempiere-cli server api |
| Kubernetes pod | Server | idempiere-cli server api |
| Remote execution | Server | curl -X POST http://server:8765/api/dict/registry/list |
| AI assistant (Claude) | Server | idempiere-cli server mcp |
| Interactive debugging | Shell | idempiere-cli shell |
| Docker exec | Shell | docker exec -it cli idempiere-cli shell |
| SSH session | Shell | ssh server "idempiere-cli shell" |
Consequences
Positive
- Complete deployment coverage: All scenarios supported
- Container-friendly: Server modes run as PID 1 in foreground
- Remote execution: REST API enables automation without SSH
- Reuses existing logic: All modes share
org.idempiere.cli.ai.shared - Interactive debugging: Shell mode for troubleshooting
Negative
- Additional maintenance: Three modes to maintain
- Security considerations: REST API needs authentication
- Dependency: JLine3 for shell mode
Neutral
- MCP server remains separate (specialized protocol)
- One-shot mode unchanged (backward compatible)
AI Connection Without MCP
Key Question: Can AI connect to CLI without MCP protocol?
Answer: YES - The server api command exposes standard REST/HTTP endpoints that ANY client can use:
| Method | Protocol | AI Client | Command |
|---|---|---|---|
| MCP | MCP over HTTP/SSE | Claude Desktop/Code only | server mcp |
| REST API | HTTP/JSON | Any AI (n8n, OpenAI agents, custom) | server api |
| LangChain4j | Local (embedded) | Built-in AI agent | ai ask |
┌─────────────────────────────────────────────────────────────────┐
│ AI Connection Methods │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. MCP (Claude-specific) │
│ Claude Desktop → MCP Protocol → server mcp → Tools │
│ │
│ 2. REST API (Universal) ← For any AI │
│ Any AI Agent → HTTP/JSON → server api → Tools │
│ (OpenAI, n8n, custom agents, scripts) │
│ │
│ 3. LangChain4j (Embedded) │
│ User → ai ask "question" → LLM → Tools → Response │
│ │
└─────────────────────────────────────────────────────────────────┘
Example: n8n calling CLI via REST:
HTTP Request Node → http://localhost:8765/api/registry/tables
Example: Custom Python AI agent:
import requests
response = requests.get("http://localhost:8765/api/registry/tables?pattern=C_Order%")
Security Considerations
For server api mode:
- Authentication: Require API token via header or query param
- HTTPS: Support TLS for production
- Rate limiting: Prevent abuse
- Read-only by default: Dangerous operations require explicit flag