optikit 1.2.4 → 1.3.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.
Files changed (104) hide show
  1. package/.claude/commands/build.md +57 -0
  2. package/.claude/commands/clean.md +45 -0
  3. package/.claude/commands/generate.md +64 -0
  4. package/.claude/commands/rollback.md +67 -0
  5. package/.claude/commands/run.md +63 -0
  6. package/.claude/commands/setup.md +69 -0
  7. package/.claude/commands/sync-docs.md +148 -0
  8. package/.claude/commands/version.md +63 -0
  9. package/.claude/settings.local.json +28 -0
  10. package/.claude-plugin/marketplace.json +36 -0
  11. package/.claude-plugin/plugin.json +25 -0
  12. package/.history/README_20260325211923.md +268 -0
  13. package/.history/README_20260325212116.md +268 -0
  14. package/.history/README_20260325212234.md +266 -0
  15. package/.history/README_20260325221838.md +321 -0
  16. package/.history/README_20260325221920.md +327 -0
  17. package/.history/README_20260325222245.md +328 -0
  18. package/.history/README_20260325222247.md +328 -0
  19. package/.mcp.json +8 -0
  20. package/CHANGELOG.md +79 -46
  21. package/CLAUDE.md +57 -206
  22. package/OPTIKIT_AGENT.md +398 -0
  23. package/README.md +293 -60
  24. package/dist/cli.js +75 -241
  25. package/dist/commands/build/commands.js +146 -0
  26. package/dist/commands/build/testflight.js +14 -0
  27. package/dist/commands/clean/commands.js +41 -0
  28. package/dist/commands/clean/flutter.js +8 -14
  29. package/dist/commands/clean/ios.js +12 -15
  30. package/dist/commands/config/aliases.js +122 -0
  31. package/dist/commands/config/commands.js +49 -0
  32. package/dist/commands/config/initApp.js +191 -0
  33. package/dist/commands/config/rollback.js +15 -4
  34. package/dist/commands/config/upgrade.js +36 -0
  35. package/dist/commands/mcp/commands.js +21 -0
  36. package/dist/commands/mcp/server.js +27 -0
  37. package/dist/commands/mcp/setup.js +62 -0
  38. package/dist/commands/mcp/tools.js +359 -0
  39. package/dist/commands/project/commands.js +132 -0
  40. package/dist/commands/project/devices.js +10 -26
  41. package/dist/commands/project/doctor.js +58 -0
  42. package/dist/commands/project/generate.js +183 -30
  43. package/dist/commands/project/setup.js +10 -28
  44. package/dist/commands/project/status.js +65 -0
  45. package/dist/commands/version/bump.js +96 -82
  46. package/dist/commands/version/commands.js +63 -0
  47. package/dist/commands/version/update.js +36 -24
  48. package/dist/constants.js +6 -1
  49. package/dist/styles.js +42 -5
  50. package/dist/utils/helpers/error.js +14 -0
  51. package/dist/utils/helpers/file.js +1 -1
  52. package/dist/utils/helpers/version.js +2 -1
  53. package/dist/utils/services/backup.js +12 -1
  54. package/dist/utils/services/command.js +1 -34
  55. package/dist/utils/services/exec.js +76 -101
  56. package/dist/utils/services/logger.js +10 -4
  57. package/dist/utils/validators/validation.js +24 -12
  58. package/docs/INSTALLATION.md +72 -0
  59. package/docs/TROUBLESHOOT.md +140 -0
  60. package/docs/USAGE.md +185 -0
  61. package/docs/VERSION_MANAGEMENT.md +177 -0
  62. package/package.json +7 -11
  63. package/src/cli.ts +82 -362
  64. package/src/commands/build/commands.ts +169 -0
  65. package/src/commands/build/testflight.ts +18 -0
  66. package/src/commands/clean/commands.ts +43 -0
  67. package/src/commands/clean/flutter.ts +9 -13
  68. package/src/commands/clean/ios.ts +13 -13
  69. package/src/commands/config/aliases.ts +150 -0
  70. package/src/commands/config/commands.ts +50 -0
  71. package/src/commands/config/initApp.ts +213 -0
  72. package/src/commands/config/rollback.ts +16 -4
  73. package/src/commands/config/upgrade.ts +40 -0
  74. package/src/commands/mcp/commands.ts +23 -0
  75. package/src/commands/mcp/server.ts +35 -0
  76. package/src/commands/mcp/setup.ts +69 -0
  77. package/src/commands/mcp/tools.ts +365 -0
  78. package/src/commands/project/commands.ts +132 -0
  79. package/src/commands/project/devices.ts +11 -24
  80. package/src/commands/project/doctor.ts +81 -0
  81. package/src/commands/project/generate.ts +211 -32
  82. package/src/commands/project/setup.ts +13 -30
  83. package/src/commands/project/status.ts +72 -0
  84. package/src/commands/version/bump.ts +124 -85
  85. package/src/commands/version/commands.ts +76 -0
  86. package/src/commands/version/update.ts +86 -75
  87. package/src/constants.ts +7 -1
  88. package/src/styles.ts +49 -7
  89. package/src/utils/helpers/error.ts +16 -0
  90. package/src/utils/helpers/file.ts +1 -1
  91. package/src/utils/helpers/version.ts +2 -1
  92. package/src/utils/services/backup.ts +17 -1
  93. package/src/utils/services/command.ts +1 -58
  94. package/src/utils/services/exec.ts +92 -117
  95. package/src/utils/services/logger.ts +12 -4
  96. package/src/utils/validators/validation.ts +24 -12
  97. package/CODE_QUALITY.md +0 -398
  98. package/ENHANCEMENTS.md +0 -310
  99. package/FEATURE_ENHANCEMENTS.md +0 -435
  100. package/INSTALLATION.md +0 -118
  101. package/SAFETY_FEATURES.md +0 -396
  102. package/TROUBLESHOOT.md +0 -60
  103. package/USAGE.md +0 -388
  104. package/VERSION_MANAGEMENT.md +0 -438
