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.
- package/.claude/commands/build.md +57 -0
- package/.claude/commands/clean.md +45 -0
- package/.claude/commands/generate.md +64 -0
- package/.claude/commands/rollback.md +67 -0
- package/.claude/commands/run.md +63 -0
- package/.claude/commands/setup.md +69 -0
- package/.claude/commands/sync-docs.md +148 -0
- package/.claude/commands/version.md +63 -0
- package/.claude/settings.local.json +28 -0
- package/.claude-plugin/marketplace.json +36 -0
- package/.claude-plugin/plugin.json +25 -0
- package/.history/README_20260325211923.md +268 -0
- package/.history/README_20260325212116.md +268 -0
- package/.history/README_20260325212234.md +266 -0
- package/.history/README_20260325221838.md +321 -0
- package/.history/README_20260325221920.md +327 -0
- package/.history/README_20260325222245.md +328 -0
- package/.history/README_20260325222247.md +328 -0
- package/.mcp.json +8 -0
- package/CHANGELOG.md +79 -46
- package/CLAUDE.md +57 -206
- package/OPTIKIT_AGENT.md +398 -0
- package/README.md +293 -60
- package/dist/cli.js +75 -241
- package/dist/commands/build/commands.js +146 -0
- package/dist/commands/build/testflight.js +14 -0
- package/dist/commands/clean/commands.js +41 -0
- package/dist/commands/clean/flutter.js +8 -14
- package/dist/commands/clean/ios.js +12 -15
- package/dist/commands/config/aliases.js +122 -0
- package/dist/commands/config/commands.js +49 -0
- package/dist/commands/config/initApp.js +191 -0
- package/dist/commands/config/rollback.js +15 -4
- package/dist/commands/config/upgrade.js +36 -0
- package/dist/commands/mcp/commands.js +21 -0
- package/dist/commands/mcp/server.js +27 -0
- package/dist/commands/mcp/setup.js +62 -0
- package/dist/commands/mcp/tools.js +359 -0
- package/dist/commands/project/commands.js +132 -0
- package/dist/commands/project/devices.js +10 -26
- package/dist/commands/project/doctor.js +58 -0
- package/dist/commands/project/generate.js +183 -30
- package/dist/commands/project/setup.js +10 -28
- package/dist/commands/project/status.js +65 -0
- package/dist/commands/version/bump.js +96 -82
- package/dist/commands/version/commands.js +63 -0
- package/dist/commands/version/update.js +36 -24
- package/dist/constants.js +6 -1
- package/dist/styles.js +42 -5
- package/dist/utils/helpers/error.js +14 -0
- package/dist/utils/helpers/file.js +1 -1
- package/dist/utils/helpers/version.js +2 -1
- package/dist/utils/services/backup.js +12 -1
- package/dist/utils/services/command.js +1 -34
- package/dist/utils/services/exec.js +76 -101
- package/dist/utils/services/logger.js +10 -4
- package/dist/utils/validators/validation.js +24 -12
- package/docs/INSTALLATION.md +72 -0
- package/docs/TROUBLESHOOT.md +140 -0
- package/docs/USAGE.md +185 -0
- package/docs/VERSION_MANAGEMENT.md +177 -0
- package/package.json +7 -11
- package/src/cli.ts +82 -362
- package/src/commands/build/commands.ts +169 -0
- package/src/commands/build/testflight.ts +18 -0
- package/src/commands/clean/commands.ts +43 -0
- package/src/commands/clean/flutter.ts +9 -13
- package/src/commands/clean/ios.ts +13 -13
- package/src/commands/config/aliases.ts +150 -0
- package/src/commands/config/commands.ts +50 -0
- package/src/commands/config/initApp.ts +213 -0
- package/src/commands/config/rollback.ts +16 -4
- package/src/commands/config/upgrade.ts +40 -0
- package/src/commands/mcp/commands.ts +23 -0
- package/src/commands/mcp/server.ts +35 -0
- package/src/commands/mcp/setup.ts +69 -0
- package/src/commands/mcp/tools.ts +365 -0
- package/src/commands/project/commands.ts +132 -0
- package/src/commands/project/devices.ts +11 -24
- package/src/commands/project/doctor.ts +81 -0
- package/src/commands/project/generate.ts +211 -32
- package/src/commands/project/setup.ts +13 -30
- package/src/commands/project/status.ts +72 -0
- package/src/commands/version/bump.ts +124 -85
- package/src/commands/version/commands.ts +76 -0
- package/src/commands/version/update.ts +86 -75
- package/src/constants.ts +7 -1
- package/src/styles.ts +49 -7
- package/src/utils/helpers/error.ts +16 -0
- package/src/utils/helpers/file.ts +1 -1
- package/src/utils/helpers/version.ts +2 -1
- package/src/utils/services/backup.ts +17 -1
- package/src/utils/services/command.ts +1 -58
- package/src/utils/services/exec.ts +92 -117
- package/src/utils/services/logger.ts +12 -4
- package/src/utils/validators/validation.ts +24 -12
- package/CODE_QUALITY.md +0 -398
- package/ENHANCEMENTS.md +0 -310
- package/FEATURE_ENHANCEMENTS.md +0 -435
- package/INSTALLATION.md +0 -118
- package/SAFETY_FEATURES.md +0 -396
- package/TROUBLESHOOT.md +0 -60
- package/USAGE.md +0 -388
- 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 {
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
|
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",
|
|
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",
|
|
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",
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|