alchemy 0.2.5 → 0.3.1

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.
Files changed (197) hide show
  1. package/README.md +3 -3
  2. package/lib/ai/approve.d.ts +99 -0
  3. package/lib/ai/approve.js +76 -0
  4. package/lib/ai/astro-file.d.ts +137 -0
  5. package/lib/ai/astro-file.js +156 -0
  6. package/lib/ai/client.d.ts +7 -8
  7. package/lib/ai/client.js +48 -19
  8. package/lib/ai/css-file.d.ts +131 -0
  9. package/lib/ai/css-file.js +141 -0
  10. package/lib/ai/data.d.ts +51 -6
  11. package/lib/ai/data.js +58 -12
  12. package/lib/ai/document.d.ts +108 -24
  13. package/lib/ai/document.js +166 -52
  14. package/lib/ai/html-file.d.ts +128 -0
  15. package/lib/ai/html-file.js +138 -0
  16. package/lib/ai/index.d.ts +9 -0
  17. package/lib/ai/index.js +9 -0
  18. package/lib/ai/json-file.d.ts +160 -0
  19. package/lib/ai/json-file.js +187 -0
  20. package/lib/ai/review.d.ts +122 -0
  21. package/lib/ai/review.js +101 -0
  22. package/lib/ai/typescript-file.d.ts +139 -0
  23. package/lib/ai/typescript-file.js +145 -0
  24. package/lib/ai/vue-file.d.ts +126 -0
  25. package/lib/ai/vue-file.js +136 -0
  26. package/lib/ai/yaml-file.d.ts +169 -0
  27. package/lib/ai/yaml-file.js +200 -0
  28. package/lib/alchemy.js +17 -2
  29. package/lib/apply.js +1 -1
  30. package/lib/cloudflare/dns.d.ts +88 -0
  31. package/lib/cloudflare/dns.js +192 -0
  32. package/lib/cloudflare/generate-asset-manifest.js +1 -1
  33. package/lib/cloudflare/index.d.ts +2 -1
  34. package/lib/cloudflare/index.js +2 -1
  35. package/lib/cloudflare/r2-rest-state-store.d.ts +125 -0
  36. package/lib/cloudflare/r2-rest-state-store.js +254 -0
  37. package/lib/cloudflare/response.d.ts +12 -0
  38. package/lib/cloudflare/response.js +0 -0
  39. package/lib/cloudflare/static-site-router.js +16 -7
  40. package/lib/cloudflare/static-site.js +3 -0
  41. package/lib/cloudflare/upload-asset-manifest.js +4 -2
  42. package/lib/cloudflare/zone.d.ts +18 -0
  43. package/lib/cloudflare/zone.js +17 -4
  44. package/lib/dns/godaddy.d.ts +7 -0
  45. package/lib/dns/godaddy.js +21 -0
  46. package/lib/dns/import-dns.d.ts +82 -0
  47. package/lib/dns/import-dns.js +114 -0
  48. package/lib/dns/index.d.ts +2 -0
  49. package/lib/dns/index.js +2 -0
  50. package/lib/dns/record.d.ts +88 -0
  51. package/lib/dns/record.js +13 -0
  52. package/lib/fs/copy-file.d.ts +54 -0
  53. package/lib/fs/copy-file.js +63 -0
  54. package/lib/fs/file-system-state-store.d.ts +17 -0
  55. package/lib/fs/file-system-state-store.js +87 -0
  56. package/lib/fs/file.d.ts +13 -0
  57. package/lib/fs/file.js +24 -5
  58. package/lib/fs/folder.d.ts +10 -0
  59. package/lib/fs/folder.js +2 -2
  60. package/lib/fs/index.d.ts +10 -4
  61. package/lib/fs/index.js +10 -4
  62. package/lib/fs/static-astro-file.d.ts +34 -0
  63. package/lib/fs/static-astro-file.js +39 -0
  64. package/lib/fs/static-css-file.d.ts +24 -0
  65. package/lib/fs/static-css-file.js +29 -0
  66. package/lib/fs/static-html-file.d.ts +31 -0
  67. package/lib/fs/static-html-file.js +36 -0
  68. package/lib/fs/{json-file.d.ts → static-json-file.d.ts} +3 -3
  69. package/lib/fs/static-json-file.js +15 -0
  70. package/lib/fs/{text-file.d.ts → static-text-file.d.ts} +2 -2
  71. package/lib/fs/static-text-file.js +8 -0
  72. package/lib/fs/{typescript-file.d.ts → static-typescript-file.d.ts} +3 -3
  73. package/lib/fs/{typescript-file.js → static-typescript-file.js} +3 -2
  74. package/lib/fs/static-vue-file.d.ts +28 -0
  75. package/lib/fs/static-vue-file.js +33 -0
  76. package/lib/fs/static-yaml-file.d.ts +22 -0
  77. package/lib/fs/{yaml-file.js → static-yaml-file.js} +3 -2
  78. package/lib/internal/getting-started.d.ts +21 -0
  79. package/lib/internal/getting-started.js +87 -0
  80. package/lib/internal/index.d.ts +3 -0
  81. package/lib/internal/index.js +3 -0
  82. package/lib/internal/providers.d.ts +27 -0
  83. package/lib/internal/providers.js +172 -0
  84. package/lib/internal/tutorial.d.ts +104 -0
  85. package/lib/internal/tutorial.js +251 -0
  86. package/lib/resource.js +8 -1
  87. package/lib/scope.d.ts +1 -1
  88. package/lib/scope.js +4 -2
  89. package/lib/state.d.ts +1 -16
  90. package/lib/state.js +0 -87
  91. package/lib/test/bun.d.ts +11 -0
  92. package/lib/test/bun.js +36 -8
  93. package/lib/web/astro.d.ts +147 -0
  94. package/lib/web/astro.js +414 -0
  95. package/lib/{shadcn/component.js → web/shadcn-component.js} +2 -9
  96. package/lib/web/shadcn.d.ts +97 -0
  97. package/lib/web/shadcn.js +99 -0
  98. package/lib/web/tailwind.d.ts +64 -0
  99. package/lib/web/tailwind.js +97 -0
  100. package/lib/{vite → web}/vite.d.ts +5 -0
  101. package/lib/{vite → web}/vite.js +44 -44
  102. package/lib/web/vitepress/config.d.ts +10 -0
  103. package/lib/web/vitepress/config.js +31 -0
  104. package/lib/web/vitepress/custom-theme.d.ts +173 -0
  105. package/lib/web/vitepress/custom-theme.js +336 -0
  106. package/lib/{vitepress → web/vitepress}/dependencies.d.ts +2 -2
  107. package/lib/{vitepress → web/vitepress}/dependencies.js +1 -1
  108. package/lib/web/vitepress/home-page.d.ts +246 -0
  109. package/lib/web/vitepress/home-page.js +147 -0
  110. package/lib/{vitepress → web/vitepress}/index.d.ts +2 -0
  111. package/lib/{vitepress → web/vitepress}/index.js +2 -0
  112. package/lib/{vitepress → web/vitepress}/vitepress.d.ts +15 -12
  113. package/lib/{vitepress → web/vitepress}/vitepress.js +26 -44
  114. package/package.json +6 -2
  115. package/src/ai/approve.ts +163 -0
  116. package/src/ai/astro-file.ts +269 -0
  117. package/src/ai/client.ts +60 -21
  118. package/src/ai/css-file.ts +247 -0
  119. package/src/ai/data.ts +81 -19
  120. package/src/ai/document.ts +240 -62
  121. package/src/ai/html-file.ts +244 -0
  122. package/src/ai/index.ts +9 -0
  123. package/src/ai/json-file.ts +325 -0
  124. package/src/ai/review.ts +213 -0
  125. package/src/ai/typescript-file.ts +259 -0
  126. package/src/ai/vue-file.ts +242 -0
  127. package/src/ai/yaml-file.ts +328 -0
  128. package/src/alchemy.ts +18 -7
  129. package/src/apply.ts +8 -9
  130. package/src/cloudflare/dns.ts +344 -0
  131. package/src/cloudflare/generate-asset-manifest.ts +4 -4
  132. package/src/cloudflare/index.ts +2 -1
  133. package/src/cloudflare/r2-rest-state-store.ts +353 -0
  134. package/src/cloudflare/response.ts +9 -0
  135. package/src/cloudflare/static-site-router.ts +18 -8
  136. package/src/cloudflare/static-site.ts +11 -7
  137. package/src/cloudflare/upload-asset-manifest.ts +6 -4
  138. package/src/cloudflare/worker.ts +44 -43
  139. package/src/cloudflare/zone.ts +39 -23
  140. package/src/dns/godaddy.ts +29 -0
  141. package/src/dns/import-dns.ts +213 -0
  142. package/src/dns/index.ts +2 -0
  143. package/src/dns/record.ts +121 -0
  144. package/src/fs/copy-file.ts +115 -0
  145. package/src/fs/file-system-state-store.ts +109 -0
  146. package/src/fs/file.ts +35 -9
  147. package/src/fs/folder.ts +14 -2
  148. package/src/fs/index.ts +10 -4
  149. package/src/fs/static-astro-file.ts +45 -0
  150. package/src/fs/static-css-file.ts +35 -0
  151. package/src/fs/static-html-file.ts +42 -0
  152. package/src/fs/static-json-file.ts +34 -0
  153. package/src/fs/{text-file.ts → static-text-file.ts} +9 -3
  154. package/src/fs/{typescript-file.ts → static-typescript-file.ts} +7 -6
  155. package/src/fs/static-vue-file.ts +39 -0
  156. package/src/fs/static-yaml-file.ts +33 -0
  157. package/src/internal/getting-started.ts +115 -0
  158. package/src/internal/index.ts +3 -0
  159. package/src/internal/providers.ts +241 -0
  160. package/src/internal/tutorial.ts +392 -0
  161. package/src/resource.ts +10 -1
  162. package/src/scope.ts +5 -6
  163. package/src/state.ts +1 -111
  164. package/src/test/bun.ts +60 -10
  165. package/src/web/astro.ts +644 -0
  166. package/src/{shadcn/component.ts → web/shadcn-component.ts} +5 -13
  167. package/src/web/shadcn.ts +219 -0
  168. package/src/web/tailwind.ts +167 -0
  169. package/src/{vite → web}/vite.ts +54 -56
  170. package/src/web/vitepress/config.ts +48 -0
  171. package/src/web/vitepress/custom-theme.ts +514 -0
  172. package/src/{vitepress → web/vitepress}/dependencies.ts +2 -2
  173. package/src/web/vitepress/home-page.ts +375 -0
  174. package/src/{vitepress → web/vitepress}/index.ts +2 -0
  175. package/src/{vitepress → web/vitepress}/vitepress.ts +62 -72
  176. package/lib/cloudflare/state.d.ts +0 -82
  177. package/lib/cloudflare/state.js +0 -108
  178. package/lib/fs/json-file.js +0 -7
  179. package/lib/fs/text-file.js +0 -7
  180. package/lib/fs/yaml-file.d.ts +0 -19
  181. package/lib/internal/docs.d.ts +0 -5
  182. package/lib/internal/docs.js +0 -287
  183. package/lib/shadcn/index.d.ts +0 -1
  184. package/lib/shadcn/index.js +0 -1
  185. package/lib/vite/index.d.ts +0 -1
  186. package/lib/vite/index.js +0 -1
  187. package/lib/vitepress/home-page.d.ts +0 -133
  188. package/lib/vitepress/home-page.js +0 -11
  189. package/src/cloudflare/state.ts +0 -138
  190. package/src/fs/json-file.ts +0 -23
  191. package/src/fs/yaml-file.ts +0 -26
  192. package/src/internal/docs.ts +0 -338
  193. package/src/shadcn/index.ts +0 -1
  194. package/src/vite/index.ts +0 -1
  195. package/src/vitepress/home-page.ts +0 -166
  196. /package/lib/{shadcn/component.d.ts → web/shadcn-component.d.ts} +0 -0
  197. /package/src/{vitepress → web/vitepress}/index.md +0 -0