@@ -3,7 +3,9 @@ import path from "path";
3
3
  import { execCommand } from "../../utils/services/exec.js";
4
4
  import { LoggerHelpers } from "../../utils/services/logger.js";
5
5
  import { validateFlutterProject, validateFlutterSdk } from "../../utils/validators/validation.js";
6
- import { createBackup } from "../../utils/services/backup.js";
6
+ import { createBackupWithCleanup } from "../../utils/services/backup.js";
7
+ import { handleCommandError } from "../../utils/helpers/error.js";
8
+ import { getFlutterCommand } from "../../utils/services/command.js";
7
9
 
8
10
  export { cleanProject };
9
11
 
@@ -22,18 +24,18 @@ async function cleanProject(noFvm: boolean) {
22
24
  }
23
25
 
24
26
  try {
27
+ const useFvm = !noFvm;
28
+
25
29
  // Step 1: Run flutter clean
26
- const flutterCommand = noFvm ? "flutter clean" : "fvm flutter clean";
27
30
  LoggerHelpers.info("Running Flutter clean...");
28
- await execCommand(flutterCommand);
31
+ await execCommand(getFlutterCommand("flutter clean", useFvm));
29
32
  LoggerHelpers.success("Flutter clean completed.");
30
33
 
31
34
  // Step 2: Remove pubspec.lock using Node.js fs (cross-platform)
32
35
  const pubspecLockPath = path.join(process.cwd(), "pubspec.lock");
33
36
  if (fs.existsSync(pubspecLockPath)) {
34
37
  LoggerHelpers.info("Removing pubspec.lock...");
35
- // Create backup before deletion
36
- createBackup(pubspecLockPath);
38
+ createBackupWithCleanup(pubspecLockPath);
37
39
  fs.unlinkSync(pubspecLockPath);
38
40
  LoggerHelpers.success("pubspec.lock removed.");
39
41
  } else {
@@ -41,18 +43,12 @@ async function cleanProject(noFvm: boolean) {
41
43
  }
42
44
 
43
45
  // Step 3: Run flutter pub get
44
- const pubGetCommand = noFvm ? "flutter pub get" : "fvm flutter pub get";
45
46
  LoggerHelpers.info("Running Flutter pub get...");
46
- await execCommand(pubGetCommand);
47
+ await execCommand(getFlutterCommand("flutter pub get", useFvm));
47
48
  LoggerHelpers.success("Flutter pub get completed.");
48
49
 
49
50
  LoggerHelpers.success("Project cleaned successfully.");
50
51
  } catch (error) {
51
- if (error instanceof Error) {
52
- LoggerHelpers.error(`Error during clean: ${error.message}`);
53
- } else {
54
- LoggerHelpers.error(`Error during clean: ${error}`);
55
- }
56
- process.exit(1);
52
+ handleCommandError(error, "Error during clean");
57
53
  }
58
54
  }
@@ -3,6 +3,8 @@ import * as path from "path";
3
3
  import { LoggerHelpers } from "../../utils/services/logger.js";
4
4
  import { execInIos, execCommand, iosDirectory, execInIosWithRetry } from "../../utils/services/exec.js";
5
5
  import { validateFlutterProject, validateIosProject } from "../../utils/validators/validation.js";
