@formwright/ai 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Formwright contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # @formwright/ai
2
+
3
+ > Generate a **validated** [Formwright](https://github.com/aliarsalan177/formwright) schema from a natural-language description — with any LLM.
4
+
5
+ Describe a form in English; get back a schema the runtime accepts. Provider-agnostic:
6
+ the validate → repair loop is built in, and the model call is pluggable — Claude
7
+ (default), OpenAI/GPT, or anything else, each through its own native SDK.
8
+
9
+ ```bash
10
+ npm i @formwright/ai
11
+ ```
12
+
13
+ ## Claude (default)
14
+
15
+ ```ts
16
+ import { generateSchema } from "@formwright/ai";
17
+ import { Form } from "@formwright/core";
18
+ import "@formwright/dom";
19
+
20
+ // Uses ANTHROPIC_API_KEY; model defaults to claude-opus-4-8
21
+ const { schema, attempts } = await generateSchema(
22
+ "a checkout form: email, a plan dropdown (Free/Pro), and a promo code shown only when Pro is selected",
23
+ );
24
+
25
+ new Form(schema).mount(document.getElementById("root")!);
26
+ ```
27
+
28
+ ## OpenAI (GPT)
29
+
30
+ Bring your own `openai` client — no extra dependency is bundled:
31
+
32
+ ```ts
33
+ import OpenAI from "openai";
34
+ import { generateSchema, openaiProvider } from "@formwright/ai";
35
+
36
+ const { schema } = await generateSchema("a contact form with name, email, and message", {
37
+ provider: openaiProvider({ client: new OpenAI(), model: "gpt-4o" }),
38
+ });
39
+ ```
40
+
41
+ ## Any other model (Gemini, Mistral, local, …)
42
+
43
+ Wrap any async function that returns a candidate schema object; the repair loop does the rest:
44
+
45
+ ```ts
46
+ import { generateSchema, defineProvider, buildPrompt } from "@formwright/ai";
47
+
48
+ const gemini = defineProvider(async (input) => {
49
+ const text = await callYourModel({ system: input.system, prompt: buildPrompt(input) });
50
+ return JSON.parse(text); // return the parsed schema object
51
+ });
52
+
53
+ const { schema } = await generateSchema("an event RSVP form", { provider: gemini });
54
+ ```
55
+
56
+ `input.repair` is populated on retries with the previous (invalid) output and the exact
57
+ validation issues, so your provider can pass them back to the model.
58
+
59
+ ## How it works
60
+
61
+ 1. The provider proposes a schema object from your description.
62
+ 2. It's checked with `@formwright/schema`'s validator.
63
+ 3. If invalid, the precise, path-addressed issues are fed back for repair (up to
64
+ `maxRepairAttempts`, default 2).
65
+ 4. You get a `FormSchema` that the runtime is guaranteed to accept — or a thrown
66
+ `SchemaGenerationError` carrying the remaining issues.
67
+
68
+ ## API
69
+
70
+ ```ts
71
+ generateSchema(description: string, options?: {
72
+ provider?: SchemaProvider; // default: Claude
73
+ apiKey?: string; // for the default Claude provider
74
+ model?: string; // override the default model
75
+ maxRepairAttempts?: number; // default 2
76
+ system?: string; // override the Formwright DSL prompt
77
+ guidelines?: string; // append extra guidance
78
+ }): Promise<{ schema: FormSchema; attempts: number }>
79
+ ```
80
+
81
+ Providers: `claudeProvider(opts)`, `openaiProvider({ client, model? })`, `defineProvider(fn)`.
82
+
83
+ ## License
84
+
85
+ MIT
@@ -0,0 +1,156 @@
1
+ import Anthropic from '@anthropic-ai/sdk';
2
+ import { validateSchema } from '@formwright/schema';
3
+
4
+ // src/claude.ts
5
+
6
+ // src/openai.ts
7
+ function openaiProvider(options) {
8
+ const model = options.model ?? "gpt-4o";
9
+ return {
10
+ async propose(input) {
11
+ const response = await options.client.chat.completions.create({
12
+ model,
13
+ response_format: { type: "json_object" },
14
+ messages: [
15
+ { role: "system", content: input.system },
16
+ {
17
+ role: "user",
18
+ content: `${buildPrompt(input)}
19
+
20
+ Respond with ONLY the JSON schema object.`
21
+ }
22
+ ]
23
+ });
24
+ const content = response.choices[0]?.message?.content;
25
+ if (!content) throw new Error("OpenAI returned no content.");
26
+ return JSON.parse(content);
27
+ }
28
+ };
29
+ }
30
+
31
+ // src/index.ts
32
+ var SchemaGenerationError = class extends Error {
33
+ issues;
34
+ constructor(message, issues) {
35
+ super(message);
36
+ this.name = "SchemaGenerationError";
37
+ this.issues = issues;
38
+ }
39
+ };
40
+ function defineProvider(propose) {
41
+ return { propose };
42
+ }
43
+ function buildPrompt(input) {
44
+ if (!input.repair) {
45
+ return `Design a Formwright form for: ${input.description}`;
46
+ }
47
+ return `Design a Formwright form for: ${input.description}
48
+
49
+ Your previous attempt was INVALID:
50
+ ${JSON.stringify(input.repair.previous, null, 2)}
51
+
52
+ Validation issues to fix:
53
+ ` + input.repair.issues.map((i) => `- ${i.path}: ${i.message}`).join("\n") + `
54
+
55
+ Return a corrected schema that resolves every issue.`;
56
+ }
57
+ function unwrap(candidate) {
58
+ if (candidate && typeof candidate === "object" && "schema" in candidate) {
59
+ return candidate.schema;
60
+ }
61
+ return candidate;
62
+ }
63
+ async function generateSchema(description, options = {}) {
64
+ const provider = options.provider ?? await defaultProvider(options);
65
+ const maxRepair = options.maxRepairAttempts ?? 2;
66
+ const system = (options.system ?? SYSTEM_PROMPT) + (options.guidelines ? `
67
+
68
+ Additional guidelines:
69
+ ${options.guidelines}` : "");
70
+ let repair;
71
+ let lastIssues = [];
72
+ for (let attempt = 1; attempt <= maxRepair + 1; attempt++) {
73
+ const input = repair ? { description, system, repair } : { description, system };
74
+ const candidate = await provider.propose(input);
75
+ const result = validateSchema(unwrap(candidate));
76
+ if (result.ok) return { schema: result.value, attempts: attempt };
77
+ lastIssues = result.issues;
78
+ if (attempt > maxRepair) break;
79
+ repair = { previous: unwrap(candidate), issues: result.issues };
80
+ }
81
+ throw new SchemaGenerationError(
82
+ `Could not produce a valid schema after ${maxRepair + 1} attempts.`,
83
+ lastIssues
84
+ );
85
+ }
86
+ async function defaultProvider(options) {
87
+ const { claudeProvider: claudeProvider2 } = await import('./claude-BNP64G4N.js');
88
+ const opts = {};
89
+ if (options.apiKey !== void 0) opts.apiKey = options.apiKey;
90
+ if (options.model !== void 0) opts.model = options.model;
91
+ return claudeProvider2(opts);
92
+ }
93
+ var SYSTEM_PROMPT = `You design forms as Formwright schemas \u2014 plain JSON, no code.
94
+
95
+ A FormSchema has:
96
+ - "id" (string), "version" (string, e.g. "1.0"), optional "title", and "fields" (non-empty array).
97
+ - optional "submit": { "endpoint": { "method": "POST"|"GET"|"PUT"|"PATCH"|"DELETE", "url": string }, "transform"?, "onSuccess"?, "onError"? }.
98
+
99
+ Each field has "id" (unique within its scope) and "type", plus optional "label", "placeholder", "help", "description", "defaultValue".
100
+ Field types:
101
+ - "text" | "email" | "password" | "number" | "textarea"
102
+ - "checkbox" | "toggle" (boolean; toggle renders as a switch)
103
+ - "select" | "radio" \u2014 need "options": [{ "label": string, "value": string|number }]
104
+ - "group" \u2014 a nested object; needs "fields": [ ...child fields ]. Produces an object in the payload.
105
+ - "collection" \u2014 a repeatable list of objects; needs "fields", optional "itemLabel", "addLabel", "minItems", "maxItems". Produces an array of objects.
106
+
107
+ Validation (optional): "validation": { "kind": "string"|"number", "required"?, "min"?, "max"?, "minLength"?, "maxLength"?, "pattern"?, "format"?: "email"|"url"|"uuid", "message"? }.
108
+
109
+ Conditional logic \u2014 data, not code \u2014 via "visibleWhen" / "enabledWhen" / "requiredWhen", a JSONLogic-style expression:
110
+ { "==": [a, b] }, "!=", ">", ">=", "<", "<=", { "in": [needle, haystack] }, { "and": [...] }, { "or": [...] }, { "not": x }, and { "var": "fieldId" } to read another field's value.
111
+ Names resolve to a sibling first, then outward to the form root \u2014 so a field inside a group or collection row can react to an outer toggle.
112
+
113
+ Other per-field options: "omit": true (keep in the UI but exclude from the payload), "labelPosition": "start"|"end" (checkbox/toggle), "layout" ("accordion" for group; "cards"|"accordion" for collection).
114
+
115
+ Produce sensible labels, validation, and conditions matching the request. Prefer "toggle" for yes/no switches and add helpful placeholders. Output ONLY the schema object.`;
116
+
117
+ // src/claude.ts
118
+ var DEFAULT_CLAUDE_MODEL = "claude-opus-4-8";
119
+ var TOOL_NAME = "emit_form_schema";
120
+ var TOOL = {
121
+ name: TOOL_NAME,
122
+ description: "Emit the complete Formwright form schema as a structured object.",
123
+ input_schema: {
124
+ type: "object",
125
+ properties: {
126
+ schema: { type: "object", description: "A complete, valid Formwright FormSchema." }
127
+ },
128
+ required: ["schema"]
129
+ }
130
+ };
131
+ function claudeProvider(options = {}) {
132
+ const client = options.client ?? new Anthropic(options.apiKey ? { apiKey: options.apiKey } : void 0);
133
+ const model = options.model ?? DEFAULT_CLAUDE_MODEL;
134
+ const maxTokens = options.maxTokens ?? 16e3;
135
+ return {
136
+ async propose(input) {
137
+ const response = await client.messages.create({
138
+ model,
139
+ max_tokens: maxTokens,
140
+ system: input.system,
141
+ tools: [TOOL],
142
+ tool_choice: { type: "tool", name: TOOL_NAME },
143
+ messages: [{ role: "user", content: buildPrompt(input) }]
144
+ });
145
+ const toolUse = response.content.find(
146
+ (block) => block.type === "tool_use"
147
+ );
148
+ if (!toolUse) throw new Error("Claude did not emit a schema via the tool.");
149
+ return toolUse.input;
150
+ }
151
+ };
152
+ }
153
+
154
+ export { DEFAULT_CLAUDE_MODEL, SYSTEM_PROMPT, SchemaGenerationError, buildPrompt, claudeProvider, defineProvider, generateSchema, openaiProvider };
155
+ //# sourceMappingURL=chunk-I3HZFUC7.js.map
156
+ //# sourceMappingURL=chunk-I3HZFUC7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/openai.ts","../src/index.ts","../src/claude.ts"],"names":["claudeProvider"],"mappings":";;;;;;AAkCO,SAAS,eAAe,OAAA,EAAgD;AAC7E,EAAA,MAAM,KAAA,GAAQ,QAAQ,KAAA,IAAS,QAAA;AAC/B,EAAA,OAAO;AAAA,IACL,MAAM,QAAQ,KAAA,EAAuC;AACnD,MAAA,MAAM,WAAW,MAAM,OAAA,CAAQ,MAAA,CAAO,IAAA,CAAK,YAAY,MAAA,CAAO;AAAA,QAC5D,KAAA;AAAA,QACA,eAAA,EAAiB,EAAE,IAAA,EAAM,aAAA,EAAc;AAAA,QACvC,QAAA,EAAU;AAAA,UACR,EAAE,IAAA,EAAM,QAAA,EAAU,OAAA,EAAS,MAAM,MAAA,EAAO;AAAA,UACxC;AAAA,YACE,IAAA,EAAM,MAAA;AAAA,YACN,OAAA,EAAS,CAAA,EAAG,WAAA,CAAY,KAAK,CAAC;;AAAA,yCAAA;AAAA;AAChC;AACF,OACD,CAAA;AACD,MAAA,MAAM,OAAA,GAAU,QAAA,CAAS,OAAA,CAAQ,CAAC,GAAG,OAAA,EAAS,OAAA;AAC9C,MAAA,IAAI,CAAC,OAAA,EAAS,MAAM,IAAI,MAAM,6BAA6B,CAAA;AAC3D,MAAA,OAAO,IAAA,CAAK,MAAM,OAAO,CAAA;AAAA,IAC3B;AAAA,GACF;AACF;;;ACMO,IAAM,qBAAA,GAAN,cAAoC,KAAA,CAAM;AAAA,EACtC,MAAA;AAAA,EACT,WAAA,CAAY,SAAiB,MAAA,EAAoC;AAC/D,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,uBAAA;AACZ,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AACF;AAGO,SAAS,eAAe,OAAA,EAAoE;AACjG,EAAA,OAAO,EAAE,OAAA,EAAQ;AACnB;AAGO,SAAS,YAAY,KAAA,EAA6B;AACvD,EAAA,IAAI,CAAC,MAAM,MAAA,EAAQ;AACjB,IAAA,OAAO,CAAA,8BAAA,EAAiC,MAAM,WAAW,CAAA,CAAA;AAAA,EAC3D;AACA,EAAA,OACE,CAAA,8BAAA,EAAiC,MAAM,WAAW;;AAAA;AAAA,EACX,KAAK,SAAA,CAAU,KAAA,CAAM,OAAO,QAAA,EAAU,IAAA,EAAM,CAAC,CAAC;;AAAA;AAAA,CAAA,GAErF,MAAM,MAAA,CAAO,MAAA,CAAO,GAAA,CAAI,CAAC,MAAM,CAAA,EAAA,EAAK,CAAA,CAAE,IAAI,CAAA,EAAA,EAAK,EAAE,OAAO,CAAA,CAAE,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA,GACrE;;AAAA,oDAAA,CAAA;AAEJ;AAGA,SAAS,OAAO,SAAA,EAA6B;AAC3C,EAAA,IAAI,SAAA,IAAa,OAAO,SAAA,KAAc,QAAA,IAAY,YAAY,SAAA,EAAW;AACvE,IAAA,OAAQ,SAAA,CAAkC,MAAA;AAAA,EAC5C;AACA,EAAA,OAAO,SAAA;AACT;AAOA,eAAsB,cAAA,CACpB,WAAA,EACA,OAAA,GAA2B,EAAC,EACH;AACzB,EAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,QAAA,IAAa,MAAM,gBAAgB,OAAO,CAAA;AACnE,EAAA,MAAM,SAAA,GAAY,QAAQ,iBAAA,IAAqB,CAAA;AAC/C,EAAA,MAAM,MAAA,GAAA,CACH,OAAA,CAAQ,MAAA,IAAU,aAAA,KAClB,QAAQ,UAAA,GAAa;;AAAA;AAAA,EAA+B,OAAA,CAAQ,UAAU,CAAA,CAAA,GAAK,EAAA,CAAA;AAE9E,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI,aAAyC,EAAC;AAE9C,EAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,IAAW,SAAA,GAAY,GAAG,OAAA,EAAA,EAAW;AACzD,IAAA,MAAM,KAAA,GAAsB,SAAS,EAAE,WAAA,EAAa,QAAQ,MAAA,EAAO,GAAI,EAAE,WAAA,EAAa,MAAA,EAAO;AAC7F,IAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,OAAA,CAAQ,KAAK,CAAA;AAC9C,IAAA,MAAM,MAAA,GAAS,cAAA,CAAe,MAAA,CAAO,SAAS,CAAC,CAAA;AAC/C,IAAA,IAAI,MAAA,CAAO,IAAI,OAAO,EAAE,QAAQ,MAAA,CAAO,KAAA,EAAO,UAAU,OAAA,EAAQ;AAEhE,IAAA,UAAA,GAAa,MAAA,CAAO,MAAA;AACpB,IAAA,IAAI,UAAU,SAAA,EAAW;AACzB,IAAA,MAAA,GAAS,EAAE,QAAA,EAAU,MAAA,CAAO,SAAS,CAAA,EAAG,MAAA,EAAQ,OAAO,MAAA,EAAO;AAAA,EAChE;AAEA,EAAA,MAAM,IAAI,qBAAA;AAAA,IACR,CAAA,uCAAA,EAA0C,YAAY,CAAC,CAAA,UAAA,CAAA;AAAA,IACvD;AAAA,GACF;AACF;AAGA,eAAe,gBAAgB,OAAA,EAAmD;AAChF,EAAA,MAAM,EAAE,cAAA,EAAAA,eAAAA,EAAe,GAAI,MAAM,OAAO,sBAAa,CAAA;AACrD,EAAA,MAAM,OAA4C,EAAC;AACnD,EAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,MAAA,EAAW,IAAA,CAAK,SAAS,OAAA,CAAQ,MAAA;AACxD,EAAA,IAAI,OAAA,CAAQ,KAAA,KAAU,MAAA,EAAW,IAAA,CAAK,QAAQ,OAAA,CAAQ,KAAA;AACtD,EAAA,OAAOA,gBAAe,IAAI,CAAA;AAC5B;AAEO,IAAM,aAAA,GAAgB,CAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA,0KAAA;;;ACpItB,IAAM,oBAAA,GAAuB;AAUpC,IAAM,SAAA,GAAY,kBAAA;AAElB,IAAM,IAAA,GAAuB;AAAA,EAC3B,IAAA,EAAM,SAAA;AAAA,EACN,WAAA,EAAa,kEAAA;AAAA,EACb,YAAA,EAAc;AAAA,IACZ,IAAA,EAAM,QAAA;AAAA,IACN,UAAA,EAAY;AAAA,MACV,MAAA,EAAQ,EAAE,IAAA,EAAM,QAAA,EAAU,aAAa,0CAAA;AAA2C,KACpF;AAAA,IACA,QAAA,EAAU,CAAC,QAAQ;AAAA;AAEvB,CAAA;AAEO,SAAS,cAAA,CAAe,OAAA,GAAiC,EAAC,EAAmB;AAClF,EAAA,MAAM,MAAA,GACJ,OAAA,CAAQ,MAAA,IAAU,IAAI,SAAA,CAAU,OAAA,CAAQ,MAAA,GAAS,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAO,GAAI,MAAS,CAAA;AACzF,EAAA,MAAM,KAAA,GAAQ,QAAQ,KAAA,IAAS,oBAAA;AAC/B,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,IAAA;AAEvC,EAAA,OAAO;AAAA,IACL,MAAM,QAAQ,KAAA,EAAuC;AACnD,MAAA,MAAM,QAAA,GAAW,MAAM,MAAA,CAAO,QAAA,CAAS,MAAA,CAAO;AAAA,QAC5C,KAAA;AAAA,QACA,UAAA,EAAY,SAAA;AAAA,QACZ,QAAQ,KAAA,CAAM,MAAA;AAAA,QACd,KAAA,EAAO,CAAC,IAAI,CAAA;AAAA,QACZ,WAAA,EAAa,EAAE,IAAA,EAAM,MAAA,EAAQ,MAAM,SAAA,EAAU;AAAA,QAC7C,QAAA,EAAU,CAAC,EAAE,IAAA,EAAM,QAAQ,OAAA,EAAS,WAAA,CAAY,KAAK,CAAA,EAAG;AAAA,OACzD,CAAA;AACD,MAAA,MAAM,OAAA,GAAU,SAAS,OAAA,CAAQ,IAAA;AAAA,QAC/B,CAAC,KAAA,KAA2C,KAAA,CAAM,IAAA,KAAS;AAAA,OAC7D;AACA,MAAA,IAAI,CAAC,OAAA,EAAS,MAAM,IAAI,MAAM,4CAA4C,CAAA;AAC1E,MAAA,OAAO,OAAA,CAAQ,KAAA;AAAA,IACjB;AAAA,GACF;AACF","file":"chunk-I3HZFUC7.js","sourcesContent":["/**\n * OpenAI (GPT) provider — uses JSON mode on the Chat Completions API.\n *\n * To avoid a hard dependency on the `openai` package, pass your own client:\n *\n * import OpenAI from \"openai\";\n * import { generateSchema, openaiProvider } from \"@formwright/ai\";\n * const { schema } = await generateSchema(\"a contact form\", {\n * provider: openaiProvider({ client: new OpenAI() }),\n * });\n *\n * Any client matching {@link OpenAILike} works (Azure OpenAI, compatible gateways).\n */\nimport { buildPrompt, type ProposeInput, type SchemaProvider } from \"./index.js\";\n\n/** Minimal structural shape of an OpenAI client's `chat.completions.create`. */\nexport interface OpenAILike {\n chat: {\n completions: {\n create(body: {\n model: string;\n messages: Array<{ role: \"system\" | \"user\" | \"assistant\"; content: string }>;\n response_format?: { type: \"json_object\" };\n }): Promise<{ choices: Array<{ message: { content: string | null } }> }>;\n };\n };\n}\n\nexport interface OpenAIProviderOptions {\n /** A configured OpenAI client (e.g. `new OpenAI()`). Required. */\n readonly client: OpenAILike;\n readonly model?: string;\n}\n\nexport function openaiProvider(options: OpenAIProviderOptions): SchemaProvider {\n const model = options.model ?? \"gpt-4o\";\n return {\n async propose(input: ProposeInput): Promise<unknown> {\n const response = await options.client.chat.completions.create({\n model,\n response_format: { type: \"json_object\" },\n messages: [\n { role: \"system\", content: input.system },\n {\n role: \"user\",\n content: `${buildPrompt(input)}\\n\\nRespond with ONLY the JSON schema object.`,\n },\n ],\n });\n const content = response.choices[0]?.message?.content;\n if (!content) throw new Error(\"OpenAI returned no content.\");\n return JSON.parse(content) as unknown;\n },\n };\n}\n","/**\n * @formwright/ai — turn a natural-language form description into a *validated*\n * Formwright schema using an LLM.\n *\n * import { generateSchema } from \"@formwright/ai\";\n * const { schema } = await generateSchema(\"a signup form with a US-only state field\");\n * new Form(schema).mount(el);\n *\n * Provider-agnostic: the validate → repair loop lives here, and the actual model\n * call is a pluggable {@link SchemaProvider}. A Claude provider ships built-in\n * (the default); an OpenAI provider and a custom-function provider let you use\n * GPT, Gemini, Mistral, a local model, or anything else — each through its own\n * native SDK, never a compatibility shim.\n *\n * Whatever the provider returns is checked with `@formwright/schema`'s validator;\n * if it's invalid, the precise issues are fed back for repair — so what you get\n * out always satisfies the runtime (or a thrown {@link SchemaGenerationError}).\n */\nimport { validateSchema, type FormSchema, type ValidationIssue } from \"@formwright/schema\";\n\nexport { claudeProvider, type ClaudeProviderOptions } from \"./claude.js\";\nexport { openaiProvider, type OpenAIProviderOptions, type OpenAILike } from \"./openai.js\";\n\n/** A pluggable model backend: produce a candidate schema object for a request. */\nexport interface SchemaProvider {\n propose(input: ProposeInput): Promise<unknown>;\n}\n\nexport interface ProposeInput {\n readonly description: string;\n readonly system: string;\n /** Present on a repair attempt: the previous (invalid) output and why it failed. */\n readonly repair?: RepairContext;\n}\n\nexport interface RepairContext {\n readonly previous: unknown;\n readonly issues: readonly ValidationIssue[];\n}\n\nexport interface GenerateOptions {\n /** Model backend. Defaults to Claude (needs `ANTHROPIC_API_KEY` or `apiKey`). */\n readonly provider?: SchemaProvider;\n /** Convenience for the default Claude provider when `provider` is omitted. */\n readonly apiKey?: string;\n readonly model?: string;\n /** How many times to feed validation errors back for repair (default 2). */\n readonly maxRepairAttempts?: number;\n /** Override the Formwright DSL system prompt. */\n readonly system?: string;\n /** Extra guidance appended to the system prompt. */\n readonly guidelines?: string;\n}\n\nexport interface GenerateResult {\n readonly schema: FormSchema;\n /** Number of model round-trips it took (1 = valid on the first try). */\n readonly attempts: number;\n}\n\nexport class SchemaGenerationError extends Error {\n readonly issues: readonly ValidationIssue[];\n constructor(message: string, issues: readonly ValidationIssue[]) {\n super(message);\n this.name = \"SchemaGenerationError\";\n this.issues = issues;\n }\n}\n\n/** Wrap a plain async function as a provider (for Gemini, Mistral, local models, …). */\nexport function defineProvider(propose: (input: ProposeInput) => Promise<unknown>): SchemaProvider {\n return { propose };\n}\n\n/** Build the user-facing instruction, including repair feedback when retrying. */\nexport function buildPrompt(input: ProposeInput): string {\n if (!input.repair) {\n return `Design a Formwright form for: ${input.description}`;\n }\n return (\n `Design a Formwright form for: ${input.description}\\n\\n` +\n `Your previous attempt was INVALID:\\n${JSON.stringify(input.repair.previous, null, 2)}\\n\\n` +\n `Validation issues to fix:\\n` +\n input.repair.issues.map((i) => `- ${i.path}: ${i.message}`).join(\"\\n\") +\n `\\n\\nReturn a corrected schema that resolves every issue.`\n );\n}\n\n/** Some providers wrap the schema under a `schema` key; accept either shape. */\nfunction unwrap(candidate: unknown): unknown {\n if (candidate && typeof candidate === \"object\" && \"schema\" in candidate) {\n return (candidate as { schema: unknown }).schema;\n }\n return candidate;\n}\n\n/**\n * Generate a validated {@link FormSchema} from a natural-language description.\n * Throws {@link SchemaGenerationError} if the model can't produce a valid schema\n * within `maxRepairAttempts`.\n */\nexport async function generateSchema(\n description: string,\n options: GenerateOptions = {},\n): Promise<GenerateResult> {\n const provider = options.provider ?? (await defaultProvider(options));\n const maxRepair = options.maxRepairAttempts ?? 2;\n const system =\n (options.system ?? SYSTEM_PROMPT) +\n (options.guidelines ? `\\n\\nAdditional guidelines:\\n${options.guidelines}` : \"\");\n\n let repair: RepairContext | undefined;\n let lastIssues: readonly ValidationIssue[] = [];\n\n for (let attempt = 1; attempt <= maxRepair + 1; attempt++) {\n const input: ProposeInput = repair ? { description, system, repair } : { description, system };\n const candidate = await provider.propose(input);\n const result = validateSchema(unwrap(candidate));\n if (result.ok) return { schema: result.value, attempts: attempt };\n\n lastIssues = result.issues;\n if (attempt > maxRepair) break;\n repair = { previous: unwrap(candidate), issues: result.issues };\n }\n\n throw new SchemaGenerationError(\n `Could not produce a valid schema after ${maxRepair + 1} attempts.`,\n lastIssues,\n );\n}\n\n/** Lazily construct the default (Claude) provider so OpenAI-only users don't need a key. */\nasync function defaultProvider(options: GenerateOptions): Promise<SchemaProvider> {\n const { claudeProvider } = await import(\"./claude.js\");\n const opts: { apiKey?: string; model?: string } = {};\n if (options.apiKey !== undefined) opts.apiKey = options.apiKey;\n if (options.model !== undefined) opts.model = options.model;\n return claudeProvider(opts);\n}\n\nexport const SYSTEM_PROMPT = `You design forms as Formwright schemas — plain JSON, no code.\n\nA FormSchema has:\n- \"id\" (string), \"version\" (string, e.g. \"1.0\"), optional \"title\", and \"fields\" (non-empty array).\n- optional \"submit\": { \"endpoint\": { \"method\": \"POST\"|\"GET\"|\"PUT\"|\"PATCH\"|\"DELETE\", \"url\": string }, \"transform\"?, \"onSuccess\"?, \"onError\"? }.\n\nEach field has \"id\" (unique within its scope) and \"type\", plus optional \"label\", \"placeholder\", \"help\", \"description\", \"defaultValue\".\nField types:\n- \"text\" | \"email\" | \"password\" | \"number\" | \"textarea\"\n- \"checkbox\" | \"toggle\" (boolean; toggle renders as a switch)\n- \"select\" | \"radio\" — need \"options\": [{ \"label\": string, \"value\": string|number }]\n- \"group\" — a nested object; needs \"fields\": [ ...child fields ]. Produces an object in the payload.\n- \"collection\" — a repeatable list of objects; needs \"fields\", optional \"itemLabel\", \"addLabel\", \"minItems\", \"maxItems\". Produces an array of objects.\n\nValidation (optional): \"validation\": { \"kind\": \"string\"|\"number\", \"required\"?, \"min\"?, \"max\"?, \"minLength\"?, \"maxLength\"?, \"pattern\"?, \"format\"?: \"email\"|\"url\"|\"uuid\", \"message\"? }.\n\nConditional logic — data, not code — via \"visibleWhen\" / \"enabledWhen\" / \"requiredWhen\", a JSONLogic-style expression:\n{ \"==\": [a, b] }, \"!=\", \">\", \">=\", \"<\", \"<=\", { \"in\": [needle, haystack] }, { \"and\": [...] }, { \"or\": [...] }, { \"not\": x }, and { \"var\": \"fieldId\" } to read another field's value.\nNames resolve to a sibling first, then outward to the form root — so a field inside a group or collection row can react to an outer toggle.\n\nOther per-field options: \"omit\": true (keep in the UI but exclude from the payload), \"labelPosition\": \"start\"|\"end\" (checkbox/toggle), \"layout\" (\"accordion\" for group; \"cards\"|\"accordion\" for collection).\n\nProduce sensible labels, validation, and conditions matching the request. Prefer \"toggle\" for yes/no switches and add helpful placeholders. Output ONLY the schema object.`;\n","/**\n * Claude provider — emits the schema through a forced tool call on the Anthropic\n * Messages API. Default model is Claude Opus 4.8 (`claude-opus-4-8`).\n */\nimport Anthropic from \"@anthropic-ai/sdk\";\nimport { buildPrompt, type ProposeInput, type SchemaProvider } from \"./index.js\";\n\n/** Claude Opus 4.8 — the most capable model. */\nexport const DEFAULT_CLAUDE_MODEL = \"claude-opus-4-8\";\n\nexport interface ClaudeProviderOptions {\n readonly apiKey?: string;\n /** Bring your own configured client (takes precedence over `apiKey`). */\n readonly client?: Anthropic;\n readonly model?: string;\n readonly maxTokens?: number;\n}\n\nconst TOOL_NAME = \"emit_form_schema\";\n\nconst TOOL: Anthropic.Tool = {\n name: TOOL_NAME,\n description: \"Emit the complete Formwright form schema as a structured object.\",\n input_schema: {\n type: \"object\",\n properties: {\n schema: { type: \"object\", description: \"A complete, valid Formwright FormSchema.\" },\n },\n required: [\"schema\"],\n },\n};\n\nexport function claudeProvider(options: ClaudeProviderOptions = {}): SchemaProvider {\n const client =\n options.client ?? new Anthropic(options.apiKey ? { apiKey: options.apiKey } : undefined);\n const model = options.model ?? DEFAULT_CLAUDE_MODEL;\n const maxTokens = options.maxTokens ?? 16000;\n\n return {\n async propose(input: ProposeInput): Promise<unknown> {\n const response = await client.messages.create({\n model,\n max_tokens: maxTokens,\n system: input.system,\n tools: [TOOL],\n tool_choice: { type: \"tool\", name: TOOL_NAME },\n messages: [{ role: \"user\", content: buildPrompt(input) }],\n });\n const toolUse = response.content.find(\n (block): block is Anthropic.ToolUseBlock => block.type === \"tool_use\",\n );\n if (!toolUse) throw new Error(\"Claude did not emit a schema via the tool.\");\n return toolUse.input;\n },\n };\n}\n"]}
@@ -0,0 +1,3 @@
1
+ export { DEFAULT_CLAUDE_MODEL, claudeProvider } from './chunk-I3HZFUC7.js';
2
+ //# sourceMappingURL=claude-BNP64G4N.js.map
3
+ //# sourceMappingURL=claude-BNP64G4N.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"claude-BNP64G4N.js"}
package/dist/index.cjs ADDED
@@ -0,0 +1,196 @@
1
+ 'use strict';
2
+
3
+ var Anthropic = require('@anthropic-ai/sdk');
4
+ var schema = require('@formwright/schema');
5
+
6
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
+
8
+ var Anthropic__default = /*#__PURE__*/_interopDefault(Anthropic);
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __esm = (fn, res) => function __init() {
13
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
14
+ };
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+
20
+ // src/claude.ts
21
+ var claude_exports = {};
22
+ __export(claude_exports, {
23
+ DEFAULT_CLAUDE_MODEL: () => DEFAULT_CLAUDE_MODEL,
24
+ claudeProvider: () => claudeProvider
25
+ });
26
+ function claudeProvider(options = {}) {
27
+ const client = options.client ?? new Anthropic__default.default(options.apiKey ? { apiKey: options.apiKey } : void 0);
28
+ const model = options.model ?? DEFAULT_CLAUDE_MODEL;
29
+ const maxTokens = options.maxTokens ?? 16e3;
30
+ return {
31
+ async propose(input) {
32
+ const response = await client.messages.create({
33
+ model,
34
+ max_tokens: maxTokens,
35
+ system: input.system,
36
+ tools: [TOOL],
37
+ tool_choice: { type: "tool", name: TOOL_NAME },
38
+ messages: [{ role: "user", content: buildPrompt(input) }]
39
+ });
40
+ const toolUse = response.content.find(
41
+ (block) => block.type === "tool_use"
42
+ );
43
+ if (!toolUse) throw new Error("Claude did not emit a schema via the tool.");
44
+ return toolUse.input;
45
+ }
46
+ };
47
+ }
48
+ var DEFAULT_CLAUDE_MODEL, TOOL_NAME, TOOL;
49
+ var init_claude = __esm({
50
+ "src/claude.ts"() {
51
+ init_index();
52
+ DEFAULT_CLAUDE_MODEL = "claude-opus-4-8";
53
+ TOOL_NAME = "emit_form_schema";
54
+ TOOL = {
55
+ name: TOOL_NAME,
56
+ description: "Emit the complete Formwright form schema as a structured object.",
57
+ input_schema: {
58
+ type: "object",
59
+ properties: {
60
+ schema: { type: "object", description: "A complete, valid Formwright FormSchema." }
61
+ },
62
+ required: ["schema"]
63
+ }
64
+ };
65
+ }
66
+ });
67
+
68
+ // src/openai.ts
69
+ function openaiProvider(options) {
70
+ const model = options.model ?? "gpt-4o";
71
+ return {
72
+ async propose(input) {
73
+ const response = await options.client.chat.completions.create({
74
+ model,
75
+ response_format: { type: "json_object" },
76
+ messages: [
77
+ { role: "system", content: input.system },
78
+ {
79
+ role: "user",
80
+ content: `${buildPrompt(input)}
81
+
82
+ Respond with ONLY the JSON schema object.`
83
+ }
84
+ ]
85
+ });
86
+ const content = response.choices[0]?.message?.content;
87
+ if (!content) throw new Error("OpenAI returned no content.");
88
+ return JSON.parse(content);
89
+ }
90
+ };
91
+ }
92
+ var init_openai = __esm({
93
+ "src/openai.ts"() {
94
+ init_index();
95
+ }
96
+ });
97
+ function defineProvider(propose) {
98
+ return { propose };
99
+ }
100
+ function buildPrompt(input) {
101
+ if (!input.repair) {
102
+ return `Design a Formwright form for: ${input.description}`;
103
+ }
104
+ return `Design a Formwright form for: ${input.description}
105
+
106
+ Your previous attempt was INVALID:
107
+ ${JSON.stringify(input.repair.previous, null, 2)}
108
+
109
+ Validation issues to fix:
110
+ ` + input.repair.issues.map((i) => `- ${i.path}: ${i.message}`).join("\n") + `
111
+
112
+ Return a corrected schema that resolves every issue.`;
113
+ }
114
+ function unwrap(candidate) {
115
+ if (candidate && typeof candidate === "object" && "schema" in candidate) {
116
+ return candidate.schema;
117
+ }
118
+ return candidate;
119
+ }
120
+ async function generateSchema(description, options = {}) {
121
+ const provider = options.provider ?? await defaultProvider(options);
122
+ const maxRepair = options.maxRepairAttempts ?? 2;
123
+ const system = (options.system ?? exports.SYSTEM_PROMPT) + (options.guidelines ? `
124
+
125
+ Additional guidelines:
126
+ ${options.guidelines}` : "");
127
+ let repair;
128
+ let lastIssues = [];
129
+ for (let attempt = 1; attempt <= maxRepair + 1; attempt++) {
130
+ const input = repair ? { description, system, repair } : { description, system };
131
+ const candidate = await provider.propose(input);
132
+ const result = schema.validateSchema(unwrap(candidate));
133
+ if (result.ok) return { schema: result.value, attempts: attempt };
134
+ lastIssues = result.issues;
135
+ if (attempt > maxRepair) break;
136
+ repair = { previous: unwrap(candidate), issues: result.issues };
137
+ }
138
+ throw new exports.SchemaGenerationError(
139
+ `Could not produce a valid schema after ${maxRepair + 1} attempts.`,
140
+ lastIssues
141
+ );
142
+ }
143
+ async function defaultProvider(options) {
144
+ const { claudeProvider: claudeProvider2 } = await Promise.resolve().then(() => (init_claude(), claude_exports));
145
+ const opts = {};
146
+ if (options.apiKey !== void 0) opts.apiKey = options.apiKey;
147
+ if (options.model !== void 0) opts.model = options.model;
148
+ return claudeProvider2(opts);
149
+ }
150
+ exports.SchemaGenerationError = void 0; exports.SYSTEM_PROMPT = void 0;
151
+ var init_index = __esm({
152
+ "src/index.ts"() {
153
+ init_claude();
154
+ init_openai();
155
+ exports.SchemaGenerationError = class extends Error {
156
+ issues;
157
+ constructor(message, issues) {
158
+ super(message);
159
+ this.name = "SchemaGenerationError";
160
+ this.issues = issues;
161
+ }
162
+ };
163
+ exports.SYSTEM_PROMPT = `You design forms as Formwright schemas \u2014 plain JSON, no code.
164
+
165
+ A FormSchema has:
166
+ - "id" (string), "version" (string, e.g. "1.0"), optional "title", and "fields" (non-empty array).
167
+ - optional "submit": { "endpoint": { "method": "POST"|"GET"|"PUT"|"PATCH"|"DELETE", "url": string }, "transform"?, "onSuccess"?, "onError"? }.
168
+
169
+ Each field has "id" (unique within its scope) and "type", plus optional "label", "placeholder", "help", "description", "defaultValue".
170
+ Field types:
171
+ - "text" | "email" | "password" | "number" | "textarea"
172
+ - "checkbox" | "toggle" (boolean; toggle renders as a switch)
173
+ - "select" | "radio" \u2014 need "options": [{ "label": string, "value": string|number }]
174
+ - "group" \u2014 a nested object; needs "fields": [ ...child fields ]. Produces an object in the payload.
175
+ - "collection" \u2014 a repeatable list of objects; needs "fields", optional "itemLabel", "addLabel", "minItems", "maxItems". Produces an array of objects.
176
+
177
+ Validation (optional): "validation": { "kind": "string"|"number", "required"?, "min"?, "max"?, "minLength"?, "maxLength"?, "pattern"?, "format"?: "email"|"url"|"uuid", "message"? }.
178
+
179
+ Conditional logic \u2014 data, not code \u2014 via "visibleWhen" / "enabledWhen" / "requiredWhen", a JSONLogic-style expression:
180
+ { "==": [a, b] }, "!=", ">", ">=", "<", "<=", { "in": [needle, haystack] }, { "and": [...] }, { "or": [...] }, { "not": x }, and { "var": "fieldId" } to read another field's value.
181
+ Names resolve to a sibling first, then outward to the form root \u2014 so a field inside a group or collection row can react to an outer toggle.
182
+
183
+ Other per-field options: "omit": true (keep in the UI but exclude from the payload), "labelPosition": "start"|"end" (checkbox/toggle), "layout" ("accordion" for group; "cards"|"accordion" for collection).
184
+
185
+ Produce sensible labels, validation, and conditions matching the request. Prefer "toggle" for yes/no switches and add helpful placeholders. Output ONLY the schema object.`;
186
+ }
187
+ });
188
+ init_index();
189
+
190
+ exports.buildPrompt = buildPrompt;
191
+ exports.claudeProvider = claudeProvider;
192
+ exports.defineProvider = defineProvider;
193
+ exports.generateSchema = generateSchema;
194
+ exports.openaiProvider = openaiProvider;
195
+ //# sourceMappingURL=index.cjs.map
196
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/claude.ts","../src/openai.ts","../src/index.ts"],"names":["Anthropic","SYSTEM_PROMPT","validateSchema","SchemaGenerationError","claudeProvider"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Claude provider — emits the schema through a forced tool call on the Anthropic\n * Messages API. Default model is Claude Opus 4.8 (`claude-opus-4-8`).\n */\nimport Anthropic from \"@anthropic-ai/sdk\";\nimport { buildPrompt, type ProposeInput, type SchemaProvider } from \"./index.js\";\n\n/** Claude Opus 4.8 — the most capable model. */\nexport const DEFAULT_CLAUDE_MODEL = \"claude-opus-4-8\";\n\nexport interface ClaudeProviderOptions {\n readonly apiKey?: string;\n /** Bring your own configured client (takes precedence over `apiKey`). */\n readonly client?: Anthropic;\n readonly model?: string;\n readonly maxTokens?: number;\n}\n\nconst TOOL_NAME = \"emit_form_schema\";\n\nconst TOOL: Anthropic.Tool = {\n name: TOOL_NAME,\n description: \"Emit the complete Formwright form schema as a structured object.\",\n input_schema: {\n type: \"object\",\n properties: {\n schema: { type: \"object\", description: \"A complete, valid Formwright FormSchema.\" },\n },\n required: [\"schema\"],\n },\n};\n\nexport function claudeProvider(options: ClaudeProviderOptions = {}): SchemaProvider {\n const client =\n options.client ?? new Anthropic(options.apiKey ? { apiKey: options.apiKey } : undefined);\n const model = options.model ?? DEFAULT_CLAUDE_MODEL;\n const maxTokens = options.maxTokens ?? 16000;\n\n return {\n async propose(input: ProposeInput): Promise<unknown> {\n const response = await client.messages.create({\n model,\n max_tokens: maxTokens,\n system: input.system,\n tools: [TOOL],\n tool_choice: { type: \"tool\", name: TOOL_NAME },\n messages: [{ role: \"user\", content: buildPrompt(input) }],\n });\n const toolUse = response.content.find(\n (block): block is Anthropic.ToolUseBlock => block.type === \"tool_use\",\n );\n if (!toolUse) throw new Error(\"Claude did not emit a schema via the tool.\");\n return toolUse.input;\n },\n };\n}\n","/**\n * OpenAI (GPT) provider — uses JSON mode on the Chat Completions API.\n *\n * To avoid a hard dependency on the `openai` package, pass your own client:\n *\n * import OpenAI from \"openai\";\n * import { generateSchema, openaiProvider } from \"@formwright/ai\";\n * const { schema } = await generateSchema(\"a contact form\", {\n * provider: openaiProvider({ client: new OpenAI() }),\n * });\n *\n * Any client matching {@link OpenAILike} works (Azure OpenAI, compatible gateways).\n */\nimport { buildPrompt, type ProposeInput, type SchemaProvider } from \"./index.js\";\n\n/** Minimal structural shape of an OpenAI client's `chat.completions.create`. */\nexport interface OpenAILike {\n chat: {\n completions: {\n create(body: {\n model: string;\n messages: Array<{ role: \"system\" | \"user\" | \"assistant\"; content: string }>;\n response_format?: { type: \"json_object\" };\n }): Promise<{ choices: Array<{ message: { content: string | null } }> }>;\n };\n };\n}\n\nexport interface OpenAIProviderOptions {\n /** A configured OpenAI client (e.g. `new OpenAI()`). Required. */\n readonly client: OpenAILike;\n readonly model?: string;\n}\n\nexport function openaiProvider(options: OpenAIProviderOptions): SchemaProvider {\n const model = options.model ?? \"gpt-4o\";\n return {\n async propose(input: ProposeInput): Promise<unknown> {\n const response = await options.client.chat.completions.create({\n model,\n response_format: { type: \"json_object\" },\n messages: [\n { role: \"system\", content: input.system },\n {\n role: \"user\",\n content: `${buildPrompt(input)}\\n\\nRespond with ONLY the JSON schema object.`,\n },\n ],\n });\n const content = response.choices[0]?.message?.content;\n if (!content) throw new Error(\"OpenAI returned no content.\");\n return JSON.parse(content) as unknown;\n },\n };\n}\n","/**\n * @formwright/ai — turn a natural-language form description into a *validated*\n * Formwright schema using an LLM.\n *\n * import { generateSchema } from \"@formwright/ai\";\n * const { schema } = await generateSchema(\"a signup form with a US-only state field\");\n * new Form(schema).mount(el);\n *\n * Provider-agnostic: the validate → repair loop lives here, and the actual model\n * call is a pluggable {@link SchemaProvider}. A Claude provider ships built-in\n * (the default); an OpenAI provider and a custom-function provider let you use\n * GPT, Gemini, Mistral, a local model, or anything else — each through its own\n * native SDK, never a compatibility shim.\n *\n * Whatever the provider returns is checked with `@formwright/schema`'s validator;\n * if it's invalid, the precise issues are fed back for repair — so what you get\n * out always satisfies the runtime (or a thrown {@link SchemaGenerationError}).\n */\nimport { validateSchema, type FormSchema, type ValidationIssue } from \"@formwright/schema\";\n\nexport { claudeProvider, type ClaudeProviderOptions } from \"./claude.js\";\nexport { openaiProvider, type OpenAIProviderOptions, type OpenAILike } from \"./openai.js\";\n\n/** A pluggable model backend: produce a candidate schema object for a request. */\nexport interface SchemaProvider {\n propose(input: ProposeInput): Promise<unknown>;\n}\n\nexport interface ProposeInput {\n readonly description: string;\n readonly system: string;\n /** Present on a repair attempt: the previous (invalid) output and why it failed. */\n readonly repair?: RepairContext;\n}\n\nexport interface RepairContext {\n readonly previous: unknown;\n readonly issues: readonly ValidationIssue[];\n}\n\nexport interface GenerateOptions {\n /** Model backend. Defaults to Claude (needs `ANTHROPIC_API_KEY` or `apiKey`). */\n readonly provider?: SchemaProvider;\n /** Convenience for the default Claude provider when `provider` is omitted. */\n readonly apiKey?: string;\n readonly model?: string;\n /** How many times to feed validation errors back for repair (default 2). */\n readonly maxRepairAttempts?: number;\n /** Override the Formwright DSL system prompt. */\n readonly system?: string;\n /** Extra guidance appended to the system prompt. */\n readonly guidelines?: string;\n}\n\nexport interface GenerateResult {\n readonly schema: FormSchema;\n /** Number of model round-trips it took (1 = valid on the first try). */\n readonly attempts: number;\n}\n\nexport class SchemaGenerationError extends Error {\n readonly issues: readonly ValidationIssue[];\n constructor(message: string, issues: readonly ValidationIssue[]) {\n super(message);\n this.name = \"SchemaGenerationError\";\n this.issues = issues;\n }\n}\n\n/** Wrap a plain async function as a provider (for Gemini, Mistral, local models, …). */\nexport function defineProvider(propose: (input: ProposeInput) => Promise<unknown>): SchemaProvider {\n return { propose };\n}\n\n/** Build the user-facing instruction, including repair feedback when retrying. */\nexport function buildPrompt(input: ProposeInput): string {\n if (!input.repair) {\n return `Design a Formwright form for: ${input.description}`;\n }\n return (\n `Design a Formwright form for: ${input.description}\\n\\n` +\n `Your previous attempt was INVALID:\\n${JSON.stringify(input.repair.previous, null, 2)}\\n\\n` +\n `Validation issues to fix:\\n` +\n input.repair.issues.map((i) => `- ${i.path}: ${i.message}`).join(\"\\n\") +\n `\\n\\nReturn a corrected schema that resolves every issue.`\n );\n}\n\n/** Some providers wrap the schema under a `schema` key; accept either shape. */\nfunction unwrap(candidate: unknown): unknown {\n if (candidate && typeof candidate === \"object\" && \"schema\" in candidate) {\n return (candidate as { schema: unknown }).schema;\n }\n return candidate;\n}\n\n/**\n * Generate a validated {@link FormSchema} from a natural-language description.\n * Throws {@link SchemaGenerationError} if the model can't produce a valid schema\n * within `maxRepairAttempts`.\n */\nexport async function generateSchema(\n description: string,\n options: GenerateOptions = {},\n): Promise<GenerateResult> {\n const provider = options.provider ?? (await defaultProvider(options));\n const maxRepair = options.maxRepairAttempts ?? 2;\n const system =\n (options.system ?? SYSTEM_PROMPT) +\n (options.guidelines ? `\\n\\nAdditional guidelines:\\n${options.guidelines}` : \"\");\n\n let repair: RepairContext | undefined;\n let lastIssues: readonly ValidationIssue[] = [];\n\n for (let attempt = 1; attempt <= maxRepair + 1; attempt++) {\n const input: ProposeInput = repair ? { description, system, repair } : { description, system };\n const candidate = await provider.propose(input);\n const result = validateSchema(unwrap(candidate));\n if (result.ok) return { schema: result.value, attempts: attempt };\n\n lastIssues = result.issues;\n if (attempt > maxRepair) break;\n repair = { previous: unwrap(candidate), issues: result.issues };\n }\n\n throw new SchemaGenerationError(\n `Could not produce a valid schema after ${maxRepair + 1} attempts.`,\n lastIssues,\n );\n}\n\n/** Lazily construct the default (Claude) provider so OpenAI-only users don't need a key. */\nasync function defaultProvider(options: GenerateOptions): Promise<SchemaProvider> {\n const { claudeProvider } = await import(\"./claude.js\");\n const opts: { apiKey?: string; model?: string } = {};\n if (options.apiKey !== undefined) opts.apiKey = options.apiKey;\n if (options.model !== undefined) opts.model = options.model;\n return claudeProvider(opts);\n}\n\nexport const SYSTEM_PROMPT = `You design forms as Formwright schemas — plain JSON, no code.\n\nA FormSchema has:\n- \"id\" (string), \"version\" (string, e.g. \"1.0\"), optional \"title\", and \"fields\" (non-empty array).\n- optional \"submit\": { \"endpoint\": { \"method\": \"POST\"|\"GET\"|\"PUT\"|\"PATCH\"|\"DELETE\", \"url\": string }, \"transform\"?, \"onSuccess\"?, \"onError\"? }.\n\nEach field has \"id\" (unique within its scope) and \"type\", plus optional \"label\", \"placeholder\", \"help\", \"description\", \"defaultValue\".\nField types:\n- \"text\" | \"email\" | \"password\" | \"number\" | \"textarea\"\n- \"checkbox\" | \"toggle\" (boolean; toggle renders as a switch)\n- \"select\" | \"radio\" — need \"options\": [{ \"label\": string, \"value\": string|number }]\n- \"group\" — a nested object; needs \"fields\": [ ...child fields ]. Produces an object in the payload.\n- \"collection\" — a repeatable list of objects; needs \"fields\", optional \"itemLabel\", \"addLabel\", \"minItems\", \"maxItems\". Produces an array of objects.\n\nValidation (optional): \"validation\": { \"kind\": \"string\"|\"number\", \"required\"?, \"min\"?, \"max\"?, \"minLength\"?, \"maxLength\"?, \"pattern\"?, \"format\"?: \"email\"|\"url\"|\"uuid\", \"message\"? }.\n\nConditional logic — data, not code — via \"visibleWhen\" / \"enabledWhen\" / \"requiredWhen\", a JSONLogic-style expression:\n{ \"==\": [a, b] }, \"!=\", \">\", \">=\", \"<\", \"<=\", { \"in\": [needle, haystack] }, { \"and\": [...] }, { \"or\": [...] }, { \"not\": x }, and { \"var\": \"fieldId\" } to read another field's value.\nNames resolve to a sibling first, then outward to the form root — so a field inside a group or collection row can react to an outer toggle.\n\nOther per-field options: \"omit\": true (keep in the UI but exclude from the payload), \"labelPosition\": \"start\"|\"end\" (checkbox/toggle), \"layout\" (\"accordion\" for group; \"cards\"|\"accordion\" for collection).\n\nProduce sensible labels, validation, and conditions matching the request. Prefer \"toggle\" for yes/no switches and add helpful placeholders. Output ONLY the schema object.`;\n"]}
@@ -0,0 +1,129 @@
1
+ import { ValidationIssue, FormSchema } from '@formwright/schema';
2
+ import Anthropic from '@anthropic-ai/sdk';
3
+
4
+ /**
5
+ * Claude provider — emits the schema through a forced tool call on the Anthropic
6
+ * Messages API. Default model is Claude Opus 4.8 (`claude-opus-4-8`).
7
+ */
8
+
9
+ interface ClaudeProviderOptions {
10
+ readonly apiKey?: string;
11
+ /** Bring your own configured client (takes precedence over `apiKey`). */
12
+ readonly client?: Anthropic;
13
+ readonly model?: string;
14
+ readonly maxTokens?: number;
15
+ }
16
+ declare function claudeProvider(options?: ClaudeProviderOptions): SchemaProvider;
17
+
18
+ /**
19
+ * OpenAI (GPT) provider — uses JSON mode on the Chat Completions API.
20
+ *
21
+ * To avoid a hard dependency on the `openai` package, pass your own client:
22
+ *
23
+ * import OpenAI from "openai";
24
+ * import { generateSchema, openaiProvider } from "@formwright/ai";
25
+ * const { schema } = await generateSchema("a contact form", {
26
+ * provider: openaiProvider({ client: new OpenAI() }),
27
+ * });
28
+ *
29
+ * Any client matching {@link OpenAILike} works (Azure OpenAI, compatible gateways).
30
+ */
31
+
32
+ /** Minimal structural shape of an OpenAI client's `chat.completions.create`. */
33
+ interface OpenAILike {
34
+ chat: {
35
+ completions: {
36
+ create(body: {
37
+ model: string;
38
+ messages: Array<{
39
+ role: "system" | "user" | "assistant";
40
+ content: string;
41
+ }>;
42
+ response_format?: {
43
+ type: "json_object";
44
+ };
45
+ }): Promise<{
46
+ choices: Array<{
47
+ message: {
48
+ content: string | null;
49
+ };
50
+ }>;
51
+ }>;
52
+ };
53
+ };
54
+ }
55
+ interface OpenAIProviderOptions {
56
+ /** A configured OpenAI client (e.g. `new OpenAI()`). Required. */
57
+ readonly client: OpenAILike;
58
+ readonly model?: string;
59
+ }
60
+ declare function openaiProvider(options: OpenAIProviderOptions): SchemaProvider;
61
+
62
+ /**
63
+ * @formwright/ai — turn a natural-language form description into a *validated*
64
+ * Formwright schema using an LLM.
65
+ *
66
+ * import { generateSchema } from "@formwright/ai";
67
+ * const { schema } = await generateSchema("a signup form with a US-only state field");
68
+ * new Form(schema).mount(el);
69
+ *
70
+ * Provider-agnostic: the validate → repair loop lives here, and the actual model
71
+ * call is a pluggable {@link SchemaProvider}. A Claude provider ships built-in
72
+ * (the default); an OpenAI provider and a custom-function provider let you use
73
+ * GPT, Gemini, Mistral, a local model, or anything else — each through its own
74
+ * native SDK, never a compatibility shim.
75
+ *
76
+ * Whatever the provider returns is checked with `@formwright/schema`'s validator;
77
+ * if it's invalid, the precise issues are fed back for repair — so what you get
78
+ * out always satisfies the runtime (or a thrown {@link SchemaGenerationError}).
79
+ */
80
+
81
+ /** A pluggable model backend: produce a candidate schema object for a request. */
82
+ interface SchemaProvider {
83
+ propose(input: ProposeInput): Promise<unknown>;
84
+ }
85
+ interface ProposeInput {
86
+ readonly description: string;
87
+ readonly system: string;
88
+ /** Present on a repair attempt: the previous (invalid) output and why it failed. */
89
+ readonly repair?: RepairContext;
90
+ }
91
+ interface RepairContext {
92
+ readonly previous: unknown;
93
+ readonly issues: readonly ValidationIssue[];
94
+ }
95
+ interface GenerateOptions {
96
+ /** Model backend. Defaults to Claude (needs `ANTHROPIC_API_KEY` or `apiKey`). */
97
+ readonly provider?: SchemaProvider;
98
+ /** Convenience for the default Claude provider when `provider` is omitted. */
99
+ readonly apiKey?: string;
100
+ readonly model?: string;
101
+ /** How many times to feed validation errors back for repair (default 2). */
102
+ readonly maxRepairAttempts?: number;
103
+ /** Override the Formwright DSL system prompt. */
104
+ readonly system?: string;
105
+ /** Extra guidance appended to the system prompt. */
106
+ readonly guidelines?: string;
107
+ }
108
+ interface GenerateResult {
109
+ readonly schema: FormSchema;
110
+ /** Number of model round-trips it took (1 = valid on the first try). */
111
+ readonly attempts: number;
112
+ }
113
+ declare class SchemaGenerationError extends Error {
114
+ readonly issues: readonly ValidationIssue[];
115
+ constructor(message: string, issues: readonly ValidationIssue[]);
116
+ }
117
+ /** Wrap a plain async function as a provider (for Gemini, Mistral, local models, …). */
118
+ declare function defineProvider(propose: (input: ProposeInput) => Promise<unknown>): SchemaProvider;
119
+ /** Build the user-facing instruction, including repair feedback when retrying. */
120
+ declare function buildPrompt(input: ProposeInput): string;
121
+ /**
122
+ * Generate a validated {@link FormSchema} from a natural-language description.
123
+ * Throws {@link SchemaGenerationError} if the model can't produce a valid schema
124
+ * within `maxRepairAttempts`.
125
+ */
126
+ declare function generateSchema(description: string, options?: GenerateOptions): Promise<GenerateResult>;
127
+ declare const SYSTEM_PROMPT = "You design forms as Formwright schemas \u2014 plain JSON, no code.\n\nA FormSchema has:\n- \"id\" (string), \"version\" (string, e.g. \"1.0\"), optional \"title\", and \"fields\" (non-empty array).\n- optional \"submit\": { \"endpoint\": { \"method\": \"POST\"|\"GET\"|\"PUT\"|\"PATCH\"|\"DELETE\", \"url\": string }, \"transform\"?, \"onSuccess\"?, \"onError\"? }.\n\nEach field has \"id\" (unique within its scope) and \"type\", plus optional \"label\", \"placeholder\", \"help\", \"description\", \"defaultValue\".\nField types:\n- \"text\" | \"email\" | \"password\" | \"number\" | \"textarea\"\n- \"checkbox\" | \"toggle\" (boolean; toggle renders as a switch)\n- \"select\" | \"radio\" \u2014 need \"options\": [{ \"label\": string, \"value\": string|number }]\n- \"group\" \u2014 a nested object; needs \"fields\": [ ...child fields ]. Produces an object in the payload.\n- \"collection\" \u2014 a repeatable list of objects; needs \"fields\", optional \"itemLabel\", \"addLabel\", \"minItems\", \"maxItems\". Produces an array of objects.\n\nValidation (optional): \"validation\": { \"kind\": \"string\"|\"number\", \"required\"?, \"min\"?, \"max\"?, \"minLength\"?, \"maxLength\"?, \"pattern\"?, \"format\"?: \"email\"|\"url\"|\"uuid\", \"message\"? }.\n\nConditional logic \u2014 data, not code \u2014 via \"visibleWhen\" / \"enabledWhen\" / \"requiredWhen\", a JSONLogic-style expression:\n{ \"==\": [a, b] }, \"!=\", \">\", \">=\", \"<\", \"<=\", { \"in\": [needle, haystack] }, { \"and\": [...] }, { \"or\": [...] }, { \"not\": x }, and { \"var\": \"fieldId\" } to read another field's value.\nNames resolve to a sibling first, then outward to the form root \u2014 so a field inside a group or collection row can react to an outer toggle.\n\nOther per-field options: \"omit\": true (keep in the UI but exclude from the payload), \"labelPosition\": \"start\"|\"end\" (checkbox/toggle), \"layout\" (\"accordion\" for group; \"cards\"|\"accordion\" for collection).\n\nProduce sensible labels, validation, and conditions matching the request. Prefer \"toggle\" for yes/no switches and add helpful placeholders. Output ONLY the schema object.";
128
+
129
+ export { type ClaudeProviderOptions, type GenerateOptions, type GenerateResult, type OpenAILike, type OpenAIProviderOptions, type ProposeInput, type RepairContext, SYSTEM_PROMPT, SchemaGenerationError, type SchemaProvider, buildPrompt, claudeProvider, defineProvider, generateSchema, openaiProvider };
@@ -0,0 +1,129 @@
1
+ import { ValidationIssue, FormSchema } from '@formwright/schema';
2
+ import Anthropic from '@anthropic-ai/sdk';
3
+
4
+ /**
5
+ * Claude provider — emits the schema through a forced tool call on the Anthropic
6
+ * Messages API. Default model is Claude Opus 4.8 (`claude-opus-4-8`).
7
+ */
8
+
9
+ interface ClaudeProviderOptions {
10
+ readonly apiKey?: string;
11
+ /** Bring your own configured client (takes precedence over `apiKey`). */
12
+ readonly client?: Anthropic;
13
+ readonly model?: string;
14
+ readonly maxTokens?: number;
15
+ }
16
+ declare function claudeProvider(options?: ClaudeProviderOptions): SchemaProvider;
17
+
18
+ /**
19
+ * OpenAI (GPT) provider — uses JSON mode on the Chat Completions API.
20
+ *
21
+ * To avoid a hard dependency on the `openai` package, pass your own client:
22
+ *
23
+ * import OpenAI from "openai";
24
+ * import { generateSchema, openaiProvider } from "@formwright/ai";
25
+ * const { schema } = await generateSchema("a contact form", {
26
+ * provider: openaiProvider({ client: new OpenAI() }),
27
+ * });
28
+ *
29
+ * Any client matching {@link OpenAILike} works (Azure OpenAI, compatible gateways).
30
+ */
31
+
32
+ /** Minimal structural shape of an OpenAI client's `chat.completions.create`. */
33
+ interface OpenAILike {
34
+ chat: {
35
+ completions: {
36
+ create(body: {
37
+ model: string;
38
+ messages: Array<{
39
+ role: "system" | "user" | "assistant";
40
+ content: string;
41
+ }>;
42
+ response_format?: {
43
+ type: "json_object";
44
+ };
45
+ }): Promise<{
46
+ choices: Array<{
47
+ message: {
48
+ content: string | null;
49
+ };
50
+ }>;
51
+ }>;
52
+ };
53
+ };
54
+ }
55
+ interface OpenAIProviderOptions {
56
+ /** A configured OpenAI client (e.g. `new OpenAI()`). Required. */
57
+ readonly client: OpenAILike;
58
+ readonly model?: string;
59
+ }
60
+ declare function openaiProvider(options: OpenAIProviderOptions): SchemaProvider;
61
+
62
+ /**
63
+ * @formwright/ai — turn a natural-language form description into a *validated*
64
+ * Formwright schema using an LLM.
65
+ *
66
+ * import { generateSchema } from "@formwright/ai";
67
+ * const { schema } = await generateSchema("a signup form with a US-only state field");
68
+ * new Form(schema).mount(el);
69
+ *
70
+ * Provider-agnostic: the validate → repair loop lives here, and the actual model
71
+ * call is a pluggable {@link SchemaProvider}. A Claude provider ships built-in
72
+ * (the default); an OpenAI provider and a custom-function provider let you use
73
+ * GPT, Gemini, Mistral, a local model, or anything else — each through its own
74
+ * native SDK, never a compatibility shim.
75
+ *
76
+ * Whatever the provider returns is checked with `@formwright/schema`'s validator;
77
+ * if it's invalid, the precise issues are fed back for repair — so what you get
78
+ * out always satisfies the runtime (or a thrown {@link SchemaGenerationError}).
79
+ */
80
+
81
+ /** A pluggable model backend: produce a candidate schema object for a request. */
82
+ interface SchemaProvider {
83
+ propose(input: ProposeInput): Promise<unknown>;
84
+ }
85
+ interface ProposeInput {
86
+ readonly description: string;
87
+ readonly system: string;
88
+ /** Present on a repair attempt: the previous (invalid) output and why it failed. */
89
+ readonly repair?: RepairContext;
90
+ }
91
+ interface RepairContext {
92
+ readonly previous: unknown;
93
+ readonly issues: readonly ValidationIssue[];
94
+ }
95
+ interface GenerateOptions {
96
+ /** Model backend. Defaults to Claude (needs `ANTHROPIC_API_KEY` or `apiKey`). */
97
+ readonly provider?: SchemaProvider;
98
+ /** Convenience for the default Claude provider when `provider` is omitted. */
99
+ readonly apiKey?: string;
100
+ readonly model?: string;
101
+ /** How many times to feed validation errors back for repair (default 2). */
102
+ readonly maxRepairAttempts?: number;
103
+ /** Override the Formwright DSL system prompt. */
104
+ readonly system?: string;
105
+ /** Extra guidance appended to the system prompt. */
106
+ readonly guidelines?: string;
107
+ }
108
+ interface GenerateResult {
109
+ readonly schema: FormSchema;
110
+ /** Number of model round-trips it took (1 = valid on the first try). */
111
+ readonly attempts: number;
112
+ }
113
+ declare class SchemaGenerationError extends Error {
114
+ readonly issues: readonly ValidationIssue[];
115
+ constructor(message: string, issues: readonly ValidationIssue[]);
116
+ }
117
+ /** Wrap a plain async function as a provider (for Gemini, Mistral, local models, …). */
118
+ declare function defineProvider(propose: (input: ProposeInput) => Promise<unknown>): SchemaProvider;
119
+ /** Build the user-facing instruction, including repair feedback when retrying. */
120
+ declare function buildPrompt(input: ProposeInput): string;
121
+ /**
122
+ * Generate a validated {@link FormSchema} from a natural-language description.
123
+ * Throws {@link SchemaGenerationError} if the model can't produce a valid schema
124
+ * within `maxRepairAttempts`.
125
+ */
126
+ declare function generateSchema(description: string, options?: GenerateOptions): Promise<GenerateResult>;
127
+ declare const SYSTEM_PROMPT = "You design forms as Formwright schemas \u2014 plain JSON, no code.\n\nA FormSchema has:\n- \"id\" (string), \"version\" (string, e.g. \"1.0\"), optional \"title\", and \"fields\" (non-empty array).\n- optional \"submit\": { \"endpoint\": { \"method\": \"POST\"|\"GET\"|\"PUT\"|\"PATCH\"|\"DELETE\", \"url\": string }, \"transform\"?, \"onSuccess\"?, \"onError\"? }.\n\nEach field has \"id\" (unique within its scope) and \"type\", plus optional \"label\", \"placeholder\", \"help\", \"description\", \"defaultValue\".\nField types:\n- \"text\" | \"email\" | \"password\" | \"number\" | \"textarea\"\n- \"checkbox\" | \"toggle\" (boolean; toggle renders as a switch)\n- \"select\" | \"radio\" \u2014 need \"options\": [{ \"label\": string, \"value\": string|number }]\n- \"group\" \u2014 a nested object; needs \"fields\": [ ...child fields ]. Produces an object in the payload.\n- \"collection\" \u2014 a repeatable list of objects; needs \"fields\", optional \"itemLabel\", \"addLabel\", \"minItems\", \"maxItems\". Produces an array of objects.\n\nValidation (optional): \"validation\": { \"kind\": \"string\"|\"number\", \"required\"?, \"min\"?, \"max\"?, \"minLength\"?, \"maxLength\"?, \"pattern\"?, \"format\"?: \"email\"|\"url\"|\"uuid\", \"message\"? }.\n\nConditional logic \u2014 data, not code \u2014 via \"visibleWhen\" / \"enabledWhen\" / \"requiredWhen\", a JSONLogic-style expression:\n{ \"==\": [a, b] }, \"!=\", \">\", \">=\", \"<\", \"<=\", { \"in\": [needle, haystack] }, { \"and\": [...] }, { \"or\": [...] }, { \"not\": x }, and { \"var\": \"fieldId\" } to read another field's value.\nNames resolve to a sibling first, then outward to the form root \u2014 so a field inside a group or collection row can react to an outer toggle.\n\nOther per-field options: \"omit\": true (keep in the UI but exclude from the payload), \"labelPosition\": \"start\"|\"end\" (checkbox/toggle), \"layout\" (\"accordion\" for group; \"cards\"|\"accordion\" for collection).\n\nProduce sensible labels, validation, and conditions matching the request. Prefer \"toggle\" for yes/no switches and add helpful placeholders. Output ONLY the schema object.";
128
+
129
+ export { type ClaudeProviderOptions, type GenerateOptions, type GenerateResult, type OpenAILike, type OpenAIProviderOptions, type ProposeInput, type RepairContext, SYSTEM_PROMPT, SchemaGenerationError, type SchemaProvider, buildPrompt, claudeProvider, defineProvider, generateSchema, openaiProvider };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { SYSTEM_PROMPT, SchemaGenerationError, buildPrompt, claudeProvider, defineProvider, generateSchema, openaiProvider } from './chunk-I3HZFUC7.js';
2
+ //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@formwright/ai",
3
+ "version": "0.1.0",
4
+ "description": "Generate a validated Formwright schema from a natural-language description using Claude.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/aliarsalan177/formwright.git",
9
+ "directory": "packages/ai"
10
+ },
11
+ "homepage": "https://github.com/aliarsalan177/formwright#readme",
12
+ "bugs": "https://github.com/aliarsalan177/formwright/issues",
13
+ "type": "module",
14
+ "sideEffects": false,
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js",
22
+ "require": "./dist/index.cjs"
23
+ }
24
+ },
25
+ "main": "./dist/index.cjs",
26
+ "module": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "dependencies": {
29
+ "@anthropic-ai/sdk": "^0.68.0",
30
+ "@formwright/schema": "0.1.0"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "scripts": {
36
+ "build": "tsup",
37
+ "dev": "tsup --watch",
38
+ "typecheck": "tsc --noEmit",
39
+ "test": "vitest run",
40
+ "clean": "rm -rf dist .turbo"
41
+ }
42
+ }