mxcli-olc-setup 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # mxcli-olc-setup
2
+
3
+ Automates [mxcli](https://github.com/mendixlabs/mxcli) setup for Mendix projects. Downloads the correct mxcli binary for your platform, initializes it, adds AI-agent skills, and configures your project's `.gitignore`.
4
+
5
+ ## Quick Start
6
+
7
+ Run from your **Mendix project root**:
8
+
9
+ ```bash
10
+ npx mxcli-olc-setup
11
+ ```
12
+
13
+ Or with an explicit path:
14
+
15
+ ```bash
16
+ npx mxcli-olc-setup /path/to/mendix-project
17
+ ```
18
+
19
+ ## What It Does
20
+
21
+ 1. **Downloads mxcli** — fetches the latest release binary for your OS/architecture
22
+ 2. **Runs `mxcli init`** — initializes mxcli with Claude and OpenCode tool support
23
+ 3. **Adds Mendix Developer Skill** — copies the AI skill file to `.ai-context/skills/`
24
+ 4. **Updates `.gitignore`** — appends entries for AI/mxcli generated files
25
+ 5. **Creates knowledge base** — generates a `project-knowledge-base.md` template for AI agents
26
+
27
+ ## Global Install
28
+
29
+ ```bash
30
+ npm install -g mxcli-olc-setup
31
+ mxcli-olc-setup
32
+ ```
33
+
34
+ ## Prerequisites
35
+
36
+ - **Node.js** >= 14
37
+ - **Internet access** (downloads mxcli from GitHub releases)
38
+ - Run from a **Mendix project directory**
39
+
40
+ ## Supported Platforms
41
+
42
+ | OS | Architecture |
43
+ |----|-------------|
44
+ | Windows | x64, arm64 |
45
+ | macOS | x64 (Intel), arm64 (Apple Silicon) |
46
+ | Linux | x64, arm64 |
@@ -0,0 +1,177 @@
1
+ # Mendix Developer Skill
2
+
3
+ This file is used for AI agent to carry out development or analysis task in Mendix project. Everytime any task to be executed in the Mendix project, please refer to this file.
4
+
5
+ ## Mendix Model Inspection Guardrail
6
+
7
+ When analyzing Mendix pages, never rely only on rendered HTML or text search. Decode the `.mpr` Unit contents and recursively inspect page/snippet widgets, especially nested DataGrid 2 `CustomWidgets$CustomWidget` properties, to find actual actions and target microflows/pages.
8
+
9
+ ## Project Search Workflow
10
+
11
+ Always inspect the `.mpr` model, not only project files.
12
+
13
+ Plain text search with `rg` often misses Mendix pages, captions, widget actions, and microflows because they are stored inside the `.mpr` database. Search both:
14
+
15
+ 1. Normal files with `rg`.
16
+ 2. The `.mpr` SQLite `Unit` table by decoding `Contents`.
17
+
18
+ Search by exact phrase first, then variants. For requirement text like `Reference Documents Review`, search:
19
+
20
+ ```text
21
+ Reference Documents Review
22
+ Reference Document Approval
23
+ ReferenceDocuments
24
+ ReferenceDocumentApproval
25
+ ApprovalOverview
26
+ ```
27
+
28
+ ## Map Labels To Mendix Artifacts
29
+
30
+ When a user-facing label is found, identify:
31
+
32
+ 1. Module name.
33
+ 2. Page or snippet name.
34
+ 3. Menu/navigation entry.
35
+ 4. Data source microflow.
36
+ 5. Action microflow or page target.
37
+ 6. Related entity.
38
+
39
+ Example output:
40
+
41
+ ```text
42
+ Reference Document Approval menu opens OMS.ReferenceDocuments_ApprovalOverview, whose header is Reference Documents Review.
43
+ ```
44
+
45
+ For requirement matching, keep a known-alias mapping table as analysis proceeds:
46
+
47
+ | User label | Technical artifact |
48
+ | --- | --- |
49
+ | OMS Tasks | `OMSHomepage.Home_OMS` / `SNP_OMSHome_OMSTask` |
50
+ | Notification Centre | `OMSHomepage.Home_OMS` / `notificationcenterdatagrid` |
51
+ | Reference Documents Review | `OMS.ReferenceDocuments_ApprovalOverview` |
52
+ | Activity Forms Table View | `ActivityFormManagement.ActivityForm_Overview` |
53
+ | Daily Morning Meeting Table View | `MeetingManagement.MeetingRecords_Overview_DailyRecords` |
54
+
55
+ ## Page And Action Tracing
56
+
57
+ For page/action analysis, trace clickable widgets. Do not stop at DOM or page name. For each relevant page/grid, inspect:
58
+
59
+ 1. `Forms$ActionButton`
60
+ 2. `Forms$MicroflowAction`
61
+ 3. `Forms$FormAction`
62
+ 4. `OnClickAction`
63
+ 5. `OnDoubleClickAction`
64
+ 6. Widget `DefaultAction`
65
+ 7. Nested widgets inside DataGrid 2 custom widgets
66
+
67
+ For DataGrid 2, inspect nested widget properties. Mendix DataGrid 2 appears as `CustomWidgets$CustomWidget`, and buttons are often nested deep inside `Object.Properties[*].Value.Objects[*]...Widgets`. Recursively scan nested widgets for `Forms$ActionButton`.
68
+
69
+ Distinguish row actions from button/link actions. Report clearly whether the action is:
70
+
71
+ 1. Row double-click/default action.
72
+ 2. Hyperlink/action button inside a column.
73
+ 3. Toolbar button.
74
+ 4. Bulk selection button.
75
+
76
+ This matters because a requirement may say "double-click line" while the implementation only has a link or button.
77
+
78
+ ## Microflow Trace Requirements
79
+
80
+ Trace called microflows until the final page. If a button calls a microflow, inspect the microflow for:
81
+
82
+ 1. Retrieve source.
83
+ 2. XPath constraints.
84
+ 3. Selected object.
85
+ 4. Empty checks.
86
+ 5. `ShowFormAction`.
87
+ 6. Final page opened.
88
+ 7. Parameter mappings.
89
+
90
+ Flag unsafe retrieve patterns as risks:
91
+
92
+ 1. `SingleObject = true` without sort.
93
+ 2. Retrieving by non-unique fields like `LotNo + ProductionLine`.
94
+ 3. No empty check before using a retrieved object.
95
+ 4. Assuming child records share the same parent/order.
96
+ 5. Unused parameters.
97
+ 6. Opening the first matching Record Sheet instead of the exact linked one.
98
+
99
+ Prefer association-based retrieval over string matching whenever possible.
100
+
101
+ Example:
102
+
103
+ ```text
104
+ Instead of finding Record Sheet by LotNo + StringProductionLine, prefer a direct association to ProductionOrder, Batch, OMSRecordSheet, or OMSRSUnitProcess.
105
+ ```
106
+
107
+ ## Navigation Review Trace Format
108
+
109
+ When reviewing navigation requirements, build a small trace:
110
+
111
+ ```text
112
+ User label:
113
+ Menu item:
114
+ Page:
115
+ Grid/snippet:
116
+ Clickable widget:
117
+ Action:
118
+ Microflow logic:
119
+ Final opened page:
120
+ Risk:
121
+ Recommendation:
122
+ ```
123
+
124
+ ## Generated Analysis Reports
125
+
126
+ Keep generated analysis reports for repetitive scanning. Prefer CSV or Markdown reports listing:
127
+
128
+ 1. Page.
129
+ 2. Grid name.
130
+ 3. Entity/data source.
131
+ 4. Buttons.
132
+ 5. Button caption.
133
+ 6. Action type.
134
+ 7. Target page/microflow.
135
+ 8. Whether it opens Record Sheet.
136
+
137
+ ## UI/UX and SCSS Guardrails
138
+
139
+ When making UI/UX or CSS styling changes in Mendix, especially in `.scss` files, do not target generated Mendix element names directly for new styles.
140
+
141
+ Avoid hardcoding selectors such as:
142
+
143
+ ```scss
144
+ .mx-name-dropDown5 { ... }
145
+ .mx-name-textBox4 { ... }
146
+ .mx-name-comboBox9 { ... }
147
+ ```
148
+
149
+ Instead, create a semantic CSS class name and apply that class to the relevant Mendix widget or container in Studio Pro.
150
+
151
+ Preferred pattern:
152
+
153
+ ```scss
154
+ .oms-decision-dropdown {
155
+ // styles here
156
+ }
157
+ ```
158
+
159
+ When adding a new style, document:
160
+
161
+ 1. The CSS class name.
162
+ 2. The widget or element where the class should be applied.
163
+ 3. The purpose of the class.
164
+
165
+ Example:
166
+
167
+ ```text
168
+ Class name: oms-decision-dropdown
169
+ Apply to: the Dynamic Yes/No decision dropdown widget
170
+ Purpose: align decision dropdown spacing and sizing in the record sheet action row
171
+ ```
172
+
173
+ Existing legacy styles that already target `.mx-name-*` selectors should not be expanded unless the task is specifically to clean up or migrate them.
174
+
175
+ ## Git ignore
176
+
177
+ Whenever AI or mxcli generated any files in the project directory, make sure the files to be included in git ignore file of the Mendix project.
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "mxcli-olc-setup",
3
+ "version": "1.0.0",
4
+ "description": "Automates mxcli setup for Mendix projects — downloads mxcli binary, runs init, adds custom skills, and configures gitignore",
5
+ "bin": {
6
+ "mxcli-olc-setup": "./setup.js"
7
+ },
8
+ "files": [
9
+ "setup.js",
10
+ "assets/",
11
+ "README.md"
12
+ ],
13
+ "engines": {
14
+ "node": ">=14"
15
+ },
16
+ "keywords": ["mendix", "mxcli", "ai", "low-code", "cli", "setup"],
17
+ "license": "UNLICENSED"
18
+ }
package/setup.js ADDED
@@ -0,0 +1,367 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const https = require('https');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+ const { execSync } = require('child_process');
9
+
10
+ const GITIGNORE_ENTRIES = [
11
+ '.ai-context',
12
+ '.claude',
13
+ '.devcontainer',
14
+ '.playwright',
15
+ 'AGENTS.md',
16
+ 'CLAUDE.md',
17
+ '.playwright-mcp',
18
+ '.mxcli',
19
+ '.mxcli-logs',
20
+ '.mxcli-plugins',
21
+ '.mxcli-plugins-lock.json',
22
+ '.mxcli-plugins-lock.json.bak',
23
+ '/outputs/',
24
+ '*.mdl',
25
+ 'project-knowledge-base.md',
26
+ '/.tools/',
27
+ ];
28
+
29
+ const PLATFORM_MAP = {
30
+ 'win32-x64': { assetPattern: /mxcli-windows-amd64\.exe$/, binaryName: 'mxcli.exe' },
31
+ 'win32-arm64': { assetPattern: /mxcli-windows-arm64\.exe$/, binaryName: 'mxcli.exe' },
32
+ 'darwin-x64': { assetPattern: /mxcli-darwin-amd64$/, binaryName: 'mxcli' },
33
+ 'darwin-arm64':{ assetPattern: /mxcli-darwin-arm64$/, binaryName: 'mxcli' },
34
+ 'linux-x64': { assetPattern: /mxcli-linux-amd64$/, binaryName: 'mxcli' },
35
+ 'linux-arm64': { assetPattern: /mxcli-linux-arm64$/, binaryName: 'mxcli' },
36
+ };
37
+
38
+ function log(msg) {
39
+ console.log(`[mxcli-olc-setup] ${msg}`);
40
+ }
41
+
42
+ function warn(msg) {
43
+ console.warn(`[mxcli-olc-setup] WARNING: ${msg}`);
44
+ }
45
+
46
+ function fail(msg) {
47
+ console.error(`[mxcli-olc-setup] ERROR: ${msg}`);
48
+ process.exit(1);
49
+ }
50
+
51
+ function printHelp() {
52
+ const pkg = require('./package.json');
53
+ console.log(`
54
+ ${pkg.name} v${pkg.version}
55
+
56
+ Automates mxcli setup for Mendix projects.
57
+
58
+ Usage:
59
+ npx mxcli-olc-setup [project-path]
60
+ mxcli-olc-setup [project-path]
61
+
62
+ Arguments:
63
+ project-path Path to the Mendix project root (default: current directory)
64
+
65
+ Options:
66
+ --help, -h Show this help message
67
+ --version, -v Show version number
68
+
69
+ What it does:
70
+ 1. Downloads the latest mxcli binary for your platform
71
+ 2. Runs mxcli init with Claude and OpenCode tools
72
+ 3. Adds the Mendix Developer Skill to .ai-context/skills/
73
+ 4. Appends AI/mxcli entries to .gitignore
74
+ 5. Creates a project-knowledge-base.md template
75
+ `);
76
+ }
77
+
78
+ function parseArgs() {
79
+ const args = process.argv.slice(2);
80
+ for (const arg of args) {
81
+ if (arg === '--help' || arg === '-h') {
82
+ printHelp();
83
+ process.exit(0);
84
+ }
85
+ if (arg === '--version' || arg === '-v') {
86
+ const pkg = require('./package.json');
87
+ console.log(pkg.version);
88
+ process.exit(0);
89
+ }
90
+ }
91
+ return args.find(a => !a.startsWith('-')) || null;
92
+ }
93
+
94
+ function getProjectRoot() {
95
+ const cliPath = parseArgs();
96
+ if (cliPath) return path.resolve(cliPath);
97
+ return process.env.INIT_CWD || process.cwd();
98
+ }
99
+
100
+ function getPlatformKey() {
101
+ const platform = os.platform();
102
+ const arch = os.arch();
103
+ const key = `${platform}-${arch}`;
104
+ if (!PLATFORM_MAP[key]) {
105
+ fail(`Unsupported platform: ${key}. Supported: ${Object.keys(PLATFORM_MAP).join(', ')}`);
106
+ }
107
+ return key;
108
+ }
109
+
110
+ function httpsGetJson(url) {
111
+ return new Promise((resolve, reject) => {
112
+ https.get(url, { headers: { 'User-Agent': 'mxcli-olc-setup' } }, (res) => {
113
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
114
+ httpsGetJson(res.headers.location).then(resolve).catch(reject);
115
+ return;
116
+ }
117
+ if (res.statusCode !== 200) {
118
+ reject(new Error(`HTTP ${res.statusCode} from ${url}`));
119
+ return;
120
+ }
121
+ let data = '';
122
+ res.on('data', chunk => data += chunk);
123
+ res.on('end', () => {
124
+ try { resolve(JSON.parse(data)); } catch (e) { reject(e); }
125
+ });
126
+ }).on('error', reject);
127
+ });
128
+ }
129
+
130
+ function downloadFile(url, destPath) {
131
+ return new Promise((resolve, reject) => {
132
+ const file = fs.createWriteStream(destPath);
133
+ let settled = false;
134
+
135
+ function finish(err) {
136
+ if (settled) return;
137
+ settled = true;
138
+ if (err) {
139
+ file.close(() => {
140
+ try { fs.unlinkSync(destPath); } catch (_) {}
141
+ reject(err);
142
+ });
143
+ } else {
144
+ process.stdout.write('\n');
145
+ file.close(resolve);
146
+ }
147
+ }
148
+
149
+ file.on('error', finish);
150
+
151
+ function doDownload(dlUrl, redirects) {
152
+ if (redirects > 10) { finish(new Error('Too many redirects')); return; }
153
+ const proto = dlUrl.startsWith('https') ? https : require('http');
154
+ const req = proto.get(dlUrl, { headers: { 'User-Agent': 'mxcli-olc-setup' } }, (res) => {
155
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
156
+ doDownload(res.headers.location, redirects + 1);
157
+ return;
158
+ }
159
+ if (res.statusCode !== 200) {
160
+ finish(new Error(`HTTP ${res.statusCode} downloading ${dlUrl}`));
161
+ return;
162
+ }
163
+ const total = parseInt(res.headers['content-length'], 10);
164
+ let downloaded = 0;
165
+ res.on('data', chunk => {
166
+ downloaded += chunk.length;
167
+ if (total) {
168
+ const pct = Math.round((downloaded / total) * 100);
169
+ process.stdout.write(`\r[mxcli-olc-setup] Downloading mxcli... ${pct}%`);
170
+ }
171
+ });
172
+ res.pipe(file);
173
+ file.on('finish', () => finish(null));
174
+ });
175
+ req.on('error', finish);
176
+ }
177
+ doDownload(url, 0);
178
+ });
179
+ }
180
+
181
+ async function getLatestRelease() {
182
+ log('Fetching latest mxcli release info...');
183
+ const release = await httpsGetJson('https://api.github.com/repos/mendixlabs/mxcli/releases/latest');
184
+ return release;
185
+ }
186
+
187
+ function findAsset(release, platformKey) {
188
+ const { assetPattern } = PLATFORM_MAP[platformKey];
189
+ const asset = release.assets.find(a => assetPattern.test(a.name));
190
+ if (!asset) {
191
+ fail(`No mxcli release asset found for ${platformKey}. Available: ${release.assets.map(a => a.name).join(', ')}`);
192
+ }
193
+ return asset;
194
+ }
195
+
196
+ async function downloadMxcli(projectRoot, release, platformKey) {
197
+ const { binaryName } = PLATFORM_MAP[platformKey];
198
+ const installDir = path.join(projectRoot, '.tools', 'mxcli');
199
+ const binaryPath = path.join(installDir, binaryName);
200
+
201
+ if (fs.existsSync(binaryPath)) {
202
+ log(`mxcli binary already exists at ${binaryPath}`);
203
+ try {
204
+ const result = execSync(`"${binaryPath}" --version`, { encoding: 'utf8', stdio: 'pipe' });
205
+ log(`Existing mxcli version: ${result.trim()}`);
206
+ return binaryPath;
207
+ } catch (e) {
208
+ warn('Existing binary failed --version check, will re-download');
209
+ }
210
+ }
211
+
212
+ fs.mkdirSync(installDir, { recursive: true });
213
+
214
+ const asset = findAsset(release, platformKey);
215
+ log(`Downloading mxcli ${release.tag_name}: ${asset.name} (${(asset.size / 1024 / 1024).toFixed(1)} MB)...`);
216
+
217
+ const tmpPath = path.join(os.tmpdir(), asset.name);
218
+ await downloadFile(asset.browser_download_url, tmpPath);
219
+
220
+ fs.copyFileSync(tmpPath, binaryPath);
221
+ try { fs.unlinkSync(tmpPath); } catch (_) {}
222
+
223
+ if (os.platform() !== 'win32') {
224
+ fs.chmodSync(binaryPath, 0o755);
225
+ }
226
+
227
+ log(`mxcli installed to ${binaryPath}`);
228
+ return binaryPath;
229
+ }
230
+
231
+ function runMxcliInit(binaryPath, projectRoot) {
232
+ log('Running mxcli init...');
233
+ try {
234
+ const result = execSync(
235
+ `"${binaryPath}" init --tool claude --tool opencode "${projectRoot}"`,
236
+ { encoding: 'utf8', stdio: 'pipe', cwd: projectRoot }
237
+ );
238
+ log('mxcli init completed successfully');
239
+ if (result.trim()) console.log(result.trim());
240
+ } catch (e) {
241
+ warn(`mxcli init had issues (may already be initialized): ${e.message}`);
242
+ if (e.stdout) console.log(e.stdout);
243
+ if (e.stderr) console.error(e.stderr);
244
+ }
245
+ }
246
+
247
+ function addCustomSkill(projectRoot) {
248
+ const skillSrc = path.join(__dirname, 'assets', 'mendix-developer-skill.md');
249
+ const skillDir = path.join(projectRoot, '.ai-context', 'skills');
250
+ const skillDest = path.join(skillDir, 'mendix-developer-skill.md');
251
+
252
+ if (!fs.existsSync(skillSrc)) {
253
+ warn(`Custom skill file not found at ${skillSrc}, skipping`);
254
+ return;
255
+ }
256
+
257
+ fs.mkdirSync(skillDir, { recursive: true });
258
+ fs.copyFileSync(skillSrc, skillDest);
259
+ log('Custom Mendix Developer Skill added to .ai-context/skills/');
260
+ }
261
+
262
+ function updateGitignore(projectRoot) {
263
+ const gitignorePath = path.join(projectRoot, '.gitignore');
264
+ let existingContent = '';
265
+
266
+ if (fs.existsSync(gitignorePath)) {
267
+ existingContent = fs.readFileSync(gitignorePath, 'utf8');
268
+ }
269
+
270
+ const existingLines = existingContent.split(/\r?\n/).map(l => l.trim());
271
+
272
+ const missing = GITIGNORE_ENTRIES.filter(entry => {
273
+ return !existingLines.some(line => line === entry || line === entry.replace(/^\//, ''));
274
+ });
275
+
276
+ if (missing.length === 0) {
277
+ log('All .gitignore entries already present, nothing to add');
278
+ return;
279
+ }
280
+
281
+ let append = '';
282
+ if (existingContent && !existingContent.endsWith('\n')) append += '\n';
283
+ append += '\n# mxcli-olc-setup auto-generated\n';
284
+ append += missing.join('\n') + '\n';
285
+
286
+ fs.appendFileSync(gitignorePath, append, 'utf8');
287
+ log(`Added ${missing.length} entries to .gitignore: ${missing.join(', ')}`);
288
+ }
289
+
290
+ function createKnowledgeBase(projectRoot) {
291
+ const kbPath = path.join(projectRoot, 'project-knowledge-base.md');
292
+
293
+ if (fs.existsSync(kbPath)) {
294
+ log('project-knowledge-base.md already exists, skipping');
295
+ return;
296
+ }
297
+
298
+ const header = `# Mendix Project Knowledge Base
299
+
300
+ > This file is maintained by AI agents working on this project.
301
+ > Every time an AI agent makes observations or discovers important context about the project, it should update this file for future reference.
302
+
303
+ ## Project Overview
304
+
305
+ <!-- Add project description, purpose, and key stakeholders -->
306
+
307
+ ## Architecture Notes
308
+
309
+ <!-- Document architectural decisions, patterns, and constraints -->
310
+
311
+ ## Module Map
312
+
313
+ <!-- List modules and their responsibilities -->
314
+
315
+ ## Key Entities
316
+
317
+ <!-- Document important domain entities and their relationships -->
318
+
319
+ ## Important Microflows
320
+
321
+ <!-- List critical microflows and what they do -->
322
+
323
+ ## Known Issues / Gotchas
324
+
325
+ <!-- Document pitfalls, edge cases, and things to watch out for -->
326
+
327
+ ## Change Log
328
+
329
+ <!-- Agents: add dated entries when you discover or change something important -->
330
+ `;
331
+
332
+ fs.writeFileSync(kbPath, header, 'utf8');
333
+ log('Created project-knowledge-base.md');
334
+ }
335
+
336
+ async function main() {
337
+ const projectRoot = getProjectRoot();
338
+ log(`Project root: ${projectRoot}`);
339
+ log(`Platform: ${os.platform()} ${os.arch()}`);
340
+
341
+ const platformKey = getPlatformKey();
342
+
343
+ let release;
344
+ try {
345
+ release = await getLatestRelease();
346
+ log(`Latest mxcli release: ${release.tag_name}`);
347
+ } catch (e) {
348
+ fail(`Failed to fetch mxcli release info: ${e.message}`);
349
+ }
350
+
351
+ const binaryPath = await downloadMxcli(projectRoot, release, platformKey);
352
+
353
+ runMxcliInit(binaryPath, projectRoot);
354
+
355
+ addCustomSkill(projectRoot);
356
+
357
+ updateGitignore(projectRoot);
358
+
359
+ createKnowledgeBase(projectRoot);
360
+
361
+ log('Setup complete!');
362
+ }
363
+
364
+ main().catch(e => {
365
+ console.error(`[mxcli-olc-setup] FATAL: ${e.message}`);
366
+ process.exit(1);
367
+ });