optikit 1.2.5 → 1.4.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 +31 -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 +72 -95
  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 -244
  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 +356 -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 +75 -101
  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 -371
  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 +414 -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 +98 -110
  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 -412
  104. package/VERSION_MANAGEMENT.md +0 -438
@@ -1,28 +1,41 @@
1
- import { exec, spawn } from "child_process";
2
- import { promisify } from "util";
1
+ import { spawn } from "child_process";
3
2
  import path from "path";
4
3
  import { LoggerHelpers } from "./logger.js";
4
+ import { RETRY_CONFIG } from "../../constants.js";
5
+ import { isDryRunMode, DryRunManager } from "../helpers/dryRun.js";
5
6
  export const iosDirectory = path.join(process.cwd(), "ios");
6
- const execAsync = promisify(exec);
7
- export function handleExecResult(err, stdout, stderr) {
8
- if (err) {
9
- LoggerHelpers.error(`Error: ${err.message}`);
10
- return;
7
+ /**
8
+ * Shared base executor with proper timeout support via setTimeout + kill.
9
+ * Both execCommand and execInIos delegate to this.
10
+ */
11
+ function execBase(fullCommand, options = {}) {
12
+ const { silent = false, timeoutMs } = options;
13
+ // Dry-run: log the command instead of executing
14
+ if (isDryRunMode()) {
15
+ const dryRun = new DryRunManager();
16
+ dryRun.logCommand("Execute command", fullCommand);
17
+ return Promise.resolve("");
11
18
  }
12
- if (stderr) {
13
- LoggerHelpers.warning(`stderr: ${stderr}`);
14
- }
15
- if (stdout) {
16
- LoggerHelpers.success(`stdout: ${stdout}`);
17
- }
18
- }
19
- export async function execCommand(command) {
20
19
  return new Promise((resolve, reject) => {
21
- const fullCommand = `${command}`;
22
- const process = spawn(fullCommand, { shell: true });
20
+ const childProcess = spawn(fullCommand, { shell: true });
21
+ const startTime = Date.now();
23
22
  let output = "";
24
23
  let lastLogLine = "";
25
- process.stdout.on("data", (data) => {
24
+ let killed = false;
25
+ // Enforce timeout via setTimeout + kill (spawn doesn't support timeout)
26
+ let timer;
27
+ if (timeoutMs && timeoutMs > 0) {
28
+ timer = setTimeout(() => {
29
+ killed = true;
30
+ childProcess.kill("SIGTERM");
31
+ reject(new Error(`Command timed out after ${timeoutMs / 1000}s: ${fullCommand}`));
32
+ }, timeoutMs);
33
+ }
34
+ childProcess.stdout.on("data", (data) => {
35
+ if (silent) {
36
+ output += data.toString();
37
+ return;
38
+ }
26
39
  const dataString = data.toString().trim();
27
40
  output += dataString;
28
41
  if (dataString) {
@@ -38,78 +51,29 @@ export async function execCommand(command) {
38
51
  }
39
52
  }
40
53
  });
41
- process.stderr.on("data", (data) => {
42
- LoggerHelpers.error(data.toString());
43
- });
44
- process.on("close", (code) => {
45
- if (lastLogLine) {
46
- LoggerHelpers.success(lastLogLine);
47
- }
48
- if (code !== 0) {
49
- reject(new Error(`Command failed with exit code ${code}`));
54
+ childProcess.stderr.on("data", (data) => {
55
+ if (silent) {
56
+ output += data.toString();
50
57
  }
51
58
  else {
52
- resolve(output);
59
+ LoggerHelpers.error(data.toString());
53
60
  }
54
61
  });
55
- process.on("error", (err) => {
56
- reject(new Error(`Failed to start subprocess: ${err.message}`));
57
- });
58
- });
59
- }
60
- export async function execCommandSilent(command) {
61
- return new Promise((resolve, reject) => {
62
- const fullCommand = `${command}`;
63
- const process = spawn(fullCommand, { shell: true });
64
- let output = "";
65
- process.stdout.on("data", (data) => {
66
- output += data.toString();
67
- });
68
- process.stderr.on("data", (data) => {
69
- output += data.toString();
70
- });
71
- process.on("close", (code) => {
72
- if (code !== 0) {
73
- reject(new Error(`Command failed with exit code ${code}`));
74
- }
75
- else {
76
- resolve(output);
62
+ childProcess.on("close", (code) => {
63
+ if (timer)
64
+ clearTimeout(timer);
65
+ if (killed)
66
+ return;
67
+ if (!silent && lastLogLine) {
68
+ LoggerHelpers.success(lastLogLine);
77
69
  }
78
- });
79
- process.on("error", (err) => {
80
- reject(new Error(`Failed to start subprocess: ${err.message}`));
81
- });
82
- });
83
- }
84
- export async function execInIos(command) {
85
- return new Promise((resolve, reject) => {
86
- const fullCommand = `cd "${iosDirectory}" && ${command}`;
87
- const process = spawn(fullCommand, { shell: true, timeout: 600000 });
88
- let output = "";
89
- let lastLogLine = "";
90
- process.stdout.on("data", (data) => {
91
- const dataString = data.toString().trim();
92
- output += dataString;
93
- if (dataString) {
94
- if ((dataString.endsWith("ms") || dataString.endsWith("s")) &&
95
- lastLogLine) {
96
- lastLogLine += ` ${dataString}`;
97
- }
98
- else {
99
- if (lastLogLine) {
100
- LoggerHelpers.success(lastLogLine);
101
- }
102
- lastLogLine = dataString;
70
+ // Show elapsed time for non-silent commands over 1 second
71
+ if (!silent) {
72
+ const elapsed = Date.now() - startTime;
73
+ if (elapsed > 1000) {
74
+ LoggerHelpers.info(`Completed in ${formatElapsed(elapsed)}`);
103
75
  }
104
76
  }
105
- });
106
- process.stderr.on("data", (data) => {
107
- LoggerHelpers.error(data.toString());
108
- });
109
- process.on("close", (code) => {
110
- if (lastLogLine) {
111
- LoggerHelpers.success(lastLogLine);
112
- }
113
77
  if (code !== 0) {
114
78
  reject(new Error(`Command failed with exit code ${code}`));
115
79
  }
@@ -117,40 +81,51 @@ export async function execInIos(command) {
117
81
  resolve(output);
118
82
  }
119
83
  });
120
- process.on("error", (err) => {
84
+ childProcess.on("error", (err) => {
85
+ if (timer)
86
+ clearTimeout(timer);
121
87
  reject(new Error(`Failed to start subprocess: ${err.message}`));
122
88
  });
123
89
  });
124
90
  }
125
- export async function execInIosWithRetry(command, retries = 3, delay = 5000) {
91
+ export async function execCommand(command) {
92
+ return execBase(command);
93
+ }
94
+ export async function execCommandSilent(command) {
95
+ return execBase(command, { silent: true });
96
+ }
97
+ export async function execInIos(command, timeoutMs = RETRY_CONFIG.IOS_TIMEOUT_MS) {
98
+ const fullCommand = `cd "${iosDirectory}" && ${command}`;
99
+ return execBase(fullCommand, { timeoutMs });
100
+ }
101
+ export async function execInIosWithRetry(command, retries = RETRY_CONFIG.DEFAULT_ATTEMPTS, delay = RETRY_CONFIG.DEFAULT_DELAY_MS) {
126
102
  let attempts = 0;
127
103
  let lastError;
128
104
  while (attempts < retries) {
129
105
  try {
130
- const result = await execInIos(command);
131
- return result;
106
+ return await execInIos(command);
132
107
  }
133
108
  catch (error) {
134
109
  attempts++;
135
110
  lastError = error;
136
- if (error instanceof Error) {
137
- LoggerHelpers.error(`Attempt ${attempts} failed. Error: ${error.message}`);
138
- }
139
- else {
140
- LoggerHelpers.error(`Attempt ${attempts} failed. Unknown error: ${JSON.stringify(error)}`);
141
- }
111
+ const message = error instanceof Error ? error.message : JSON.stringify(error);
112
+ LoggerHelpers.error(`Attempt ${attempts} failed. Error: ${message}`);
142
113
  if (attempts >= retries) {
143
- if (lastError instanceof Error) {
144
- LoggerHelpers.error(`Command failed after ${retries} attempts: ${lastError.message}`);
145
- }
146
- else {
147
- LoggerHelpers.error(`Command failed after ${retries} attempts: Unknown error`);
148
- }
149
- throw new Error(`Command failed after ${retries} attempts: ${lastError instanceof Error ? lastError.message : "Unknown error"}`);
114
+ const finalMessage = lastError instanceof Error ? lastError.message : "Unknown error";
115
+ LoggerHelpers.error(`Command failed after ${retries} attempts: ${finalMessage}`);
116
+ throw new Error(`Command failed after ${retries} attempts: ${finalMessage}`);
150
117
  }
151
118
  LoggerHelpers.error(`Retrying in ${delay / 1000} seconds...`);
152
- await new Promise(resolve => setTimeout(resolve, delay));
119
+ await new Promise((resolve) => setTimeout(resolve, delay));
153
120
  }
154
121
  }
155
122
  throw lastError;
156
123
  }
124
+ function formatElapsed(ms) {
125
+ const seconds = Math.floor(ms / 1000);
126
+ if (seconds < 60)
127
+ return `${seconds}s`;
128
+ const minutes = Math.floor(seconds / 60);
129
+ const remainingSeconds = seconds % 60;
130
+ return `${minutes}m ${remainingSeconds}s`;
131
+ }
@@ -1,15 +1,21 @@
1
1
  import chalk from "chalk";
2
2
  export class LoggerHelpers {
3
3
  static success(message) {
4
- console.log(chalk.green(message));
4
+ console.log(chalk.green(` ✔ ${message}`));
5
5
  }
6
6
  static error(message) {
7
- console.log(chalk.red(message));
7
+ console.log(chalk.red(` ✖ ${message}`));
8
8
  }
9
9
  static warning(message) {
10
- console.log(chalk.yellow(message));
10
+ console.log(chalk.yellow(` ⚠ ${message}`));
11
11
  }
12
12
  static info(message) {
13
- console.log(chalk.hex('#add8e6')(message));
13
+ console.log(chalk.cyan(` ℹ ${message}`));
14
+ }
15
+ static step(message) {
16
+ console.log(chalk.white(` → ${message}`));
17
+ }
18
+ static dim(message) {
19
+ console.log(chalk.gray(` ${message}`));
14
20
  }
15
21
  }
@@ -7,17 +7,21 @@ export { validateFlutterProject, validateFlutterSdk, validateIosProject, validat
7
7
  * Validates that the current directory is a Flutter project
8
8
  * by checking for pubspec.yaml
9
9
  */
10
- function validateFlutterProject() {
10
+ function validateFlutterProject(silent = false) {
11
11
  const pubspecPath = path.join(process.cwd(), "pubspec.yaml");
12
12
  if (!fs.existsSync(pubspecPath)) {
13
- LoggerHelpers.error("Not a Flutter project: pubspec.yaml not found.");
14
- LoggerHelpers.info("Please run this command from the root of a Flutter project.");
13
+ if (!silent) {
14
+ LoggerHelpers.error("Not a Flutter project: pubspec.yaml not found.");
15
+ LoggerHelpers.info("Please run this command from the root of a Flutter project.");
16
+ }
15
17
  return false;
16
18
  }
17
19
  // Check if pubspec.yaml contains Flutter SDK
18
20
  const pubspecContent = fs.readFileSync(pubspecPath, "utf8");
19
21
  if (!pubspecContent.includes("flutter:")) {
20
- LoggerHelpers.error("Not a Flutter project: pubspec.yaml does not reference Flutter SDK.");
22
+ if (!silent) {
23
+ LoggerHelpers.error("Not a Flutter project: pubspec.yaml does not reference Flutter SDK.");
24
+ }
21
25
  return false;
22
26
  }
23
27
  return true;
@@ -60,17 +64,21 @@ async function validateFlutterSdk(useFvm = false) {
60
64
  /**
61
65
  * Validates that the iOS project exists
62
66
  */
63
- function validateIosProject() {
67
+ function validateIosProject(silent = false) {
64
68
  const iosPath = path.join(process.cwd(), "ios");
65
69
  if (!fs.existsSync(iosPath)) {
66
- LoggerHelpers.error("iOS project directory not found.");
67
- LoggerHelpers.info("Run 'flutter create .' to add iOS support.");
70
+ if (!silent) {
71
+ LoggerHelpers.error("iOS project directory not found.");
72
+ LoggerHelpers.info("Run 'flutter create .' to add iOS support.");
73
+ }
68
74
  return false;
69
75
  }
70
76
  const xcodeProjPath = path.join(iosPath, "Runner.xcodeproj");
71
77
  const xcworkspacePath = path.join(iosPath, "Runner.xcworkspace");
72
78
  if (!fs.existsSync(xcodeProjPath) && !fs.existsSync(xcworkspacePath)) {
73
- LoggerHelpers.error("No Xcode project or workspace found in ios/ directory.");
79
+ if (!silent) {
80
+ LoggerHelpers.error("No Xcode project or workspace found in ios/ directory.");
81
+ }
74
82
  return false;
75
83
  }
76
84
  return true;
@@ -78,17 +86,21 @@ function validateIosProject() {
78
86
  /**
79
87
  * Validates that the Android project exists
80
88
  */
81
- function validateAndroidProject() {
89
+ function validateAndroidProject(silent = false) {
82
90
  const androidPath = path.join(process.cwd(), "android");
83
91
  if (!fs.existsSync(androidPath)) {
84
- LoggerHelpers.error("Android project directory not found.");
85
- LoggerHelpers.info("Run 'flutter create .' to add Android support.");
92
+ if (!silent) {
93
+ LoggerHelpers.error("Android project directory not found.");
94
+ LoggerHelpers.info("Run 'flutter create .' to add Android support.");
95
+ }
86
96
  return false;
87
97
  }
88
98
  const buildGradlePath = path.join(androidPath, "build.gradle");
89
99
  const buildGradleKtsPath = path.join(androidPath, "build.gradle.kts");
90
100
  if (!fs.existsSync(buildGradlePath) && !fs.existsSync(buildGradleKtsPath)) {
91
- LoggerHelpers.error("No build.gradle found in android/ directory.");
101
+ if (!silent) {
102
+ LoggerHelpers.error("No build.gradle found in android/ directory.");
103
+ }
92
104
  return false;
93
105
  }
94
106
  return true;
@@ -0,0 +1,72 @@
1
+ # 🔧 Installation Guide
2
+
3
+ ---
4
+
5
+ ## ⚡ NPM (Recommended)
6
+
7
+ ```bash
8
+ npm install -g optikit
9
+ ```
10
+
11
+ Verify:
12
+
13
+ ```bash
14
+ optikit --version
15
+ ```
16
+
17
+ You get two commands — `optikit` and `ok` — both work identically.
18
+
19
+ ---
20
+
21
+ ## 📦 Homebrew
22
+
23
+ ### Via HTTPS
24
+
25
+ ```bash
26
+ brew tap dev-mahmoud-elshenawy/optikit
27
+ brew install optikit
28
+ ```
29
+
30
+ ### Via SSH (Recommended)
31
+
32
+ ```bash
33
+ brew tap dev-mahmoud-elshenawy/optikit git@github.com:dev-mahmoud-elshenawy/optikit.git
34
+ brew install optikit
35
+ ```
36
+
37
+ ---
38
+
39
+ ## 🛠️ From Source
40
+
41
+ ```bash
42
+ git clone https://github.com/dev-mahmoud-elshenawy/optikit.git
43
+ cd optikit
44
+ npm install && npm run build
45
+ npm link
46
+ ```
47
+
48
+ Verify:
49
+
50
+ ```bash
51
+ optikit --version
52
+ ```
53
+
54
+ ---
55
+
56
+ ## ✅ Shell Autocomplete (Optional)
57
+
58
+ Enable tab completion for all commands and flags:
59
+
60
+ ```bash
61
+ optikit completion >> ~/.zshrc && source ~/.zshrc
62
+ ```
63
+
64
+ ---
65
+
66
+ ## 🚀 Next Steps
67
+
68
+ ```bash
69
+ optikit init # Setup OptiKit in your Flutter project
70
+ optikit aliases # See all commands & shortcuts
71
+ optikit --help # Full help
72
+ ```
@@ -0,0 +1,140 @@
1
+ # 🔍 Troubleshooting
2
+
3
+ Quick fixes for common OptiKit issues.
4
+
5
+ ---
6
+
7
+ ## ❌ `optikit` or `ok` command not found
8
+
9
+ ```bash
10
+ # Reinstall globally
11
+ npm install -g optikit
12
+
13
+ # Verify
14
+ optikit --version
15
+ ```
16
+
17
+ If still not found, check your npm global bin is in PATH:
18
+
19
+ ```bash
20
+ npm bin -g
21
+ # Add the output path to your shell config if missing
22
+ ```
23
+
24
+ ---
25
+
26
+ ## ❌ FVM not found
27
+
28
+ ```
29
+ ✖ FVM Flutter SDK not found at .fvm/flutter_sdk
30
+ ```
31
+
32
+ **Fix:** Install FVM and set up the project:
33
+
34
+ ```bash
35
+ fvm install
36
+ fvm use <version>
37
+ ```
38
+
39
+ Or skip FVM for this command:
40
+
41
+ ```bash
42
+ optikit apk -f # -f = --disable-fvm
43
+ ```
44
+
45
+ Or disable FVM globally in `.optikitrc.json`:
46
+
47
+ ```json
48
+ {
49
+ "useFvmByDefault": false
50
+ }
51
+ ```
52
+
53
+ ---
54
+
55
+ ## ❌ Not a Flutter project
56
+
57
+ ```
58
+ ✖ Not a Flutter project: pubspec.yaml not found.
59
+ ```
60
+
61
+ **Fix:** Run OptiKit from the **root** of your Flutter project (where `pubspec.yaml` is).
62
+
63
+ ---
64
+
65
+ ## ❌ iOS / Android project not found
66
+
67
+ ```
68
+ ✖ iOS project directory not found.
69
+ ✖ Android project directory not found.
70
+ ```
71
+
72
+ **Fix:** Add platform support:
73
+
74
+ ```bash
75
+ flutter create . # Adds missing ios/ and android/ directories
76
+ ```
77
+
78
+ ---
79
+
80
+ ## ❌ Build fails
81
+
82
+ 1. **Check environment first:**
83
+
84
+ ```bash
85
+ optikit dr # Doctor checks Flutter, FVM, platforms
86
+ ```
87
+
88
+ 2. **Try a clean build:**
89
+
90
+ ```bash
91
+ optikit apk --clean # Clean before building
92
+ optikit ipa --clean # Clean before building (iOS)
93
+ ```
94
+
95
+ 3. **For iOS CocoaPods issues:**
96
+
97
+ ```bash
98
+ optikit ci -cu # Clean iOS + clear cache + update repo
99
+ ```
100
+
101
+ ---
102
+
103
+ ## ❌ Wrong version after bump
104
+
105
+ **Check current state:**
106
+
107
+ ```bash
108
+ optikit v # Show current version info
109
+ ```
110
+
111
+ **Restore from backup:**
112
+
113
+ ```bash
114
+ optikit undo # List all backups
115
+ optikit undo --restore 1 # Restore the most recent backup
116
+ ```
117
+
118
+ ---
119
+
120
+ ## ❌ Module generation overwrites existing files
121
+
122
+ OptiKit warns before overwriting. If you accidentally overwrote:
123
+
124
+ ```bash
125
+ # Check git for the original
126
+ git diff lib/module/<module_name>/
127
+ git checkout -- lib/module/<module_name>/
128
+ ```
129
+
130
+ ---
131
+
132
+ ## 💡 General Tips
133
+
134
+ | Tip | Command |
135
+ |-----|---------|
136
+ | Check environment health | `optikit dr` |
137
+ | Preview without executing | `optikit apk --dry-run` |
138
+ | See all available commands | `optikit aliases` |
139
+ | Get help for any command | `optikit <command> --help` |
140
+ | Check for CLI updates | `optikit up` |