@tmhs/mobile-mcp 0.10.0 → 0.12.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.
@@ -0,0 +1,291 @@
1
+ import { z } from "zod";
2
+ import { writeFileSync, mkdirSync, existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { textResponse, errorResponse } from "../types.js";
5
+ const inputSchema = {
6
+ project_path: z
7
+ .string()
8
+ .optional()
9
+ .describe("Absolute path to the project root. Defaults to cwd."),
10
+ framework: z
11
+ .enum(["expo", "flutter"])
12
+ .optional()
13
+ .default("expo")
14
+ .describe("Framework (default: expo)."),
15
+ module_name: z
16
+ .string()
17
+ .describe("Name of the native module (PascalCase, e.g. 'Haptics')."),
18
+ output_directory: z
19
+ .string()
20
+ .optional()
21
+ .default("modules")
22
+ .describe("Output directory relative to project root (default: modules)."),
23
+ };
24
+ function toPascalCase(name) {
25
+ return name.charAt(0).toUpperCase() + name.slice(1);
26
+ }
27
+ function toKebabCase(name) {
28
+ return name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
29
+ }
30
+ function generateExpoModuleIndex(name) {
31
+ return `import ${name}Module from "./${name}Module";
32
+
33
+ export function hello(): string {
34
+ return ${name}Module.hello();
35
+ }
36
+
37
+ export function multiply(a: number, b: number): number {
38
+ return ${name}Module.multiply(a, b);
39
+ }
40
+ `;
41
+ }
42
+ function generateExpoModuleNative(name) {
43
+ return `import { requireNativeModule } from "expo-modules-core";
44
+
45
+ const ${name}Module = requireNativeModule("${name}");
46
+
47
+ export default ${name}Module;
48
+ `;
49
+ }
50
+ function generateExpoModuleConfig(name) {
51
+ return `{
52
+ "name": "${name}",
53
+ "platforms": ["ios", "android"],
54
+ "ios": {
55
+ "modules": ["${name}Module"]
56
+ },
57
+ "android": {
58
+ "modules": ["${name}Module"]
59
+ }
60
+ }
61
+ `;
62
+ }
63
+ function generateSwiftModule(name) {
64
+ return `import ExpoModulesCore
65
+
66
+ public class ${name}Module: Module {
67
+ public func definition() -> ModuleDefinition {
68
+ Name("${name}")
69
+
70
+ Function("hello") {
71
+ return "Hello from ${name} native module!"
72
+ }
73
+
74
+ Function("multiply") { (a: Double, b: Double) -> Double in
75
+ return a * b
76
+ }
77
+ }
78
+ }
79
+ `;
80
+ }
81
+ function generateKotlinModule(name) {
82
+ const pkg = `expo.modules.${name.toLowerCase()}`;
83
+ return `package ${pkg}
84
+
85
+ import expo.modules.kotlin.modules.Module
86
+ import expo.modules.kotlin.modules.ModuleDefinition
87
+
88
+ class ${name}Module : Module() {
89
+ override fun definition() = ModuleDefinition {
90
+ Name("${name}")
91
+
92
+ Function("hello") {
93
+ "Hello from ${name} native module!"
94
+ }
95
+
96
+ Function("multiply") { a: Double, b: Double ->
97
+ a * b
98
+ }
99
+ }
100
+ }
101
+ `;
102
+ }
103
+ function generateFlutterPluginDart(name) {
104
+ const snakeName = toKebabCase(name).replace(/-/g, "_");
105
+ return `import 'dart:async';
106
+ import 'package:flutter/services.dart';
107
+
108
+ class ${name} {
109
+ static const MethodChannel _channel = MethodChannel('${snakeName}');
110
+
111
+ static Future<String> hello() async {
112
+ final result = await _channel.invokeMethod<String>('hello');
113
+ return result ?? '';
114
+ }
115
+
116
+ static Future<double> multiply(double a, double b) async {
117
+ final result = await _channel.invokeMethod<double>('multiply', {
118
+ 'a': a,
119
+ 'b': b,
120
+ });
121
+ return result ?? 0;
122
+ }
123
+ }
124
+ `;
125
+ }
126
+ function generateFlutterPlatformInterface(name) {
127
+ const snakeName = toKebabCase(name).replace(/-/g, "_");
128
+ return `import 'package:flutter/services.dart';
129
+
130
+ class ${name}Platform {
131
+ static const MethodChannel _channel = MethodChannel('${snakeName}');
132
+
133
+ Future<String> hello() async {
134
+ final result = await _channel.invokeMethod<String>('hello');
135
+ return result ?? '';
136
+ }
137
+
138
+ Future<double> multiply(double a, double b) async {
139
+ final result = await _channel.invokeMethod<double>('multiply', {
140
+ 'a': a,
141
+ 'b': b,
142
+ });
143
+ return result ?? 0;
144
+ }
145
+ }
146
+ `;
147
+ }
148
+ function generateSwiftPlugin(name) {
149
+ const snakeName = toKebabCase(name).replace(/-/g, "_");
150
+ return `import Flutter
151
+ import UIKit
152
+
153
+ public class ${name}Plugin: NSObject, FlutterPlugin {
154
+ public static func register(with registrar: FlutterPluginRegistrar) {
155
+ let channel = FlutterMethodChannel(
156
+ name: "${snakeName}",
157
+ binaryMessenger: registrar.messenger()
158
+ )
159
+ let instance = ${name}Plugin()
160
+ registrar.addMethodCallDelegate(instance, channel: channel)
161
+ }
162
+
163
+ public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
164
+ switch call.method {
165
+ case "hello":
166
+ result("Hello from ${name} native module!")
167
+ case "multiply":
168
+ guard let args = call.arguments as? [String: Any],
169
+ let a = args["a"] as? Double,
170
+ let b = args["b"] as? Double else {
171
+ result(FlutterError(code: "INVALID_ARGS", message: "Expected a and b", details: nil))
172
+ return
173
+ }
174
+ result(a * b)
175
+ default:
176
+ result(FlutterMethodNotImplemented)
177
+ }
178
+ }
179
+ }
180
+ `;
181
+ }
182
+ function generateKotlinPlugin(name) {
183
+ const snakeName = toKebabCase(name).replace(/-/g, "_");
184
+ return `package com.example.${snakeName}
185
+
186
+ import io.flutter.embedding.engine.plugins.FlutterPlugin
187
+ import io.flutter.plugin.common.MethodCall
188
+ import io.flutter.plugin.common.MethodChannel
189
+
190
+ class ${name}Plugin : FlutterPlugin, MethodChannel.MethodCallHandler {
191
+ private lateinit var channel: MethodChannel
192
+
193
+ override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
194
+ channel = MethodChannel(binding.binaryMessenger, "${snakeName}")
195
+ channel.setMethodCallHandler(this)
196
+ }
197
+
198
+ override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
199
+ when (call.method) {
200
+ "hello" -> result.success("Hello from ${name} native module!")
201
+ "multiply" -> {
202
+ val a = call.argument<Double>("a") ?: return result.error("INVALID_ARGS", "Missing a", null)
203
+ val b = call.argument<Double>("b") ?: return result.error("INVALID_ARGS", "Missing b", null)
204
+ result.success(a * b)
205
+ }
206
+ else -> result.notImplemented()
207
+ }
208
+ }
209
+
210
+ override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
211
+ channel.setMethodCallHandler(null)
212
+ }
213
+ }
214
+ `;
215
+ }
216
+ export function register(server) {
217
+ server.tool("mobile_createNativeModule", "Scaffold an Expo Module or Flutter platform plugin with Swift/Kotlin stubs and TypeScript/Dart bindings.", inputSchema, async (args) => {
218
+ try {
219
+ const root = args.project_path || process.cwd();
220
+ const name = toPascalCase(args.module_name);
221
+ const kebab = toKebabCase(name);
222
+ const isFlutter = args.framework === "flutter";
223
+ const moduleDir = join(root, args.output_directory, kebab);
224
+ if (existsSync(moduleDir)) {
225
+ return errorResponse(new Error(`Module directory already exists: ${moduleDir}`));
226
+ }
227
+ const created = [];
228
+ if (isFlutter) {
229
+ const libDir = join(moduleDir, "lib");
230
+ const iosDir = join(moduleDir, "ios", "Classes");
231
+ const androidDir = join(moduleDir, "android", "src", "main", "kotlin", "com", "example", kebab.replace(/-/g, "_"));
232
+ mkdirSync(libDir, { recursive: true });
233
+ mkdirSync(iosDir, { recursive: true });
234
+ mkdirSync(androidDir, { recursive: true });
235
+ const files = [
236
+ { path: join(libDir, `${kebab.replace(/-/g, "_")}.dart`), content: generateFlutterPluginDart(name) },
237
+ { path: join(libDir, `${kebab.replace(/-/g, "_")}_platform.dart`), content: generateFlutterPlatformInterface(name) },
238
+ { path: join(iosDir, `${name}Plugin.swift`), content: generateSwiftPlugin(name) },
239
+ { path: join(androidDir, `${name}Plugin.kt`), content: generateKotlinPlugin(name) },
240
+ ];
241
+ for (const f of files) {
242
+ writeFileSync(f.path, f.content, "utf-8");
243
+ created.push(f.path);
244
+ }
245
+ }
246
+ else {
247
+ const srcDir = join(moduleDir, "src");
248
+ const iosDir = join(moduleDir, "ios");
249
+ const androidDir = join(moduleDir, "android", "src", "main", "java", "expo", "modules", name.toLowerCase());
250
+ mkdirSync(srcDir, { recursive: true });
251
+ mkdirSync(iosDir, { recursive: true });
252
+ mkdirSync(androidDir, { recursive: true });
253
+ const files = [
254
+ { path: join(srcDir, "index.ts"), content: generateExpoModuleIndex(name) },
255
+ { path: join(srcDir, `${name}Module.ts`), content: generateExpoModuleNative(name) },
256
+ { path: join(moduleDir, "expo-module.config.json"), content: generateExpoModuleConfig(name) },
257
+ { path: join(iosDir, `${name}Module.swift`), content: generateSwiftModule(name) },
258
+ { path: join(androidDir, `${name}Module.kt`), content: generateKotlinModule(name) },
259
+ ];
260
+ for (const f of files) {
261
+ writeFileSync(f.path, f.content, "utf-8");
262
+ created.push(f.path);
263
+ }
264
+ }
265
+ const nextSteps = isFlutter
266
+ ? [
267
+ `Add the plugin to pubspec.yaml as a path dependency: path: ${args.output_directory}/${kebab}`,
268
+ "Run flutter pub get",
269
+ "Import and call the module from Dart code",
270
+ "Customize Swift and Kotlin implementations for your use case",
271
+ ]
272
+ : [
273
+ `Import from "${args.output_directory}/${kebab}/src" in your app code`,
274
+ "Run npx expo prebuild to generate native projects",
275
+ "Customize the Swift and Kotlin Module definitions",
276
+ "Add new native functions following the existing pattern",
277
+ ];
278
+ return textResponse(JSON.stringify({
279
+ success: true,
280
+ framework: args.framework,
281
+ module_name: name,
282
+ files_created: created,
283
+ next_steps: nextSteps,
284
+ }, null, 2));
285
+ }
286
+ catch (err) {
287
+ return errorResponse(err);
288
+ }
289
+ });
290
+ }
291
+ //# sourceMappingURL=createNativeModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createNativeModule.js","sourceRoot":"","sources":["../../src/tools/createNativeModule.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function register(server: McpServer): void;
3
+ //# sourceMappingURL=setupFeatureFlags.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setupFeatureFlags.d.ts","sourceRoot":"","sources":["../../src/tools/setupFeatureFlags.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAqNzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAwFhD"}
@@ -0,0 +1,266 @@
1
+ import { z } from "zod";
2
+ import { writeFileSync, mkdirSync, existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { textResponse, errorResponse } from "../types.js";
5
+ const inputSchema = {
6
+ project_path: z
7
+ .string()
8
+ .optional()
9
+ .describe("Absolute path to the project root. Defaults to cwd."),
10
+ framework: z
11
+ .enum(["expo", "flutter"])
12
+ .optional()
13
+ .default("expo")
14
+ .describe("Framework (default: expo)."),
15
+ provider: z
16
+ .enum(["posthog", "launchdarkly", "firebase"])
17
+ .optional()
18
+ .default("posthog")
19
+ .describe("Feature flag provider (default: posthog)."),
20
+ output_directory: z
21
+ .string()
22
+ .optional()
23
+ .default("lib")
24
+ .describe("Output directory relative to project root (default: lib)."),
25
+ };
26
+ function generatePostHogExpo() {
27
+ return `import PostHog from "posthog-react-native";
28
+
29
+ const posthog = new PostHog(process.env.EXPO_PUBLIC_POSTHOG_KEY!, {
30
+ host: process.env.EXPO_PUBLIC_POSTHOG_HOST ?? "https://us.i.posthog.com",
31
+ });
32
+
33
+ export type FeatureFlags = {
34
+ new_onboarding: boolean;
35
+ premium_features: boolean;
36
+ experimental_ui: boolean;
37
+ };
38
+
39
+ const FLAG_DEFAULTS: FeatureFlags = {
40
+ new_onboarding: false,
41
+ premium_features: false,
42
+ experimental_ui: false,
43
+ };
44
+
45
+ export function getFlag<K extends keyof FeatureFlags>(key: K): FeatureFlags[K] {
46
+ const value = posthog.getFeatureFlag(key);
47
+ if (value === undefined || value === null) return FLAG_DEFAULTS[key];
48
+ return value as FeatureFlags[K];
49
+ }
50
+
51
+ export function useFeatureFlag<K extends keyof FeatureFlags>(key: K): FeatureFlags[K] {
52
+ const value = posthog.useFeatureFlag(key);
53
+ if (value === undefined || value === null) return FLAG_DEFAULTS[key];
54
+ return value as FeatureFlags[K];
55
+ }
56
+
57
+ export function identifyUser(userId: string, properties?: Record<string, unknown>): void {
58
+ posthog.identify(userId, properties);
59
+ }
60
+
61
+ export function resetUser(): void {
62
+ posthog.reset();
63
+ }
64
+
65
+ export { posthog };
66
+ `;
67
+ }
68
+ function generateLaunchDarklyExpo() {
69
+ return `import { LDClient, LDConfig, LDContext } from "@launchdarkly/react-native-client-sdk";
70
+
71
+ const config: LDConfig = {
72
+ mobileKey: process.env.EXPO_PUBLIC_LAUNCHDARKLY_KEY!,
73
+ debug: __DEV__,
74
+ };
75
+
76
+ const client = new LDClient();
77
+
78
+ export type FeatureFlags = {
79
+ new_onboarding: boolean;
80
+ premium_features: boolean;
81
+ experimental_ui: boolean;
82
+ };
83
+
84
+ const FLAG_DEFAULTS: FeatureFlags = {
85
+ new_onboarding: false,
86
+ premium_features: false,
87
+ experimental_ui: false,
88
+ };
89
+
90
+ let initialized = false;
91
+
92
+ export async function initFeatureFlags(userId: string): Promise<void> {
93
+ if (initialized) return;
94
+ const context: LDContext = { kind: "user", key: userId };
95
+ await client.configure(config, context);
96
+ initialized = true;
97
+ }
98
+
99
+ export function getFlag<K extends keyof FeatureFlags>(key: K): FeatureFlags[K] {
100
+ return (client.boolVariation(key, FLAG_DEFAULTS[key]) as FeatureFlags[K]);
101
+ }
102
+
103
+ export function onFlagChange(key: keyof FeatureFlags, callback: (value: boolean) => void): () => void {
104
+ const listener = (flagKey: string, value: unknown) => {
105
+ if (flagKey === key) callback(value as boolean);
106
+ };
107
+ client.registerFeatureFlagListener(key, listener);
108
+ return () => client.unregisterFeatureFlagListener(key, listener);
109
+ }
110
+
111
+ export { client as ldClient };
112
+ `;
113
+ }
114
+ function generateFirebaseExpo() {
115
+ return `import remoteConfig from "@react-native-firebase/remote-config";
116
+
117
+ export type FeatureFlags = {
118
+ new_onboarding: boolean;
119
+ premium_features: boolean;
120
+ experimental_ui: boolean;
121
+ };
122
+
123
+ const FLAG_DEFAULTS: FeatureFlags = {
124
+ new_onboarding: false,
125
+ premium_features: false,
126
+ experimental_ui: false,
127
+ };
128
+
129
+ export async function initFeatureFlags(): Promise<void> {
130
+ await remoteConfig().setDefaults(FLAG_DEFAULTS as Record<string, boolean>);
131
+ await remoteConfig().setConfigSettings({
132
+ minimumFetchIntervalMillis: __DEV__ ? 0 : 3600000,
133
+ });
134
+ await remoteConfig().fetchAndActivate();
135
+ }
136
+
137
+ export function getFlag<K extends keyof FeatureFlags>(key: K): FeatureFlags[K] {
138
+ return remoteConfig().getBoolean(key) as FeatureFlags[K];
139
+ }
140
+
141
+ export function getAllFlags(): FeatureFlags {
142
+ return {
143
+ new_onboarding: remoteConfig().getBoolean("new_onboarding"),
144
+ premium_features: remoteConfig().getBoolean("premium_features"),
145
+ experimental_ui: remoteConfig().getBoolean("experimental_ui"),
146
+ };
147
+ }
148
+
149
+ export async function refreshFlags(): Promise<void> {
150
+ await remoteConfig().fetchAndActivate();
151
+ }
152
+ `;
153
+ }
154
+ function generateFlutterFeatureFlags(provider) {
155
+ const providerImport = provider === "firebase"
156
+ ? "import 'package:firebase_remote_config/firebase_remote_config.dart';"
157
+ : "// Add your feature flag provider package import here";
158
+ return `${providerImport}
159
+
160
+ enum FeatureFlag {
161
+ newOnboarding('new_onboarding', false),
162
+ premiumFeatures('premium_features', false),
163
+ experimentalUi('experimental_ui', false);
164
+
165
+ const FeatureFlag(this.key, this.defaultValue);
166
+ final String key;
167
+ final bool defaultValue;
168
+ }
169
+
170
+ class FeatureFlagService {
171
+ static final FeatureFlagService _instance = FeatureFlagService._();
172
+ factory FeatureFlagService() => _instance;
173
+ FeatureFlagService._();
174
+
175
+ final Map<String, bool> _overrides = {};
176
+
177
+ Future<void> init() async {
178
+ ${provider === "firebase" ? ` final rc = FirebaseRemoteConfig.instance;
179
+ await rc.setDefaults({
180
+ for (final flag in FeatureFlag.values) flag.key: flag.defaultValue,
181
+ });
182
+ await rc.setConfigSettings(RemoteConfigSettings(
183
+ fetchTimeout: const Duration(seconds: 10),
184
+ minimumFetchInterval: const Duration(hours: 1),
185
+ ));
186
+ await rc.fetchAndActivate();` : ` // Initialize your feature flag provider here
187
+ // Load remote flag values`}
188
+ }
189
+
190
+ bool isEnabled(FeatureFlag flag) {
191
+ if (_overrides.containsKey(flag.key)) return _overrides[flag.key]!;
192
+ ${provider === "firebase" ? ` return FirebaseRemoteConfig.instance.getBool(flag.key);` : ` return flag.defaultValue;`}
193
+ }
194
+
195
+ void setOverride(FeatureFlag flag, bool value) {
196
+ _overrides[flag.key] = value;
197
+ }
198
+
199
+ void clearOverrides() {
200
+ _overrides.clear();
201
+ }
202
+
203
+ Future<void> refresh() async {
204
+ ${provider === "firebase" ? ` await FirebaseRemoteConfig.instance.fetchAndActivate();` : ` // Refresh from remote provider`}
205
+ }
206
+ }
207
+ `;
208
+ }
209
+ export function register(server) {
210
+ server.tool("mobile_setupFeatureFlags", "Add a typed feature flag system with default values, remote sync, and provider integration (PostHog, LaunchDarkly, or Firebase Remote Config).", inputSchema, async (args) => {
211
+ try {
212
+ const root = args.project_path || process.cwd();
213
+ const outDir = join(root, args.output_directory);
214
+ mkdirSync(outDir, { recursive: true });
215
+ const isFlutter = args.framework === "flutter";
216
+ const ext = isFlutter ? "dart" : "ts";
217
+ const fileName = `feature-flags.${ext}`;
218
+ const filePath = join(outDir, fileName);
219
+ if (existsSync(filePath)) {
220
+ return errorResponse(new Error(`File already exists: ${filePath}`));
221
+ }
222
+ let content;
223
+ let dependencies;
224
+ const nextSteps = [];
225
+ if (isFlutter) {
226
+ content = generateFlutterFeatureFlags(args.provider);
227
+ dependencies = args.provider === "firebase"
228
+ ? ["firebase_remote_config"]
229
+ : [];
230
+ nextSteps.push("Call FeatureFlagService().init() in main() before runApp()", "Use FeatureFlagService().isEnabled(FeatureFlag.newOnboarding) to check flags", "Add new flags to the FeatureFlag enum as needed", `Configure ${args.provider} dashboard with matching flag keys`);
231
+ }
232
+ else {
233
+ switch (args.provider) {
234
+ case "launchdarkly":
235
+ content = generateLaunchDarklyExpo();
236
+ dependencies = ["@launchdarkly/react-native-client-sdk"];
237
+ nextSteps.push("Set EXPO_PUBLIC_LAUNCHDARKLY_KEY in .env", "Call initFeatureFlags(userId) after authentication", "Use getFlag('new_onboarding') to check flags");
238
+ break;
239
+ case "firebase":
240
+ content = generateFirebaseExpo();
241
+ dependencies = ["@react-native-firebase/remote-config", "@react-native-firebase/app"];
242
+ nextSteps.push("Ensure Firebase is configured (google-services.json / GoogleService-Info.plist)", "Call initFeatureFlags() in app root before rendering", "Use getFlag('new_onboarding') to check flags");
243
+ break;
244
+ default:
245
+ content = generatePostHogExpo();
246
+ dependencies = ["posthog-react-native"];
247
+ nextSteps.push("Set EXPO_PUBLIC_POSTHOG_KEY in .env", "Use useFeatureFlag('new_onboarding') in components", "Call identifyUser(userId) after authentication");
248
+ break;
249
+ }
250
+ }
251
+ writeFileSync(filePath, content, "utf-8");
252
+ return textResponse(JSON.stringify({
253
+ success: true,
254
+ provider: args.provider,
255
+ framework: args.framework,
256
+ file_created: filePath,
257
+ dependencies_needed: dependencies,
258
+ next_steps: nextSteps,
259
+ }, null, 2));
260
+ }
261
+ catch (err) {
262
+ return errorResponse(err);
263
+ }
264
+ });
265
+ }
266
+ //# sourceMappingURL=setupFeatureFlags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setupFeatureFlags.js","sourceRoot":"","sources":["../../src/tools/setupFeatureFlags.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function register(server: McpServer): void;
3
+ //# sourceMappingURL=setupTheming.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setupTheming.d.ts","sourceRoot":"","sources":["../../src/tools/setupTheming.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAmQzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA2EhD"}