6
+ import { FLUTTER_COMMANDS, FVM_COMMANDS, RETRY_CONFIG } from "../../constants.js";
7
+ import { handleCommandError } from "../../utils/helpers/error.js";
6
8
 
7
9
  export { cleanIosProject };
8
10
 
@@ -41,9 +43,14 @@ async function ensureFlutterArtifactsExist() {
41
43
  if (!fs.existsSync(flutterXcframeworkPath)) {
42
44
  LoggerHelpers.warning("Flutter.xcframework not found.");
43
45
 
46
+ const useFvm = fs.existsSync(path.join(process.cwd(), ".fvm", "flutter_sdk"));
47
+ const precacheCommand = useFvm
48
+ ? FVM_COMMANDS.PRECACHE_IOS
49
+ : FLUTTER_COMMANDS.PRECACHE_IOS;
50
+
44
51
  try {
45
52
  LoggerHelpers.info("Downloading Flutter.xcframework...");
46
- await execCommand("fvm flutter precache --ios");
53
+ await execCommand(precacheCommand);
47
54
  LoggerHelpers.success(
48
55
  "Flutter.xcframework has been downloaded successfully."
49
56
  );
@@ -56,7 +63,7 @@ async function ensureFlutterArtifactsExist() {
56
63
  }
57
64
  } else {
58
65
  LoggerHelpers.success(
59
- "Flutter.xcframework exists. No need to run 'fvm flutter precache --ios'."
66
+ "Flutter.xcframework exists. No need to run precache."
60
67
  );
61
68
  }
62
69
  }
@@ -98,7 +105,7 @@ async function cleanIosProject(cleanCache: boolean, repoUpdate: boolean) {
98
105
  if (repoUpdate) {
99
106
  LoggerHelpers.info("Updating CocoaPods repositories...");
100
107
  try {
101
- await execInIosWithRetry("pod repo update", 3, 10000);
108
+ await execInIosWithRetry("pod repo update", RETRY_CONFIG.DEFAULT_ATTEMPTS, RETRY_CONFIG.DEFAULT_DELAY_MS);
102
109
  LoggerHelpers.success("Updated CocoaPods repositories.");
103
110
  } catch (error) {
104
111
  LoggerHelpers.error("Failed to update CocoaPods repositories. Retrying...");
@@ -106,24 +113,17 @@ async function cleanIosProject(cleanCache: boolean, repoUpdate: boolean) {
106
113
 
107
114
  LoggerHelpers.info("Installing pods with repo update...");
108
115
  try {
109
- await execInIosWithRetry("pod update", 3, 10000);
116
+ await execInIosWithRetry("pod update", RETRY_CONFIG.DEFAULT_ATTEMPTS, RETRY_CONFIG.DEFAULT_DELAY_MS);
110
117
  LoggerHelpers.success("Installed pods with repo update.");
111
118
  } catch (error) {
112
119
  LoggerHelpers.error("Failed to update pods. Retrying...");
113
120
  }
114
121
  } else {
115
122
  LoggerHelpers.info("Installing pods without repo update...");
116
- await execInIosWithRetry("pod install", 3, 10000);
123
+ await execInIosWithRetry("pod install", RETRY_CONFIG.DEFAULT_ATTEMPTS, RETRY_CONFIG.DEFAULT_DELAY_MS);
117
124
  LoggerHelpers.success("Installed pods without repo update.");
118
125
  }
119
126
  } catch (error) {
120
- if (error instanceof Error) {
121
- LoggerHelpers.error(`Error: ${error.message}`);
122
- } else if (typeof error === "string") {
123
- LoggerHelpers.error(`Error: ${error}`);
124
- } else {
125
- LoggerHelpers.error(`Unknown error: ${JSON.stringify(error)}`);
126
- }
127
- process.exit(1);
127
+ handleCommandError(error, "Error cleaning iOS project");
128
128
  }
129
129
  }
@@ -0,0 +1,150 @@
1
+ import chalk from "chalk";
2
+
3
+ interface AliasEntry {
4
+ alias: string;
5
+ command: string;
6
+ description: string;
7
+ }
8
+
9
+ interface FlagEntry {
10
+ short: string;
11
+ long: string;
12
+ commands: string;
13
+ }
14
+
15
+ const buildAliases: AliasEntry[] = [
16
+ { alias: "apk", command: "flutter-build-apk", description: "Build release APK" },
17
+ { alias: "aab", command: "flutter-build-bundle", description: "Build release AAB bundle" },
18
+ { alias: "ios", command: "flutter-build-ios", description: "Build iOS app" },
19
+ { alias: "ipa", command: "flutter-build-ipa", description: "Build release IPA" },
20
+ { alias: "tf", command: "testflight", description: "Bump iOS + build IPA" },
21
+ ];
22
+
23
+ const cleanAliases: AliasEntry[] = [
24
+ { alias: "c", command: "clean", description: "Clean Flutter (use -a for all)" },
25
+ { alias: "cf", command: "clean-flutter", description: "Clean Flutter project" },
26
+ { alias: "ci", command: "clean-ios", description: "Clean iOS / CocoaPods" },
27
+ ];
28
+
29
+ const versionAliases: AliasEntry[] = [
30
+ { alias: "v", command: "version", description: "Show current version" },
31
+ { alias: "bi", command: "bump-ios", description: "Bump iOS build number" },
32
+ { alias: "ba", command: "bump-android", description: "Bump Android build number" },
33
+ { alias: "bb", command: "bump-build", description: "Bump both build numbers" },
34
+ { alias: "vset", command: "flutter-update-version", description: "Set version manually" },
35
+ ];
36
+
37
+ const projectAliases: AliasEntry[] = [
38
+ { alias: "gen", command: "generate", description: "Generate module or repo" },
39
+ { alias: "route", command: "add-route", description: "Add route to app_router.dart" },
40
+ { alias: "devs", command: "devices", description: "List connected devices" },
41
+ { alias: "rs", command: "run-select", description: "Interactive device select & run" },
42
+ { alias: "info", command: "status", description: "Project status snapshot" },
43
+ { alias: "dr", command: "doctor", description: "Check environment health" },
44
+ ];
45
+
46
+ const openAliases: AliasEntry[] = [
47
+ { alias: "xcode", command: "open-ios", description: "Open project in Xcode" },
48
+ { alias: "studio", command: "open-android", description: "Open project in Android Studio" },
49
+ ];
50
+
51
+ const configAliases: AliasEntry[] = [
52
+ { alias: "vscode", command: "setup-vscode", description: "Setup VS Code settings" },
53
+ { alias: "undo", command: "rollback", description: "List/restore backups" },
54
+ { alias: "up", command: "upgrade", description: "Check for CLI updates" },
55
+ ];
56
+
57
+ const shortFlags: FlagEntry[] = [
58
+ { short: "-f", long: "--disable-fvm", commands: "build, clean, run, devs" },
59
+ { short: "-a", long: "--all", commands: "clean (flutter + iOS)" },
60
+ { short: "-i", long: "--ios", commands: "clean (also clean iOS)" },
61
+ { short: "-c", long: "--clean-cache", commands: "clean-ios" },
62
+ { short: "-u", long: "--repo-update", commands: "clean-ios" },
63
+ { short: "-r", long: "--with-route", commands: "gen module" },
64
+ { short: "-y", long: "--yes", commands: "bump commands" },
65
+ { short: "-r", long: "--release", commands: "run, run-select" },
66
+ { short: "-d", long: "--device", commands: "run" },
67
+ ];
68
+
69
+ const comboFlags: FlagEntry[] = [
70
+ { short: "--clean", long: "", commands: "apk, aab, ios, ipa Clean before building" },
71
+ { short: "-o", long: "--open", commands: "apk, aab, ipa, tf Open output after build" },
72
+ { short: "-b", long: "--bump", commands: "apk, aab, ios, ipa Bump version before build" },
73
+ { short: "-i", long: "--bump-ios", commands: "ipa Bump iOS build before build" },
74
+ ];
75
+
76
+ function printGroup(title: string, entries: AliasEntry[]): void {
77
+ console.log(chalk.cyan.bold(`\n ${title}`));
78
+ console.log(chalk.gray(` ${"─".repeat(title.length + 4)}`));
79
+ for (const { alias, command, description } of entries) {
80
+ const aliasCol = chalk.white.bold(alias.padEnd(10));
81
+ const cmdCol = chalk.gray(command.padEnd(24));
82
+ const descCol = chalk.gray(description);
83
+ console.log(` ${aliasCol} ${cmdCol} ${descCol}`);
84
+ }
85
+ }
86
+
87
+ function printFlags(title: string, flags: FlagEntry[]): void {
88
+ console.log(chalk.cyan.bold(`\n ${title}`));
89
+ console.log(chalk.gray(` ${"─".repeat(title.length + 4)}`));
90
+ for (const { short: s, long: l, commands } of flags) {
91
+ const shortCol = chalk.white.bold(s.padEnd(8));
92
+ const longCol = l ? chalk.gray(l.padEnd(16)) : "".padEnd(16);
93
+ const cmdCol = chalk.gray(commands);
94
+ console.log(` ${shortCol} ${longCol} ${cmdCol}`);
95
+ }
96
+ }
97
+
98
+ function printExamples(): void {
99
+ console.log(chalk.cyan.bold("\n Common Workflows"));
100
+ console.log(chalk.gray(` ${"─".repeat(22)}`));
101
+
102
+ const examples = [
103
+ ["ok apk", "Build release APK"],
104
+ ["ok apk --clean", "Clean + build APK"],
105
+ ["ok apk -b patch -o", "Bump patch + build APK + open"],
106
+ ["ok ipa --clean -o", "Clean + build IPA + open"],
107
+ ["ok ipa -i -o", "Bump iOS + build IPA + open"],
108
+ ["ok tf", "TestFlight (bump iOS + build IPA)"],
109
+ ["ok tf -o", "TestFlight + open output"],
110
+ ["", ""],
111
+ ["ok c -a", "Clean Flutter + iOS"],
112
+ ["ok ci -cu", "Clean iOS: cache + repo update"],
113
+ ["ok bump patch", "Bump patch version"],
114
+ ["ok gen module login -r", "Generate module with auto-route"],
115
+ ["ok rs -r", "Run on selected device (release)"],
116
+ ];
117
+
118
+ for (const [cmd, desc] of examples) {
119
+ if (!cmd) {
120
+ console.log();
121
+ continue;
122
+ }
123
+ console.log(` ${chalk.green(cmd.padEnd(28))} ${chalk.gray(desc)}`);
124
+ }
125
+ }
126
+
127
+ function printSetupTip(): void {
128
+ console.log(chalk.cyan.bold("\n Usage"));
129
+ console.log(chalk.gray(` ${"─".repeat(9)}`));
130
+ console.log(chalk.gray(" Use ") + chalk.white.bold("ok") + chalk.gray(" or ") + chalk.white.bold("optikit") + chalk.gray(" — both work identically."));
131
+ console.log(chalk.gray("\n Enable tab completion:"));
132
+ console.log(chalk.white.bold(" ok completion >> ~/.zshrc"));
133
+ console.log();
134
+ }
135
+
136
+ export function showAliases(): void {
137
+ console.log(chalk.cyan.bold("\n OptiKit Alias Reference"));
138
+ console.log(chalk.gray(" ═══════════════════════════"));
139
+
140
+ printGroup("Build", buildAliases);
141
+ printGroup("Clean", cleanAliases);
142
+ printGroup("Version & Bump", versionAliases);
143
+ printGroup("Project", projectAliases);
144
+ printGroup("Open", openAliases);
145
+ printGroup("Config", configAliases);
146
+ printFlags("Short Flags", shortFlags);
147
+ printFlags("Build Combo Flags", comboFlags);
148
+ printExamples();
149
+ printSetupTip();
150
+ }
@@ -0,0 +1,50 @@
1
+ import type { CommandModule } from "yargs";
2
+ import { initializeProject } from "./init.js";
3
+ import { initOpticoreApp } from "./initApp.js";
4
+ import { rollbackFiles, rollbackRestore } from "./rollback.js";
5
+ import { checkUpgrade } from "./upgrade.js";
6
+ import { showAliases } from "./aliases.js";
7
+
8
+ export const configCommands: CommandModule[] = [
9
+ {
10
+ command: "init",
11
+ describe: "Initialize OptiKit configuration in the current project",
12
+ handler: async () => { await initializeProject(); },
13
+ },
14
+ {
15
+ command: "init-app",
16
+ describe: "Scaffold Opticore app structure (main.dart, config, router)",
17
+ builder: {
18
+ "app-name": { type: "string", default: "MyApp", description: "The app name for CoreSetup configuration" },
19
+ },
20
+ handler: async (argv) => { await initOpticoreApp(argv.appName as string); },
21
+ },
22
+ {
23
+ command: "rollback",
24
+ aliases: ["undo"],
25
+ describe: "List and restore files from OptiKit backups",
26
+ builder: {
27
+ restore: { type: "number", description: "Restore backup by index number" },
28
+ before: { type: "string", description: "Show backups before date (YYYY-MM-DD)" },
29
+ },
30
+ handler: async (argv) => {
31
+ const restoreIndex = argv.restore as number | undefined;
32
+ if (restoreIndex !== undefined) {
33
+ await rollbackRestore(restoreIndex);
34
+ } else {
35
+ await rollbackFiles(argv.before as string | undefined);
36
+ }
37
+ },
38
+ },
39
+ {
40
+ command: "upgrade",
41
+ aliases: ["up"],
42
+ describe: "Check for OptiKit CLI updates",
43
+ handler: async () => { await checkUpgrade(); },
44
+ },
45
+ {
46
+ command: "aliases",
47
+ describe: "Show all command aliases, short flags, and examples",
48
+ handler: () => { showAliases(); },
49
+ },
50
+ ];
@@ -0,0 +1,213 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { LoggerHelpers } from "../../utils/services/logger.js";
4
+ import { validateFlutterProject } from "../../utils/validators/validation.js";
5
+ import { handleCommandError } from "../../utils/helpers/error.js";
6
+ import { writeFile, createDirectories } from "../../utils/helpers/file.js";
7
+
8
+ export { initOpticoreApp };
9
+
10
+ async function initOpticoreApp(appName: string): Promise<void> {
11
+ if (!validateFlutterProject()) {
12
+ process.exit(1);
13
+ }
14
+
15
+ try {
16
+ const libDir = path.join(process.cwd(), "lib");
17
+ const configDir = path.join(libDir, "config");
18
+
19
+ // Create config directory
20
+ createDirectories(configDir, []);
21
+ if (!fs.existsSync(configDir)) {
22
+ fs.mkdirSync(configDir, { recursive: true });
23
+ }
24
+
25
+ LoggerHelpers.info(`Initializing Opticore app: ${appName}...`);
26
+
27
+ // 1. Generate main.dart
28
+ generateMainDart(libDir, appName);
29
+
30
+ // 2. Generate app_router.dart
31
+ generateAppRouter(configDir);
32
+
33
+ // 3. Generate app_config_setup.dart
34
+ generateAppConfigSetup(configDir, appName);
35
+
36
+ // 4. Generate network_setup.dart
37
+ generateNetworkSetup(configDir);
38
+
39
+ LoggerHelpers.success(`Opticore app "${appName}" initialized successfully.`);
40
+ LoggerHelpers.info("\nGenerated files:");
41
+ LoggerHelpers.info(" lib/main.dart");
42
+ LoggerHelpers.info(" lib/config/app_router.dart");
43
+ LoggerHelpers.info(" lib/config/app_config_setup.dart");
44
+ LoggerHelpers.info(" lib/config/network_setup.dart");
45
+ LoggerHelpers.info("\nNext steps:");
46
+ LoggerHelpers.info(" 1. Review and customize lib/config/app_config_setup.dart");
47
+ LoggerHelpers.info(" 2. Add routes in lib/config/app_router.dart");
48
+ LoggerHelpers.info(" 3. Generate modules with: optikit generate module <name>");
49
+ LoggerHelpers.info(" 4. Register routes with: optikit add-route <name>");
50
+ } catch (error) {
51
+ handleCommandError(error, "Error initializing Opticore app");
52
+ }
53
+ }
54
+
55
+ function generateMainDart(libDir: string, appName: string) {
56
+ const mainPath = path.join(libDir, "main.dart");
57
+
58
+ if (fs.existsSync(mainPath)) {
59
+ LoggerHelpers.warning("main.dart already exists. Skipping to avoid overwrite.");
60
+ LoggerHelpers.info("To regenerate, delete lib/main.dart first.");
61
+ return;
62
+ }
63
+
64
+ const template = `import 'package:flutter/material.dart';
65
+ import 'package:opticore/opticore.dart';
66
+ import 'config/app_config_setup.dart';
67
+ import 'config/app_router.dart';
68
+ import 'config/network_setup.dart';
69
+
70
+ void main() async {
71
+ WidgetsFlutterBinding.ensureInitialized();
72
+
73
+ runApp(
74
+ CoreSetup(
75
+ appConfig: AppConfig(
76
+ appTitle: '${appName}',
77
+ theme: AppConfigSetup.theme,
78
+ initialRoute: AppRouter.initialRoute,
79
+ onGenerateRoute: AppRouter.generateRoute,
80
+ ),
81
+ prepConfig: () async {
82
+ await NetworkSetup.initialize();
83
+ // Add other initialization here (Firebase, Hive, etc.)
84
+ },
85
+ ),
86
+ );
87
+ }
88
+ `;
89
+
90
+ writeFile(mainPath, template);
91
+ LoggerHelpers.success("Created lib/main.dart");
92
+ }
93
+
94
+ function generateAppRouter(configDir: string) {
95
+ const routerPath = path.join(configDir, "app_router.dart");
96
+
97
+ const template = `import 'package:flutter/material.dart';
98
+ import 'package:opticore/opticore.dart';
99
+
100
+ class AppRouter {
101
+ static const String initialRoute = '/';
102
+
103
+ static Route<dynamic> generateRoute(RouteSettings settings) {
104
+ switch (settings.name) {
105
+ // Routes will be added here by: optikit add-route <moduleName>
106
+ // === OPTIKIT_ROUTE_ANCHOR === (do not remove this line)
107
+ default:
108
+ return MaterialPageRoute(
109
+ builder: (_) => Scaffold(
110
+ body: Center(
111
+ child: Text('No route defined for \${settings.name}'),
112
+ ),
113
+ ),
114
+ );
115
+ }
116
+ }
117
+ }
118
+ `;
119
+
120
+ writeFile(routerPath, template);
121
+ LoggerHelpers.success("Created lib/config/app_router.dart");
122
+ }
123
+
124
+ function generateAppConfigSetup(configDir: string, appName: string) {
125
+ const configPath = path.join(configDir, "app_config_setup.dart");
126
+
127
+ const template = `import 'package:flutter/material.dart';
128
+ import 'package:opticore/opticore.dart';
129
+
130
+ class AppConfigSetup {
131
+ static ThemeData get theme => ThemeData(
132
+ colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
133
+ useMaterial3: true,
134
+ );
135
+
136
+ static void initializeConfigs() {
137
+ // Toast notification colors
138
+ ToastConfig.instantiate(
139
+ const ToastConfig(
140
+ successColor: Colors.green,
141
+ errorColor: Colors.red,
142
+ infoColor: Colors.blue,
143
+ warningColor: Colors.orange,
144
+ ),
145
+ );
146
+
147
+ // API response messages
148
+ ApiResponseConfig.instantiate(
149
+ ApiResponseConfig(
150
+ errorMessage: 'Something went wrong. Please try again.',
151
+ requestTimeoutMessage: 'Request timed out. Check your connection.',
152
+ networkIssuesMessage: 'Network error. Please check your internet.',
153
+ ),
154
+ );
155
+
156
+ // Unauthenticated handler (401/403)
157
+ UnAuthenticatedConfig.instantiate(
158
+ UnAuthenticatedConfig(
159
+ onUnauthenticated: () {
160
+ // Clear user cache and navigate to login
161
+ // Example: RouteHelper.pushAndRemoveUntil(LoginScreen());
162
+ },
163
+ ),
164
+ );
165
+ }
166
+ }
167
+ `;
168
+
169
+ writeFile(configPath, template);
170
+ LoggerHelpers.success("Created lib/config/app_config_setup.dart");
171
+ }
172
+
173
+ function generateNetworkSetup(configDir: string) {
174
+ const networkPath = path.join(configDir, "network_setup.dart");
175
+
176
+ const template = `import 'package:opticore/opticore.dart';
177
+
178
+ class NetworkSetup {
179
+ static const String baseUrl = 'https://api.example.com';
180
+
181
+ static Future<void> initialize() async {
182
+ // Configure base URL
183
+ NetworkHelper.instance.setBaseUrl(baseUrl);
184
+
185
+ // Set default headers
186
+ await NetworkConfig.updateHeaders(
187
+ newHeaders: {
188
+ 'Content-Type': 'application/json',
189
+ 'Accept': 'application/json',
190
+ },
191
+ );
192
+
193
+ // Optional: Enable SSL certificate pinning
194
+ // await NetworkHelper.loadPinningCertificate();
195
+ }
196
+
197
+ static Future<void> setAuthToken(String token) async {
198
+ await NetworkConfig.updateHeaders(
199
+ newHeaders: {
200
+ 'Authorization': 'Bearer \$token',
201
+ },
202
+ );
203
+ }
204
+
205
+ static Future<void> clearAuthToken() async {
206
+ await NetworkConfig.removeHeaders(keys: ['Authorization']);
207
+ }
208
+ }
209
+ `;
210
+
211
+ writeFile(networkPath, template);
212
+ LoggerHelpers.success("Created lib/config/network_setup.dart");
213
+ }
@@ -4,16 +4,28 @@ import { LoggerHelpers } from "../../utils/services/logger.js";
4
4
  import { restoreBackup } from "../../utils/services/backup.js";
5
5
  import chalk from "chalk";
6
6
 
7
- export { rollbackFiles };
7
+ export { rollbackFiles, rollbackRestore };
8
8
 
9
9
  /**
10
10
  * Lists all available backups and allows restoration
11
11
  */
12
- async function rollbackFiles(): Promise<void> {
12
+ async function rollbackFiles(beforeDate?: string): Promise<void> {
13
13
  try {
14
14
  LoggerHelpers.info("Searching for OptiKit backups...");
15
15
 
16
- const backups = findAllBackups(process.cwd());
16
+ let backups = findAllBackups(process.cwd());
17
+
18
+ // Filter by date if --before is provided
19
+ if (beforeDate) {
20
+ const cutoff = new Date(beforeDate);
21
+ if (isNaN(cutoff.getTime())) {
22
+ LoggerHelpers.error(`Invalid date format: ${beforeDate}. Use YYYY-MM-DD.`);
23
+ process.exit(1);
24
+ }
25
+ cutoff.setHours(23, 59, 59, 999);
26
+ backups = backups.filter((b) => b.timestamp <= cutoff);
27
+ LoggerHelpers.info(`Filtering backups before ${beforeDate}...`);
28
+ }
17
29
 
18
30
  if (backups.length === 0) {
19
31
  LoggerHelpers.warning("No backups found in this project.");
@@ -94,7 +106,7 @@ async function rollbackFiles(): Promise<void> {
94
106
  /**
95
107
  * Restores a specific backup by index
96
108
  */
97
- export async function rollbackRestore(index: number): Promise<void> {
109
+ async function rollbackRestore(index: number): Promise<void> {
98
110
  try {
99
111
  const backups = findAllBackups(process.cwd());
100
112
 
@@ -0,0 +1,40 @@
1
+ import chalk from "chalk";
2
+ import { execCommandSilent } from "../../utils/services/exec.js";
3
+ import { LoggerHelpers } from "../../utils/services/logger.js";
4
+ import { createRequire } from "module";
5
+
6
+ const require = createRequire(import.meta.url);
7
+
8
+ export { checkUpgrade };
9
+
10
+ async function checkUpgrade(): Promise<void> {
11
+ try {
12
+ const packageInfo: { version: string } = require("../../../package.json");
13
+ const currentVersion = packageInfo.version;
14
+
15
+ LoggerHelpers.info("Checking for updates...");
16
+
17
+ let latestVersion: string;
18
+ try {
19
+ const output = await execCommandSilent("npm view optikit version");
20
+ latestVersion = output.trim();
21
+ } catch {
22
+ LoggerHelpers.warning("Could not check npm registry. Are you online?");
23
+ console.log(chalk.cyan("\n Current:"), chalk.white(currentVersion));
24
+ return;
25
+ }
26
+
27
+ console.log(chalk.bold("\nOptiKit Version Check\n"));
28
+ console.log(chalk.cyan(" Current:"), chalk.white(currentVersion));
29
+ console.log(chalk.cyan(" Latest: "), chalk.white(latestVersion));
30
+
31
+ if (currentVersion === latestVersion) {
32
+ console.log(chalk.green("\n You're up to date!\n"));
33
+ } else {
34
+ console.log(chalk.yellow(`\n Update available: ${currentVersion} → ${latestVersion}`));
35
+ console.log(chalk.white(" Run:"), chalk.cyan("npm install -g optikit\n"));
36
+ }
37
+ } catch (error) {
38
+ LoggerHelpers.error(`Error checking for updates: ${error instanceof Error ? error.message : error}`);
39
+ }
40
+ }
@@ -0,0 +1,23 @@
1
+ import type { CommandModule } from "yargs";
2
+ import { startMcpServer } from "./server.js";
3
+ import { setupClaude } from "./setup.js";
4
+
5
+ export const mcpCommands: CommandModule[] = [
6
+ {
7
+ command: "mcp",
8
+ describe: "Start OptiKit MCP server for Claude Code integration",
9
+ handler: async () => { await startMcpServer(); },
10
+ },
11
+ {
12
+ command: "setup-claude",
13
+ describe: "Register OptiKit MCP server with Claude Code",
14
+ builder: {
15
+ uninstall: {
16
+ type: "boolean",
17
+ default: false,
18
+ description: "Remove OptiKit MCP server from Claude Code",
19
+ },
20
+ },
21
+ handler: (argv) => { setupClaude(argv.uninstall as boolean); },
22
+ },
23
+ ];
@@ -0,0 +1,35 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { createRequire } from "module";
4
+ import { MCP_CONFIG } from "../../constants.js";
5
+ import { tools } from "./tools.js";
6
+
7
+ const require = createRequire(import.meta.url);
8
+ const packageInfo: { version: string } = require("../../../package.json");
9
+
10
+ export async function startMcpServer(): Promise<void> {
11
+ const server = new McpServer({
12
+ name: MCP_CONFIG.SERVER_NAME,
13
+ version: packageInfo.version,
14
+ });
15
+
16
+ for (const tool of tools) {
17
+ server.tool(
18
+ tool.name,
19
+ tool.description,
20
+ { ...tool.schema.shape },
21
+ async (args) => {
22
+ try {
23
+ const result = await tool.handler(args as Record<string, unknown>);
24
+ return { content: [{ type: "text" as const, text: result }] };
25
+ } catch (error) {
26
+ const message = error instanceof Error ? error.message : String(error);
27
+ return { content: [{ type: "text" as const, text: `Error: ${message}` }], isError: true };
28
+ }
29
+ },
30
+ );
31
+ }
32
+
33
+ const transport = new StdioServerTransport();
34
+ await server.connect(transport);
35
+ }