@@ -0,0 +1,259 @@
1
+ import { generateText } from "ai";
2
+ import type { Context } from "../context";
3
+ import { StaticTypeScriptFile } from "../fs/static-typescript-file";
4
+ import { Resource } from "../resource";
5
+ import type { Secret } from "../secret";
6
+ import { createModel, type ModelConfig } from "./client";
7
+
8
+ /**
9
+ * Properties for creating or updating a TypeScriptFile
10
+ */
11
+ export interface TypeScriptFileProps {
12
+ /**
13
+ * Path to the TypeScript file
14
+ */
15
+ path: string;
16
+
17
+ /**
18
+ * Base URL for the OpenAI API
19
+ * @default 'https://api.openai.com/v1'
20
+ */
21
+ baseURL?: string;
22
+
23
+ /**
24
+ * Prompt for generating content
25
+ * Use alchemy template literals to include file context:
26
+ * @example
27
+ * prompt: await alchemy`
28
+ * Generate a TypeScript utility function using:
29
+ * ${alchemy.file("src/types.ts")}
30
+ * `
31
+ */
32
+ prompt: string;
33
+
34
+ /**
35
+ * System prompt for the model
36
+ * This is used to provide instructions to the model about how to format the response
37
+ * The default system prompt instructs the model to return TypeScript code inside ```ts fences
38
+ * @default "You are a TypeScript code generator. Create TypeScript code based on the user's requirements. Your response MUST include only TypeScript code inside ```ts fences. Do not include any other text, explanations, or multiple code blocks."
39
+ */
40
+ system?: string;
41
+
42
+ /**
43
+ * OpenAI API key to use for generating content
44
+ * If not provided, will use OPENAI_API_KEY environment variable
45
+ */
46
+ apiKey?: Secret;
47
+
48
+ /**
49
+ * Model configuration
50
+ */
51
+ model?: ModelConfig;
52
+
53
+ /**
54
+ * Temperature for controlling randomness in generation.
55
+ * Higher values (e.g., 0.8) make output more random,
56
+ * lower values (e.g., 0.2) make it more deterministic.
57
+ * @default 0.7
58
+ */
59
+ temperature?: number;
60
+
61
+ /**
62
+ * Prettier configuration to use for formatting the TypeScript code
63
+ * If not provided, will use the default Prettier configuration
64
+ */
65
+ prettierConfig?: object;
66
+ }
67
+
68
+ /**
69
+ * A TypeScript file that can be created, updated, and deleted
70
+ */
71
+ export interface TypeScriptFile
72
+ extends TypeScriptFileProps,
73
+ Resource<"ai::TypeScriptFile"> {
74
+ /**
75
+ * Content of the TypeScript file
76
+ */
77
+ content: string;
78
+
79
+ /**
80
+ * Time at which the file was created
81
+ */
82
+ createdAt: number;
83
+
84
+ /**
85
+ * Time at which the file was last updated
86
+ */
87
+ updatedAt: number;
88
+ }
89
+
90
+ /**
91
+ * Default system prompt for TypeScript file generation
92
+ */
93
+ const DEFAULT_TS_SYSTEM_PROMPT =
94
+ "You are a TypeScript code generator. Create TypeScript code based on the user's requirements. Your response MUST include only TypeScript code inside ```ts fences. Do not include any other text, explanations, or multiple code blocks.";
95
+
96
+ /**
97
+ * Resource for generating TypeScript files using AI models.
98
+ * Extracts TypeScript code from between ```ts fences, validates the response,
99
+ * and formats the code with Prettier.
100
+ *
101
+ * @example
102
+ * // Create a utility function
103
+ * const utils = await TypeScriptFile("string-utils", {
104
+ * path: "./src/utils/string-utils.ts",
105
+ * prompt: await alchemy`
106
+ * Generate TypeScript utility functions for string manipulation:
107
+ * - Capitalize first letter
108
+ * - Truncate with ellipsis
109
+ * - Convert to camelCase and kebab-case
110
+ * - Remove special characters
111
+ * `,
112
+ * model: {
113
+ * id: "gpt-4o",
114
+ * provider: "openai"
115
+ * }
116
+ * });
117
+ *
118
+ * @example
119
+ * // Generate a TypeScript class with custom formatting
120
+ * const userService = await TypeScriptFile("user-service", {
121
+ * path: "./src/services/UserService.ts",
122
+ * prompt: await alchemy`
123
+ * Create a UserService class that handles user authentication and profile management.
124
+ * The service should use the User type from:
125
+ * ${alchemy.file("src/types/User.ts")}
126
+ *
127
+ * Include methods for:
128
+ * - login(email, password)
129
+ * - register(user)
130
+ * - updateProfile(userId, profileData)
131
+ * - deleteAccount(userId)
132
+ * `,
133
+ * temperature: 0.2,
134
+ * prettierConfig: {
135
+ * semi: false,
136
+ * singleQuote: true,
137
+ * printWidth: 120
138
+ * }
139
+ * });
140
+ *
141
+ * @example
142
+ * // Generate a React hook with custom system prompt
143
+ * const useFormHook = await TypeScriptFile("use-form", {
144
+ * path: "./src/hooks/useForm.ts",
145
+ * prompt: await alchemy`
146
+ * Create a custom React hook called useForm that handles form state, validation, and submission.
147
+ * It should support:
148
+ * - Initial values
149
+ * - Validation rules
150
+ * - Field errors
151
+ * - Form submission with loading state
152
+ * - Reset functionality
153
+ * `,
154
+ * system: "You are an expert React developer specializing in TypeScript hooks. Create a single TypeScript file inside ```ts fences with no additional text. Follow React best practices and include proper typing.",
155
+ * model: {
156
+ * id: "claude-3-opus-20240229",
157
+ * provider: "anthropic"
158
+ * }
159
+ * });
160
+ */
161
+ export const TypeScriptFile = Resource(
162
+ "ai::TypeScriptFile",
163
+ async function (
164
+ this: Context<TypeScriptFile>,
165
+ id: string,
166
+ props: TypeScriptFileProps
167
+ ): Promise<TypeScriptFile> {
168
+ // Handle delete phase
169
+ if (this.phase === "delete") {
170
+ // StaticTypeScriptFile will handle the deletion
171
+ return this.destroy();
172
+ }
173
+
174
+ // Use provided system prompt or default
175
+ const system = props.system || DEFAULT_TS_SYSTEM_PROMPT;
176
+
177
+ // Generate initial content
178
+ const { text } = await generateText({
179
+ model: createModel(props),
180
+ prompt: props.prompt,
181
+ system,
182
+ providerOptions: props.model?.options,
183
+ ...(props.temperature === undefined
184
+ ? {}
185
+ : { temperature: props.temperature }),
186
+ });
187
+
188
+ // Extract and validate TypeScript code
189
+ let { code, error } = await extractTypeScriptCode(text);
190
+
191
+ // Re-prompt if there are validation errors
192
+ if (error) {
193
+ const errorSystem = `${system}\n\nERROR: ${error}\n\nPlease try again and ensure your response contains exactly one TypeScript code block inside \`\`\`ts fences.`;
194
+
195
+ const { text: retryText } = await generateText({
196
+ model: createModel(props),
197
+ prompt: props.prompt,
198
+ system: errorSystem,
199
+ providerOptions: props.model?.options,
200
+ ...(props.temperature === undefined
201
+ ? {}
202
+ : { temperature: props.temperature }),
203
+ });
204
+
205
+ const retryResult = await extractTypeScriptCode(retryText);
206
+
207
+ if (retryResult.error) {
208
+ throw new Error(
209
+ `Failed to generate valid TypeScript code: ${retryResult.error}`
210
+ );
211
+ }
212
+
213
+ code = retryResult.code;
214
+ }
215
+
216
+ // Use StaticTypeScriptFile to create/update the file
217
+ const file = await StaticTypeScriptFile("file", props.path, code);
218
+
219
+ // Return the resource
220
+ return this({
221
+ ...props,
222
+ content: file.content,
223
+ createdAt: Date.now(),
224
+ updatedAt: Date.now(),
225
+ });
226
+ }
227
+ );
228
+
229
+ /**
230
+ * Extracts TypeScript code from between ```ts fences
231
+ * Validates that exactly one TypeScript code block exists
232
+ *
233
+ * @param text The text to extract TypeScript code from
234
+ * @returns The extracted TypeScript code or error message
235
+ */
236
+ async function extractTypeScriptCode(
237
+ text: string
238
+ ): Promise<{ code: string; error?: string }> {
239
+ const tsCodeRegex = /```ts\s*([\s\S]*?)```/g;
240
+ const matches = Array.from(text.matchAll(tsCodeRegex));
241
+
242
+ if (matches.length === 0) {
243
+ return {
244
+ code: "",
245
+ error:
246
+ "No TypeScript code block found in the response. Please include your code within ```ts fences.",
247
+ };
248
+ }
249
+
250
+ if (matches.length > 1) {
251
+ return {
252
+ code: "",
253
+ error:
254
+ "Multiple TypeScript code blocks found in the response. Please provide exactly one code block within ```ts fences.",
255
+ };
256
+ }
257
+
258
+ return { code: matches[0][1].trim() };
259
+ }
@@ -0,0 +1,242 @@
1
+ import { generateText } from "ai";
2
+ import type { Context } from "../context";
3
+ import { StaticVueFile } from "../fs/static-vue-file";
4
+ import { Resource } from "../resource";
5
+ import type { Secret } from "../secret";
6
+ import { type ModelConfig, createModel } from "./client";
7
+
8
+ /**
9
+ * Properties for creating or updating a VueFile
10
+ */
11
+ export interface VueFileProps {
12
+ /**
13
+ * Path to the Vue file
14
+ */
15
+ path: string;
16
+
17
+ /**
18
+ * Base URL for the OpenAI API
19
+ * @default 'https://api.openai.com/v1'
20
+ */
21
+ baseURL?: string;
22
+
23
+ /**
24
+ * Prompt for generating content
25
+ * Use alchemy template literals to include file context:
26
+ * @example
27
+ * prompt: await alchemy`
28
+ * Generate a Vue component using:
29
+ * ${alchemy.file("src/api.ts")}
30
+ * `
31
+ */
32
+ prompt: string;
33
+
34
+ /**
35
+ * System prompt for the model
36
+ * This is used to provide instructions to the model about how to format the response
37
+ * The default system prompt instructs the model to return a single Vue component inside ```vue fences
38
+ * @default "You are a Vue component generator. Create a single Vue component based on the user's requirements. Your response MUST include only a single Vue component inside ```vue fences. Do not include any other text, explanations, or multiple code blocks."
39
+ */
40
+ system?: string;
41
+
42
+ /**
43
+ * OpenAI API key to use for generating content
44
+ * If not provided, will use OPENAI_API_KEY environment variable
45
+ */
46
+ apiKey?: Secret;
47
+
48
+ /**
49
+ * Model configuration
50
+ */
51
+ model?: ModelConfig;
52
+
53
+ /**
54
+ * Temperature for controlling randomness in generation.
55
+ * Higher values (e.g., 0.8) make output more random,
56
+ * lower values (e.g., 0.2) make it more deterministic.
57
+ * @default 0.7
58
+ */
59
+ temperature?: number;
60
+ }
61
+
62
+ /**
63
+ * A Vue file that can be created, updated, and deleted
64
+ */
65
+ export interface VueFile extends VueFileProps, Resource<"ai::VueFile"> {
66
+ /**
67
+ * Content of the Vue file
68
+ */
69
+ content: string;
70
+
71
+ /**
72
+ * Time at which the file was created
73
+ */
74
+ createdAt: number;
75
+
76
+ /**
77
+ * Time at which the file was last updated
78
+ */
79
+ updatedAt: number;
80
+ }
81
+
82
+ /**
83
+ * Default system prompt for Vue file generation
84
+ */
85
+ const DEFAULT_VUE_SYSTEM_PROMPT =
86
+ "You are a Vue component generator. Create a single Vue component based on the user's requirements. Your response MUST include only a single Vue component inside ```vue fences. Do not include any other text, explanations, or multiple code blocks.";
87
+
88
+ /**
89
+ * Resource for generating Vue files using AI models.
90
+ * Extracts Vue code from between ```vue fences and validates the response.
91
+ *
92
+ * @example
93
+ * // Create a simple Vue component
94
+ * const button = await VueFile("button-component", {
95
+ * path: "./src/components/Button.vue",
96
+ * prompt: await alchemy`
97
+ * Generate a customizable button Vue component with:
98
+ * - Primary, secondary, and outline variants
99
+ * - Small, medium, and large sizes
100
+ * - Loading state with spinner
101
+ * - Disabled state
102
+ * `,
103
+ * model: {
104
+ * id: "gpt-4o",
105
+ * provider: "openai"
106
+ * }
107
+ * });
108
+ *
109
+ * @example
110
+ * // Generate a Vue component using existing files as reference
111
+ * const userCard = await VueFile("user-card", {
112
+ * path: "./src/components/UserCard.vue",
113
+ * prompt: await alchemy`
114
+ * Create a UserCard Vue component that displays user information.
115
+ * Follow the styling patterns from:
116
+ * ${alchemy.file("src/components/Card.vue")}
117
+ *
118
+ * Use the user type from:
119
+ * ${alchemy.file("src/types/User.ts")}
120
+ * `,
121
+ * temperature: 0.2
122
+ * });
123
+ *
124
+ * @example
125
+ * // Generate a complex form component with validation and custom system prompt
126
+ * const form = await VueFile("registration-form", {
127
+ * path: "./src/components/RegistrationForm.vue",
128
+ * prompt: await alchemy`
129
+ * Generate a registration form Vue component with:
130
+ * - Email, password, and confirm password fields
131
+ * - Form validation using Vuelidate or similar
132
+ * - Error messages for each field
133
+ * - Submit handler that emits form data
134
+ *
135
+ * Follow these style guidelines:
136
+ * ${alchemy.file("src/styles/guidelines.md")}
137
+ * `,
138
+ * system: "You are an expert Vue component creator specializing in form components with validation. Create a single Vue component inside ```vue fences with no additional text.",
139
+ * model: {
140
+ * id: "claude-3-opus-20240229",
141
+ * provider: "anthropic"
142
+ * }
143
+ * });
144
+ */
145
+ export const VueFile = Resource(
146
+ "ai::VueFile",
147
+ async function (
148
+ this: Context<VueFile>,
149
+ id: string,
150
+ props: VueFileProps
151
+ ): Promise<VueFile> {
152
+ // Handle deletion phase
153
+ if (this.phase === "delete") {
154
+ return this.destroy();
155
+ }
156
+
157
+ // Use provided system prompt or default
158
+ const system = props.system || DEFAULT_VUE_SYSTEM_PROMPT;
159
+
160
+ // Generate initial content
161
+ const { text } = await generateText({
162
+ model: createModel(props),
163
+ prompt: props.prompt,
164
+ system,
165
+ providerOptions: props.model?.options,
166
+ ...(props.temperature === undefined
167
+ ? {}
168
+ : { temperature: props.temperature }),
169
+ });
170
+
171
+ // Extract and validate Vue code
172
+ let { code, error } = await extractVueCode(text);
173
+
174
+ // Re-prompt if there are validation errors
175
+ if (error) {
176
+ const errorSystem = `${system}\n\nERROR: ${error}\n\nPlease try again and ensure your response contains exactly one Vue component inside \`\`\`vue fences.`;
177
+
178
+ const { text: retryText } = await generateText({
179
+ model: createModel(props),
180
+ prompt: props.prompt,
181
+ system: errorSystem,
182
+ providerOptions: props.model?.options,
183
+ ...(props.temperature === undefined
184
+ ? {}
185
+ : { temperature: props.temperature }),
186
+ });
187
+
188
+ const retryResult = await extractVueCode(retryText);
189
+
190
+ if (retryResult.error) {
191
+ throw new Error(
192
+ `Failed to generate valid Vue code: ${retryResult.error}`
193
+ );
194
+ }
195
+
196
+ code = retryResult.code;
197
+ }
198
+
199
+ // Use StaticVueFile to create/update the file
200
+ const file = await StaticVueFile("file", props.path, code);
201
+
202
+ // Return the resource
203
+ return this({
204
+ ...props,
205
+ content: file.content,
206
+ createdAt: Date.now(),
207
+ updatedAt: Date.now(),
208
+ });
209
+ }
210
+ );
211
+
212
+ /**
213
+ * Extracts Vue code from between ```vue fences
214
+ * Validates that exactly one Vue code block exists
215
+ *
216
+ * @param text The text to extract Vue code from
217
+ * @returns The extracted Vue code or error message
218
+ */
219
+ async function extractVueCode(
220
+ text: string
221
+ ): Promise<{ code: string; error?: string }> {
222
+ const vueCodeRegex = /```vue\s*([\s\S]*?)```/g;
223
+ const matches = Array.from(text.matchAll(vueCodeRegex));
224
+
225
+ if (matches.length === 0) {
226
+ return {
227
+ code: "",
228
+ error:
229
+ "No Vue code block found in the response. Please include your code within ```vue fences.",
230
+ };
231
+ }
232
+
233
+ if (matches.length > 1) {
234
+ return {
235
+ code: "",
236
+ error:
237
+ "Multiple Vue code blocks found in the response. Please provide exactly one code block within ```vue fences.",
238
+ };
239
+ }
240
+
241
+ return { code: matches[0][1].trim() };
242
+ }