Initial commit of the Flutter Cursor Generator project, including the core generator tool, project brief schema, example project setup, and CI configuration. Added README documentation outlining repository structure, quick start guide, and detailed descriptions of features and architecture pillars.

This commit is contained in:
2026-05-12 22:29:55 +05:30
commit 6dfb9a8aa5
72 changed files with 4542 additions and 0 deletions
@@ -0,0 +1,180 @@
// wizard.dart — Interactive CLI wizard (brief discovery UX gap fix)
import 'dart:io';
import 'logger.dart';
class Wizard {
static Future<void> run({required String outputPath}) async {
Logger.info('\n🧙 cursor_gen Interactive Wizard');
Logger.info('' * 42);
Logger.info('Answer the questions below to generate your project-brief.yaml.\n');
final answers = <String, dynamic>{};
// Project basics
answers['name'] = _ask('Project name', hint: 'ShopEasy');
answers['package'] = _ask('Package ID', hint: 'com.company.shopeasy');
answers['description'] = _ask('Short description', hint: 'E-commerce app with real-time inventory');
answers['scale'] = _askChoice('Project scale', ['small', 'medium', 'large'], defaultIdx: 1);
Logger.info('');
// Stack
answers['state_management'] = _askChoice('State management',
['bloc', 'riverpod', 'getx', 'hooks_riverpod'], defaultIdx: 1);
answers['architecture'] = _askChoice('Architecture',
['clean', 'feature_first', 'mvvm', 'mvc', 'layered'], defaultIdx: 0);
answers['routing'] = _askChoice('Routing',
['gorouter', 'getx_nav', 'auto_route'], defaultIdx: 0);
answers['backend'] = _askChoice('Backend(s)',
['firebase', 'supabase', 'rest', 'firebase+rest', 'supabase+rest'], defaultIdx: 2);
answers['auth'] = _askChoice('Auth method',
['firebase_auth', 'supabase_auth', 'jwt_rest', 'oauth2', 'none'], defaultIdx: 4);
Logger.info('');
// Platforms (Pillar 4)
answers['platforms'] = _askMultiChoice('Target platforms',
['ios', 'android', 'web', 'desktop'], defaults: [0, 1]);
// Codegen (Pillar 4)
answers['codegen'] = _askMultiChoice('Code generation tools (optional)',
['freezed', 'json_serializable', 'injectable', 'retrofit'], defaults: []);
Logger.info('');
// Environments
final flavorsInput = _ask('Build flavors (comma-separated)', hint: 'dev,staging,prod');
answers['flavors'] = flavorsInput.split(',').map((e) => e.trim()).toList();
answers['cicd'] = _askChoice('CI/CD', ['github_actions', 'codemagic', 'fastlane', 'none'], defaultIdx: 0);
// Testing
answers['testing_depth'] = _askChoice('Testing depth',
['unit_widget', 'integration', 'e2e', 'full'], defaultIdx: 0);
// Localization
final l10n = _askBool('Enable localization / i18n?', defaultYes: false);
answers['i18n'] = l10n;
// Telemetry opt-in (Pillar 6)
final telemetry = _askBool('\nOpt in to local telemetry (rule trigger logging, stored locally)?', defaultYes: false);
answers['telemetry'] = telemetry;
Logger.info('');
final yaml = _buildYaml(answers);
final file = File(outputPath);
await file.writeAsString(yaml);
Logger.success('✔ project-brief.yaml written to $outputPath');
Logger.info('\nNext: dart run cursor_gen --validate → dart run cursor_gen');
}
static String _ask(String label, {String hint = ''}) {
final display = hint.isNotEmpty ? '$label [$hint]: ' : '$label: ';
stdout.write(' $display');
final input = stdin.readLineSync()?.trim() ?? '';
return input.isEmpty ? hint : input;
}
static String _askChoice(String label, List<String> options, {int defaultIdx = 0}) {
Logger.info(' $label:');
for (var i = 0; i < options.length; i++) {
final marker = i == defaultIdx ? '' : '';
Logger.dim(' ${i + 1}) ${options[i]}$marker');
}
stdout.write(' Choice [${defaultIdx + 1}]: ');
final input = stdin.readLineSync()?.trim() ?? '';
if (input.isEmpty) return options[defaultIdx];
final idx = int.tryParse(input);
if (idx != null && idx >= 1 && idx <= options.length) return options[idx - 1];
return options[defaultIdx];
}
static List<String> _askMultiChoice(String label, List<String> options, {List<int> defaults = const []}) {
Logger.info(' $label (comma-separated numbers, or leave blank):');
for (var i = 0; i < options.length; i++) {
final marker = defaults.contains(i) ? '' : '';
Logger.dim(' ${i + 1}) ${options[i]}$marker');
}
final defaultStr = defaults.map((d) => '${d + 1}').join(',');
stdout.write(' Choices [$defaultStr]: ');
final input = stdin.readLineSync()?.trim() ?? '';
if (input.isEmpty) return defaults.map((d) => options[d]).toList();
return input.split(',')
.map((s) => int.tryParse(s.trim()))
.where((i) => i != null && i >= 1 && i <= options.length)
.map((i) => options[i! - 1])
.toList();
}
static bool _askBool(String label, {bool defaultYes = false}) {
final hint = defaultYes ? 'Y/n' : 'y/N';
stdout.write(' $label ($hint): ');
final input = stdin.readLineSync()?.trim().toLowerCase() ?? '';
if (input.isEmpty) return defaultYes;
return input == 'y' || input == 'yes';
}
static String _buildYaml(Map<String, dynamic> a) {
final platforms = (a['platforms'] as List).map((p) => '"$p"').join(', ');
final codegen = (a['codegen'] as List).map((c) => '"$c"').join(', ');
final flavors = (a['flavors'] as List).map((f) => '"$f"').join(', ');
return '''# project-brief.yaml — cursor_gen configuration
# Generated by cursor_gen --wizard
# Run: dart run cursor_gen to generate .cursor/
# Run: dart run cursor_gen --refresh to update after changes
# Pillar 1: Pin to template version for reproducibility
cursor_templates_version: "1.0.0"
project:
name: "${a['name']}"
package: "${a['package']}"
description: "${a['description']}"
scale: "${a['scale']}"
stack:
state_management: "${a['state_management']}"
routing: "${a['routing']}"
architecture: "${a['architecture']}"
backend: "${a['backend']}"
auth: "${a['auth']}"
# Pillar 4: Platform targets
platforms: [$platforms]
# Pillar 4: Code generation tools
codegen: [$codegen]
environments:
flavors: [$flavors]
cicd: "${a['cicd']}"
testing:
depth: "${a['testing_depth']}"
e2e_tool: "patrol"
design:
source: "none"
figma_url: ""
api_docs:
format: "none"
path: ""
references:
repos: []
local_paths: []
features:
modules: []
special: []
localization:
enabled: ${a['i18n']}
locales: ["en"]
# Pillar 6: Opt-in local telemetry (logs rule trigger frequency locally)
telemetry_opt_in: ${a['telemetry']}
''';
}
}