ghostfill 0.2.4 → 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.
package/dist/mcp.d.mts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/mcp.mjs ADDED
@@ -0,0 +1,315 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/mcp.ts
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { z } from "zod";
7
+
8
+ // src/faker.ts
9
+ var FIRST_NAMES = ["James", "Sarah", "Michael", "Emma", "Robert", "Olivia", "David", "Sophia", "Daniel", "Isabella", "Ahmed", "Fatima", "Carlos", "Yuki", "Priya"];
10
+ var LAST_NAMES = ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Wilson", "Anderson", "Taylor", "Thomas", "Moore", "Martin", "Lee"];
11
+ var COMPANIES = ["Acme Corp", "TechVault Inc", "Nexus Solutions", "Blue Ridge Systems", "Summit Digital", "CloudPeak Ltd", "DataForge Labs", "Apex Industries"];
12
+ var CITIES = ["New York", "San Francisco", "Chicago", "Austin", "Seattle", "Boston", "Denver", "Portland"];
13
+ var STREETS = ["123 Oak Street", "456 Maple Ave", "789 Pine Road", "321 Elm Blvd", "654 Cedar Lane", "987 Birch Drive"];
14
+ var DOMAINS = ["gmail.com", "outlook.com", "yahoo.com", "company.com", "example.com"];
15
+ var LOREM = ["Project planning and requirements gathering", "Implementation of core features", "Stakeholder review session", "Technical design workshop", "Sprint retrospective and planning", "Customer onboarding process", "Data migration strategy", "Integration testing phase"];
16
+ var TITLES = ["Discovery Workshop", "Requirements Review", "Design Sprint", "Technical Assessment", "Strategy Session", "Kick-off Meeting", "UAT Planning", "Go-Live Preparation"];
17
+ function pick(arr) {
18
+ return arr[Math.floor(Math.random() * arr.length)];
19
+ }
20
+ function randInt(min, max) {
21
+ return Math.floor(Math.random() * (max - min + 1)) + min;
22
+ }
23
+ function randDate(futureMonths = 6) {
24
+ const now = /* @__PURE__ */ new Date();
25
+ const future = new Date(now.getTime() + randInt(1, futureMonths * 30) * 864e5);
26
+ return future.toISOString().slice(0, 10);
27
+ }
28
+ function randDateTime(futureMonths = 6) {
29
+ const now = /* @__PURE__ */ new Date();
30
+ const future = new Date(now.getTime() + randInt(1, futureMonths * 30) * 864e5);
31
+ future.setHours(randInt(8, 18), randInt(0, 3) * 15, 0);
32
+ return future.toISOString().slice(0, 16);
33
+ }
34
+ function generateForField(field, context) {
35
+ const label = field.label.toLowerCase();
36
+ if (field.type === "select") {
37
+ if (field.options?.length) return pick(field.options);
38
+ return "__FIRST__";
39
+ }
40
+ if (field.type === "date") return randDate();
41
+ if (field.type === "datetime-local") return randDateTime();
42
+ if (field.type === "time") return `${String(randInt(8, 18)).padStart(2, "0")}:${String(randInt(0, 3) * 15).padStart(2, "0")}`;
43
+ if (field.type === "month") {
44
+ const d = /* @__PURE__ */ new Date();
45
+ d.setMonth(d.getMonth() + randInt(0, 6));
46
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
47
+ }
48
+ if (field.type === "email" || label.includes("email")) {
49
+ return context.email;
50
+ }
51
+ if (field.type === "tel" || label.includes("phone") || label.includes("mobile")) {
52
+ return `+1${randInt(200, 999)}${randInt(100, 999)}${randInt(1e3, 9999)}`;
53
+ }
54
+ if (field.type === "url" || label.includes("website") || label.includes("url")) {
55
+ return `https://www.${context.company.toLowerCase().replace(/\s+/g, "")}.com`;
56
+ }
57
+ if (field.type === "number" || field.type === "range") {
58
+ const min = field.min ? parseInt(field.min, 10) || 1 : 1;
59
+ const max = field.max ? parseInt(field.max, 10) || 100 : 100;
60
+ return String(randInt(min, max));
61
+ }
62
+ if (label.includes("first name") || label.includes("firstname")) return context.firstName;
63
+ if (label.includes("last name") || label.includes("lastname") || label.includes("surname")) return context.lastName;
64
+ if (label.includes("full name") || label === "name") return `${context.firstName} ${context.lastName}`;
65
+ if (label.includes("company") || label.includes("organization") || label.includes("business")) return context.company;
66
+ if (label.includes("address") || label.includes("street")) return pick(STREETS);
67
+ if (label.includes("city")) return pick(CITIES);
68
+ if (label.includes("state")) return pick(["California", "New York", "Texas", "Florida", "Washington"]);
69
+ if (label.includes("zip") || label.includes("postal")) return String(randInt(1e4, 99999));
70
+ if (label.includes("country")) return "United States";
71
+ if (label.includes("title") || label.includes("subject") || label.includes("name")) {
72
+ return pick(TITLES);
73
+ }
74
+ if (label.includes("job") || label.includes("role") || label.includes("position")) {
75
+ return pick(["Software Engineer", "Product Manager", "Business Analyst", "Solution Architect", "Project Manager"]);
76
+ }
77
+ if (label.includes("location") || label.includes("channel") || label.includes("venue")) {
78
+ return pick(["Microsoft Teams", "Zoom", "Google Meet", "On-site", "Conference Room A"]);
79
+ }
80
+ if (field.type === "textarea" || label.includes("description") || label.includes("note") || label.includes("comment") || label.includes("objective") || label.includes("goal")) {
81
+ return pick(LOREM);
82
+ }
83
+ if (field.type === "password") return "P@ssw0rd123!";
84
+ return pick([context.firstName, context.company, pick(TITLES)]);
85
+ }
86
+ function generateFakeData(fields) {
87
+ const firstName = pick(FIRST_NAMES);
88
+ const lastName = pick(LAST_NAMES);
89
+ const company = pick(COMPANIES);
90
+ const email = `${firstName.toLowerCase()}.${lastName.toLowerCase()}@${pick(DOMAINS)}`;
91
+ const context = { firstName, lastName, email, company };
92
+ return fields.map((field, index) => {
93
+ if (field.type === "checkbox") {
94
+ return { index, value: "true", checked: true };
95
+ }
96
+ const value = generateForField(field, context);
97
+ return { index, value };
98
+ });
99
+ }
100
+
101
+ // src/detector.ts
102
+ var INPUT_SELECTORS = [
103
+ "input:not([type=hidden]):not([type=submit]):not([type=button]):not([type=reset]):not([type=image])",
104
+ "textarea",
105
+ "select",
106
+ // Custom dropdown triggers (Headless UI Listbox, Radix, etc.)
107
+ "button[role=combobox]",
108
+ "[role=combobox]:not(input)",
109
+ "button[aria-haspopup=listbox]"
110
+ ].join(", ");
111
+ function describeFields(fields) {
112
+ return fields.map((f, i) => {
113
+ let desc = `[${i}] "${f.label}" (type: ${f.type}`;
114
+ if (f.required) desc += ", required";
115
+ if (f.options?.length) desc += `, options: [${f.options.join(", ")}]`;
116
+ if (f.min) desc += `, min: ${f.min}`;
117
+ if (f.max) desc += `, max: ${f.max}`;
118
+ if (f.pattern) desc += `, pattern: ${f.pattern}`;
119
+ desc += ")";
120
+ return desc;
121
+ }).join("\n");
122
+ }
123
+
124
+ // src/ai.ts
125
+ var SYSTEM_PROMPT = `You are a form-filling assistant. Given a list of form fields and an optional user prompt, generate realistic fake data to fill ALL fields.
126
+
127
+ Rules:
128
+ - Return ONLY a JSON object with a "fields" array of objects, each with "index" and "value" keys
129
+ - You MUST fill EVERY field \u2014 do not skip any
130
+ - Match the field type (email \u2192 valid email, phone \u2192 valid phone with country code, date \u2192 YYYY-MM-DD, datetime-local \u2192 YYYY-MM-DDTHH:MM, etc.)
131
+ - For select/dropdown fields: you MUST pick one of the listed options EXACTLY as written
132
+ - For checkboxes: add a "checked" boolean (true or false)
133
+ - For radio buttons: only fill one per group, use the option value
134
+ - Respect min/max constraints and patterns
135
+ - Generate contextually coherent data (same person's name, matching city/state/zip, etc.)
136
+ - If no user prompt is given, infer appropriate data from the field labels
137
+ - Do NOT request or rely on secrets, tokens, passwords, or existing form values
138
+ - Do NOT wrap the JSON in markdown code blocks \u2014 return raw JSON only`;
139
+ function isRecord(value) {
140
+ return typeof value === "object" && value !== null;
141
+ }
142
+ function toFieldFillData(value) {
143
+ if (!isRecord(value)) {
144
+ throw new Error("AI response item is not an object");
145
+ }
146
+ const index = value.index;
147
+ const rawValue = value.value;
148
+ const checked = value.checked;
149
+ if (typeof index !== "number" || !Number.isInteger(index)) {
150
+ throw new Error("AI response item is missing a numeric index");
151
+ }
152
+ if (typeof rawValue !== "string") {
153
+ throw new Error("AI response item is missing a string value");
154
+ }
155
+ return {
156
+ index,
157
+ value: rawValue,
158
+ checked: typeof checked === "boolean" ? checked : void 0
159
+ };
160
+ }
161
+ function buildFillMessages(request) {
162
+ const fieldDescription = describeFields(request.fields);
163
+ let userContent = `Form fields:
164
+ ${fieldDescription}`;
165
+ if (request.prompt) {
166
+ userContent += `
167
+
168
+ User instructions: ${request.prompt}`;
169
+ } else {
170
+ userContent += "\n\nNo specific instructions \u2014 generate realistic, contextually appropriate data for all fields.";
171
+ }
172
+ return [
173
+ {
174
+ role: "system",
175
+ content: request.systemPrompt ? `${request.systemPrompt}
176
+
177
+ ${SYSTEM_PROMPT}` : SYSTEM_PROMPT
178
+ },
179
+ {
180
+ role: "user",
181
+ content: userContent
182
+ }
183
+ ];
184
+ }
185
+ function parseFillDataPayload(payload) {
186
+ if (typeof payload === "string") {
187
+ const jsonMatch = payload.match(/```(?:json)?\s*([\s\S]*?)```/) || [null, payload];
188
+ return parseFillDataPayload(JSON.parse(jsonMatch[1].trim()));
189
+ }
190
+ if (Array.isArray(payload)) {
191
+ return payload.map(toFieldFillData);
192
+ }
193
+ if (isRecord(payload)) {
194
+ if (Array.isArray(payload.choices)) {
195
+ const content = payload.choices[0]?.message;
196
+ if (typeof content?.content === "string") {
197
+ return parseFillDataPayload(content.content);
198
+ }
199
+ }
200
+ const candidate = payload.fields ?? payload.data ?? payload.items ?? payload.result;
201
+ if (Array.isArray(candidate)) {
202
+ return candidate.map(toFieldFillData);
203
+ }
204
+ }
205
+ throw new Error("AI response is not an array of field fills");
206
+ }
207
+
208
+ // src/types.ts
209
+ var PROVIDERS = {
210
+ openai: { label: "OpenAI", model: "gpt-4o-mini", baseURL: "https://api.openai.com/v1", helpText: "Uses gpt-4o-mini \u2014 fast & cheap" },
211
+ xai: { label: "xAI", model: "grok-4-fast", baseURL: "https://api.x.ai/v1", helpText: "Uses Grok 4 Fast" },
212
+ moonshot: { label: "Moonshot", model: "kimi-k2", baseURL: "https://api.moonshot.ai/v1", helpText: "Uses Kimi K2 \u2014 fast & cheap" }
213
+ };
214
+
215
+ // src/mcp.ts
216
+ var FieldSchema = z.object({
217
+ label: z.string().describe("Field label or visible name"),
218
+ type: z.string().describe("Field type: text, email, tel, select, checkbox, radio, date, datetime-local, number, textarea, password, url, etc."),
219
+ name: z.string().optional().default("").describe("Field name attribute"),
220
+ options: z.array(z.string()).optional().describe("For select/radio fields, the available option values"),
221
+ required: z.boolean().optional().default(false),
222
+ min: z.string().optional().describe("Minimum value constraint"),
223
+ max: z.string().optional().describe("Maximum value constraint"),
224
+ pattern: z.string().optional().describe("Regex pattern constraint")
225
+ });
226
+ function toDetectedFields(fields) {
227
+ return fields.map((f) => ({
228
+ element: null,
229
+ currentValue: "",
230
+ type: f.type,
231
+ name: f.name ?? "",
232
+ label: f.label,
233
+ options: f.options,
234
+ required: f.required ?? false,
235
+ min: f.min,
236
+ max: f.max,
237
+ pattern: f.pattern
238
+ }));
239
+ }
240
+ var server = new McpServer({
241
+ name: "ghostfill",
242
+ version: "0.2.4"
243
+ });
244
+ server.tool(
245
+ "ghostfill_generate",
246
+ "Generate realistic fake data for form fields. No API key required. Pass field descriptions and get back fill data with coherent identity (matching name, email, company, etc.).",
247
+ {
248
+ fields: z.array(FieldSchema).describe("Array of form field descriptions to generate data for")
249
+ },
250
+ async ({ fields }) => {
251
+ const result = generateFakeData(toDetectedFields(fields));
252
+ return {
253
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
254
+ };
255
+ }
256
+ );
257
+ server.tool(
258
+ "ghostfill_generate_ai",
259
+ "Generate contextually-aware form data using an AI provider (OpenAI, xAI, or Moonshot). Produces more intelligent, context-sensitive data than local generation.",
260
+ {
261
+ fields: z.array(FieldSchema).describe("Array of form field descriptions"),
262
+ provider: z.enum(["openai", "xai", "moonshot"]).optional().default("openai").describe("AI provider to use"),
263
+ apiKey: z.string().describe("API key for the chosen provider"),
264
+ prompt: z.string().optional().default("").describe("Optional instructions for what kind of data to generate"),
265
+ systemPrompt: z.string().optional().describe("Optional custom system prompt to prepend")
266
+ },
267
+ async ({ fields, provider, apiKey, prompt, systemPrompt }) => {
268
+ const promptFields = fields.map((f, i) => ({
269
+ index: i,
270
+ type: f.type,
271
+ name: f.name ?? "",
272
+ label: f.label,
273
+ options: f.options,
274
+ required: f.required ?? false,
275
+ min: f.min,
276
+ max: f.max,
277
+ pattern: f.pattern
278
+ }));
279
+ const request = {
280
+ provider,
281
+ prompt: prompt ?? "",
282
+ systemPrompt,
283
+ fields: promptFields
284
+ };
285
+ const messages = buildFillMessages(request);
286
+ const providerConfig = PROVIDERS[provider] ?? PROVIDERS.openai;
287
+ const response = await fetch(`${providerConfig.baseURL}/chat/completions`, {
288
+ method: "POST",
289
+ headers: {
290
+ "Content-Type": "application/json",
291
+ Authorization: `Bearer ${apiKey}`
292
+ },
293
+ body: JSON.stringify({
294
+ model: providerConfig.model,
295
+ messages,
296
+ temperature: 0.7
297
+ })
298
+ });
299
+ if (!response.ok) {
300
+ const errorText = await response.text();
301
+ return {
302
+ content: [{ type: "text", text: `AI API error (${response.status}): ${errorText}` }],
303
+ isError: true
304
+ };
305
+ }
306
+ const payload = await response.json();
307
+ const result = parseFillDataPayload(payload);
308
+ return {
309
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
310
+ };
311
+ }
312
+ );
313
+ var transport = new StdioServerTransport();
314
+ await server.connect(transport);
315
+ //# sourceMappingURL=mcp.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mcp.ts","../src/faker.ts","../src/detector.ts","../src/ai.ts","../src/types.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { z } from \"zod\";\nimport { generateFakeData } from \"./faker\";\nimport { buildFillMessages, parseFillDataPayload } from \"./ai\";\nimport { PROVIDERS } from \"./types\";\nimport type { DetectedField, GhostFillAIRequest, Provider } from \"./types\";\n\nconst FieldSchema = z.object({\n label: z.string().describe(\"Field label or visible name\"),\n type: z.string().describe(\"Field type: text, email, tel, select, checkbox, radio, date, datetime-local, number, textarea, password, url, etc.\"),\n name: z.string().optional().default(\"\").describe(\"Field name attribute\"),\n options: z.array(z.string()).optional().describe(\"For select/radio fields, the available option values\"),\n required: z.boolean().optional().default(false),\n min: z.string().optional().describe(\"Minimum value constraint\"),\n max: z.string().optional().describe(\"Maximum value constraint\"),\n pattern: z.string().optional().describe(\"Regex pattern constraint\"),\n});\n\nfunction toDetectedFields(fields: z.infer<typeof FieldSchema>[]): DetectedField[] {\n return fields.map((f) => ({\n element: null as unknown as HTMLElement,\n currentValue: \"\",\n type: f.type,\n name: f.name ?? \"\",\n label: f.label,\n options: f.options,\n required: f.required ?? false,\n min: f.min,\n max: f.max,\n pattern: f.pattern,\n }));\n}\n\nconst server = new McpServer({\n name: \"ghostfill\",\n version: \"0.2.4\",\n});\n\nserver.tool(\n \"ghostfill_generate\",\n \"Generate realistic fake data for form fields. No API key required. Pass field descriptions and get back fill data with coherent identity (matching name, email, company, etc.).\",\n {\n fields: z.array(FieldSchema).describe(\"Array of form field descriptions to generate data for\"),\n },\n async ({ fields }) => {\n const result = generateFakeData(toDetectedFields(fields));\n return {\n content: [{ type: \"text\" as const, text: JSON.stringify(result, null, 2) }],\n };\n }\n);\n\nserver.tool(\n \"ghostfill_generate_ai\",\n \"Generate contextually-aware form data using an AI provider (OpenAI, xAI, or Moonshot). Produces more intelligent, context-sensitive data than local generation.\",\n {\n fields: z.array(FieldSchema).describe(\"Array of form field descriptions\"),\n provider: z.enum([\"openai\", \"xai\", \"moonshot\"]).optional().default(\"openai\").describe(\"AI provider to use\"),\n apiKey: z.string().describe(\"API key for the chosen provider\"),\n prompt: z.string().optional().default(\"\").describe(\"Optional instructions for what kind of data to generate\"),\n systemPrompt: z.string().optional().describe(\"Optional custom system prompt to prepend\"),\n },\n async ({ fields, provider, apiKey, prompt, systemPrompt }) => {\n const promptFields = fields.map((f, i) => ({\n index: i,\n type: f.type,\n name: f.name ?? \"\",\n label: f.label,\n options: f.options,\n required: f.required ?? false,\n min: f.min,\n max: f.max,\n pattern: f.pattern,\n }));\n\n const request: GhostFillAIRequest = {\n provider: provider as Provider,\n prompt: prompt ?? \"\",\n systemPrompt,\n fields: promptFields,\n };\n\n const messages = buildFillMessages(request);\n const providerConfig = PROVIDERS[provider as Provider] ?? PROVIDERS.openai;\n\n const response = await fetch(`${providerConfig.baseURL}/chat/completions`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model: providerConfig.model,\n messages,\n temperature: 0.7,\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return {\n content: [{ type: \"text\" as const, text: `AI API error (${response.status}): ${errorText}` }],\n isError: true,\n };\n }\n\n const payload = await response.json();\n const result = parseFillDataPayload(payload);\n return {\n content: [{ type: \"text\" as const, text: JSON.stringify(result, null, 2) }],\n };\n }\n);\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n","import type { DetectedField, FieldFillData } from \"./types\";\n\nconst FIRST_NAMES = [\"James\", \"Sarah\", \"Michael\", \"Emma\", \"Robert\", \"Olivia\", \"David\", \"Sophia\", \"Daniel\", \"Isabella\", \"Ahmed\", \"Fatima\", \"Carlos\", \"Yuki\", \"Priya\"];\nconst LAST_NAMES = [\"Smith\", \"Johnson\", \"Williams\", \"Brown\", \"Jones\", \"Garcia\", \"Miller\", \"Davis\", \"Wilson\", \"Anderson\", \"Taylor\", \"Thomas\", \"Moore\", \"Martin\", \"Lee\"];\nconst COMPANIES = [\"Acme Corp\", \"TechVault Inc\", \"Nexus Solutions\", \"Blue Ridge Systems\", \"Summit Digital\", \"CloudPeak Ltd\", \"DataForge Labs\", \"Apex Industries\"];\nconst CITIES = [\"New York\", \"San Francisco\", \"Chicago\", \"Austin\", \"Seattle\", \"Boston\", \"Denver\", \"Portland\"];\nconst STREETS = [\"123 Oak Street\", \"456 Maple Ave\", \"789 Pine Road\", \"321 Elm Blvd\", \"654 Cedar Lane\", \"987 Birch Drive\"];\nconst DOMAINS = [\"gmail.com\", \"outlook.com\", \"yahoo.com\", \"company.com\", \"example.com\"];\nconst LOREM = [\"Project planning and requirements gathering\", \"Implementation of core features\", \"Stakeholder review session\", \"Technical design workshop\", \"Sprint retrospective and planning\", \"Customer onboarding process\", \"Data migration strategy\", \"Integration testing phase\"];\nconst TITLES = [\"Discovery Workshop\", \"Requirements Review\", \"Design Sprint\", \"Technical Assessment\", \"Strategy Session\", \"Kick-off Meeting\", \"UAT Planning\", \"Go-Live Preparation\"];\n\nfunction pick<T>(arr: T[]): T {\n return arr[Math.floor(Math.random() * arr.length)]!;\n}\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\nfunction randDate(futureMonths = 6): string {\n const now = new Date();\n const future = new Date(now.getTime() + randInt(1, futureMonths * 30) * 86400000);\n return future.toISOString().slice(0, 10);\n}\n\nfunction randDateTime(futureMonths = 6): string {\n const now = new Date();\n const future = new Date(now.getTime() + randInt(1, futureMonths * 30) * 86400000);\n future.setHours(randInt(8, 18), randInt(0, 3) * 15, 0);\n return future.toISOString().slice(0, 16);\n}\n\nfunction generateForField(field: DetectedField, context: { firstName: string; lastName: string; email: string; company: string }): string {\n const label = field.label.toLowerCase();\n\n // Select — pick a random option, or signal \"pick first\" if no options known\n if (field.type === \"select\") {\n if (field.options?.length) return pick(field.options);\n return \"__FIRST__\";\n }\n\n // Dates\n if (field.type === \"date\") return randDate();\n if (field.type === \"datetime-local\") return randDateTime();\n if (field.type === \"time\") return `${String(randInt(8, 18)).padStart(2, \"0\")}:${String(randInt(0, 3) * 15).padStart(2, \"0\")}`;\n if (field.type === \"month\") {\n const d = new Date();\n d.setMonth(d.getMonth() + randInt(0, 6));\n return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, \"0\")}`;\n }\n\n // Email\n if (field.type === \"email\" || label.includes(\"email\")) {\n return context.email;\n }\n\n // Phone\n if (field.type === \"tel\" || label.includes(\"phone\") || label.includes(\"mobile\")) {\n return `+1${randInt(200, 999)}${randInt(100, 999)}${randInt(1000, 9999)}`;\n }\n\n // URL\n if (field.type === \"url\" || label.includes(\"website\") || label.includes(\"url\")) {\n return `https://www.${context.company.toLowerCase().replace(/\\s+/g, \"\")}.com`;\n }\n\n // Number\n if (field.type === \"number\" || field.type === \"range\") {\n const min = field.min ? parseInt(field.min, 10) || 1 : 1;\n const max = field.max ? parseInt(field.max, 10) || 100 : 100;\n return String(randInt(min, max));\n }\n\n // Name fields\n if (label.includes(\"first name\") || label.includes(\"firstname\")) return context.firstName;\n if (label.includes(\"last name\") || label.includes(\"lastname\") || label.includes(\"surname\")) return context.lastName;\n if (label.includes(\"full name\") || label === \"name\") return `${context.firstName} ${context.lastName}`;\n\n // Company\n if (label.includes(\"company\") || label.includes(\"organization\") || label.includes(\"business\")) return context.company;\n\n // Address\n if (label.includes(\"address\") || label.includes(\"street\")) return pick(STREETS);\n if (label.includes(\"city\")) return pick(CITIES);\n if (label.includes(\"state\")) return pick([\"California\", \"New York\", \"Texas\", \"Florida\", \"Washington\"]);\n if (label.includes(\"zip\") || label.includes(\"postal\")) return String(randInt(10000, 99999));\n if (label.includes(\"country\")) return \"United States\";\n\n // Title-like\n if (label.includes(\"title\") || label.includes(\"subject\") || label.includes(\"name\")) {\n return pick(TITLES);\n }\n\n // Job\n if (label.includes(\"job\") || label.includes(\"role\") || label.includes(\"position\")) {\n return pick([\"Software Engineer\", \"Product Manager\", \"Business Analyst\", \"Solution Architect\", \"Project Manager\"]);\n }\n\n // Location / Channel\n if (label.includes(\"location\") || label.includes(\"channel\") || label.includes(\"venue\")) {\n return pick([\"Microsoft Teams\", \"Zoom\", \"Google Meet\", \"On-site\", \"Conference Room A\"]);\n }\n\n // Description / Notes / Objective / Goal\n if (field.type === \"textarea\" || label.includes(\"description\") || label.includes(\"note\") || label.includes(\"comment\") || label.includes(\"objective\") || label.includes(\"goal\")) {\n return pick(LOREM);\n }\n\n // Password\n if (field.type === \"password\") return \"P@ssw0rd123!\";\n\n // Generic text fallback\n return pick([context.firstName, context.company, pick(TITLES)]);\n}\n\n/** Generate fake data for all fields without calling an API */\nexport function generateFakeData(fields: DetectedField[]): FieldFillData[] {\n // Create a coherent identity for this fill\n const firstName = pick(FIRST_NAMES);\n const lastName = pick(LAST_NAMES);\n const company = pick(COMPANIES);\n const email = `${firstName.toLowerCase()}.${lastName.toLowerCase()}@${pick(DOMAINS)}`;\n const context = { firstName, lastName, email, company };\n\n return fields.map((field, index) => {\n if (field.type === \"checkbox\") {\n return { index, value: \"true\", checked: true };\n }\n\n const value = generateForField(field, context);\n return { index, value };\n });\n}\n","import type { DetectedField, GhostFillPromptField } from \"./types\";\n\nconst INPUT_SELECTORS = [\n \"input:not([type=hidden]):not([type=submit]):not([type=button]):not([type=reset]):not([type=image])\",\n \"textarea\",\n \"select\",\n // Custom dropdown triggers (Headless UI Listbox, Radix, etc.)\n \"button[role=combobox]\",\n \"[role=combobox]:not(input)\",\n \"button[aria-haspopup=listbox]\",\n].join(\", \");\n\n/** Find the label text for a form field */\nexport function findLabel(el: HTMLElement): string {\n // 1. Explicit <label for=\"id\">\n if (el.id) {\n const label = document.querySelector<HTMLLabelElement>(\n `label[for=\"${CSS.escape(el.id)}\"]`\n );\n if (label?.textContent?.trim()) return label.textContent.trim();\n }\n\n // 2. Wrapping <label>\n const parentLabel = el.closest(\"label\");\n if (parentLabel) {\n // Get text content excluding the input itself\n const clone = parentLabel.cloneNode(true) as HTMLElement;\n clone.querySelectorAll(\"input, textarea, select\").forEach((c) => c.remove());\n const text = clone.textContent?.trim();\n if (text) return text;\n }\n\n // 3. aria-label\n const ariaLabel = el.getAttribute(\"aria-label\");\n if (ariaLabel?.trim()) return ariaLabel.trim();\n\n // 4. aria-labelledby\n const labelledBy = el.getAttribute(\"aria-labelledby\");\n if (labelledBy) {\n const parts = labelledBy\n .split(/\\s+/)\n .map((id) => document.getElementById(id)?.textContent?.trim())\n .filter(Boolean);\n if (parts.length) return parts.join(\" \");\n }\n\n // 5. placeholder\n if (\"placeholder\" in el) {\n const ph = (el as HTMLInputElement).placeholder;\n if (ph?.trim()) return ph.trim();\n }\n\n // 6. title\n const title = el.getAttribute(\"title\");\n if (title?.trim()) return title.trim();\n\n // 7. Preceding sibling text (common pattern: <span>Label</span><input>)\n const prev = el.previousElementSibling;\n if (prev && ![\"INPUT\", \"TEXTAREA\", \"SELECT\"].includes(prev.tagName)) {\n const prevText = prev.textContent?.trim();\n if (prevText && prevText.length < 60) return prevText;\n }\n\n // 8. Parent's first text node or heading\n const parent = el.parentElement;\n if (parent) {\n // Look for a heading or label-like element before this input in the parent\n for (const child of Array.from(parent.children)) {\n if (child === el) break;\n if ([\"LABEL\", \"SPAN\", \"P\", \"H1\", \"H2\", \"H3\", \"H4\", \"H5\", \"H6\", \"LEGEND\", \"DIV\"].includes(child.tagName)) {\n const text = child.textContent?.trim();\n if (text && text.length < 60 && !child.querySelector(\"input, textarea, select\")) {\n return text;\n }\n }\n }\n }\n\n // 9. id attribute humanized\n if (el.id) return el.id.replace(/[_\\-]/g, \" \").replace(/([a-z])([A-Z])/g, \"$1 $2\").trim();\n\n // 10. name attribute as fallback\n const name = el.getAttribute(\"name\");\n if (name) return name.replace(/[_\\-[\\]]/g, \" \").replace(/([a-z])([A-Z])/g, \"$1 $2\").trim();\n\n return \"unknown\";\n}\n\n/** Detect all fillable fields within a container element */\nexport function detectFields(container: HTMLElement): DetectedField[] {\n const elements = container.querySelectorAll<\n HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement\n >(INPUT_SELECTORS);\n\n const fields: DetectedField[] = [];\n\n elements.forEach((el) => {\n // Skip disabled fields\n if ((el as HTMLInputElement).disabled) return;\n if ((el as HTMLInputElement).readOnly) return;\n // Skip hidden elements\n if (el.offsetParent === null && (el as HTMLInputElement).type !== \"hidden\") return;\n\n // Custom combobox / listbox (Headless UI, Radix, etc.)\n const isCustomDropdown =\n (el.getAttribute(\"role\") === \"combobox\" && !(el instanceof HTMLInputElement)) ||\n el.getAttribute(\"aria-haspopup\") === \"listbox\";\n if (isCustomDropdown) {\n // Find listbox options: aria-controls, sibling [role=listbox], or Headless UI pattern\n const listboxId = el.getAttribute(\"aria-controls\");\n let listbox: Element | null = listboxId ? document.getElementById(listboxId) : null;\n if (!listbox) {\n // Headless UI puts the listbox as a sibling inside the same relative container\n listbox = el.parentElement?.querySelector(\"[role=listbox]\") || null;\n }\n\n const options: string[] = [];\n if (listbox) {\n listbox.querySelectorAll(\"[role=option]\").forEach((opt) => {\n const text = opt.textContent?.trim();\n if (text) options.push(text);\n });\n }\n\n // Get current display value from button text\n const buttonText = el.textContent?.trim() || \"\";\n // Check if it looks like a placeholder\n const looksLikePlaceholder = buttonText.toLowerCase().startsWith(\"select\") || buttonText === \"\";\n\n const field: DetectedField = {\n element: el as HTMLElement,\n type: \"select\",\n name: el.id || el.getAttribute(\"name\") || \"\",\n label: findLabel(el),\n required: el.getAttribute(\"aria-required\") === \"true\",\n currentValue: looksLikePlaceholder ? \"\" : buttonText,\n options: options.length > 0 ? options : undefined,\n };\n fields.push(field);\n return;\n }\n\n const field: DetectedField = {\n element: el as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n type: el instanceof HTMLSelectElement ? \"select\" : (el as HTMLInputElement).type || \"text\",\n name: (el as HTMLInputElement).name || el.id || \"\",\n label: findLabel(el),\n required: (el as HTMLInputElement).required || el.getAttribute(\"aria-required\") === \"true\",\n currentValue: (el as HTMLInputElement).value,\n };\n\n // Collect select options\n if (el instanceof HTMLSelectElement) {\n field.options = Array.from(el.options)\n .filter((opt) => opt.value && !opt.disabled)\n .map((opt) => opt.textContent?.trim() || opt.value);\n }\n\n // Collect constraints\n if (\"min\" in el && (el as HTMLInputElement).min) {\n field.min = (el as HTMLInputElement).min;\n }\n if (\"max\" in el && (el as HTMLInputElement).max) {\n field.max = (el as HTMLInputElement).max;\n }\n if (\"pattern\" in el && (el as HTMLInputElement).pattern) {\n field.pattern = (el as HTMLInputElement).pattern;\n }\n\n fields.push(field);\n });\n\n return fields;\n}\n\n/** Build a description of fields for the AI prompt */\nexport function describeFields(\n fields: Array<\n Pick<\n DetectedField | GhostFillPromptField,\n \"type\" | \"label\" | \"required\" | \"options\" | \"min\" | \"max\" | \"pattern\"\n >\n >\n): string {\n return fields\n .map((f, i) => {\n let desc = `[${i}] \"${f.label}\" (type: ${f.type}`;\n if (f.required) desc += \", required\";\n if (f.options?.length) desc += `, options: [${f.options.join(\", \")}]`;\n if (f.min) desc += `, min: ${f.min}`;\n if (f.max) desc += `, max: ${f.max}`;\n if (f.pattern) desc += `, pattern: ${f.pattern}`;\n desc += \")\";\n return desc;\n })\n .join(\"\\n\");\n}\n","import { describeFields } from \"./detector\";\nimport type {\n DetectedField,\n FieldFillData,\n GhostFillAIOptions,\n GhostFillAIRequest,\n GhostFillPromptField,\n Provider,\n} from \"./types\";\n\nexport const SYSTEM_PROMPT = `You are a form-filling assistant. Given a list of form fields and an optional user prompt, generate realistic fake data to fill ALL fields.\n\nRules:\n- Return ONLY a JSON object with a \"fields\" array of objects, each with \"index\" and \"value\" keys\n- You MUST fill EVERY field — do not skip any\n- Match the field type (email → valid email, phone → valid phone with country code, date → YYYY-MM-DD, datetime-local → YYYY-MM-DDTHH:MM, etc.)\n- For select/dropdown fields: you MUST pick one of the listed options EXACTLY as written\n- For checkboxes: add a \"checked\" boolean (true or false)\n- For radio buttons: only fill one per group, use the option value\n- Respect min/max constraints and patterns\n- Generate contextually coherent data (same person's name, matching city/state/zip, etc.)\n- If no user prompt is given, infer appropriate data from the field labels\n- Do NOT request or rely on secrets, tokens, passwords, or existing form values\n- Do NOT wrap the JSON in markdown code blocks — return raw JSON only`;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction toFieldFillData(value: unknown): FieldFillData {\n if (!isRecord(value)) {\n throw new Error(\"AI response item is not an object\");\n }\n\n const index = value.index;\n const rawValue = value.value;\n const checked = value.checked;\n\n if (typeof index !== \"number\" || !Number.isInteger(index)) {\n throw new Error(\"AI response item is missing a numeric index\");\n }\n\n if (typeof rawValue !== \"string\") {\n throw new Error(\"AI response item is missing a string value\");\n }\n\n return {\n index,\n value: rawValue,\n checked: typeof checked === \"boolean\" ? checked : undefined,\n };\n}\n\nexport function toPromptFields(fields: DetectedField[]): GhostFillPromptField[] {\n return fields.map((field, index) => ({\n index,\n type: field.type,\n name: field.name,\n label: field.label,\n options: field.options,\n required: field.required,\n min: field.min,\n max: field.max,\n pattern: field.pattern,\n }));\n}\n\nexport function buildFillMessages(\n request: GhostFillAIRequest\n): Array<{ role: \"system\" | \"user\"; content: string }> {\n const fieldDescription = describeFields(request.fields);\n let userContent = `Form fields:\\n${fieldDescription}`;\n\n if (request.prompt) {\n userContent += `\\n\\nUser instructions: ${request.prompt}`;\n } else {\n userContent +=\n \"\\n\\nNo specific instructions — generate realistic, contextually appropriate data for all fields.\";\n }\n\n return [\n {\n role: \"system\",\n content: request.systemPrompt\n ? `${request.systemPrompt}\\n\\n${SYSTEM_PROMPT}`\n : SYSTEM_PROMPT,\n },\n {\n role: \"user\",\n content: userContent,\n },\n ];\n}\n\nexport function parseFillDataPayload(payload: unknown): FieldFillData[] {\n if (typeof payload === \"string\") {\n const jsonMatch =\n payload.match(/```(?:json)?\\s*([\\s\\S]*?)```/) || [null, payload];\n return parseFillDataPayload(JSON.parse(jsonMatch[1]!.trim()));\n }\n\n if (Array.isArray(payload)) {\n return payload.map(toFieldFillData);\n }\n\n if (isRecord(payload)) {\n if (Array.isArray(payload.choices)) {\n const content = (payload.choices[0] as Record<string, unknown> | undefined)\n ?.message as Record<string, unknown> | undefined;\n if (typeof content?.content === \"string\") {\n return parseFillDataPayload(content.content);\n }\n }\n\n const candidate =\n payload.fields ?? payload.data ?? payload.items ?? payload.result;\n if (Array.isArray(candidate)) {\n return candidate.map(toFieldFillData);\n }\n }\n\n throw new Error(\"AI response is not an array of field fills\");\n}\n\nfunction createRequest(\n fields: DetectedField[],\n userPrompt: string,\n provider: Provider,\n systemPrompt?: string\n): GhostFillAIRequest {\n return {\n provider,\n prompt: userPrompt,\n systemPrompt,\n fields: toPromptFields(fields),\n };\n}\n\n/** Call a secure backend or callback to generate fill data. */\nexport async function generateFillData(\n fields: DetectedField[],\n userPrompt: string,\n provider: Provider,\n transport: GhostFillAIOptions,\n systemPrompt?: string\n): Promise<FieldFillData[]> {\n const request = createRequest(fields, userPrompt, provider, systemPrompt);\n\n if (transport.requestFillData) {\n return transport.requestFillData(request);\n }\n\n const endpoint = transport.endpoint ?? \"/api/ghostfill\";\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(request),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`AI API error (${response.status}): ${error}`);\n }\n\n const contentType = response.headers.get(\"content-type\") || \"\";\n const payload = contentType.includes(\"application/json\")\n ? await response.json()\n : await response.text();\n\n return parseFillDataPayload(payload);\n}\n","/** Configuration options for GhostFill */\nexport interface GhostFillOptions {\n /**\n * @deprecated Browser API keys are insecure and ignored.\n * Configure `ai` and keep provider credentials on your backend instead.\n */\n apiKey?: string;\n /** Keyboard shortcut to toggle GhostFill (default: \"Alt+G\") */\n shortcut?: string;\n /** Custom system prompt to prepend */\n systemPrompt?: string;\n /** Secure AI transport configuration */\n ai?: GhostFillAIOptions;\n}\n\nexport type Provider = \"openai\" | \"xai\" | \"moonshot\";\n\nexport const PROVIDERS: Record<Provider, { label: string; model: string; baseURL: string; helpText: string }> = {\n openai: { label: \"OpenAI\", model: \"gpt-4o-mini\", baseURL: \"https://api.openai.com/v1\", helpText: \"Uses gpt-4o-mini — fast & cheap\" },\n xai: { label: \"xAI\", model: \"grok-4-fast\", baseURL: \"https://api.x.ai/v1\", helpText: \"Uses Grok 4 Fast\" },\n moonshot: { label: \"Moonshot\", model: \"kimi-k2\", baseURL: \"https://api.moonshot.ai/v1\", helpText: \"Uses Kimi K2 — fast & cheap\" },\n};\n\n/** Non-secret field metadata sent to an AI backend */\nexport interface GhostFillPromptField {\n index: number;\n type: string;\n name: string;\n label: string;\n options?: string[];\n required: boolean;\n min?: string;\n max?: string;\n pattern?: string;\n}\n\n/** Secure AI request payload for a backend route or callback */\nexport interface GhostFillAIRequest {\n provider: Provider;\n prompt: string;\n systemPrompt?: string;\n fields: GhostFillPromptField[];\n}\n\nexport type GhostFillAIHandler = (\n request: GhostFillAIRequest\n) => Promise<FieldFillData[]>;\n\n/** Secure AI configuration. Requests must go through a backend or custom handler. */\nexport interface GhostFillAIOptions {\n /** Same-origin backend route. Defaults to `/api/ghostfill` when `ai` is enabled. */\n endpoint?: string;\n /** Optional custom transport that forwards requests to a secure backend. */\n requestFillData?: GhostFillAIHandler;\n /** Default provider shown in the UI. */\n provider?: Provider;\n}\n\n/** A saved prompt preset */\nexport interface Preset {\n id: string;\n name: string;\n prompt: string;\n}\n\n/** Persisted settings (localStorage) */\nexport interface GhostFillSettings {\n apiKey: string;\n provider: Provider;\n highlightColor: string;\n theme: \"dark\" | \"light\";\n useAI: boolean;\n presets: Preset[];\n activePresetId: string | null;\n}\n\n/** A detected form field */\nexport interface DetectedField {\n /** The DOM element */\n element: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | HTMLElement;\n /** Field type: text, email, number, select, textarea, checkbox, radio, date, etc. */\n type: string;\n /** Field name attribute */\n name: string;\n /** Field label (from <label>, aria-label, or placeholder) */\n label: string;\n /** For <select>, the available options */\n options?: string[];\n /** Whether the field is required */\n required: boolean;\n /** Current value */\n currentValue: string;\n /** Min/max for number/date fields */\n min?: string;\n max?: string;\n /** Pattern attribute */\n pattern?: string;\n}\n\n/** Field data returned by the AI */\nexport interface FieldFillData {\n /** Index matching the DetectedField array */\n index: number;\n /** Value to fill */\n value: string;\n /** For checkboxes: whether to check */\n checked?: boolean;\n}\n\n/** Internal state */\nexport interface GhostFillState {\n active: boolean;\n selecting: boolean;\n selectedBlock: HTMLElement | null;\n fields: DetectedField[];\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghostfill",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "description": "Stop demoing with test123. Realistic, domain-aware form data in one click.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -15,8 +15,15 @@
15
15
  "types": "./dist/server.d.ts",
