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 +46 -0
- package/assets/mendix-developer-skill.md +177 -0
- package/package.json +18 -0
- package/setup.js +367 -0
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
|
+
});
|