mad-pro-cli 1.1.0 → 1.2.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/index.js CHANGED
@@ -2,17 +2,46 @@
2
2
 
3
3
  import { program } from 'commander';
4
4
  import initCommand from './lib/commands/init.js';
5
+ import addCommand from './lib/commands/add.js';
6
+ import listCommand from './lib/commands/list.js';
7
+ import createCommand from './lib/commands/create.js';
8
+ import doctorCommand from './lib/commands/doctor.js';
9
+ import promptCommand from './lib/commands/prompt.js';
5
10
 
6
11
  program
7
12
  .name('mad-pro')
8
13
  .description('MAD Pro CLI - Boost your project with AI Expert Skills')
9
- .version('1.0.0');
14
+ .version('1.2.0');
10
15
 
11
16
  program
12
17
  .command('init')
13
- .description('Initialize AI skills for your project')
14
- .option('--ai <name>', 'Specify the AI bridge to use (e.g., antigravity)')
18
+ .description('Initialize AI skills for your project (Interactive Wizard)')
15
19
  .option('--ide <type>', 'Specify the IDE (vscode, cursor, windsurf, android-studio, intellij, sublime, vim, neovim, zed, code-insiders)')
16
20
  .action(initCommand);
17
21
 
22
+ program
23
+ .command('create [name]')
24
+ .description('Scaffold a fresh MAD-compliant Android project')
25
+ .action(createCommand);
26
+
27
+ program
28
+ .command('add')
29
+ .description('Add specific skills to your current project')
30
+ .action(addCommand);
31
+
32
+ program
33
+ .command('list')
34
+ .description('List skills installed in the current project')
35
+ .action(listCommand);
36
+
37
+ program
38
+ .command('doctor')
39
+ .description('Check architectural health of your project')
40
+ .action(doctorCommand);
41
+
42
+ program
43
+ .command('prompt')
44
+ .description('Generate optimized system instructions for your AI Agent')
45
+ .action(promptCommand);
46
+
18
47
  program.parse();