16
16
  "import": "./dist/server.mjs",
17
17
  "require": "./dist/server.js"
18
+ },
19
+ "./mcp": {
20
+ "types": "./dist/mcp.d.ts",
21
+ "import": "./dist/mcp.mjs"
18
22
  }
19
23
  },
24
+ "bin": {
25
+ "ghostfill-mcp": "dist/mcp.mjs"
26
+ },
20
27
  "files": [
21
28
  "dist"
22
29
  ],
@@ -33,7 +40,9 @@
33
40
  "openai",
34
41
  "fake-data",
35
42
  "testing",
36
- "developer-tool"
43
+ "developer-tool",
44
+ "mcp",
45
+ "model-context-protocol"
37
46
  ],
38
47
  "author": "afkhalid",
39
48
  "license": "PolyForm-Shield-1.0.0",
@@ -48,5 +57,17 @@
48
57
  "devDependencies": {
49
58
  "tsup": "^8.0.0",
50
59
  "typescript": "^5.4.0"
60
+ },
61
+ "peerDependencies": {
62
+ "@modelcontextprotocol/sdk": "^1.27.1",
63
+ "zod": "^3.23.0 || ^4.0.0"
64
+ },
65
+ "peerDependenciesMeta": {
66
+ "@modelcontextprotocol/sdk": {
67
+ "optional": true
68
+ },
69
+ "zod": {
70
+ "optional": true
71
+ }
51
72
  }
52
73
  }