flutterflow-mcp 0.2.5 → 0.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.
@@ -0,0 +1,116 @@
1
+ import { z } from "zod";
2
+ import YAML from "yaml";
3
+ import { cacheRead, cacheMeta } from "../utils/cache.js";
4
+ async function resolvePageName(projectId, scaffoldId) {
5
+ const content = await cacheRead(projectId, `page/id-${scaffoldId}`);
6
+ if (!content)
7
+ return scaffoldId;
8
+ const nameMatch = content.match(/^name:\s*(.+)$/m);
9
+ return nameMatch ? nameMatch[1].trim() : scaffoldId;
10
+ }
11
+ export function registerGetGeneralSettingsTool(server) {
12
+ server.tool("get_general_settings", "Get General settings — App Details (name, package, initial page, routing), App Assets (icon, splash, error image), Nav Bar & App Bar. Mirrors the FlutterFlow 'General' settings section. Cache-based, no API calls. Run sync_project first.", {
13
+ projectId: z.string().describe("The FlutterFlow project ID"),
14
+ }, async ({ projectId }) => {
15
+ const meta = await cacheMeta(projectId);
16
+ if (!meta) {
17
+ return {
18
+ content: [
19
+ {
20
+ type: "text",
21
+ text: `No cache found for project "${projectId}". Run sync_project first.`,
22
+ },
23
+ ],
24
+ };
25
+ }
26
+ const [appDetailsRaw, appAssetsRaw, navBarRaw] = await Promise.all([
27
+ cacheRead(projectId, "app-details"),
28
+ cacheRead(projectId, "app-assets"),
29
+ cacheRead(projectId, "nav-bar"),
30
+ ]);
31
+ const sections = ["# General Settings"];
32
+ // --- App Details ---
33
+ if (appDetailsRaw) {
34
+ const appDetails = YAML.parse(appDetailsRaw);
35
+ const appName = appDetails.name || "Unknown";
36
+ // Package name: take first env's packageName
37
+ let packageName = "not set";
38
+ const allAppNames = appDetails.allAppNames;
39
+ const appNames = allAppNames?.appNames;
40
+ if (appNames) {
41
+ const firstEnvKey = Object.keys(appNames)[0];
42
+ if (firstEnvKey) {
43
+ packageName = appNames[firstEnvKey].packageName || "not set";
44
+ }
45
+ }
46
+ // Initial page
47
+ const initialPageKey = appDetails.initialPageKeyRef?.key;
48
+ let initialPageLine = "not set";
49
+ if (initialPageKey) {
50
+ const name = await resolvePageName(projectId, initialPageKey);
51
+ initialPageLine = `${name} (${initialPageKey})`;
52
+ }
53
+ // Routing
54
+ const routing = appDetails.routingSettings;
55
+ const routingEnabled = routing?.enableRouting === true;
56
+ const subroutes = routing?.pagesAreSubroutesOfRoot === true;
57
+ sections.push(`\n## App Details`, `Name: ${appName}`, `Package name: ${packageName}`, `Initial page: ${initialPageLine}`, `Routing: ${routingEnabled ? "enabled" : "disabled"}, pages are subroutes: ${subroutes ? "yes" : "no"}`);
58
+ }
59
+ else {
60
+ sections.push(`\n## App Details`, `(not configured)`);
61
+ }
62
+ // --- App Assets ---
63
+ if (appAssetsRaw) {
64
+ const appAssets = YAML.parse(appAssetsRaw);
65
+ const appIconPath = appAssets.appIconPath || "not set";
66
+ const splashImage = appAssets.splashImage;
67
+ const errorImagePath = appAssets.errorImagePath || "not set";
68
+ sections.push(`\n## App Assets`);
69
+ sections.push(`App icon: ${appIconPath}`);
70
+ if (splashImage) {
71
+ const splashPath = splashImage.path || "not set";
72
+ const splashFit = splashImage.fit || "not set";
73
+ const splashDuration = splashImage.minSplashScreenDuration;
74
+ sections.push(`Splash image: ${splashPath} (fit: ${splashFit})`);
75
+ if (splashDuration)
76
+ sections.push(`Splash duration: ${splashDuration}ms`);
77
+ }
78
+ else {
79
+ sections.push(`Splash image: not set`);
80
+ }
81
+ sections.push(`Error image: ${errorImagePath}`);
82
+ }
83
+ else {
84
+ sections.push(`\n## App Assets`, `(not configured)`);
85
+ }
86
+ // --- Nav Bar & App Bar ---
87
+ if (navBarRaw) {
88
+ const navBar = YAML.parse(navBarRaw);
89
+ const visible = navBar.show === true;
90
+ if (visible) {
91
+ const navType = navBar.navBarType || "UNKNOWN";
92
+ const labels = navBar.labels === true;
93
+ const pageRefs = navBar.pageKeyRefOrder;
94
+ sections.push(`\n## Nav Bar & App Bar`, `Visible: Yes`, `Type: ${navType}`, `Labels: ${labels ? "Yes" : "No"}`);
95
+ if (pageRefs && pageRefs.length > 0) {
96
+ const tabs = [];
97
+ for (let i = 0; i < pageRefs.length; i++) {
98
+ const scaffoldId = pageRefs[i].key;
99
+ const name = await resolvePageName(projectId, scaffoldId);
100
+ tabs.push(` ${i + 1}. ${name} (${scaffoldId})`);
101
+ }
102
+ sections.push(`Tabs:`, ...tabs);
103
+ }
104
+ }
105
+ else {
106
+ sections.push(`\n## Nav Bar & App Bar`, `Visible: No`);
107
+ }
108
+ }
109
+ else {
110
+ sections.push(`\n## Nav Bar & App Bar`, `(not configured)`);
111
+ }
112
+ return {
113
+ content: [{ type: "text", text: sections.join("\n") }],
114
+ };
115
+ });
116
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerGetInAppPurchasesTool(server: McpServer): void;
@@ -0,0 +1,51 @@
1
+ import { z } from "zod";
2
+ import YAML from "yaml";
3
+ import { cacheRead, cacheMeta } from "../utils/cache.js";
4
+ const PROVIDERS = [
5
+ { key: "stripe", label: "Stripe" },
6
+ { key: "braintree", label: "Braintree" },
7
+ { key: "revenue-cat", label: "RevenueCat" },
8
+ { key: "razorpay", label: "Razorpay" },
9
+ ];
10
+ export function registerGetInAppPurchasesTool(server) {
11
+ server.tool("get_in_app_purchases", "Get In-App Purchases & Subscriptions settings — Stripe, Braintree, RevenueCat, Razorpay. Mirrors the FlutterFlow 'In App Purchases & Subscriptions' section. Cache-based, no API calls. Run sync_project first.", {
12
+ projectId: z.string().describe("The FlutterFlow project ID"),
13
+ }, async ({ projectId }) => {
14
+ const meta = await cacheMeta(projectId);
15
+ if (!meta) {
16
+ return {
17
+ content: [
18
+ {
19
+ type: "text",
20
+ text: `No cache found for project "${projectId}". Run sync_project first.`,
21
+ },
22
+ ],
23
+ };
24
+ }
25
+ const sections = ["# In-App Purchases & Subscriptions"];
26
+ for (const provider of PROVIDERS) {
27
+ const raw = await cacheRead(projectId, provider.key);
28
+ sections.push(`\n## ${provider.label}`);
29
+ if (!raw) {
30
+ sections.push("(not configured)");
31
+ continue;
32
+ }
33
+ const doc = YAML.parse(raw);
34
+ const enabled = doc.enabled === true;
35
+ sections.push(`Enabled: ${enabled ? "Yes" : "No"}`);
36
+ // Show any additional top-level scalar fields (skip 'enabled' itself and complex objects)
37
+ for (const [key, value] of Object.entries(doc)) {
38
+ if (key === "enabled")
39
+ continue;
40
+ if (value === null || value === undefined)
41
+ continue;
42
+ if (typeof value === "object")
43
+ continue;
44
+ sections.push(`${key}: ${String(value)}`);
45
+ }
46
+ }
47
+ return {
48
+ content: [{ type: "text", text: sections.join("\n") }],
49
+ };
50
+ });
51
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerGetIntegrationsTool(server: McpServer): void;
@@ -0,0 +1,137 @@
1
+ import { z } from "zod";
2
+ import YAML from "yaml";
3
+ import { cacheRead, cacheMeta } from "../utils/cache.js";
4
+ /**
5
+ * Fields that should never be output — API keys, secrets, tokens, etc.
6
+ * Only IDs and non-sensitive configuration are shown.
7
+ */
8
+ const SENSITIVE_KEYS = new Set([
9
+ "searchApiKey",
10
+ "supabaseAnonKey",
11
+ "supabaseServiceRoleKey",
12
+ "apiKey",
13
+ "secretKey",
14
+ "secret",
15
+ "token",
16
+ "accessToken",
17
+ "refreshToken",
18
+ "privateKey",
19
+ ]);
20
+ // ---------------------------------------------------------------------------
21
+ // Custom formatters for specific integrations
22
+ // ---------------------------------------------------------------------------
23
+ function formatAlgolia(doc) {
24
+ const lines = [];
25
+ const enabled = doc.enabled === true;
26
+ lines.push(`Enabled: ${enabled ? "Yes" : "No"}`);
27
+ const appId = doc.applicationId || "not set";
28
+ lines.push(`Application ID: ${appId}`);
29
+ const collections = doc.indexedCollections;
30
+ if (collections && collections.length > 0) {
31
+ lines.push(`Indexed collections:`);
32
+ for (const col of collections) {
33
+ lines.push(` - ${col.name || "unnamed"}`);
34
+ }
35
+ }
36
+ else {
37
+ lines.push(`Indexed collections: none`);
38
+ }
39
+ return lines;
40
+ }
41
+ function formatGoogleMaps(doc) {
42
+ const androidKey = doc.androidKey || "not set";
43
+ const iosKey = doc.iosKey || "not set";
44
+ const webKey = doc.webKey || "not set";
45
+ return [
46
+ `Android key: ${androidKey}`,
47
+ `iOS key: ${iosKey}`,
48
+ `Web key: ${webKey}`,
49
+ ];
50
+ }
51
+ function formatFirebaseAnalytics(doc) {
52
+ const lines = [];
53
+ const enabled = doc.enabled === true;
54
+ lines.push(`Enabled: ${enabled ? "Yes" : "No"}`);
55
+ const settings = doc.automaticEventSettings;
56
+ if (settings) {
57
+ lines.push(`Automatic event settings:`);
58
+ lines.push(` onPageLoad: ${settings.onPageLoad === true ? "Yes" : "No"}`);
59
+ lines.push(` onActionsStart: ${settings.onActionsStart === true ? "Yes" : "No"}`);
60
+ lines.push(` onAuth: ${settings.onAuth === true ? "Yes" : "No"}`);
61
+ }
62
+ return lines;
63
+ }
64
+ /**
65
+ * Generic formatter: shows `enabled` status and all non-sensitive
66
+ * top-level string/boolean fields.
67
+ */
68
+ function formatGeneric(doc) {
69
+ const lines = [];
70
+ if ("enabled" in doc) {
71
+ lines.push(`Enabled: ${doc.enabled === true ? "Yes" : "No"}`);
72
+ }
73
+ for (const [key, value] of Object.entries(doc)) {
74
+ if (key === "enabled")
75
+ continue;
76
+ if (SENSITIVE_KEYS.has(key))
77
+ continue;
78
+ if (typeof value === "string" || typeof value === "boolean") {
79
+ lines.push(`${key}: ${typeof value === "boolean" ? (value ? "Yes" : "No") : value}`);
80
+ }
81
+ }
82
+ return lines;
83
+ }
84
+ // ---------------------------------------------------------------------------
85
+ // Integration list — order matches the spec
86
+ // ---------------------------------------------------------------------------
87
+ const INTEGRATIONS = [
88
+ { fileKey: "supabase", displayName: "Supabase" },
89
+ { fileKey: "sqlite", displayName: "SQLite" },
90
+ { fileKey: "github", displayName: "GitHub" },
91
+ { fileKey: "algolia", displayName: "Algolia", format: formatAlgolia },
92
+ { fileKey: "firebase-analytics", displayName: "Google Analytics", format: formatFirebaseAnalytics },
93
+ { fileKey: "google-maps", displayName: "Google Maps", format: formatGoogleMaps },
94
+ { fileKey: "admob", displayName: "AdMob" },
95
+ { fileKey: "mux", displayName: "Mux Livestream" },
96
+ { fileKey: "onesignal", displayName: "OneSignal" },
97
+ { fileKey: "gemini", displayName: "Gemini" },
98
+ ];
99
+ // ---------------------------------------------------------------------------
100
+ // Tool registration
101
+ // ---------------------------------------------------------------------------
102
+ export function registerGetIntegrationsTool(server) {
103
+ server.tool("get_integrations", "Get Integrations settings — Supabase, SQLite, GitHub, Algolia, Google Analytics, Google Maps, AdMob, Mux Livestream, OneSignal, Gemini. Mirrors the FlutterFlow 'Integrations' section. Cache-based, no API calls. Run sync_project first.", {
104
+ projectId: z.string().describe("The FlutterFlow project ID"),
105
+ }, async ({ projectId }) => {
106
+ const meta = await cacheMeta(projectId);
107
+ if (!meta) {
108
+ return {
109
+ content: [
110
+ {
111
+ type: "text",
112
+ text: `No cache found for project "${projectId}". Run sync_project first.`,
113
+ },
114
+ ],
115
+ };
116
+ }
117
+ // Read all integration files in parallel
118
+ const rawResults = await Promise.all(INTEGRATIONS.map((def) => cacheRead(projectId, def.fileKey)));
119
+ const sections = ["# Integrations"];
120
+ for (let i = 0; i < INTEGRATIONS.length; i++) {
121
+ const def = INTEGRATIONS[i];
122
+ const raw = rawResults[i];
123
+ sections.push(`\n## ${def.displayName}`);
124
+ if (!raw) {
125
+ sections.push("(not configured)");
126
+ continue;
127
+ }
128
+ const doc = YAML.parse(raw);
129
+ const formatter = def.format || formatGeneric;
130
+ const lines = formatter(doc);
131
+ sections.push(lines.join("\n"));
132
+ }
133
+ return {
134
+ content: [{ type: "text", text: sections.join("\n") }],
135
+ };
136
+ });
137
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerGetProjectSetupTool(server: McpServer): void;
@@ -0,0 +1,212 @@
1
+ import { z } from "zod";
2
+ import YAML from "yaml";
3
+ import { cacheRead, cacheMeta } from "../utils/cache.js";
4
+ import { resolveDataType } from "../utils/resolve-data-type.js";
5
+ export function registerGetProjectSetupTool(server) {
6
+ server.tool("get_project_setup", "Get Project Setup settings — Firebase services, Languages, Platforms, Permissions, Project Dependencies, Dev Environments. Mirrors the FlutterFlow 'Project Setup' settings section. Cache-based, no API calls. Run sync_project first.", {
7
+ projectId: z.string().describe("The FlutterFlow project ID"),
8
+ }, async ({ projectId }) => {
9
+ const meta = await cacheMeta(projectId);
10
+ if (!meta) {
11
+ return {
12
+ content: [
13
+ {
14
+ type: "text",
15
+ text: `No cache found for project "${projectId}". Run sync_project first.`,
16
+ },
17
+ ],
18
+ };
19
+ }
20
+ const [analyticsRaw, appCheckRaw, crashlyticsRaw, perfMonRaw, remoteConfigRaw, languagesRaw, platformsRaw, permissionsRaw, dependenciesRaw, customCodeDepsRaw, envSettingsRaw,] = await Promise.all([
21
+ cacheRead(projectId, "firebase-analytics"),
22
+ cacheRead(projectId, "firebase-app-check"),
23
+ cacheRead(projectId, "firebase-crashlytics"),
24
+ cacheRead(projectId, "firebase-performance-monitoring"),
25
+ cacheRead(projectId, "firebase-remote-config"),
26
+ cacheRead(projectId, "languages"),
27
+ cacheRead(projectId, "platforms"),
28
+ cacheRead(projectId, "permissions"),
29
+ cacheRead(projectId, "dependencies"),
30
+ cacheRead(projectId, "custom-code-dependencies"),
31
+ cacheRead(projectId, "environment-settings"),
32
+ ]);
33
+ const sections = ["# Project Setup"];
34
+ // --- Firebase ---
35
+ sections.push(`\n## Firebase`);
36
+ const anyFirebase = analyticsRaw || appCheckRaw || crashlyticsRaw || perfMonRaw || remoteConfigRaw;
37
+ if (anyFirebase) {
38
+ // Analytics
39
+ if (analyticsRaw) {
40
+ const analytics = YAML.parse(analyticsRaw);
41
+ const enabled = analytics.enabled === true;
42
+ sections.push(`Analytics: ${enabled ? "enabled" : "disabled"}`);
43
+ if (enabled) {
44
+ const eventSettings = analytics.automaticEventSettings;
45
+ if (eventSettings) {
46
+ sections.push(` onPageLoad: ${eventSettings.onPageLoad === true ? "Yes" : "No"}`, ` onActionsStart: ${eventSettings.onActionsStart === true ? "Yes" : "No"}`, ` onAuth: ${eventSettings.onAuth === true ? "Yes" : "No"}`);
47
+ }
48
+ }
49
+ }
50
+ // App Check
51
+ if (appCheckRaw) {
52
+ const appCheck = YAML.parse(appCheckRaw);
53
+ sections.push(`App Check: ${appCheck.enabled === true ? "enabled" : "disabled"}`);
54
+ }
55
+ // Crashlytics
56
+ if (crashlyticsRaw) {
57
+ const crashlytics = YAML.parse(crashlyticsRaw);
58
+ sections.push(`Crashlytics: ${crashlytics.enabled === true ? "enabled" : "disabled"}`);
59
+ }
60
+ // Performance Monitoring
61
+ if (perfMonRaw) {
62
+ const perfMon = YAML.parse(perfMonRaw);
63
+ sections.push(`Performance Monitoring: ${perfMon.enabled === true ? "enabled" : "disabled"}`);
64
+ }
65
+ // Remote Config
66
+ if (remoteConfigRaw) {
67
+ const remoteConfig = YAML.parse(remoteConfigRaw);
68
+ const enabled = remoteConfig.enabled === true;
69
+ sections.push(`Remote Config: ${enabled ? "enabled" : "disabled"}`);
70
+ if (enabled) {
71
+ const fields = remoteConfig.fields || [];
72
+ if (fields.length > 0) {
73
+ sections.push(` Fields:`);
74
+ for (const field of fields) {
75
+ const name = field.parameter?.identifier?.name || "unknown";
76
+ const dt = resolveDataType(field.parameter?.dataType || {});
77
+ const defaultVal = field.serializedDefaultValue ?? "";
78
+ sections.push(` - ${name}: ${dt} (default: "${defaultVal}")`);
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
84
+ else {
85
+ sections.push(`(not configured)`);
86
+ }
87
+ // --- Languages ---
88
+ sections.push(`\n## Languages`);
89
+ if (languagesRaw) {
90
+ const langs = YAML.parse(languagesRaw);
91
+ const primaryLang = langs.primaryLanguage
92
+ ?.language;
93
+ const displayLang = langs.displayLanguage
94
+ ?.language;
95
+ const allLangs = langs.languages || [];
96
+ sections.push(`Primary: ${primaryLang || "not set"}`);
97
+ sections.push(`Display: ${displayLang || "not set"}`);
98
+ if (allLangs.length > 0) {
99
+ const langCodes = allLangs.map((l) => l.language).join(", ");
100
+ sections.push(`Available: ${langCodes}`);
101
+ }
102
+ }
103
+ else {
104
+ sections.push(`(not configured)`);
105
+ }
106
+ // --- Platforms ---
107
+ sections.push(`\n## Platforms`);
108
+ if (platformsRaw) {
109
+ const platforms = YAML.parse(platformsRaw);
110
+ sections.push(`Web: ${platforms.enableWeb === true ? "enabled" : "disabled"}`);
111
+ }
112
+ else {
113
+ sections.push(`(not configured)`);
114
+ }
115
+ // --- Permissions ---
116
+ sections.push(`\n## Permissions`);
117
+ if (permissionsRaw) {
118
+ const permissions = YAML.parse(permissionsRaw);
119
+ const builtIn = permissions.permissionMessages;
120
+ const custom = permissions.userDefinedPermissions;
121
+ const hasBuiltIn = builtIn && builtIn.length > 0;
122
+ const hasCustom = custom && custom.length > 0;
123
+ if (hasBuiltIn || hasCustom) {
124
+ if (hasBuiltIn) {
125
+ sections.push(`Built-in:`);
126
+ for (const perm of builtIn) {
127
+ const permType = perm.permissionType || "UNKNOWN";
128
+ const msg = perm.message?.textValue?.inputValue;
129
+ sections.push(` - ${permType}: ${msg ? `"${msg}"` : "(no message)"}`);
130
+ }
131
+ }
132
+ if (hasCustom) {
133
+ sections.push(`Custom:`);
134
+ for (const perm of custom) {
135
+ const names = perm.names;
136
+ const iosName = names?.iosName || "?";
137
+ const androidName = names?.androidName || "?";
138
+ const msg = perm.message?.textValue?.inputValue;
139
+ sections.push(` - ${iosName} / ${androidName}: ${msg ? `"${msg}"` : "(no message)"}`);
140
+ }
141
+ }
142
+ }
143
+ else {
144
+ sections.push(`(not configured)`);
145
+ }
146
+ }
147
+ else {
148
+ sections.push(`(not configured)`);
149
+ }
150
+ // --- Project Dependencies ---
151
+ sections.push(`\n## Project Dependencies`);
152
+ const hasDeps = dependenciesRaw || customCodeDepsRaw;
153
+ if (hasDeps) {
154
+ // FF library dependencies
155
+ if (dependenciesRaw) {
156
+ const deps = YAML.parse(dependenciesRaw);
157
+ const libDeps = deps.dependencies || [];
158
+ if (libDeps.length > 0) {
159
+ sections.push(`FF Libraries:`);
160
+ for (const dep of libDeps) {
161
+ sections.push(` - ${dep.name || "unnamed"} (${dep.projectId || "?"}, version: ${dep.version || "?"})`);
162
+ }
163
+ }
164
+ }
165
+ // Pubspec dependencies
166
+ if (customCodeDepsRaw) {
167
+ const customDeps = YAML.parse(customCodeDepsRaw);
168
+ const pubspecDeps = customDeps.pubspecDependencies || [];
169
+ if (pubspecDeps.length > 0) {
170
+ sections.push(`Pubspec Dependencies:`);
171
+ for (const dep of pubspecDeps) {
172
+ sections.push(` - ${dep.name || "unnamed"}: ${dep.version || "any"}`);
173
+ }
174
+ }
175
+ }
176
+ }
177
+ else {
178
+ sections.push(`(not configured)`);
179
+ }
180
+ // --- Dev Environments ---
181
+ sections.push(`\n## Dev Environments`);
182
+ if (envSettingsRaw) {
183
+ const doc = YAML.parse(envSettingsRaw);
184
+ const currentEnv = doc.currentEnvironment;
185
+ const envValues = doc.environmentValues || [];
186
+ if (currentEnv) {
187
+ sections.push(`Current: ${currentEnv.name || "unknown"} (${currentEnv.key || "?"})`);
188
+ }
189
+ const lines = [];
190
+ for (const ev of envValues) {
191
+ const name = ev.parameter?.identifier?.name || "unknown";
192
+ const dt = resolveDataType(ev.parameter?.dataType || {});
193
+ const privateTag = ev.isPrivate ? " (private)" : "";
194
+ lines.push(`- ${name}: ${dt}${privateTag}`);
195
+ const valuesMap = ev.valuesMap || {};
196
+ for (const [envKey, envVal] of Object.entries(valuesMap)) {
197
+ const display = ev.isPrivate ? "****" : (envVal.serializedValue ?? "");
198
+ lines.push(` ${envKey}: ${display}`);
199
+ }
200
+ }
201
+ if (lines.length > 0) {
202
+ sections.push(lines.join("\n"));
203
+ }
204
+ }
205
+ else {
206
+ sections.push(`(not configured)`);
207
+ }
208
+ return {
209
+ content: [{ type: "text", text: sections.join("\n") }],
210
+ };
211
+ });
212
+ }
@@ -1,115 +1,5 @@
1
1
  import { z } from "zod";
2
- import fs from "fs";
3
- import path from "path";
4
- import { fileURLToPath } from "url";
5
- const __filename = fileURLToPath(import.meta.url);
6
- const __dirname = path.dirname(__filename);
7
- const DOCS_DIR = path.resolve(__dirname, "../../docs/ff-yaml");
8
- /** Topic-to-file mapping for fuzzy search. */
9
- const TOPIC_MAP = {
10
- // Widgets
11
- button: "04-widgets/button.md",
12
- iconbutton: "04-widgets/button.md",
13
- text: "04-widgets/text.md",
14
- richtext: "04-widgets/text.md",
15
- richtextspan: "04-widgets/text.md",
16
- textfield: "04-widgets/text-field.md",
17
- "text-field": "04-widgets/text-field.md",
18
- input: "04-widgets/text-field.md",
19
- container: "04-widgets/container.md",
20
- boxdecoration: "04-widgets/container.md",
21
- column: "04-widgets/layout.md",
22
- row: "04-widgets/layout.md",
23
- stack: "04-widgets/layout.md",
24
- wrap: "04-widgets/layout.md",
25
- layout: "04-widgets/layout.md",
26
- image: "04-widgets/image.md",
27
- form: "04-widgets/form.md",
28
- validation: "04-widgets/form.md",
29
- dropdown: "04-widgets/dropdown.md",
30
- choicechips: "04-widgets/dropdown.md",
31
- icon: "04-widgets/misc.md",
32
- progressbar: "04-widgets/misc.md",
33
- appbar: "04-widgets/misc.md",
34
- conditionalbuilder: "04-widgets/misc.md",
35
- widget: "04-widgets/README.md",
36
- widgets: "04-widgets/README.md",
37
- // Non-widget topics
38
- actions: "05-actions.md",
39
- action: "05-actions.md",
40
- trigger: "05-actions.md",
41
- navigate: "05-actions.md",
42
- navigation: "05-actions.md",
43
- ontap: "05-actions.md",
44
- variables: "06-variables.md",
45
- variable: "06-variables.md",
46
- binding: "06-variables.md",
47
- "data-binding": "06-variables.md",
48
- data: "07-data.md",
49
- collections: "07-data.md",
50
- firestore: "07-data.md",
51
- api: "07-data.md",
52
- custom: "08-custom-code.md",
53
- dart: "08-custom-code.md",
54
- "custom-code": "08-custom-code.md",
55
- theme: "09-theming.md",
56
- theming: "09-theming.md",
57
- color: "09-theming.md",
58
- colors: "09-theming.md",
59
- font: "09-theming.md",
60
- typography: "09-theming.md",
61
- editing: "10-editing-guide.md",
62
- workflow: "10-editing-guide.md",
63
- "editing-guide": "10-editing-guide.md",
64
- push: "10-editing-guide.md",
65
- overview: "00-overview.md",
66
- structure: "00-overview.md",
67
- "project-files": "01-project-files.md",
68
- config: "01-project-files.md",
69
- settings: "01-project-files.md",
70
- pages: "02-pages.md",
71
- page: "02-pages.md",
72
- scaffold: "02-pages.md",
73
- components: "03-components.md",
74
- component: "03-components.md",
75
- createcomponent: "03-components.md",
76
- refactor: "03-components.md",
77
- refactoring: "03-components.md",
78
- isdummyroot: "03-components.md",
79
- dummyroot: "03-components.md",
80
- componentclasskeyref: "03-components.md",
81
- parametervalues: "03-components.md",
82
- callback: "03-components.md",
83
- executecallbackaction: "03-components.md",
84
- // Universal patterns
85
- inputvalue: "README.md",
86
- mostrecentinputvalue: "README.md",
87
- padding: "README.md",
88
- "border-radius": "README.md",
89
- };
90
- /** List all doc files recursively. */
91
- function listDocFiles(dir, prefix = "") {
92
- const results = [];
93
- if (!fs.existsSync(dir))
94
- return results;
95
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
96
- const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
97
- if (entry.isDirectory()) {
98
- results.push(...listDocFiles(path.join(dir, entry.name), relPath));
99
- }
100
- else if (entry.name.endsWith(".md")) {
101
- results.push(relPath);
102
- }
103
- }
104
- return results;
105
- }
106
- /** Read a doc file. Returns null if not found. */
107
- function readDoc(relPath) {
108
- const filePath = path.join(DOCS_DIR, relPath);
109
- if (!fs.existsSync(filePath))
110
- return null;
111
- return fs.readFileSync(filePath, "utf-8");
112
- }
2
+ import { TOPIC_MAP, DOCS_DIR, listDocFiles, readDoc } from "../utils/topic-map.js";
113
3
  export function registerGetYamlDocsTool(server) {
114
4
  server.tool("get_yaml_docs", "Search and retrieve FlutterFlow YAML reference documentation. Use `topic` to search by keyword (e.g. 'Button', 'actions', 'theming') or `file` to fetch a specific doc file. Returns the full doc content.", {
115
5
  topic: z