fimo 0.14.0 → 0.15.0-staging.2

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 (199) hide show
  1. package/README.md +1 -0
  2. package/assets/astro/components/Boolean.astro +14 -0
  3. package/assets/astro/components/Date.astro +23 -0
  4. package/assets/astro/components/Image.astro +79 -0
  5. package/assets/astro/components/Json.astro +14 -0
  6. package/assets/astro/components/RichText.astro +17 -0
  7. package/assets/astro/components/StaticImage.astro +66 -0
  8. package/assets/astro/components/Text.astro +23 -0
  9. package/assets/astro/components/Video.astro +47 -0
  10. package/assets/astro/components/index.d.ts +8 -0
  11. package/assets/astro/components/index.js +8 -0
  12. package/assets/content-templates/shared/server.eta +56 -0
  13. package/assets/content-templates/sveltekit/content.eta +86 -0
  14. package/assets/skills/fimo/SKILL.md +13 -4
  15. package/assets/skills/fimo-cli/SKILL.md +28 -28
  16. package/assets/skills/fimo-cli/references/account.md +0 -2
  17. package/assets/skills/fimo-cli/references/agent-setup.md +7 -9
  18. package/assets/skills/fimo-cli/references/agents.md +0 -2
  19. package/assets/skills/fimo-cli/references/analytics.md +0 -2
  20. package/assets/skills/fimo-cli/references/assets.md +2 -4
  21. package/assets/skills/fimo-cli/references/branches.md +14 -15
  22. package/assets/skills/fimo-cli/references/content.md +9 -11
  23. package/assets/skills/fimo-cli/references/credits.md +0 -2
  24. package/assets/skills/fimo-cli/references/deploy.md +6 -7
  25. package/assets/skills/fimo-cli/references/describe.md +1 -3
  26. package/assets/skills/fimo-cli/references/domains.md +0 -2
  27. package/assets/skills/fimo-cli/references/forms.md +5 -7
  28. package/assets/skills/fimo-cli/references/history.md +0 -2
  29. package/assets/skills/fimo-cli/references/init.md +34 -26
  30. package/assets/skills/fimo-cli/references/integrations/firecrawl.md +0 -2
  31. package/assets/skills/fimo-cli/references/integrations.md +0 -2
  32. package/assets/skills/fimo-cli/references/invite.md +0 -2
  33. package/assets/skills/fimo-cli/references/labels.md +5 -7
  34. package/assets/skills/fimo-cli/references/locales.md +2 -4
  35. package/assets/skills/fimo-cli/references/mcp.md +2 -4
  36. package/assets/skills/fimo-cli/references/media.md +0 -2
  37. package/assets/skills/fimo-cli/references/preview.md +0 -2
  38. package/assets/skills/fimo-cli/references/project-vars-and-secrets.md +1 -3
  39. package/assets/skills/fimo-cli/references/projects.md +1 -3
  40. package/assets/skills/fimo-cli/references/schedules.md +0 -2
  41. package/assets/skills/fimo-cli/references/support.md +0 -2
  42. package/assets/skills/fimo-cli/references/webhooks.md +0 -2
  43. package/assets/skills/fimo-code/SKILL.md +11 -3
  44. package/assets/skills/fimo-code/references/assets.md +2 -2
  45. package/assets/skills/fimo-code/references/content/clients.md +5 -1
  46. package/assets/skills/fimo-code/references/content.md +3 -1
  47. package/assets/skills/fimo-code/references/frameworks/astro.md +127 -0
  48. package/assets/skills/fimo-code/references/frameworks/compatibility.md +19 -0
  49. package/assets/skills/fimo-code/references/frameworks/hosting.md +15 -3
  50. package/assets/skills/fimo-code/references/frameworks/react-router.md +37 -209
  51. package/assets/skills/fimo-code/references/frameworks/sveltekit.md +110 -0
  52. package/assets/skills/fimo-code/references/frameworks/vite-react.md +35 -166
  53. package/assets/skills/fimo-code/references/labels.md +16 -6
  54. package/assets/skills/fimo-code/references/locales.md +3 -1
  55. package/assets/skills/fimo-code/references/ui.md +19 -15
  56. package/assets/skills/fimo-migration/SKILL.md +99 -146
  57. package/assets/skills/fimo-migration/references/frameworks/nextjs.md +26 -0
  58. package/assets/skills/fimo-migration/references/inventory/coordinator.md +40 -0
  59. package/assets/skills/fimo-migration/references/inventory/locales/next-intl.md +39 -0
  60. package/assets/skills/fimo-migration/references/inventory/locales.md +41 -0
  61. package/assets/skills/fimo-migration/references/workflow.md +3 -2
  62. package/assets/skills/fimo-migration/scripts/inventory/locales/next-intl.mjs +459 -0
  63. package/assets/skills/fimo-studio/SKILL.md +1 -1
  64. package/assets/skills/fimo-studio/references/project-creation.md +10 -18
  65. package/assets/starters/fimo-site/template/.fimo/agents/content-guide/capabilities.yaml +0 -1
  66. package/assets/starters/fimo-site/template/.fimo/agents/site-review/capabilities.yaml +0 -1
  67. package/assets/sveltekit/components/Boolean.svelte +19 -0
  68. package/assets/sveltekit/components/Date.svelte +33 -0
  69. package/assets/sveltekit/components/FimoProvider.svelte +35 -0
  70. package/assets/sveltekit/components/Image.svelte +80 -0
  71. package/assets/sveltekit/components/Json.svelte +18 -0
  72. package/assets/sveltekit/components/Label.svelte +16 -0
  73. package/assets/sveltekit/components/Number.svelte +21 -0
  74. package/assets/sveltekit/components/RichText.svelte +19 -0
  75. package/assets/sveltekit/components/Text.svelte +30 -0
  76. package/assets/sveltekit/components/Video.svelte +37 -0
  77. package/assets/sveltekit/components/index.d.ts +10 -0
  78. package/assets/sveltekit/components/index.js +10 -0
  79. package/dist/build/astro/config.d.ts +4 -0
  80. package/dist/build/astro/config.js +64 -0
  81. package/dist/build/next/config.js +16 -0
  82. package/dist/build/sveltekit/config.d.ts +6 -0
  83. package/dist/build/sveltekit/config.js +8 -0
  84. package/dist/build/sveltekit/data-id.d.ts +3 -0
  85. package/dist/build/sveltekit/data-id.js +171 -0
  86. package/dist/build/sveltekit/vite.d.ts +13 -0
  87. package/dist/build/sveltekit/vite.js +15 -0
  88. package/dist/build/vite/index.js +2 -0
  89. package/dist/build/vite/plugins/data-id.d.ts +9 -0
  90. package/dist/build/vite/plugins/data-id.js +6 -1
  91. package/dist/build/vite/plugins/runtime-env.d.ts +21 -0
  92. package/dist/build/vite/plugins/runtime-env.js +68 -0
  93. package/dist/cli/bundle.json +1 -1
  94. package/dist/cli/index.js +108 -101
  95. package/dist/runtime/app/FimoProviders.d.ts +1 -1
  96. package/dist/runtime/app/FimoProviders.js +11 -5
  97. package/dist/runtime/astro/internal.d.ts +6 -0
  98. package/dist/runtime/astro/internal.js +16 -0
  99. package/dist/runtime/astro/preview.d.ts +14 -0
  100. package/dist/runtime/astro/preview.js +103 -0
  101. package/dist/runtime/astro/render-rich-text.d.ts +2 -0
  102. package/dist/runtime/astro/render-rich-text.js +1 -0
  103. package/dist/runtime/astro/server-runtime.d.ts +3 -0
  104. package/dist/runtime/astro/server-runtime.js +20 -0
  105. package/dist/runtime/astro/server.d.ts +15 -0
  106. package/dist/runtime/astro/server.js +14 -0
  107. package/dist/runtime/content/client/collection-core.d.ts +2 -17
  108. package/dist/runtime/content/client/collection-core.js +15 -8
  109. package/dist/runtime/content/client/core-types.d.ts +96 -0
  110. package/dist/runtime/content/client/core-types.js +1 -0
  111. package/dist/runtime/content/client/internal.d.ts +4 -4
  112. package/dist/runtime/content/client/internal.js +4 -3
  113. package/dist/runtime/content/client/runtime.d.ts +1 -1
  114. package/dist/runtime/content/client/server-content-client.d.ts +3 -3
  115. package/dist/runtime/content/client/server-content-client.js +2 -29
  116. package/dist/runtime/content/client/singleton-core.d.ts +2 -9
  117. package/dist/runtime/content/client/singleton-core.js +9 -4
  118. package/dist/runtime/content/client/types.d.ts +5 -115
  119. package/dist/runtime/content/index.d.ts +3 -3
  120. package/dist/runtime/labels/fetch.js +5 -3
  121. package/dist/runtime/next/client.d.ts +2 -1
  122. package/dist/runtime/next/facade.d.ts +1 -1
  123. package/dist/runtime/next/provider.js +1 -1
  124. package/dist/runtime/next/server-runtime.js +1 -1
  125. package/dist/runtime/next/server.d.ts +5 -4
  126. package/dist/runtime/paths/get-fimo-paths.js +8 -3
  127. package/dist/runtime/preview/bridge.d.ts +8 -0
  128. package/dist/runtime/preview/bridge.js +20 -0
  129. package/dist/runtime/preview/location.d.ts +23 -0
  130. package/dist/runtime/preview/location.js +93 -0
  131. package/dist/runtime/preview/routes.d.ts +10 -0
  132. package/dist/runtime/preview/routes.js +29 -0
  133. package/dist/runtime/primitives/index.d.ts +4 -1
  134. package/dist/runtime/primitives/index.js +1 -0
  135. package/dist/runtime/primitives/lib/image-optimization.d.ts +27 -2
  136. package/dist/runtime/primitives/lib/image-optimization.js +40 -0
  137. package/dist/runtime/primitives/lib/index.d.ts +1 -1
  138. package/dist/runtime/primitives/lib/primitives.d.ts +0 -54
  139. package/dist/runtime/primitives/lib/primitives.js +6 -0
  140. package/dist/runtime/primitives/lib/render-rich-text.d.ts +3 -0
  141. package/dist/runtime/primitives/lib/render-rich-text.js +112 -0
  142. package/dist/runtime/react/primitives/contracts.d.ts +40 -2
  143. package/dist/runtime/react/primitives/renderers/ImageRenderer.js +15 -34
  144. package/dist/runtime/react/primitives/renderers/RichTextRenderer.d.ts +2 -1
  145. package/dist/runtime/react/primitives/renderers/StaticImage.js +13 -30
  146. package/dist/runtime/routing/AppRoutesProvider.js +20 -8
  147. package/dist/runtime/shared/env-identity.d.ts +43 -0
  148. package/dist/runtime/shared/env-identity.js +88 -0
  149. package/dist/runtime/shared/local-runtime-env.d.ts +65 -0
  150. package/dist/runtime/shared/local-runtime-env.js +169 -0
  151. package/dist/runtime/{react-router → shared}/preview-location.d.ts +7 -13
  152. package/dist/runtime/{react-router → shared}/preview-location.js +26 -27
  153. package/dist/runtime/sveltekit/content.d.ts +25 -0
  154. package/dist/runtime/sveltekit/content.js +33 -0
  155. package/dist/runtime/sveltekit/context-contract.d.ts +5 -0
  156. package/dist/runtime/sveltekit/context-contract.js +2 -0
  157. package/dist/runtime/sveltekit/context.d.ts +8 -0
  158. package/dist/runtime/sveltekit/context.js +25 -0
  159. package/dist/runtime/sveltekit/index.d.ts +7 -0
  160. package/dist/runtime/sveltekit/index.js +4 -0
  161. package/dist/runtime/sveltekit/internal.d.ts +6 -0
  162. package/dist/runtime/sveltekit/internal.js +12 -0
  163. package/dist/runtime/sveltekit/labels.d.ts +16 -0
  164. package/dist/runtime/sveltekit/labels.js +13 -0
  165. package/dist/runtime/sveltekit/preview.d.ts +19 -0
  166. package/dist/runtime/sveltekit/preview.js +77 -0
  167. package/dist/runtime/sveltekit/runtime.d.ts +8 -0
  168. package/dist/runtime/sveltekit/runtime.js +17 -0
  169. package/dist/runtime/templates.d.ts +1 -1
  170. package/dist/runtime/templates.js +1 -1
  171. package/dist/runtime/vite/Label.d.ts +13 -0
  172. package/dist/runtime/vite/Label.js +16 -0
  173. package/dist/scripts/extract-translations.js +103 -43
  174. package/dist/scripts/sync-forms.js +1 -1
  175. package/dist/scripts/sync-schemas.js +83 -16
  176. package/package.json +57 -2
  177. package/release.json +2 -2
  178. package/templates/nextjs/AGENTS.md +10 -5
  179. package/templates/nextjs/_gitignore +1 -0
  180. package/templates/react-router/AGENTS.md +4 -3
  181. package/templates/react-router/package.json +1 -1
  182. package/templates/sveltekit/.fimo/config.json +15 -0
  183. package/templates/sveltekit/AGENTS.md +36 -0
  184. package/templates/sveltekit/CLAUDE.md +5 -0
  185. package/templates/sveltekit/_gitignore +16 -0
  186. package/templates/sveltekit/package.json +28 -0
  187. package/templates/sveltekit/pnpm-workspace.yaml +4 -0
  188. package/templates/sveltekit/src/app.html +11 -0
  189. package/templates/sveltekit/src/routes/+layout.server.ts +8 -0
  190. package/templates/sveltekit/src/routes/+layout.svelte +12 -0
  191. package/templates/sveltekit/src/routes/+page.svelte +48 -0
  192. package/templates/sveltekit/svelte.config.js +5 -0
  193. package/templates/sveltekit/tsconfig.json +14 -0
  194. package/templates/sveltekit/vite.config.ts +5 -0
  195. /package/assets/content-templates/{form-template.eta → forms/react.eta} +0 -0
  196. /package/assets/content-templates/{next-client-template.eta → next/client.eta} +0 -0
  197. /package/assets/content-templates/{next-server-template.eta → next/server.eta} +0 -0
  198. /package/assets/content-templates/{hooks-template.eta → react/client.eta} +0 -0
  199. /package/assets/content-templates/{types-template.eta → shared/types.eta} +0 -0
