From dev-assistant
/etendo:module — Create or configure an Etendo module
npx claudepluginhub etendosoftware/etendo_claude_marketplace --plugin dev-assistantThis skill uses the workspace's default tool permissions.
**Arguments:** `$ARGUMENTS` (optional: `create`, `info`, or module java package)
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.
Arguments: $ARGUMENTS (optional: create, info, or module java package)
First, read skills/etendo-_guidelines/SKILL.md and skills/etendo-_context/SKILL.md.
For AD XML structure (AD_MODULE, AD_MODULE_DBPREFIX), read references/application-dictionary.md. For publishing to Nexus, read references/module-publishing.md.
A module in Etendo is the unit of deployment. All custom tables, windows, Java code, and configurations belong to a module. Modules are identified by a Java package name (e.g. com.mycompany.mymodule) and a DB prefix (e.g. MYMOD).
These EtendoRX headless endpoints provide read/query capabilities and some creation operations. Use them for validation before creation and for info/search operations. The base URL is {ETENDO_URL}/sws/com.etendoerp.etendorx.datasource/.
| Endpoint | Methods | Use for |
|---|---|---|
moduleHeader | GET, POST, PUT | Search modules by name/javapackage, create module header |
moduleDBPrefix | GET, POST, PUT | Check if a DB prefix exists, create prefix |
moduleDependency | GET, POST, PUT | List/add dependencies |
moduleDataPackage | GET, POST, PUT | Create the data package entry (no webhook equivalent) |
All endpoints require Bearer token authentication:
ETENDO_TOKEN="..." # From context or Etendo login
curl -s -H "Authorization: Bearer ${ETENDO_TOKEN}" \
"${ETENDO_URL}/sws/com.etendoerp.etendorx.datasource/moduleHeader?javaPackage=com.example.mymod"
Strategy: Use webhooks (
CreateModule,AddModuleDependency) as the primary creation method because they handle internal logic (triggers, validation). Use headless endpoints for pre-creation checks and for the Data Package step (which has no webhook).
Based on $ARGUMENTS:
create or blank -> create a new moduleinfo or {javapackage} -> show info about an existing moduleversion-bump or bump -> update the version of the active moduleFor info: search by name, javapackage, or DB prefix.
Via headless (preferred if Tomcat is running):
# Search by javapackage:
curl -s -H "Authorization: Bearer ${ETENDO_TOKEN}" \
"${ETENDO_URL}/sws/com.etendoerp.etendorx.datasource/moduleHeader?javaPackage={javapackage}"
# Search by name (partial match):
curl -s -H "Authorization: Bearer ${ETENDO_TOKEN}" \
"${ETENDO_URL}/sws/com.etendoerp.etendorx.datasource/moduleHeader?name={name}"
# Search by DB prefix:
curl -s -H "Authorization: Bearer ${ETENDO_TOKEN}" \
"${ETENDO_URL}/sws/com.etendoerp.etendorx.datasource/moduleDBPrefix?name={PREFIX}"
Via SQL (fallback):
SELECT m.javapackage, m.name, m.version, m.isindevelopment,
p.name AS dbprefix
FROM ad_module m
LEFT JOIN ad_module_dbprefix p ON p.ad_module_id = m.ad_module_id
WHERE m.javapackage = '{module}' OR m.name ILIKE '%{args}%'
OR p.name ILIKE '%{args}%'
ORDER BY m.name;
Ask conversationally, with smart defaults:
| Field | Ask | Default / Suggestion |
|---|---|---|
| Java package | Yes | com.{company}.{modulename} |
| Module name | Yes | Title case of package last segment |
| DB prefix | Yes | Uppercase abbreviation, e.g. MYMOD — must be unique |
| Description | Optional | Infer from module name if not provided |
| Version | Default | 1.0.0 |
| Dependencies | Ask | "Does this module extend another? (e.g. com.etendoerp.etendorx)" |
DB prefix rules: Must be 3 to 7 uppercase letters only. No numbers, no special characters. Examples: MYMOD, COPDEV, SMFT. Never exceed 7 characters.
Before creating anything, verify both javapackage and DB prefix are available. Use headless endpoints if Tomcat is running, or SQL otherwise:
Check javapackage uniqueness (headless):
EXISTING=$(curl -s -H "Authorization: Bearer ${ETENDO_TOKEN}" \
"${ETENDO_URL}/sws/com.etendoerp.etendorx.datasource/moduleHeader?javaPackage={javapackage}")
# If results found, the javapackage is taken
If taken, suggest {javapackage}.new or ask the user for an alternative.
Check javapackage uniqueness (SQL fallback):
SELECT ad_module_id, name FROM ad_module WHERE javapackage = '{javapackage}';
Check DB prefix uniqueness (headless):
EXISTING=$(curl -s -H "Authorization: Bearer ${ETENDO_TOKEN}" \
"${ETENDO_URL}/sws/com.etendoerp.etendorx.datasource/moduleDBPrefix?name={PREFIX}")
# If results found, the prefix is taken
Check DB prefix uniqueness (SQL fallback):
SELECT name FROM ad_module_dbprefix WHERE UPPER(name) = UPPER('{prefix}');
If taken, suggest alternatives (e.g., add a letter: MYMOD → MYMODA).
Important: When searching modules and getting multiple results, always show the list and ask the user to pick the correct one. Module configurations are sensitive — never auto-select ambiguous matches.
CRITICAL — Do NOT create XML files in this step. The
AD_MODULE_IDUUID is generated by the DB in Step 4 (via webhook or SQL). XML files are written AFTER Step 4 once you have the realMODULE_ID. This ensures the XML and DB are always in sync. Never write XMLs manually before registering in the DB —export.databaseis the authoritative source, not hand-crafted XML.
Create only the directories and non-XML files now:
modules/{javapackage}/
build.gradle <- minimal module build file
etendo-resources/
META-INF/
beans.xml <- required for CDI/Weld (even if unused now)
src/
{com/mycompany/mymodule}/ <- Java source root (empty initially)
src-db/
database/
model/
tables/ <- table XML definitions (alter.database reads this)
sourcedata/
(XML files are created in Step 4b, after MODULE_ID is known)
beans.xml — create this file immediately. Every module must have it so that Weld (CDI) can detect injectable classes added later. Missing beans.xml causes silent CDI failures that are hard to trace:
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
bean-discovery-mode="all" version="2.0">
</beans>
Use the CreateModule webhook (requires Tomcat running + API key):
ETENDO_URL=$(cat .etendo/context.json | python3 -c "import sys,json; print(json.load(sys.stdin).get('etendoUrl','http://localhost:8080/etendo'))")
RESP=$(curl -s -X POST "${ETENDO_URL}/webhooks/CreateModule" \
-H "Authorization: Bearer ${ETENDO_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"Name\": \"{name}\",
\"JavaPackage\": \"{javapackage}\",
\"DBPrefix\": \"{PREFIX}\",
\"Description\": \"{description}\",
\"Version\": \"1.0.0\"
}")
MODULE_ID=$(echo $RESP | python3 -c "import sys,json,re; r=json.load(sys.stdin); m=re.search(r'ID:\s*([A-F0-9a-f]{32})',r.get('message','')); print(m.group(1) if m else r.get('error','FAILED'))")
echo "Module ID: $MODULE_ID"
If Tomcat is not running, fall back to SQL:
DO $$
DECLARE
v_module_id TEXT := get_uuid();
v_prefix_id TEXT := get_uuid();
BEGIN
INSERT INTO AD_MODULE (AD_MODULE_ID, AD_CLIENT_ID, AD_ORG_ID, ISACTIVE, CREATED, CREATEDBY, UPDATED, UPDATEDBY,
NAME, VERSION, DESCRIPTION, JAVAPACKAGE, TYPE, ISINDEVELOPMENT,
ISTRANSLATIONREQUIRED, ISREGISTERED, HASCHARTOFACCOUNTS,
ISTRANSLATIONMODULE, LICENSETYPE)
VALUES (v_module_id, '0', '0', 'Y', now(), '0', now(), '0',
'{name}', '1.0.0', '{description}', '{javapackage}', 'M', 'Y',
'N', 'N', 'N', 'N', 'ETENDO');
INSERT INTO AD_MODULE_DBPREFIX (AD_MODULE_DBPREFIX_ID, AD_CLIENT_ID, AD_ORG_ID, ISACTIVE,
CREATED, CREATEDBY, UPDATED, UPDATEDBY, NAME, AD_MODULE_ID)
VALUES (v_prefix_id, '0', '0', 'Y', now(), '0', now(), '0', '{PREFIX}', v_module_id);
RAISE NOTICE 'MODULE_ID: %', v_module_id;
END $$;
Step 4b: Write XML files — now that you have MODULE_ID, create the sourcedata XML files:
src-db/database/sourcedata/AD_MODULE.xml:
<?xml version='1.0' encoding='UTF-8'?>
<data>
<!--{MODULE_ID}--><AD_MODULE>
<!--{MODULE_ID}--> <AD_MODULE_ID><![CDATA[{MODULE_ID}]]></AD_MODULE_ID>
<!--{MODULE_ID}--> <AD_CLIENT_ID><![CDATA[0]]></AD_CLIENT_ID>
<!--{MODULE_ID}--> <AD_ORG_ID><![CDATA[0]]></AD_ORG_ID>
<!--{MODULE_ID}--> <ISACTIVE><![CDATA[Y]]></ISACTIVE>
<!--{MODULE_ID}--> <NAME><![CDATA[{name}]]></NAME>
<!--{MODULE_ID}--> <VERSION><![CDATA[1.0.0]]></VERSION>
<!--{MODULE_ID}--> <DESCRIPTION><![CDATA[{description}]]></DESCRIPTION>
<!--{MODULE_ID}--> <JAVAPACKAGE><![CDATA[{javapackage}]]></JAVAPACKAGE>
<!--{MODULE_ID}--> <TYPE><![CDATA[M]]></TYPE>
<!--{MODULE_ID}--> <ISINDEVELOPMENT><![CDATA[Y]]></ISINDEVELOPMENT>
<!--{MODULE_ID}--> <ISTRANSLATIONREQUIRED><![CDATA[N]]></ISTRANSLATIONREQUIRED>
<!--{MODULE_ID}--> <ISREGISTERED><![CDATA[N]]></ISREGISTERED>
<!--{MODULE_ID}--> <HASCHARTOFACCOUNTS><![CDATA[N]]></HASCHARTOFACCOUNTS>
<!--{MODULE_ID}--> <ISTRANSLATIONMODULE><![CDATA[N]]></ISTRANSLATIONMODULE>
<!--{MODULE_ID}--> <LICENSETYPE><![CDATA[ETENDO]]></LICENSETYPE>
<!--{MODULE_ID}--></AD_MODULE>
</data>
After writing all XML files, run export.database to synchronize (see _guidelines section 4). This guarantees the XMLs reflect the exact state of the DB.
Add dependencies (if specified in Step 2):
Every module needs at least a dependency on core (DependsOnModuleID="0"). Use the AddModuleDependency webhook:
# Dependency on Etendo core (almost always required):
curl -s -X POST "${ETENDO_URL}/webhooks/AddModuleDependency" \
-H "Authorization: Bearer ${ETENDO_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"ModuleID\": \"${MODULE_ID}\",
\"DependsOnModuleID\": \"0\",
\"FirstVersion\": \"3.0.0\",
\"Enforcement\": \"MAJOR\"
}"
# Dependency on another module (e.g., com.etendoerp.etendorx):
curl -s -X POST "${ETENDO_URL}/webhooks/AddModuleDependency" \
-H "Authorization: Bearer ${ETENDO_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"ModuleID\": \"${MODULE_ID}\",
\"DependsOnJavaPackage\": \"{dependency_javapackage}\",
\"FirstVersion\": \"1.0.0\",
\"Enforcement\": \"MAJOR\"
}"
If Tomcat is not running, add dependencies via SQL (see _webhooks skill for the AD_MODULE_DEPENDENCY schema).
Create the Data Package (required):
Every module needs a Data Package entry. This has no webhook equivalent — use the headless endpoint:
curl -s -X POST "${ETENDO_URL}/sws/com.etendoerp.etendorx.datasource/moduleDataPackage" \
-H "Authorization: Bearer ${ETENDO_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"module\": \"${MODULE_ID}\",
\"javaPackage\": \"{javapackage}.data\"
}"
If Tomcat is not running, fall back to SQL:
INSERT INTO AD_PACKAGE (AD_PACKAGE_ID, AD_CLIENT_ID, AD_ORG_ID, ISACTIVE,
CREATED, CREATEDBY, UPDATED, UPDATEDBY,
AD_MODULE_ID, JAVAPACKAGE, NAME)
VALUES (get_uuid(), '0', '0', 'Y', now(), '0', now(), '0',
'{MODULE_ID}', '{javapackage}.data', '{name} Data Package');
Write to .etendo/context.json (see _context skill for field definitions):
{
"module": "{javapackage}",
"modulePath": "modules/{javapackage}",
"dbPrefix": "{PREFIX}",
"etendoUrl": "http://localhost:{port}/{context.name}"
}
If the module should be listed in build.gradle dependencies (for JAR mode modules):
Show: "Add this to build.gradle dependencies? implementation('{group}:{name}:[version]')"
Only relevant for modules sourced from a repository. For in-development local modules, the modules/ directory is already scanned automatically.
When the user requests a version bump (e.g., "bump version to 1.1.0", "update version"), follow this sequence:
If no version is specified, ask: "What should the new version be? (current: {current_version})"
Follow semantic versioning: MAJOR.MINOR.PATCH
There are two places where the version must be updated:
1. AD_MODULE.xml — the source of truth for the AD:
# Find the current version line and update it:
# Path: modules/{javapackage}/src-db/database/sourcedata/AD_MODULE.xml
# Change: <VERSION><![CDATA[{old_version}]]></VERSION>
# To: <VERSION><![CDATA[{new_version}]]></VERSION>
2. build.gradle — the Gradle build descriptor:
# Find and update version in modules/{javapackage}/build.gradle
# Common patterns:
# version = '{old_version}'
# moduleVersion = '{old_version}'
Use the Read and Edit tools to make these changes. After editing:
# Verify both files have the same version:
grep -r "version" modules/{javapackage}/build.gradle
grep "VERSION" modules/{javapackage}/src-db/database/sourcedata/AD_MODULE.xml
If Tomcat is running, update the version in the DB:
docker exec etendo-db-1 psql -U {bbdd.user} -d {bbdd.sid} -t -c \
"UPDATE ad_module SET version = '{new_version}' WHERE javapackage = '{javapackage}';"
Or via SQL file if Docker:
cat > /tmp/bump_version.sql << 'EOF'
UPDATE ad_module SET version = '{new_version}' WHERE javapackage = '{javapackage}';
EOF
docker cp /tmp/bump_version.sql etendo-db-1:/tmp/bump_version.sql
docker exec etendo-db-1 psql -U {bbdd.user} -d {bbdd.sid} -f /tmp/bump_version.sql
+ Version bumped: {javapackage}
Old version: {old_version}
New version: {new_version}
Files updated:
modules/{javapackage}/build.gradle
modules/{javapackage}/src-db/database/sourcedata/AD_MODULE.xml
Next steps:
/etendo:smartbuild -> recompile and deploy with new version
+ Module created: {javapackage}
DB prefix: {PREFIX}
Data package: {javapackage}.data
Path: modules/{javapackage}/
Module ID: {UUID}
Context set to this module.
Next steps:
/etendo:alter-db -> create tables for this module
/etendo:window -> create windows
/etendo:smartbuild -> compile and deploy