npx claudepluginhub etendosoftware/etendo_claude_marketplace --plugin dev-assistantThis skill uses the workspace's default tool permissions.
This file is NOT a user-facing command. It is read by all `/etendo:*` commands as their first step to establish the operational context.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides agent creation for Claude Code plugins with file templates, frontmatter specs (name, description, model), triggering examples, system prompts, and best practices.
This file is NOT a user-facing command. It is read by all /etendo:* commands as their first step to establish the operational context.
MANDATORY RULE (enforced via CLAUDE.md): All Etendo development work MUST be done through
/etendo:*skills. Never run Gradle tasks, write AD SQL, or create modules/windows/Java code manually. Always use the Skill tool to invoke the appropriate/etendo:*command.
Verify you are inside an Etendo project by checking:
gradle.properties -> contains bbdd.sid, bbdd.user, context.name
build.gradle -> contains etendo plugin or etendo-core dependency
If neither exists in CWD, search parent directories up to 3 levels. If not found, inform the dev: "This does not appear to be an Etendo project. Run /etendo:init to bootstrap one, or navigate to your etendo_base directory."
Read build.gradle and check:
Source mode -- build.gradle contains an etendo { } block:
etendo {
coreVersion = "[25.1.0,26.1.0)"
}
etendo_core/ directory exists locally with full Java source./gradlew expandCore before ./gradlew setupJAR mode -- build.gradle contains core as a dependency:
implementation('com.etendoerp.platform:etendo-core:[25.1.0,26.1.0)')
etendo_core/ source -- core is a pre-compiled JAR./gradlew expandCore -PforceExpand=trueRead gradle.properties and check which services are dockerized:
| Property | Value | Meaning |
|---|---|---|
docker_com.etendoerp.docker_db | true | DB runs in Docker (etendo-db-1) |
docker_com.etendoerp.tomcat | true | Tomcat runs in Docker (etendo-tomcat-1) |
docker_com.etendoerp.etendorx | true | RX services in Docker |
docker_com.etendoerp.copilot | true | Copilot in Docker |
If a property is absent or false -> that service runs locally (native install).
Resource dependencies (critical):
update.database, export.database, install, smartbuildsmartbuild deploy, UI access, RX endpoints./gradlew resources.up./gradlew resources.downFrom gradle.properties:
bbdd.user -> DB username (e.g. tad)
bbdd.password -> DB password
bbdd.sid -> DB name (e.g. etendo)
bbdd.port -> DB port (default: 5432)
context.name -> Etendo context/webapp name (e.g. etendo)
Executing SQL:
# Docker DB:
docker exec -i etendo-db-1 psql -U {bbdd.user} -d {bbdd.sid}
docker exec -i etendo-db-1 psql -U {bbdd.user} -d {bbdd.sid} < script.sql
# Local DB:
psql -U {bbdd.user} -d {bbdd.sid} -h localhost -p {bbdd.port}
Resolve in this order:
.etendo/context.json in project root -- explicit override set by /etendo:contextmodules/com.x.y.z/, extract the java packageThe active module determines:
export.database -Dmodule= filterAD_MODULE_ID in all SQL INSERTs.etendo/context.json format{
"module": "com.mycompany.mymodule",
"modulePath": "modules/com.mycompany.mymodule",
"dbPrefix": "MYMOD",
"etendoUrl": "http://localhost:8080/etendo"
}
Fields:
module — Java package name (used to resolve AD_MODULE_ID via SQL)modulePath — relative path to the module directorydbPrefix — DB prefix for table/column namingetendoUrl — base URL of the Etendo instance including the context path (e.g. http://localhost:8080/etendo). The context name comes from context.name in gradle.properties. Default port is 8080.Note:
AD_MODULE_IDis NOT stored in context.json. Resolve it at runtime:MODULE_ID=$(docker exec etendo-db-1 psql -U {bbdd.user} -d {bbdd.sid} -t -c \ "SELECT ad_module_id FROM ad_module WHERE javapackage = '{module}';" | tr -d ' ')
To find dbPrefix of a module, query:
SELECT name FROM ad_module_dbprefix WHERE ad_module_id = (
SELECT ad_module_id FROM ad_module WHERE javapackage = 'com.mycompany.mymodule'
);
CRITICAL: Always use the correct JAVA_HOME. Without it, Gradle fails with "Unsupported class file major version".
Detect JAVA_HOME automatically:
# macOS with multiple JDKs:
JAVA_HOME=$(/usr/libexec/java_home -v 17 2>/dev/null || echo "$JAVA_HOME")
# Or read from gradle.properties if org.gradle.java.home is set
Always prefix Gradle commands with JAVA_HOME:
JAVA_HOME={java_home} ./gradlew {task}
| Task | Use for |
|---|---|
./gradlew setup | Initialize config/Openbravo.properties from gradle.properties |
./gradlew install | Full initial install (DB schema + WAR) |
./gradlew smartbuild | Compile + deploy after any change |
./gradlew update.database | Apply model changes to DB |
./gradlew export.database -Dmodule=X | Export AD changes to XML |
./gradlew generate.entities | Regenerate Java entities after column changes |
./gradlew resources.up | Start Docker services |
./gradlew resources.down | Stop Docker services |
./gradlew expandCore | Expand core source (source mode) |
Correct order for full deploy:
resources.down -> export.database -> resources.up -> (wait 15s) -> generate.entities -> smartbuild
export.database requires Tomcat DOWN. generate.entities and smartbuild require Tomcat UP (DB up).
Post-deploy behavior: After smartbuild deploys the WAR, Docker Tomcat auto-reloads (~30-60s), but local Tomcat requires a manual restart.
Operations that modify the database (SQL, update.database) or compile+deploy (smartbuild) should be shown to the dev first with a clear summary of what will happen. Never execute without confirmation.
Exceptions: reading files, showing status, dry-run analysis.
Always use get_uuid() (Etendo's built-in function) to generate IDs in SQL. Never use gen_random_uuid() or hardcoded UUIDs.
-- CORRECT
v_table_id TEXT := get_uuid();
INSERT INTO ad_table (ad_table_id, ...) VALUES (get_uuid(), ...);
-- WRONG -- do not use these
v_table_id TEXT := REPLACE(gen_random_uuid()::text, '-', '');
get_uuid() returns a 32-char hex string without dashes, which is the format Etendo expects for all AD IDs.
NEVER use heredoc (docker exec ... << 'EOF') — it hangs indefinitely.
Correct pattern: write to /tmp + docker cp + psql -f:
# 1. Write SQL to a local file
cat > /tmp/my_script.sql << 'EOF'
SELECT 1;
EOF
# 2. Copy to the container and execute
docker cp /tmp/my_script.sql etendo-db-1:/tmp/my_script.sql
docker exec etendo-db-1 psql -U {bbdd.user} -d {bbdd.sid} -f /tmp/my_script.sql
For short, single-line queries, docker exec -c is acceptable:
docker exec etendo-db-1 psql -U {bbdd.user} -d {bbdd.sid} -t -c "SELECT 1;"
Always prefer webhooks over manual SQL for Application Dictionary operations.
The webhooks from the com.etendoerp.copilot.devassistant module automate:
CreateAndRegisterTableCreateColumnRegisterWindowRegisterTabRegisterFieldsRegisterBGProcessWebHookSee skills/etendo-_webhooks/SKILL.md for the full invocation pattern.
Prerequisite: Tomcat UP + Bearer token (obtained via /sws/login). See _guidelines section 13.
If a Gradle task fails, read the relevant logs:
# Docker Tomcat logs
docker exec etendo-tomcat-1 sh -c 'tail -n 200 /usr/local/tomcat/logs/openbravo.log'
# Docker DB logs
docker logs etendo-db-1 --tail 50
# Gradle output is sufficient for compile errors