@@ -0,0 +1,68 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import inquirer from 'inquirer';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ export default async function addCommand() {
11
+ console.log(chalk.bold.magenta('\nšŸ“„ MAD Pro CLI - Add Skills'));
12
+ console.log(chalk.gray('Search and install specific skills to your project...\n'));
13
+
14
+ const rootDir = process.cwd();
15
+ const sourceDir = path.join(__dirname, '../../../references');
16
+
17
+ if (!fs.existsSync(sourceDir)) {
18
+ console.error(chalk.red('Error: Skill library not found. Please reinstall the CLI.'));
19
+ process.exit(1);
20
+ }
21
+
22
+ // 1. Get all available skills
23
+ const coreFiles = fs.readdirSync(sourceDir).filter(f => f.endsWith('.md'));
24
+ const industryPath = path.join(sourceDir, 'industry');
25
+ const industryFiles = fs.readdirSync(industryPath).filter(f => f.endsWith('.md')).map(f => `industry/${f}`);
26
+
27
+ const allSkills = [...coreFiles, ...industryFiles].map(f => ({
28
+ name: f.replace('.md', '').replace(/_/g, ' ').toUpperCase(),
29
+ value: f
30
+ })).sort((a, b) => a.name.localeCompare(b.name));
31
+
32
+ // 2. Interactive Selection (Searchable)
33
+ const answers = await inquirer.prompt([
34
+ {
35
+ type: 'checkbox',
36
+ name: 'skills',
37
+ message: 'Search and select skills to add:',
38
+ choices: allSkills,
39
+ pageSize: 15
40
+ }
41
+ ]);
42
+
43
+ if (answers.skills.length === 0) {
44
+ console.log(chalk.yellow('\nNo skills selected. Exit.'));
45
+ return;
46
+ }
47
+
48
+ // 3. Perform Copying
49
+ const targetRefDir = path.join(rootDir, 'references');
50
+ const targetIndustryDir = path.join(targetRefDir, 'industry');
51
+ await fs.ensureDir(targetRefDir);
52
+ await fs.ensureDir(targetIndustryDir);
53
+
54
+ console.log(chalk.cyan('\nšŸ“¦ Adding selected skills...'));
55
+
56
+ for (const skill of answers.skills) {
57
+ const src = path.join(sourceDir, skill);
58
+ const dest = path.join(targetRefDir, skill);
59
+
60
+ if (fs.existsSync(src)) {
61
+ await fs.copy(src, dest);
62
+ console.log(`${chalk.green('āœ“')} Added ${chalk.gray(skill)}`);
63
+ }
64
+ }
65
+
66
+ console.log(chalk.bold.green(`\nšŸŽ‰ Successfully added ${answers.skills.length} skills!`));
67
+ console.log(chalk.gray('Your AI Agent can now use these additional patterns.\n'));
68
+ }
@@ -0,0 +1,644 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import inquirer from 'inquirer';
5
+ import { execSync } from 'child_process';
6
+ import initCommand from './init.js';
7
+
8
+ export default async function createCommand(projectName) {
9
+ console.log(chalk.bold.magenta('\nšŸ—ļø MAD Pro - New Project Scaffold'));
10
+ console.log(chalk.gray('Generate a production-ready Android/KMP project.\n'));
11
+
12
+ if (!projectName) {
13
+ const res = await inquirer.prompt([{
14
+ type: 'input',
15
+ name: 'name',
16
+ message: 'Project name:',
17
+ default: 'MyMadApp',
18
+ validate: v => /^[A-Za-z][A-Za-z0-9]*$/.test(v) || 'Must start with a letter, alphanumeric only.'
19
+ }]);
20
+ projectName = res.name;
21
+ }
22
+
23
+ const answers = await inquirer.prompt([
24
+ {
25
+ type: 'list',
26
+ name: 'templateType',
27
+ message: 'Select Template:',
28
+ choices: [
29
+ { name: 'KrossWave KMP (Recommended - Multi-module, KMP, Koin, Voyager)', value: 'kmp' },
30
+ { name: 'Basic Android (Single module, Hilt, Navigation Compose)', value: 'basic' }
31
+ ]
32
+ },
33
+ {
34
+ type: 'input',
35
+ name: 'package',
36
+ message: 'Package name:',
37
+ default: `com.example.${projectName.toLowerCase()}`,
38
+ validate: v => /^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)+$/.test(v) || 'Invalid package (e.g. com.example.app)'
39
+ }
40
+ ]);
41
+
42
+ let basicAnswers = {};
43
+ if (answers.templateType === 'basic') {
44
+ basicAnswers = await inquirer.prompt([
45
+ {
46
+ type: 'list',
47
+ name: 'minSdk',
48
+ message: 'Minimum SDK:',
49
+ choices: ['24', '26', '28', '30'],
50
+ default: '26'
51
+ },
52
+ {
53
+ type: 'list',
54
+ name: 'targetSdk',
55
+ message: 'Target SDK:',
56
+ choices: ['34', '35'],
57
+ default: '35'
58
+ }
59
+ ]);
60
+ }
61
+
62
+ const targetDir = path.join(process.cwd(), projectName);
63
+
64
+ if (fs.existsSync(targetDir)) {
65
+ console.error(chalk.red(`\nāŒ Directory "${projectName}" already exists.`));
66
+ process.exit(1);
67
+ }
68
+
69
+ const pkg = answers.package;
70
+ const pkgPath = pkg.replace(/\./g, '/');
71
+
72
+ if (answers.templateType === 'kmp') {
73
+ console.log(chalk.cyan(`\nšŸ”Ø Cloning KrossWave KMP template...\n`));
74
+ try {
75
+ execSync(`git clone --depth 1 https://github.com/derohimat/android-kmp-starter.git "${projectName}"`, { stdio: 'inherit' });
76
+ await fs.remove(path.join(targetDir, '.git'));
77
+ console.log(` ${chalk.green('āœ“')} Template cloned successfully`);
78
+
79
+ console.log(chalk.cyan(`\nšŸ”Ø Customizing package name to ${pkg}...\n`));
80
+ const oldPkg = 'com.derohimat.krosswave';
81
+ const oldAppName = 'KrossWave'; // typical app name in that repo
82
+
83
+ async function processFolder(dir) {
84
+ const files = await fs.readdir(dir);
85
+ for (const file of files) {
86
+ const fullPath = path.join(dir, file);
87
+ const stat = await fs.stat(fullPath);
88
+
89
+ if (stat.isDirectory()) {
90
+ await processFolder(fullPath);
91
+ } else {
92
+ const ext = path.extname(fullPath);
93
+ const validExts = ['.kt', '.xml', '.gradle', '.kts', '.pro', '.properties', '.toml', '.md'];
94
+ if (validExts.includes(ext) || file === 'gradlew' || file === 'gradlew.bat') {
95
+ try {
96
+ let content = await fs.readFile(fullPath, 'utf8');
97
+ let modified = false;
98
+
99
+ if (content.includes(oldPkg)) {
100
+ content = content.replace(new RegExp(oldPkg, 'g'), pkg);
101
+ modified = true;
102
+ }
103
+ if (content.includes(oldAppName)) {
104
+ content = content.replace(new RegExp(oldAppName, 'g'), projectName);
105
+ modified = true;
106
+ }
107
+
108
+ if (modified) {
109
+ await fs.writeFile(fullPath, content, 'utf8');
110
+ }
111
+ } catch (e) {
112
+ // ignore read errors on binary files
113
+ }
114
+ }
115
+ }
116
+ }
117
+ }
118
+
119
+ await processFolder(targetDir);
120
+
121
+ async function restructureDirs(currentDir) {
122
+ const items = await fs.readdir(currentDir);
123
+ for (const item of items) {
124
+ const fullPath = path.join(currentDir, item);
125
+ if ((await fs.stat(fullPath)).isDirectory()) {
126
+ if (fullPath.endsWith(path.join('com', 'derohimat', 'krosswave'))) {
127
+ const newPath = fullPath.replace(path.join('com', 'derohimat', 'krosswave'), path.join(...pkg.split('.')));
128
+ await fs.move(fullPath, newPath, { overwrite: true });
129
+
130
+ const derohimatDir = path.dirname(fullPath);
131
+ const comDir = path.dirname(derohimatDir);
132
+ try {
133
+ await fs.rmdir(derohimatDir);
134
+ await fs.rmdir(comDir);
135
+ } catch(e) { }
136
+ } else {
137
+ await restructureDirs(fullPath);
138
+ }
139
+ }
140
+ }
141
+ }
142
+ await restructureDirs(targetDir);
143
+
144
+ console.log(` ${chalk.green('āœ“')} Package renamed to ${pkg}`);
145
+ console.log(` ${chalk.green('āœ“')} App name updated to ${projectName}\n`);
146
+
147
+ } catch (e) {
148
+ console.error(chalk.red(`\nāŒ Failed to clone or process template: ${e.message}`));
149
+ process.exit(1);
150
+ }
151
+ } else {
152
+ // Generate Basic Android (existing logic)
153
+ console.log(chalk.cyan(`\nšŸ”Ø Generating Basic Android project structure...\n`));
154
+
155
+ const compileSdk = basicAnswers.targetSdk;
156
+ const minSdk = basicAnswers.minSdk;
157
+ const targetSdk = basicAnswers.targetSdk;
158
+
159
+ const dirs = [
160
+ `app/src/main/java/${pkgPath}`,
161
+ `app/src/main/java/${pkgPath}/ui/theme`,
162
+ `app/src/main/java/${pkgPath}/ui/navigation`,
163
+ `app/src/main/java/${pkgPath}/ui/screens/home`,
164
+ `app/src/main/java/${pkgPath}/domain/model`,
165
+ `app/src/main/java/${pkgPath}/domain/usecase`,
166
+ `app/src/main/java/${pkgPath}/domain/repository`,
167
+ `app/src/main/java/${pkgPath}/data/repository`,
168
+ `app/src/main/java/${pkgPath}/data/remote`,
169
+ `app/src/main/java/${pkgPath}/data/local`,
170
+ `app/src/main/java/${pkgPath}/di`,
171
+ `app/src/main/res/drawable`,
172
+ `app/src/main/res/values`,
173
+ `app/src/main/res/mipmap-hdpi`,
174
+ `app/src/test/java/${pkgPath}`,
175
+ `app/src/androidTest/java/${pkgPath}`,
176
+ `gradle/wrapper`,
177
+ ];
178
+
179
+ for (const d of dirs) {
180
+ await fs.ensureDir(path.join(targetDir, d));
181
+ }
182
+
183
+ const files = {};
184
+ files['build.gradle.kts'] = `// Top-level build file
185
+ plugins {
186
+ alias(libs.plugins.android.application) apply false
187
+ alias(libs.plugins.kotlin.android) apply false
188
+ alias(libs.plugins.kotlin.compose) apply false
189
+ alias(libs.plugins.hilt.android) apply false
190
+ alias(libs.plugins.ksp) apply false
191
+ }
192
+ `;
193
+
194
+ files['settings.gradle.kts'] = `pluginManagement {
195
+ repositories {
196
+ google {
197
+ content {
198
+ includeGroupByRegex("com\\\\.android.*")
199
+ includeGroupByRegex("com\\\\.google.*")
200
+ includeGroupByRegex("androidx.*")
201
+ }
202
+ }
203
+ mavenCentral()
204
+ gradlePluginPortal()
205
+ }
206
+ }
207
+ dependencyResolutionManagement {
208
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
209
+ repositories {
210
+ google()
211
+ mavenCentral()
212
+ }
213
+ }
214
+
215
+ rootProject.name = "${projectName}"
216
+ include(":app")
217
+ `;
218
+
219
+ files['gradle/libs.versions.toml'] = `[versions]
220
+ agp = "8.7.3"
221
+ kotlin = "2.1.0"
222
+ ksp = "2.1.0-1.0.29"
223
+ coreKtx = "1.15.0"
224
+ lifecycle = "2.8.7"
225
+ activityCompose = "1.9.3"
226
+ composeBom = "2024.12.01"
227
+ hilt = "2.53.1"
228
+ hiltNavCompose = "1.2.0"
229
+ navigation = "2.8.5"
230
+ coroutines = "1.9.0"
231
+ junit = "4.13.2"
232
+ junitExt = "1.2.1"
233
+ espresso = "3.6.1"
234
+
235
+ [libraries]
236
+ androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
237
+ androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
238
+ androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" }
239
+ androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" }
240
+ androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
241
+ androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
242
+ androidx-ui = { group = "androidx.compose.ui", name = "ui" }
243
+ androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
244
+ androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
245
+ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
246
+ androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
247
+ androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" }
248
+ hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
249
+ hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" }
250
+ hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavCompose" }
251
+ kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }
252
+ junit = { group = "junit", name = "junit", version.ref = "junit" }
253
+ androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitExt" }
254
+ androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso" }
255
+
256
+ [plugins]
257
+ android-application = { id = "com.android.application", version.ref = "agp" }
258
+ kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
259
+ kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
260
+ hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
261
+ ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
262
+ `;
263
+
264
+ files['app/build.gradle.kts'] = `plugins {
265
+ alias(libs.plugins.android.application)
266
+ alias(libs.plugins.kotlin.android)
267
+ alias(libs.plugins.kotlin.compose)
268
+ alias(libs.plugins.hilt.android)
269
+ alias(libs.plugins.ksp)
270
+ }
271
+
272
+ android {
273
+ namespace = "${pkg}"
274
+ compileSdk = ${compileSdk}
275
+
276
+ defaultConfig {
277
+ applicationId = "${pkg}"
278
+ minSdk = ${minSdk}
279
+ targetSdk = ${targetSdk}
280
+ versionCode = 1
281
+ versionName = "1.0"
282
+
283
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
284
+ }
285
+
286
+ buildTypes {
287
+ release {
288
+ isMinifyEnabled = true
289
+ proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
290
+ }
291
+ }
292
+
293
+ compileOptions {
294
+ sourceCompatibility = JavaVersion.VERSION_17
295
+ targetCompatibility = JavaVersion.VERSION_17
296
+ }
297
+
298
+ kotlinOptions {
299
+ jvmTarget = "17"
300
+ }
301
+
302
+ buildFeatures {
303
+ compose = true
304
+ }
305
+ }
306
+
307
+ dependencies {
308
+ // Core
309
+ implementation(libs.androidx.core.ktx)
310
+ implementation(libs.androidx.lifecycle.runtime.ktx)
311
+ implementation(libs.androidx.lifecycle.viewmodel.compose)
312
+ implementation(libs.androidx.lifecycle.runtime.compose)
313
+ implementation(libs.androidx.activity.compose)
314
+
315
+ // Compose
316
+ implementation(platform(libs.androidx.compose.bom))
317
+ implementation(libs.androidx.ui)
318
+ implementation(libs.androidx.ui.graphics)
319
+ implementation(libs.androidx.ui.tooling.preview)
320
+ implementation(libs.androidx.material3)
321
+
322
+ // Navigation
323
+ implementation(libs.androidx.navigation.compose)
324
+
325
+ // Hilt
326
+ implementation(libs.hilt.android)
327
+ ksp(libs.hilt.compiler)
328
+ implementation(libs.hilt.navigation.compose)
329
+
330
+ // Coroutines
331
+ implementation(libs.kotlinx.coroutines.android)
332
+
333
+ // Testing
334
+ testImplementation(libs.junit)
335
+ androidTestImplementation(libs.androidx.junit)
336
+ androidTestImplementation(libs.androidx.espresso.core)
337
+ debugImplementation(libs.androidx.ui.tooling)
338
+ }
339
+ `;
340
+
341
+ files['gradle.properties'] = `# Project-wide Gradle settings.
342
+ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
343
+ org.gradle.parallel=true
344
+ org.gradle.caching=true
345
+ android.useAndroidX=true
346
+ kotlin.code.style=official
347
+ android.nonTransitiveRClass=true
348
+ `;
349
+
350
+ files['gradle/wrapper/gradle-wrapper.properties'] = `distributionBase=GRADLE_USER_HOME
351
+ distributionPath=wrapper/dists
352
+ distributionUrl=https\\://services.gradle.org/distributions/gradle-8.9-bin.zip
353
+ networkTimeout=10000
354
+ validateDistributionUrl=true
355
+ zipStoreBase=GRADLE_USER_HOME
356
+ zipStorePath=wrapper/dists
357
+ `;
358
+
359
+ files['app/src/main/AndroidManifest.xml'] = `<?xml version="1.0" encoding="utf-8"?>
360
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
361
+
362
+ <application
363
+ android:name=".${projectName}Application"
364
+ android:allowBackup=true
365
+ android:icon="@mipmap/ic_launcher"
366
+ android:label="${projectName}"
367
+ android:supportsRtl="true"
368
+ android:theme="@android:style/Theme.Material.Light.NoActionBar">
369
+ <activity
370
+ android:name=".MainActivity"
371
+ android:exported="true">
372
+ <intent-filter>
373
+ <action android:name="android.intent.action.MAIN" />
374
+ <category android:name="android.intent.category.LAUNCHER" />
375
+ </intent-filter>
376
+ </activity>
377
+ </application>
378
+
379
+ </manifest>
380
+ `;
381
+
382
+ files[`app/src/main/java/${pkgPath}/${projectName}Application.kt`] = `package ${pkg}
383
+
384
+ import android.app.Application
385
+ import dagger.hilt.android.HiltAndroidApp
386
+
387
+ @HiltAndroidApp
388
+ class ${projectName}Application : Application()
389
+ `;
390
+
391
+ files[`app/src/main/java/${pkgPath}/MainActivity.kt`] = `package ${pkg}
392
+
393
+ import android.os.Bundle
394
+ import androidx.activity.ComponentActivity
395
+ import androidx.activity.compose.setContent
396
+ import androidx.activity.enableEdgeToEdge
397
+ import ${pkg}.ui.navigation.AppNavHost
398
+ import ${pkg}.ui.theme.${projectName}Theme
399
+ import dagger.hilt.android.AndroidEntryPoint
400
+
401
+ @AndroidEntryPoint
402
+ class MainActivity : ComponentActivity() {
403
+ override fun onCreate(savedInstanceState: Bundle?) {
404
+ super.onCreate(savedInstanceState)
405
+ enableEdgeToEdge()
406
+ setContent {
407
+ ${projectName}Theme {
408
+ AppNavHost()
409
+ }
410
+ }
411
+ }
412
+ }
413
+ `;
414
+
415
+ files[`app/src/main/java/${pkgPath}/ui/theme/Color.kt`] = `package ${pkg}.ui.theme
416
+
417
+ import androidx.compose.ui.graphics.Color
418
+
419
+ val Purple80 = Color(0xFFD0BCFF)
420
+ val PurpleGrey80 = Color(0xFFCCC2DC)
421
+ val Pink80 = Color(0xFFEFB8C8)
422
+
423
+ val Purple40 = Color(0xFF6650a4)
424
+ val PurpleGrey40 = Color(0xFF625b71)
425
+ val Pink40 = Color(0xFF7D5260)
426
+ `;
427
+
428
+ files[`app/src/main/java/${pkgPath}/ui/theme/Type.kt`] = `package ${pkg}.ui.theme
429
+
430
+ import androidx.compose.material3.Typography
431
+ import androidx.compose.ui.text.TextStyle
432
+ import androidx.compose.ui.text.font.FontWeight
433
+ import androidx.compose.ui.unit.sp
434
+
435
+ val Typography = Typography(
436
+ displayLarge = TextStyle(
437
+ fontWeight = FontWeight.Bold,
438
+ fontSize = 57.sp,
439
+ lineHeight = 64.sp,
440
+ letterSpacing = (-0.25).sp
441
+ ),
442
+ headlineMedium = TextStyle(
443
+ fontWeight = FontWeight.SemiBold,
444
+ fontSize = 28.sp,
445
+ lineHeight = 36.sp
446
+ ),
447
+ bodyLarge = TextStyle(
448
+ fontWeight = FontWeight.Normal,
449
+ fontSize = 16.sp,
450
+ lineHeight = 24.sp,
451
+ letterSpacing = 0.5.sp
452
+ ),
453
+ labelSmall = TextStyle(
454
+ fontWeight = FontWeight.Medium,
455
+ fontSize = 11.sp,
456
+ lineHeight = 16.sp,
457
+ letterSpacing = 0.5.sp
458
+ )
459
+ )
460
+ `;
461
+
462
+ files[`app/src/main/java/${pkgPath}/ui/theme/Theme.kt`] = `package ${pkg}.ui.theme
463
+
464
+ import android.os.Build
465
+ import androidx.compose.foundation.isSystemInDarkTheme
466
+ import androidx.compose.material3.MaterialTheme
467
+ import androidx.compose.material3.darkColorScheme
468
+ import androidx.compose.material3.dynamicDarkColorScheme
469
+ import androidx.compose.material3.dynamicLightColorScheme
470
+ import androidx.compose.material3.lightColorScheme
471
+ import androidx.compose.runtime.Composable
472
+ import androidx.compose.ui.platform.LocalContext
473
+
474
+ private val DarkColorScheme = darkColorScheme(
475
+ primary = Purple80,
476
+ secondary = PurpleGrey80,
477
+ tertiary = Pink80
478
+ )
479
+
480
+ private val LightColorScheme = lightColorScheme(
481
+ primary = Purple40,
482
+ secondary = PurpleGrey40,
483
+ tertiary = Pink40
484
+ )
485
+
486
+ @Composable
487
+ fun ${projectName}Theme(
488
+ darkTheme: Boolean = isSystemInDarkTheme(),
489
+ dynamicColor: Boolean = true,
490
+ content: @Composable () -> Unit
491
+ ) {
492
+ val colorScheme = when {
493
+ dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
494
+ val context = LocalContext.current
495
+ if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
496
+ }
497
+ darkTheme -> DarkColorScheme
498
+ else -> LightColorScheme
499
+ }
500
+
501
+ MaterialTheme(
502
+ colorScheme = colorScheme,
503
+ typography = Typography,
504
+ content = content
505
+ )
506
+ }
507
+ `;
508
+
509
+ files[`app/src/main/java/${pkgPath}/ui/navigation/AppNavHost.kt`] = `package ${pkg}.ui.navigation
510
+
511
+ import androidx.compose.runtime.Composable
512
+ import androidx.navigation.compose.NavHost
513
+ import androidx.navigation.compose.composable
514
+ import androidx.navigation.compose.rememberNavController
515
+ import ${pkg}.ui.screens.home.HomeScreen
516
+
517
+ @Composable
518
+ fun AppNavHost() {
519
+ val navController = rememberNavController()
520
+ NavHost(navController = navController, startDestination = "home") {
521
+ composable("home") {
522
+ HomeScreen()
523
+ }
524
+ }
525
+ }
526
+ `;
527
+
528
+ files[`app/src/main/java/${pkgPath}/ui/screens/home/HomeScreen.kt`] = `package ${pkg}.ui.screens.home
529
+
530
+ import androidx.compose.foundation.layout.Box
531
+ import androidx.compose.foundation.layout.fillMaxSize
532
+ import androidx.compose.foundation.layout.padding
533
+ import androidx.compose.material3.ExperimentalMaterial3Api
534
+ import androidx.compose.material3.MaterialTheme
535
+ import androidx.compose.material3.Scaffold
536
+ import androidx.compose.material3.Text
537
+ import androidx.compose.material3.TopAppBar
538
+ import androidx.compose.runtime.Composable
539
+ import androidx.compose.ui.Alignment
540
+ import androidx.compose.ui.Modifier
541
+ import androidx.hilt.navigation.compose.hiltViewModel
542
+
543
+ @OptIn(ExperimentalMaterial3Api::class)
544
+ @Composable
545
+ fun HomeScreen(viewModel: HomeViewModel = hiltViewModel()) {
546
+ Scaffold(
547
+ topBar = {
548
+ TopAppBar(title = { Text("${projectName}") })
549
+ }
550
+ ) { padding ->
551
+ Box(
552
+ modifier = Modifier
553
+ .fillMaxSize()
554
+ .padding(padding),
555
+ contentAlignment = Alignment.Center
556
+ ) {
557
+ Text(
558
+ text = "Welcome to ${projectName}!",
559
+ style = MaterialTheme.typography.headlineMedium
560
+ )
561
+ }
562
+ }
563
+ }
564
+ `;
565
+
566
+ files[`app/src/main/java/${pkgPath}/ui/screens/home/HomeViewModel.kt`] = `package ${pkg}.ui.screens.home
567
+
568
+ import androidx.lifecycle.ViewModel
569
+ import dagger.hilt.android.lifecycle.HiltViewModel
570
+ import javax.inject.Inject
571
+
572
+ @HiltViewModel
573
+ class HomeViewModel @Inject constructor() : ViewModel()
574
+ `;
575
+
576
+ files[`app/src/main/java/${pkgPath}/di/AppModule.kt`] = `package ${pkg}.di
577
+
578
+ import dagger.Module
579
+ import dagger.hilt.InstallIn
580
+ import dagger.hilt.components.SingletonComponent
581
+
582
+ @Module
583
+ @InstallIn(SingletonComponent::class)
584
+ object AppModule {
585
+ // Provide your app-wide dependencies here
586
+ }
587
+ `;
588
+
589
+ files['app/src/main/res/values/strings.xml'] = `<resources>
590
+ <string name="app_name">${projectName}</string>
591
+ </resources>
592
+ `;
593
+
594
+ files['app/src/main/res/values/colors.xml'] = `<?xml version="1.0" encoding="utf-8"?>
595
+ <resources>
596
+ <color name="black">#FF000000</color>
597
+ <color name="white">#FFFFFFFF</color>
598
+ </resources>
599
+ `;
600
+
601
+ files['app/proguard-rules.pro'] = `# Add project specific ProGuard rules here.
602
+ # -keep class ${pkg}.data.remote.dto.** { *; }
603
+ `;
604
+
605
+ files['.gitignore'] = `*.iml
606
+ .gradle
607
+ /local.properties
608
+ /.idea
609
+ .DS_Store
610
+ /build
611
+ /captures
612
+ .externalNativeBuild
613
+ .cxx
614
+ local.properties
615
+ `;
616
+
617
+ let fileCount = 0;
618
+ for (const [filePath, content] of Object.entries(files)) {
619
+ const fullPath = path.join(targetDir, filePath);
620
+ await fs.ensureDir(path.dirname(fullPath));
621
+ await fs.writeFile(fullPath, content);
622
+ fileCount++;
623
+ }
624
+
625
+ console.log(` ${chalk.green('āœ“')} Generated ${fileCount} files.`);
626
+ }
627
+
628
+ // ── Chain to Skill Wizard ───────────────────────────────
629
+ console.log(chalk.yellow('\nā© Launching Skill Wizard...\n'));
630
+
631
+ const originalCwd = process.cwd();
632
+ process.chdir(targetDir);
633
+ await initCommand({});
634
+ process.chdir(originalCwd);
635
+
636
+ console.log(chalk.bold.green(`\nšŸš€ Project "${projectName}" is ready!`));
637
+ console.log(chalk.white(`\n cd ${projectName}`));
638
+
639
+ if (answers.templateType === 'kmp') {
640
+ console.log(chalk.white(` ./gradlew composeApp:run\n`));
641
+ } else {
642
+ console.log(chalk.white(` ./gradlew assembleDebug\n`));
643
+ }
644
+ }
@@ -0,0 +1,69 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+
5
+ export default async function doctorCommand() {
6
+ console.log(chalk.bold.magenta('\n🩺 MAD Pro Doctor - Project Audit'));
7
+ console.log(chalk.gray('Checking your project for architectural health...\n'));
8
+
9
+ const rootDir = process.cwd();
10
+
11
+ const checks = [
12
+ { name: 'MAD Pro Initialization', path: 'references/', type: 'dir', critical: true, help: 'Run `mad-pro init` to install base skills.' },
13
+ { name: 'Skill Master Index', path: 'references/SKILL.md', type: 'file', critical: false, help: 'Master index (SKILL.md) is missing. This weakens AI context.' },
14
+ { name: 'Clean Arch: UI Layer', path: 'app/src/main/java/', type: 'dir', contains: 'ui', critical: false, help: 'Missing "ui" package. Conventional MAD apps should have it.' },
15
+ { name: 'Clean Arch: Data Layer', path: 'app/src/main/java/', type: 'dir', contains: 'data', critical: false, help: 'Missing "data" package. Offline-first apps need a data source.' },
16
+ { name: 'Hilt Dependency Injection', path: 'app/build.gradle.kts', type: 'content', contains: 'dagger.hilt', critical: false, help: 'Consider using Hilt for easier dependency management.' },
17
+ ];
18
+
19
+ let issues = 0;
20
+
21
+ for (const check of checks) {
22
+ let passed = false;
23
+ const fullPath = path.join(rootDir, check.path);
24
+
25
+ if (check.type === 'dir') {
26
+ if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {
27
+ if (check.contains) {
28
+ passed = findFolderRecursive(fullPath, check.contains);
29
+ } else {
30
+ passed = true;
31
+ }
32
+ }
33
+ } else if (check.type === 'file') {
34
+ passed = fs.existsSync(fullPath) && fs.statSync(fullPath).isFile();
35
+ } else if (check.type === 'content') {
36
+ if (fs.existsSync(fullPath)) {
37
+ const content = fs.readFileSync(fullPath, 'utf8');
38
+ passed = content.includes(check.contains);
39
+ }
40
+ }
41
+
42
+ if (passed) {
43
+ console.log(`${chalk.green('āœ“')} ${check.name}`);
44
+ } else {
45
+ issues++;
46
+ const prefix = check.critical ? chalk.red('āŒ') : chalk.yellow('āš ļø');
47
+ console.log(`${prefix} ${check.name}`);
48
+ console.log(chalk.gray(` šŸ’” Recommendation: ${check.help}`));
49
+ }
50
+ }
51
+
52
+ if (issues === 0) {
53
+ console.log(chalk.bold.green('\nšŸŽ‰ Your project looks 100% MAD-compliant!'));
54
+ } else {
55
+ console.log(chalk.bold.yellow(`\nāš ļø Found ${issues} items to improve.`));
56
+ }
57
+ }
58
+
59
+ function findFolderRecursive(startDir, targetName) {
60
+ const files = fs.readdirSync(startDir);
61
+ for (const file of files) {
62
+ const fullPath = path.join(startDir, file);
63
+ if (fs.statSync(fullPath).isDirectory()) {
64
+ if (file.toLowerCase() === targetName.toLowerCase()) return true;
65
+ if (findFolderRecursive(fullPath, targetName)) return true;
66
+ }
67
+ }
68
+ return false;
69
+ }
@@ -1,63 +1,150 @@
1
- import path from 'path';
2
1
  import fs from 'fs-extra';