@@ -0,0 +1,459 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
4
+ import path from 'node:path';
5
+ import process from 'node:process';
6
+
7
+ const IGNORED_DIRECTORIES = new Set([
8
+ '.git',
9
+ '.next',
10
+ '.output',
11
+ '.turbo',
12
+ 'build',
13
+ 'coverage',
14
+ 'dist',
15
+ 'node_modules',
16
+ 'public',
17
+ ]);
18
+ const SOURCE_EXTENSIONS = new Set(['.js', '.jsx', '.mjs', '.mts', '.ts', '.tsx']);
19
+ const MAX_SOURCE_FILES = 5_000;
20
+ const MAX_SOURCE_BYTES = 1_000_000;
21
+ const MAX_DICTIONARY_FILES = 10_000;
22
+ const CONVENTIONAL_PATTERN = 'messages/{locale}.json';
23
+
24
+ function fail(message) {
25
+ process.stderr.write(`${message}\n`);
26
+ process.exit(1);
27
+ }
28
+
29
+ function parseArguments(argv) {
30
+ if (argv.length === 0) {
31
+ fail('Usage: next-intl.mjs <source-root> [--messages-pattern <relative-pattern>]');
32
+ }
33
+
34
+ const sourceRoot = argv[0];
35
+ let messagesPattern;
36
+
37
+ for (let index = 1; index < argv.length; index += 1) {
38
+ if (argv[index] !== '--messages-pattern' || messagesPattern !== undefined || !argv[index + 1]) {
39
+ fail('Usage: next-intl.mjs <source-root> [--messages-pattern <relative-pattern>]');
40
+ }
41
+ messagesPattern = argv[index + 1];
42
+ index += 1;
43
+ }
44
+
45
+ return { sourceRoot, messagesPattern };
46
+ }
47
+
48
+ function toPosix(value) {
49
+ return value.split(path.sep).join('/');
50
+ }
51
+
52
+ function lineOf(source, offset) {
53
+ return source.slice(0, offset).split('\n').length;
54
+ }
55
+
56
+ function validatePattern(patternValue, root) {
57
+ if (path.isAbsolute(patternValue)) {
58
+ fail('The messages pattern must be relative to the source root.');
59
+ }
60
+ if ((patternValue.match(/\{locale\}/g) ?? []).length !== 1) {
61
+ fail('The messages pattern must contain {locale} exactly once.');
62
+ }
63
+ if (!patternValue.endsWith('.json')) {
64
+ fail('The messages pattern must point to JSON files.');
65
+ }
66
+
67
+ const normalized = path.normalize(patternValue);
68
+ if ((normalized.match(/\{locale\}/g) ?? []).length !== 1) {
69
+ fail('The normalized messages pattern must retain {locale} exactly once.');
70
+ }
71
+ const resolved = path.resolve(root, normalized.replace('{locale}', '__locale__'));
72
+ if (resolved !== root && !resolved.startsWith(`${root}${path.sep}`)) {
73
+ fail('The messages pattern must stay inside the source root.');
74
+ }
75
+
76
+ return toPosix(normalized);
77
+ }
78
+
79
+ function listSourceFiles(root) {
80
+ const files = [];
81
+ let truncated = false;
82
+
83
+ function visit(directory) {
84
+ for (const entry of readdirSync(directory, { withFileTypes: true }).sort((left, right) =>
85
+ left.name.localeCompare(right.name)
86
+ )) {
87
+ if (files.length >= MAX_SOURCE_FILES) {
88
+ truncated = true;
89
+ return;
90
+ }
91
+ const absolute = path.join(directory, entry.name);
92
+ if (entry.isSymbolicLink()) {
93
+ continue;
94
+ }
95
+ if (entry.isDirectory()) {
96
+ if (!IGNORED_DIRECTORIES.has(entry.name)) {
97
+ visit(absolute);
98
+ }
99
+ continue;
100
+ }
101
+ if (
102
+ entry.isFile() &&
103
+ SOURCE_EXTENSIONS.has(path.extname(entry.name)) &&
104
+ lstatSync(absolute).size <= MAX_SOURCE_BYTES
105
+ ) {
106
+ files.push(absolute);
107
+ }
108
+ }
109
+ }
110
+
111
+ visit(root);
112
+ return { files, truncated };
113
+ }
114
+
115
+ function readPackageEvidence(root) {
116
+ const packagePath = path.join(root, 'package.json');
117
+ if (!existsSync(packagePath)) {
118
+ return { declared: false, evidence: [] };
119
+ }
120
+
121
+ let manifest;
122
+ try {
123
+ manifest = JSON.parse(readFileSync(packagePath, 'utf8'));
124
+ } catch {
125
+ return { declared: false, evidence: [] };
126
+ }
127
+
128
+ const sections = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
129
+ for (const section of sections) {
130
+ if (typeof manifest[section]?.['next-intl'] === 'string') {
131
+ const source = readFileSync(packagePath, 'utf8');
132
+ const offset = source.indexOf('"next-intl"');
133
+ return {
134
+ declared: true,
135
+ evidence: [
136
+ {
137
+ claim: 'next-intl.declared',
138
+ path: 'package.json',
139
+ line: lineOf(source, Math.max(offset, 0)),
140
+ value: manifest[section]['next-intl'],
141
+ },
142
+ ],
143
+ };
144
+ }
145
+ }
146
+
147
+ return { declared: false, evidence: [] };
148
+ }
149
+
150
+ function deriveMessagePatterns(files, root) {
151
+ const findings = [];
152
+ const importPattern = /import\s*\(\s*`([^`]*\$\{[^}]+\}[^`]*\.json)`\s*\)/g;
153
+
154
+ for (const absolute of files) {
155
+ const source = readFileSync(absolute, 'utf8');
156
+ for (const match of source.matchAll(importPattern)) {
157
+ const template = match[1];
158
+ if ((template.match(/\$\{[^}]+\}/g) ?? []).length !== 1) {
159
+ continue;
160
+ }
161
+ const relativeTemplate = template.replace(/\$\{[^}]+\}/, '{locale}');
162
+ const resolvedTemplate = path.resolve(path.dirname(absolute), relativeTemplate.replace('{locale}', '__locale__'));
163
+ if (resolvedTemplate !== root && !resolvedTemplate.startsWith(`${root}${path.sep}`)) {
164
+ continue;
165
+ }
166
+ const rootRelative = toPosix(path.relative(root, resolvedTemplate)).replace('__locale__', '{locale}');
167
+ findings.push({
168
+ pattern: rootRelative,
169
+ source: 'request-config',
170
+ path: toPosix(path.relative(root, absolute)),
171
+ line: lineOf(source, match.index ?? 0),
172
+ });
173
+ }
174
+ }
175
+
176
+ return findings;
177
+ }
178
+
179
+ function parseQuotedValues(value) {
180
+ return [...value.matchAll(/(['"])(.*?)\1/g)].map((match) => match[2]);
181
+ }
182
+
183
+ function readStaticBindings(source) {
184
+ const arrays = new Map();
185
+ const strings = new Map();
186
+
187
+ for (const match of source.matchAll(/(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*\[([^\]]*)\](?:\s+as\s+const)?/g)) {
188
+ const values = parseQuotedValues(match[2]);
189
+ if (values.length > 0) {
190
+ arrays.set(match[1], values);
191
+ }
192
+ }
193
+ for (const match of source.matchAll(/(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(['"])(.*?)\2/g)) {
194
+ strings.set(match[1], match[3]);
195
+ }
196
+
197
+ return { arrays, strings };
198
+ }
199
+
200
+ function readRoutingCandidates(files, root) {
201
+ const localeCandidates = [];
202
+ const defaultLocaleCandidates = [];
203
+
204
+ for (const absolute of files) {
205
+ const source = readFileSync(absolute, 'utf8');
206
+ if (!source.includes('defineRouting')) {
207
+ continue;
208
+ }
209
+ const bindings = readStaticBindings(source);
210
+
211
+ for (const match of source.matchAll(/\blocales\s*:\s*(\[[^\]]*\]|[A-Za-z_$][\w$]*)/g)) {
212
+ const expression = match[1];
213
+ const locales = expression.startsWith('[') ? parseQuotedValues(expression) : bindings.arrays.get(expression);
214
+ if (locales?.length) {
215
+ localeCandidates.push({
216
+ locales: [...new Set(locales)].sort(),
217
+ path: toPosix(path.relative(root, absolute)),
218
+ line: lineOf(source, match.index ?? 0),
219
+ });
220
+ }
221
+ }
222
+
223
+ for (const match of source.matchAll(/\bdefaultLocale\s*:\s*(?:(['"])(.*?)\1|([A-Za-z_$][\w$]*))/g)) {
224
+ const locale = match[2] ?? bindings.strings.get(match[3]);
225
+ if (locale) {
226
+ defaultLocaleCandidates.push({
227
+ locale,
228
+ path: toPosix(path.relative(root, absolute)),
229
+ line: lineOf(source, match.index ?? 0),
230
+ });
231
+ }
232
+ }
233
+ }
234
+
235
+ return { localeCandidates, defaultLocaleCandidates };
236
+ }
237
+
238
+ function uniqueBy(items, keyOf) {
239
+ const seen = new Set();
240
+ return items.filter((item) => {
241
+ const key = keyOf(item);
242
+ if (seen.has(key)) {
243
+ return false;
244
+ }
245
+ seen.add(key);
246
+ return true;
247
+ });
248
+ }
249
+
250
+ function selectConsistent(items, valueOf) {
251
+ const values = uniqueBy(items.map(valueOf), (value) => JSON.stringify(value));
252
+ return values.length === 1 ? values[0] : null;
253
+ }
254
+
255
+ function inspectDictionaries(root, patterns) {
256
+ const dictionaries = [];
257
+ let truncated = false;
258
+
259
+ for (const patternEntry of patterns) {
260
+ const escaped = patternEntry.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace('\\{locale\\}', '([^/]+)');
261
+ const matcher = new RegExp(`^${escaped}$`);
262
+ const prefix = patternEntry.pattern.slice(0, patternEntry.pattern.indexOf('{locale}'));
263
+ const scanRootRelative = prefix.includes('/') ? prefix.slice(0, prefix.lastIndexOf('/')) : '';
264
+ const scanRoot = path.resolve(root, scanRootRelative || '.');
265
+ if (!existsSync(scanRoot) || !lstatSync(scanRoot).isDirectory()) {
266
+ continue;
267
+ }
268
+
269
+ const candidates = [];
270
+ function visit(directory) {
271
+ for (const entry of readdirSync(directory, { withFileTypes: true }).sort((left, right) =>
272
+ left.name.localeCompare(right.name)
273
+ )) {
274
+ if (candidates.length >= MAX_DICTIONARY_FILES) {
275
+ truncated = true;
276
+ return;
277
+ }
278
+ const absolute = path.join(directory, entry.name);
279
+ if (entry.isSymbolicLink()) {
280
+ continue;
281
+ }
282
+ if (entry.isDirectory()) {
283
+ if (!IGNORED_DIRECTORIES.has(entry.name)) {
284
+ visit(absolute);
285
+ }
286
+ } else if (entry.isFile() && entry.name.endsWith('.json')) {
287
+ candidates.push(absolute);
288
+ }
289
+ }
290
+ }
291
+ visit(scanRoot);
292
+
293
+ for (const absolute of candidates) {
294
+ const relative = toPosix(path.relative(root, absolute));
295
+ const match = relative.match(matcher);
296
+ if (match) {
297
+ dictionaries.push({ locale: match[1], path: relative, pattern: patternEntry.pattern });
298
+ }
299
+ }
300
+ }
301
+
302
+ return {
303
+ dictionaries: uniqueBy(dictionaries, (item) => `${item.locale}\0${item.path}\0${item.pattern}`).sort(
304
+ (left, right) => left.locale.localeCompare(right.locale) || left.path.localeCompare(right.path)
305
+ ),
306
+ truncated,
307
+ };
308
+ }
309
+
310
+ const { sourceRoot: sourceRootArgument, messagesPattern: suppliedPattern } = parseArguments(process.argv.slice(2));
311
+ const sourceRoot = path.resolve(sourceRootArgument);
312
+ if (!existsSync(sourceRoot) || !lstatSync(sourceRoot).isDirectory()) {
313
+ fail(`Source root is not a directory: ${sourceRootArgument}`);
314
+ }
315
+ const canonicalRoot = realpathSync(sourceRoot);
316
+
317
+ const sourceInspection = listSourceFiles(canonicalRoot);
318
+ const sourceFiles = sourceInspection.files;
319
+ const packageFinding = readPackageEvidence(canonicalRoot);
320
+ const derivedPatterns = deriveMessagePatterns(sourceFiles, canonicalRoot);
321
+ const patterns = [];
322
+
323
+ if (suppliedPattern) {
324
+ patterns.push({ pattern: validatePattern(suppliedPattern, canonicalRoot), source: 'agent-supplied' });
325
+ } else {
326
+ for (const finding of derivedPatterns) {
327
+ patterns.push({ pattern: validatePattern(finding.pattern, canonicalRoot), source: finding.source });
328
+ }
329
+ if (existsSync(path.join(canonicalRoot, 'messages'))) {
330
+ patterns.push({ pattern: CONVENTIONAL_PATTERN, source: 'conventional' });
331
+ }
332
+ }
333
+
334
+ const uniquePatterns = uniqueBy(patterns, (item) => item.pattern).sort((left, right) =>
335
+ left.pattern.localeCompare(right.pattern)
336
+ );
337
+ const dictionaryInspection = inspectDictionaries(canonicalRoot, uniquePatterns);
338
+ const dictionaries = dictionaryInspection.dictionaries;
339
+ const routing = readRoutingCandidates(sourceFiles, canonicalRoot);
340
+ const configuredLocales = selectConsistent(routing.localeCandidates, (item) => item.locales);
341
+ const defaultLocale = selectConsistent(routing.defaultLocaleCandidates, (item) => item.locale);
342
+ const dictionaryLocales = [...new Set(dictionaries.map((item) => item.locale))].sort();
343
+ const unresolved = [];
344
+
345
+ if (sourceInspection.truncated) {
346
+ unresolved.push({ code: 'source-scan-truncated', claim: `Source scanning stopped after ${MAX_SOURCE_FILES} files.` });
347
+ }
348
+ if (!packageFinding.declared) {
349
+ unresolved.push({
350
+ code: 'next-intl-not-declared',
351
+ claim: 'Whether this project uses next-intl is not proven by package.json.',
352
+ });
353
+ }
354
+ if (uniquePatterns.length === 0) {
355
+ unresolved.push({
356
+ code: 'messages-pattern-not-found',
357
+ claim: 'The location of next-intl message dictionaries is not proven.',
358
+ });
359
+ } else if (dictionaries.length === 0) {
360
+ unresolved.push({
361
+ code: 'message-dictionaries-not-found',
362
+ claim: 'No JSON message dictionaries matched the selected pattern.',
363
+ });
364
+ }
365
+ if (dictionaryInspection.truncated) {
366
+ unresolved.push({
367
+ code: 'dictionary-scan-truncated',
368
+ claim: `Dictionary scanning stopped after ${MAX_DICTIONARY_FILES} JSON files.`,
369
+ });
370
+ }
371
+ if (routing.localeCandidates.length === 0) {
372
+ unresolved.push({
373
+ code: 'configured-locales-not-found',
374
+ claim: 'The statically configured next-intl locales are not proven.',
375
+ });
376
+ } else if (configuredLocales === null) {
377
+ unresolved.push({
378
+ code: 'configured-locales-conflict',
379
+ claim: 'Static routing files declare different locale lists.',
380
+ });
381
+ }
382
+ if (routing.defaultLocaleCandidates.length === 0) {
383
+ unresolved.push({
384
+ code: 'default-locale-not-found',
385
+ claim: 'The statically configured next-intl default locale is not proven.',
386
+ });
387
+ } else if (defaultLocale === null) {
388
+ unresolved.push({
389
+ code: 'default-locale-conflict',
390
+ claim: 'Static routing files declare different default locales.',
391
+ });
392
+ }
393
+ if (configuredLocales) {
394
+ for (const locale of configuredLocales.filter((value) => !dictionaryLocales.includes(value))) {
395
+ unresolved.push({
396
+ code: 'configured-locale-without-dictionary',
397
+ claim: `Configured locale ${locale} has no matching message dictionary.`,
398
+ locale,
399
+ });
400
+ }
401
+ for (const locale of dictionaryLocales.filter((value) => !configuredLocales.includes(value))) {
402
+ unresolved.push({
403
+ code: 'dictionary-locale-not-configured',
404
+ claim: `Message dictionary locale ${locale} is not in the static routing configuration.`,
405
+ locale,
406
+ });
407
+ }
408
+ }
409
+ if (defaultLocale && configuredLocales && !configuredLocales.includes(defaultLocale)) {
410
+ unresolved.push({
411
+ code: 'default-locale-not-configured',
412
+ claim: `Default locale ${defaultLocale} is not in the static routing configuration.`,
413
+ locale: defaultLocale,
414
+ });
415
+ }
416
+
417
+ const evidence = [
418
+ ...packageFinding.evidence,
419
+ ...derivedPatterns
420
+ .filter((item) => uniquePatterns.some((patternEntry) => patternEntry.pattern === item.pattern))
421
+ .map((item) => ({ claim: 'messages.pattern', path: item.path, line: item.line, value: item.pattern })),
422
+ ...dictionaries.map((item) => ({ claim: 'messages.dictionary', path: item.path, line: 1, value: item.locale })),
423
+ ...routing.localeCandidates.map((item) => ({
424
+ claim: 'routing.locales',
425
+ path: item.path,
426
+ line: item.line,
427
+ value: item.locales,
428
+ })),
429
+ ...routing.defaultLocaleCandidates.map((item) => ({
430
+ claim: 'routing.defaultLocale',
431
+ path: item.path,
432
+ line: item.line,
433
+ value: item.locale,
434
+ })),
435
+ ].sort(
436
+ (left, right) =>
437
+ left.path.localeCompare(right.path) || left.line - right.line || left.claim.localeCompare(right.claim)
438
+ );
439
+
440
+ const result = {
441
+ version: 1,
442
+ area: 'locales',
443
+ facts: {
444
+ system: 'next-intl',
445
+ declared: packageFinding.declared,
446
+ messagesPatterns: uniquePatterns,
447
+ dictionaryCandidates: dictionaries,
448
+ configuredLocaleCandidates: routing.localeCandidates,
449
+ configuredLocales,
450
+ defaultLocaleCandidates: routing.defaultLocaleCandidates,
451
+ defaultLocale,
452
+ },
453
+ evidence,
454
+ unresolved: unresolved.sort(
455
+ (left, right) => left.code.localeCompare(right.code) || (left.locale ?? '').localeCompare(right.locale ?? '')
456
+ ),
457
+ };
458
+
459
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
@@ -62,5 +62,5 @@ Read the relevant reference file when the user's question matches a topic.
62
62
  | Visual changes | Edit Mode (quick tweaks), AI Chat (bigger changes), Code Editor (full control) |
63
63
  | Something went wrong | Version History, AI Chat |
64
64
  | Working with others | Sharing, Collaboration |
65
- | Starting a project | Drawing, AI Chat |
65
+ | Starting a project | Preview & Responsive, Publish & Deploy |
66
66
  | Running out of credits | Billing |
@@ -1,24 +1,16 @@
1
- ## Starting a New Project
1
+ ## Create a Project
2
2
 
3
- <!-- TODO(audit 2026-06-03): The in-app "describe your project" creation form was removed (commit dcb977274, "chore: delete new project flow"). The dashboard no longer ships a text-prompt box, image/sketch upload, quick-start suggestion buttons, model selector, or a credit footer. The creation screen at /app/create is now a headless setup/loading screen (OrganizationNewProjectPage) that reads a prompt from localStorage ('fimo-prompt') and immediately starts generation — but no component in apps/ui/src currently writes that key, so the surface that actually collects the prompt could not be confirmed from the code. A human must confirm the current way users describe and submit a new project before this section is re-documented in detail. -->
3
+ 1. Open **Projects** in Fimo Studio.
4
+ 2. Select **Create project**. If the button is unavailable, ask an organization admin for project creation permission.
5
+ 3. Enter a project name and select **Create**.
6
+ 4. Studio opens the new project's **Getting started** view.
4
7
 
5
- To begin a new project, use the **Build my website** button. It appears on the Fimo landing page and in the top navigation when you are signed out. It takes you into the project setup flow.
8
+ ## Set Up the Project
6
9
 
7
- ## What Happens After You Start
10
+ The **For agents** tab is the default. Copy its prompt into your coding agent, or use one of the available agent shortcuts. The prompt identifies this as a newly created Fimo project, gives the agent the exact `fimo init` command, pins the selected CLI channel for every `fimo` command in the setup, and links to the coding-agent guide. Following that guide, the agent initializes the project and creates its first hosted preview with one plain preview deploy — unless you tell it not to deploy.
8
11
 
9
- 1. A setup screen appears with a spinner and the message "Setting up your project...". The AI is generating your site.
10
- 2. A **Back to projects** link in the top-left lets you cancel and return to your projects list.
11
- 3. When generation begins, you are taken to a loading screen for the new project, and then your workspace opens with the site ready to explore and edit.
12
+ Use **Manual setup** when you want to run the steps yourself. It provides the exact `fimo init` command, coding-agent shortcuts, and the final `fimo deploy` command.
12
13
 
13
- ## If You Are Low on Credits
14
+ The first deploy completes onboarding and removes **Getting started** from the project. You can also select **Dismiss getting started** to hide it without deploying.
14
15
 
15
- Creating a project uses credits. If your balance is too low, you will see a "Credit balance is too low" notification prompting you to upgrade, and you will be returned to your projects list instead of starting a new project. See **Billing & Credits** for how credits work and how to top up.
16
-
17
- ## Refining Your Site
18
-
19
- Once your workspace opens, the AI does not stop there — you keep shaping the site from inside it. Use the **AI Chat** to ask for changes ("add a pricing section", "use a dark theme"), and the **Code Editor** or **Visual Editing** for finer control. The clearer your requests, the closer the result.
20
-
21
- - **Mention the site type**: portfolio, landing page, blog, documentation, e-commerce storefront.
22
- - **List key pages**: home, about, pricing, contact, gallery.
23
- - **Describe the style**: modern and minimalist, colorful and playful, dark theme with bold typography.
24
- - **Call out specific elements**: hero section with a video background, pricing table with three tiers, testimonial carousel.
16
+ Deploying creates a preview. It does not publish the site. Publishing is a separate action in Fimo Studio; see **Publish & Deploy** when the site is ready to go live.
@@ -1,4 +1,3 @@
1
- - files:read
2
1
  - cms:read
3
2
  - i18n:read
4
3
  - net:fetch
@@ -0,0 +1,19 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes, SvelteHTMLElements } from 'svelte/elements';
3
+
4
+ import type { FimoBoolean } from 'fimo/sveltekit';
5
+ import { getFimoSource } from 'fimo/sveltekit';
6
+
7
+ type Props = Omit<HTMLAttributes<HTMLElement>, 'children'> & {
8
+ value: FimoBoolean | boolean | null | undefined;
9
+ as?: keyof SvelteHTMLElements;
10
+ [key: string]: unknown;
11
+ };
12
+
13
+ let { value, as = 'span', ...props }: Props = $props();
14
+ const source = $derived(getFimoSource(value));
15
+ </script>
16
+
17
+ {#if value != null}
18
+ <svelte:element this={as} {...props} data-fimo-source={source}>{String(value.valueOf())}</svelte:element>
19
+ {/if}
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ import type { HTMLTimeAttributes } from 'svelte/elements';
3
+
4
+ import type { FimoString } from 'fimo/sveltekit';
5
+ import { getFimoSource } from 'fimo/sveltekit';
6
+
7
+ type Props = Omit<HTMLTimeAttributes, 'datetime' | 'children'> & {
8
+ value: FimoString | globalThis.Date | string | number | null | undefined;
9
+ locale?: string;
10
+ options?: Intl.DateTimeFormatOptions;
11
+ };
12
+
13
+ let { value, locale, options, ...props }: Props = $props();
14
+ const source = $derived(getFimoSource(value));
15
+ const rendered = $derived.by(() => {
16
+ if (value == null) {
17
+ return null;
18
+ }
19
+ const raw = value instanceof String ? value.toString() : value;
20
+ const date = new globalThis.Date(raw as string | number | globalThis.Date);
21
+ if (Number.isNaN(date.getTime())) {
22
+ return null;
23
+ }
24
+ return {
25
+ datetime: date.toISOString().split('T')[0],
26
+ text: date.toLocaleDateString(locale, options ?? { year: 'numeric', month: 'short', day: 'numeric' }),
27
+ };
28
+ });
29
+ </script>
30
+
31
+ {#if rendered}
32
+ <time {...props} datetime={rendered.datetime} data-fimo-source={source}>{rendered.text}</time>
33
+ {/if}
@@ -0,0 +1,35 @@
1
+ <script lang="ts">
2
+ import { afterNavigate, goto, invalidateAll } from '$app/navigation';
3
+ import { env } from '$env/dynamic/public';
4
+ import type { LabelsSnapshot } from 'fimo/sveltekit';
5
+ import {
6
+ connectPreview,
7
+ ensurePreviewScript,
8
+ LABELS_KEY,
9
+ sendPreviewLocation,
10
+ type LabelsSource,
11
+ } from 'fimo/sveltekit/internal';
12
+ import { onMount, setContext, type Snippet } from 'svelte';
13
+
14
+ let { labels, children }: { labels: LabelsSnapshot; children: Snippet } = $props();
15
+ let previewEnabled = false;
16
+
17
+ setContext<LabelsSource>(LABELS_KEY, () => labels);
18
+
19
+ onMount(() => {
20
+ previewEnabled = ensurePreviewScript(env.PUBLIC_FIMO_PREVIEW_SCRIPT_URL);
21
+ if (!previewEnabled) {
22
+ return;
23
+ }
24
+
25
+ return connectPreview({ goto, invalidateAll });
26
+ });
27
+
28
+ afterNavigate(() => {
29
+ if (previewEnabled) {
30
+ sendPreviewLocation();
31
+ }
32
+ });
33
+ </script>
34
+
35
+ {@render children()}
@@ -0,0 +1,80 @@
1
+ <script lang="ts">
2
+ import type { HTMLImgAttributes } from 'svelte/elements';
3
+
4
+ import type { FimoMedia } from 'fimo/sveltekit';
5
+ import { getFimoSource } from 'fimo/sveltekit';
6
+ import {
7
+ mergeSvelteImageStyle,
8
+ resolveImagePresentation,
9
+ type ImageFit,
10
+ } from 'fimo/sveltekit/internal';
11
+
12
+ type Props = Omit<HTMLImgAttributes, 'src' | 'alt' | 'width' | 'height' | 'sizes' | 'srcset'> & {
13
+ value: FimoMedia | null | undefined;
14
+ alt?: string;
15
+ quality?: number;
16
+ fit?: ImageFit;
17
+ unoptimized?: boolean;
18
+ overrideSrc?: string;
19
+ width?: string | number;
20
+ height?: string | number;
21
+ sizes?: string;
22
+ srcset?: string;
23
+ srcSet?: string;
24
+ hostSuffixes?: readonly string[];
25
+ style?: string;
26
+ };
27
+
28
+ let {
29
+ value,
30
+ alt,
31
+ quality,
32
+ fit,
33
+ unoptimized,
34
+ overrideSrc,
35
+ width,
36
+ height,
37
+ sizes,
38
+ srcset,
39
+ srcSet,
40
+ hostSuffixes,
41
+ style,
42
+ ...props
43
+ }: Props = $props();
44
+
45
+ const image = $derived(
46
+ value
47
+ ? resolveImagePresentation({
48
+ src: value.url,
49
+ alt: alt ?? value.alt,
50
+ mediaWidth: value.width,
51
+ mediaHeight: value.height,
52
+ quality,
53
+ fit,
54
+ unoptimized,
55
+ overrideSrc,
56
+ width,
57
+ height,
58
+ sizes,
59
+ srcSet: srcset ?? srcSet,
60
+ hostSuffixes,
61
+ })
62
+ : null,
63
+ );
64
+ const imageStyle = $derived(image ? mergeSvelteImageStyle(style, image.objectFit) : style);
65
+ const source = $derived(getFimoSource(value));
66
+ </script>
67
+
68
+ {#if image}
69
+ <img
70
+ {...props}
71
+ src={image.src}
72
+ alt={image.alt}
73
+ width={image.width}
74
+ height={image.height}
75
+ sizes={image.sizes}
76
+ srcset={image.srcSet}
77
+ style={imageStyle}
78
+ data-fimo-source={source}
79
+ />
80
+ {/if}