2
+ import path from 'path';
3
3
  import chalk from 'chalk';
4
+ import inquirer from 'inquirer';
4
5
  import { fileURLToPath } from 'url';
5
6
 
6
7
  const __filename = fileURLToPath(import.meta.url);
7
8
  const __dirname = path.dirname(__filename);
8
9
 
9
10
  export default async function initCommand(options) {
10
- const targetDir = process.cwd();
11
- const aiName = options.ai || 'antigravity';
12
-
13
- console.log(chalk.blue(`Initializing project with ${aiName} skills...`));
14
-
15
- if (aiName === 'antigravity') {
16
- try {
17
- const ide = options.ide || 'vscode';
18
-
19
- // Mapping IDE to destination path
20
- const idePaths = {
21
- 'vscode': path.join('.agent', 'skills', 'mad-skills'),
22
- 'code-insiders': path.join('.agent', 'skills', 'mad-skills'),
23
- 'cursor': path.join('.cursor', 'skills', 'mad-skills'),
24
- 'windsurf': path.join('.windsurf', 'skills', 'mad-skills'),
25
- 'android-studio': path.join('.idea', 'skills', 'mad-skills'),
26
- 'intellij': path.join('.idea', 'skills', 'mad-skills'),
27
- 'sublime': path.join('.sublime', 'skills', 'mad-skills'),
28
- 'vim': path.join('.vim', 'skills', 'mad-skills'),
29
- 'neovim': path.join('.vim', 'skills', 'mad-skills'),
30
- 'zed': path.join('.zed', 'skills', 'mad-skills'),
31
- 'antigravity': path.join('.agent', 'skills', 'mad-skills'),
32
- 'fleet': path.join('.fleet', 'skills', 'mad-skills'),
33
- 'nova': path.join('.nova', 'skills', 'mad-skills'),
34
- 'xcode': path.join('Xcode', 'skills', 'mad-skills'),
35
- 'webstorm': path.join('.idea', 'skills', 'mad-skills')
36
- };
37
-
38
- const relativeDestPath = idePaths[ide.toLowerCase()] || idePaths['vscode'];
39
- const destinationPath = path.join(targetDir, relativeDestPath);
40
- const sourceSkillsPath = path.resolve(__dirname, '../../templates/mad-skills');
41
-
42
- console.log(chalk.yellow(`Installing skills for ${chalk.bold(ide)} to ${destinationPath}...`));
43
-
44
- await fs.ensureDir(destinationPath);
45
-
46
- // Copy SKILL.md
47
- await fs.copy(path.join(sourceSkillsPath, 'SKILL.md'), path.join(destinationPath, 'SKILL.md'));
48
-
49
- // Copy references directory
50
- await fs.copy(path.join(sourceSkillsPath, 'references'), path.join(destinationPath, 'references'));
51
-
52
- console.log(chalk.green('\nāœ… Success! MAD Skills (Antigravity Bridge) installed.'));
53
- console.log(chalk.cyan(`\nYour AI agent (Antigravity) can now find these skills at: ${destinationPath}`));
54
- console.log(chalk.gray('\nTo use these skills, make sure your agent is instructed to read the .agent/skills directory.'));
55
-
56
- } catch (error) {
57
- console.error(chalk.red('\nāŒ Error installing skills:'), error.message);
11
+ console.log(chalk.bold.magenta('\nšŸš€ MAD Pro CLI - Skill Wizard v1.2.0'));
12
+ console.log(chalk.gray('Setting up your Android project with architecture excellence...\n'));
13
+
14
+ // 1. Identify Source & Target
15
+ const rootDir = process.cwd();
16
+ const sourceDir = path.join(__dirname, '../../../references');
17
+
18
+ // Verify source exists
19
+ if (!fs.existsSync(sourceDir)) {
20
+ console.error(chalk.red('Error: Skill library not found. Please reinstall the CLI.'));
21
+ process.exit(1);
22
+ }
23
+
24
+ // 2. Interactive Prompts
25
+ const answers = await inquirer.prompt([
26
+ {
27
+ type: 'list',
28
+ name: 'ide',
29
+ message: 'Which IDE are you using?',
30
+ choices: [
31
+ { name: 'Cursor (Highly Recommended)', value: 'cursor' },
32
+ { name: 'Windsurf', value: 'windsurf' },
33
+ { name: 'VS Code', value: 'vscode' },
34
+ { name: 'Android Studio', value: 'android-studio' },
35
+ { name: 'Zed', value: 'zed' },
36
+ { name: 'Others', value: 'default' }
37
+ ],
38
+ default: options.ide || 'cursor'
39
+ },
40
+ {
41
+ type: 'checkbox',
42
+ name: 'categories',
43
+ message: 'Select Skill Categories to include:',
44
+ choices: [
45
+ { name: 'Core MAD (UI, Domain, Data Layers)', value: 'core', checked: true },
46
+ { name: 'Platform Capabilities (Camera, Biometrics, etc.)', value: 'platform', checked: true },
47
+ { name: 'AI & Emerging (Gemini, LLM UI, ARCore)', value: 'ai', checked: false },
48
+ { name: 'Industry Verticals (Banking, E-commerce, etc.)', value: 'industry', checked: false },
49
+ { name: 'Monetization & Play (Billing, Subs)', value: 'monetization', checked: false },
50
+ { name: 'Engineering Excellence (Modularization, CI/CD)', value: 'engineering', checked: false }
51
+ ]
52
+ }
53
+ ]);
54
+
55
+ // 3. Selective Industry prompt (only if 'industry' is selected)
56
+ let selectedIndustries = [];
57
+ if (answers.categories.includes('industry')) {
58
+ const industryPath = path.join(sourceDir, 'industry');
59
+ const allIndustries = fs.readdirSync(industryPath).filter(f => f.endsWith('.md') && f !== 'google_play_subscriptions.md' && f !== 'in_app_payments.md');
60
+
61
+ const industryChoices = await inquirer.prompt([
62
+ {
63
+ type: 'checkbox',
64
+ name: 'industries',
65
+ message: 'Which Industry Verticals do you need?',
66
+ choices: allIndustries.map(f => ({ name: f.replace('.md', '').replace(/_/g, ' ').toUpperCase(), value: f }))
67
+ }
68
+ ]);
69
+ selectedIndustries = industryChoices.industries;
70
+ }
71
+
72
+ // 4. Create Target Directories
73
+ const targetRefDir = path.join(rootDir, 'references');
74
+ const targetIndustryDir = path.join(targetRefDir, 'industry');
75
+ await fs.ensureDir(targetRefDir);
76
+ if (selectedIndustries.length > 0 || answers.categories.includes('monetization')) {
77
+ await fs.ensureDir(targetIndustryDir);
78
+ }
79
+
80
+ // 5. Build Mapping & Copy Files
81
+ const skillMapping = {
82
+ 'core': [
83
+ 'ui_layer_state.md', 'ui_patterns.md', 'ui_layouts.md', 'ui_modifiers.md', 'ui_theming.md', 'ui_navigation.md',
84
+ 'domain_layer_use_case.md', 'data_layer_room.md', 'data_layer_networking.md', 'data_layer_serialization.md'
85
+ ],
86
+ 'platform': [
87
+ 'camera_media.md', 'barcode_qr.md', 'image_editing.md', 'voice_speech.md', 'biometric_auth.md', 'security.md',
88
+ 'maps_location.md', 'push_notifications.md', 'widget_glance.md', 'app_shortcuts.md'
89
+ ],
90
+ 'ai': ['gemini_api.md', 'llm_ui_patterns.md', 'ar_core.md', 'on_device_ai.md'],
91
+ 'monetization': ['industry/in_app_payments.md', 'industry/google_play_subscriptions.md'],
92
+ 'engineering': [
93
+ 'modularization.md', 'architecture_di.md', 'concurrency.md', 'performance.md', 'testing.md',
94
+ 'design_systems.md', 'observability.md', 'ci_cd.md'
95
+ ]
96
+ };
97
+
98
+ let filesToCopy = [];
99
+ answers.categories.forEach(cat => {
100
+ if (skillMapping[cat]) {
101
+ filesToCopy = [...filesToCopy, ...skillMapping[cat]];
102
+ }
103
+ });
104
+
105
+ // Add individual industry files
106
+ selectedIndustries.forEach(industryFile => {
107
+ filesToCopy.push(`industry/${industryFile}`);
108
+ });
109
+
110
+ // Always include the master index but pruned
111
+ filesToCopy.push('../SKILL.md');
112
+
113
+ // Perform Copying
114
+ console.log(chalk.cyan('\nšŸ“¦ Installing selected skills...'));
115
+
116
+ for (const file of filesToCopy) {
117
+ const src = path.join(sourceDir, file);
118
+ const destName = file.includes('industry/') ? file.replace('industry/', 'industry/') : file;
119
+ const dest = path.join(targetRefDir, destName);
120
+
121
+ if (fs.existsSync(src)) {
122
+ await fs.copy(src, dest);
123
+ console.log(`${chalk.green('āœ“')} Added ${chalk.gray(destName)}`);
58
124
  }
59
- } else {
60
- console.log(chalk.red(`\nāŒ Unknown AI bridge: ${aiName}`));
61
- console.log(chalk.gray('Available bridges: antigravity'));
125
+ }
126
+
127
+ // 6. Project Configuration (IDE Specific)
128
+ await configureIDE(answers.ide, rootDir);
129
+
130
+ console.log(chalk.bold.green('\nšŸŽ‰ Setup Complete!'));
131
+ console.log(chalk.white(`Configured IDE: ${chalk.bold.yellow(answers.ide.toUpperCase())}`));
132
+ console.log(chalk.white(`Skills Installed: ${chalk.bold.yellow(filesToCopy.length - 1)} items`));
133
+ console.log(chalk.gray('\nYour AI Agent is now ready with expert context. Happy coding! šŸš€\n'));
134
+ }
135
+
136
+ async function configureIDE(ide, rootDir) {
137
+ const configFiles = {
138
+ 'cursor': { file: '.cursorrules', content: 'Use the patterns in the /references directory for all Android work.' },
139
+ 'windsurf': { file: '.windsurf/rules', content: 'Follow MAD Pro architecture in /references folder.' },
140
+ 'vscode': { file: '.vscode/settings.json', content: '{\n "android.skillPath": "./references"\n}' }
141
+ };
142
+
143
+ const config = configFiles[ide];
144
+ if (config) {
145
+ const targetFile = path.join(rootDir, config.file);
146
+ await fs.ensureDir(path.dirname(targetFile));
147
+ await fs.writeFile(targetFile, config.content);
148
+ console.log(`${chalk.blue('ℹ')} Created IDE config: ${chalk.gray(config.file)}`);
62
149
  }
63
150
  }
@@ -0,0 +1,42 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+
5
+ export default async function listCommand() {
6
+ const rootDir = process.cwd();
7
+ const targetRefDir = path.join(rootDir, 'references');
8
+
9
+ console.log(chalk.bold.magenta('\nšŸ“‹ MAD Pro Installed Skills'));
10
+
11
+ if (!fs.existsSync(targetRefDir)) {
12
+ console.log(chalk.yellow('\nNo skills initialized in this project. Run `mad-pro init` first.'));
13
+ return;
14
+ }
15
+
16
+ const getFiles = (dir) => {
17
+ let results = [];
18
+ const list = fs.readdirSync(dir);
19
+ list.forEach(file => {
20
+ const fullPath = path.join(dir, file);
21
+ const stat = fs.statSync(fullPath);
22
+ if (stat && stat.isDirectory()) {
23
+ results = results.concat(getFiles(fullPath));
24
+ } else if (file.endsWith('.md')) {
25
+ results.push(path.relative(targetRefDir, fullPath));
26
+ }
27
+ });
28
+ return results;
29
+ };
30
+
31
+ const installed = getFiles(targetRefDir);
32
+
33
+ if (installed.length === 0) {
34
+ console.log(chalk.gray('Zero skills found in /references directory.'));
35
+ } else {
36
+ installed.sort().forEach(file => {
37
+ console.log(`${chalk.green('āœ“')} ${file.replace('.md', '').toUpperCase().replace(/_/g, ' ')}`);
38
+ });
39
+ console.log(chalk.cyan(`\nTotal: ${installed.length} skills active.`));
40
+ }
41
+ console.log("");
42
+ }
@@ -0,0 +1,58 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+
5
+ export default async function promptCommand() {
6
+ const rootDir = process.cwd();
7
+ const targetRefDir = path.join(rootDir, 'references');
8
+
9
+ console.log(chalk.bold.magenta('\nšŸ¤– MAD Pro - Agent Instruction Generator'));
10
+
11
+ if (!fs.existsSync(targetRefDir)) {
12
+ console.log(chalk.yellow('\nNo skills found. Run `mad-pro init` first to generate a tailored prompt.'));
13
+ return;
14
+ }
15
+
16
+ // Identify installed skills
17
+ const getFiles = (dir) => {
18
+ let results = [];
19
+ const list = fs.readdirSync(dir);
20
+ list.forEach(file => {
21
+ const fullPath = path.join(dir, file);
22
+ if (fs.statSync(fullPath).isDirectory()) {
23
+ results = results.concat(getFiles(fullPath));
24
+ } else if (file.endsWith('.md')) {
25
+ results.push(path.basename(file, '.md').replace(/_/g, ' ').toUpperCase());
26
+ }
27
+ });
28
+ return results;
29
+ };
30
+
31
+ const skills = getFiles(targetRefDir);
32
+
33
+ const prompt = `
34
+ # AI AGENT INSTRUCTIONS (MAD PRO)
35
+ You are an expert Android Developer using the MAD Pro v1.2.0 framework.
36
+ Your goal is to build highly scalable, testable, and maintainable Android apps.
37
+
38
+ ## PROJECT CONTEXT
39
+ This project has the following architectural patterns installed in /references:
40
+ ${skills.map(s => `- ${s}`).join('\n')}
41
+
42
+ ## CORE RULES
43
+ 1. ALWAYS check the matching file in /references before implementing a new feature.
44
+ 2. Follow Clean Architecture: UI -> Domain -> Data layers.
45
+ 3. Use Hilt for Dependency Injection.
46
+ 4. Use Jetpack Compose with Unidirectional Data Flow (UDF).
47
+ 5. If a requested industry pattern (e.g., Banking, E-commerce) exists in /references/industry, adhere strictly to its security and structure guidelines.
48
+
49
+ ## OUTPUT STYLE
50
+ - Provide production-ready Kotlin code.
51
+ - Include KDoc for public functions.
52
+ - Ensure all UI components follow the defined Material 3 theme.
53
+ `;
54
+
55
+ console.log(chalk.white('\n--- COPY THE TEXT BELOW TO YOUR SYSTEM PROMPT / .cursorrules ---\n'));
56
+ console.log(chalk.cyan(prompt));
57
+ console.log(chalk.white('------------------------------------------------------------------\n'));
58
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mad-pro-cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -25,6 +25,7 @@
25
25
  "dependencies": {
26
26
  "chalk": "^5.6.2",
27
27
  "commander": "^14.0.3",
28
- "fs-extra": "^11.3.4"
28
+ "fs-extra": "^11.3.4",
29
+ "inquirer": "^12.4.2"
29
30
  }
30
31
  }