fimo 0.9.0-experimental.5 → 0.9.0-experimental.7

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 (31) hide show
  1. package/assets/agent-templates/content-translator/scripts/write-locale-files.ts +66 -0
  2. package/assets/agents-starter/claude/settings.json +19 -0
  3. package/assets/content-templates/types-template.eta +1 -0
  4. package/assets/skills/fimo/references/translations.md +75 -0
  5. package/assets/skills/fimo-cli/references/create.md +90 -0
  6. package/assets/skills/fimo-cli/references/translations.md +43 -0
  7. package/assets/skills/fimo-migration/SKILL.md +2 -0
  8. package/assets/skills/fimo-migration/references/manifest.md +11 -0
  9. package/assets/skills/fimo-migration/references/pages.md +95 -0
  10. package/dist/cli/bundle.json +1 -1
  11. package/dist/cli/index.js +217 -169
  12. package/dist/runtime/core/BuiltWithBadge.d.ts +2 -0
  13. package/dist/runtime/core/BuiltWithBadge.js +68 -0
  14. package/dist/runtime/seo/neutral.d.ts +22 -0
  15. package/dist/runtime/seo/neutral.js +17 -0
  16. package/dist/scripts/frameworks/discover.js +17 -0
  17. package/dist/scripts/frameworks/next/rewire.d.ts +13 -0
  18. package/dist/scripts/frameworks/next/rewire.js +26 -2
  19. package/dist/scripts/frameworks/next/routes.d.ts +6 -0
  20. package/dist/scripts/frameworks/next/routes.js +24 -1
  21. package/dist/scripts/inject-translations.d.ts +6 -0
  22. package/dist/scripts/inject-translations.js +168 -0
  23. package/dist/scripts/lib/discover-next-routes.d.ts +17 -0
  24. package/dist/scripts/lib/discover-next-routes.js +134 -0
  25. package/dist/scripts/routes/bindings.d.ts +93 -0
  26. package/dist/scripts/routes/bindings.js +117 -0
  27. package/dist/scripts/scan-wrappable.d.ts +1 -1
  28. package/dist/scripts/scan-wrappable.js +111 -1
  29. package/package.json +5 -1
  30. package/templates/react-router/package.json +1 -1
  31. package/templates/react-router/translations/en.json +1 -0
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Tool: write-locale-files
3
+ *
4
+ * For locale-keyed JSON files in `src/locales/<locale>.json`, fill in
5
+ * missing keys from the default-locale file using the configured
6
+ * translation provider. Run after translate-entries when projects mix
7
+ * CMS-driven copy with hardcoded i18n strings.
8
+ */
9
+
10
+ import { promises as fs } from 'node:fs';
11
+ import path from 'node:path';
12
+
13
+ interface Catalog {
14
+ [key: string]: string;
15
+ }
16
+
17
+ async function readCatalog(file: string): Promise<Catalog> {
18
+ try {
19
+ const text = await fs.readFile(file, 'utf8');
20
+ return JSON.parse(text) as Catalog;
21
+ } catch {
22
+ return {};
23
+ }
24
+ }
25
+
26
+ async function writeCatalog(file: string, catalog: Catalog): Promise<void> {
27
+ await fs.writeFile(file, JSON.stringify(catalog, null, 2) + '\n', 'utf8');
28
+ }
29
+
30
+ async function translate(text: string, _to: string): Promise<string> {
31
+ // Stub — provider-specific call.
32
+ return text;
33
+ }
34
+
35
+ async function main(): Promise<void> {
36
+ const defaultLocale = process.env.FIMO_DEFAULT_LOCALE ?? 'en';
37
+ const targetLocales = (process.env.FIMO_TARGET_LOCALES ?? '').split(',').filter(Boolean);
38
+ const localesDir = process.env.FIMO_LOCALES_DIR ?? 'src/locales';
39
+
40
+ const defaultFile = path.join(localesDir, `${defaultLocale}.json`);
41
+ const defaultCatalog = await readCatalog(defaultFile);
42
+
43
+ for (const target of targetLocales) {
44
+ const targetFile = path.join(localesDir, `${target}.json`);
45
+ const targetCatalog = await readCatalog(targetFile);
46
+ let dirty = false;
47
+
48
+ for (const [key, sourceValue] of Object.entries(defaultCatalog)) {
49
+ if (targetCatalog[key]) {
50
+ continue;
51
+ }
52
+ targetCatalog[key] = await translate(sourceValue, target);
53
+ dirty = true;
54
+ }
55
+
56
+ if (dirty) {
57
+ await writeCatalog(targetFile, targetCatalog);
58
+ console.log(`Wrote ${targetFile}`);
59
+ }
60
+ }
61
+ }
62
+
63
+ main().catch((err) => {
64
+ console.error(err);
65
+ process.exit(1);
66
+ });
@@ -0,0 +1,19 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(fimo:*)",
5
+ "Bash(npx fimo:*)",
6
+ "Bash(pnpm:*)",
7
+ "Bash(pnpm dlx:*)",
8
+ "Bash(npx:*)",
9
+ "Bash(git status)",
10
+ "Bash(git status:*)",
11
+ "Bash(git diff)",
12
+ "Bash(git diff:*)",
13
+ "Bash(git log:*)",
14
+ "Bash(git add:*)",
15
+ "Bash(git restore:*)",
16
+ "Bash(mkdir:*)"
17
+ ]
18
+ }
19
+ }
@@ -8,6 +8,7 @@
8
8
  * }
9
9
  */
10
10
  %>
11
+ // @ts-nocheck — generated internals are exempt from the app's own tsconfig strictness; exported types still work
11
12
  // AUTO-GENERATED ⚠️ – do not edit by hand
12
13
  import { type Fields, type FimoBoolean, type FimoMedia, type FimoPolymorphicReference, type FimoReference, type FimoRichText, type FimoRichTextContent, type FimoString, type Populate, type PopulatedResult, type ProjectedWithPopulate, type Query, type Sort, type Where, type WithReferences } from "fimo/ui";
13
14
  <%
@@ -0,0 +1,75 @@
1
+ # Translations — package surface
2
+
3
+ > For the CLI workflow (`fimo validate`, when to run validation, what sync does), see **`fimo-cli/references/translations.md`**.
4
+
5
+ This file covers the **package surface**: the `t()` helper, the `useTranslations()` hook, and the code-side rules for wrapping strings.
6
+
7
+ ## The rule
8
+
9
+ Every piece of **user-facing static text** in the app (page titles, button labels, nav items, form placeholders, empty-state copy, alt text, meta tags) MUST be wrapped in `t('key', 'Default text')`.
10
+
11
+ Raw string literals in JSX are a bug — they won't show up in the Fimo admin as editable content and won't be translatable. **Hard-coded JSX text (`<h1>Welcome</h1>`) skips the entire i18n + inline-editing pipeline and becomes invisible to the editor.**
12
+
13
+ ## Setup (already wired)
14
+
15
+ Translations are wired automatically by the `fimo/vite` build plugin. It exposes a `virtual:translations` module sourced from `<projectRoot>/translations/en.json`, and `useTranslations()` reads from that virtual module — there's no provider to add or remove. The app entry is `src/root.tsx`, which wraps the app in `<FimoProviders>` from `fimo/react-router`; you never import `translations/en.json` in app code. Pages and components only call `useTranslations()`.
16
+
17
+ ## Usage
18
+
19
+ ```tsx
20
+ import { useTranslations, Text } from 'fimo/ui';
21
+
22
+ export function Hero() {
23
+ const { t } = useTranslations();
24
+ return (
25
+ <section>
26
+ <h1>
27
+ <Text value={t('hero.title', 'Welcome to Acme')} />
28
+ </h1>
29
+ <p>
30
+ <Text value={t('hero.subtitle', 'The fastest way to ship your idea.')} />
31
+ </p>
32
+ <input placeholder={String(t('hero.emailPlaceholder', 'you@example.com'))} />
33
+ <img alt={String(t('hero.imageAlt', 'Team collaborating'))} src="..." />
34
+ </section>
35
+ );
36
+ }
37
+ ```
38
+
39
+ - `t(key, default)` returns a `FimoString` (wrapped for source tracking). Render it via `<Text value={...} />` inside JSX.
40
+ - For HTML attributes (`placeholder`, `alt`, `title`, `aria-label`) cast with `String(t(...))` — the attribute can't accept a `FimoString` object directly.
41
+
42
+ ## Hard rules (enforced by the pre-build linter)
43
+
44
+ **The first argument to `t()` MUST be a string literal.** Dynamic keys break source tracking and fail the lint step.
45
+
46
+ ```tsx
47
+ // ❌ BAD — variable key
48
+ t(item.key, item.label);
49
+
50
+ // ❌ BAD — template with expression
51
+ t(`nav.${page}`, 'Page');
52
+
53
+ // ✅ GOOD — put the t() call where the literal lives
54
+ const items = [{ label: t('nav.home', 'Home') }, { label: t('nav.about', 'About') }];
55
+ items.map((item) => <li>{item.label}</li>);
56
+ ```
57
+
58
+ ## Key naming
59
+
60
+ - Dot-namespaced, lowercase, kebab or camelCase leaves: `hero.title`, `nav.signIn`, `footer.copyright`, `errors.required`.
61
+ - Namespace by **page/section/component**, not by feature (`contact.form.submitLabel`, not `forms.contactSubmit`).
62
+ - The default (second arg) is the English copy — make it final, not a TODO; it ships to production if no translation exists for the active locale.
63
+
64
+ ## What NOT to translate
65
+
66
+ - Dynamic content from entries or forms — that's already stored in the DB (content via schemas, submissions via forms).
67
+ - Content-type field values rendered via `fimo/ui` primitives (`<Text>`, `<RichText>`, …) — those carry their own admin-editing metadata.
68
+ - Purely decorative / non-text (icons, logos).
69
+ - Developer-only strings (`console.log`, error messages thrown for debugging).
70
+
71
+ ## Rule of thumb
72
+
73
+ If a human reader sees the text AND the project owner might want to reword it in the admin, it goes through `t()`. When in doubt: wrap it.
74
+
75
+ After adding or changing `t()` calls, run `fimo validate` (or rely on `fimo deploy` doing it). See `fimo-cli/references/translations.md`.
@@ -0,0 +1,90 @@
1
+ # `fimo create`
2
+
3
+ Scaffold a new Fimo project linked to a remote on the Fimo platform.
4
+
5
+ ## Command
6
+
7
+ ```bash
8
+ fimo create [dir] [options]
9
+ ```
10
+
11
+ | Flag | Required | Purpose |
12
+ | ---------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
13
+ | `[dir]` positional | yes in non-TTY | Target directory (e.g. `my-site`) |
14
+ | `--org <id>` | yes in non-TTY | Target organization **id** (use the `ID` column from `fimo orgs list`, NOT the slug). Picker shown interactively if omitted and TTY. |
15
+ | `--install` / `--no-install` | yes in non-TTY | Run `pnpm install` after scaffolding (or skip it). |
16
+ | `--agents <list>` | yes in non-TTY | Comma-separated agent IDs (e.g. `claude-code,cursor`). Pass `none` to write only universal AGENTS.md. |
17
+ | `-a, --agent <id>` | alternative | Repeatable form: `-a claude-code -a cursor`. |
18
+ | `--template-root <path>` | no | Override the bundled template (development only). |
19
+
20
+ Without the required flags in a non-TTY shell, the command exits with `Cannot prompt in a non-interactive shell.`
21
+
22
+ For `--agents`, pass the AI tool you're running in: `--agents=claude-code` for Claude Code, `--agents=cursor` for Cursor, `--agents=codex` for Codex, etc. When the flag is omitted, env-var detection of the invoking harness (e.g. `CLAUDECODE` / `CLAUDE_CODE_ENTRYPOINT` → claude-code) auto-targets that agent; other agents must be selected explicitly via `--agents`/`-a` or the interactive picker.
23
+
24
+ ## What gets written
25
+
26
+ `fimo create` scaffolds:
27
+
28
+ - The project template (Vite + React 19 + TypeScript + Tailwind v4 + shadcn/ui + react-router)
29
+ - `.fimo.settings.json` — links this directory to the remote project (read-only after creation)
30
+ - `.env` — initial env vars (currently just `VITE_API_URL`)
31
+ - `AGENTS.md` — project rules read by all agentskills.io-compatible AI tools (Codex, OpenCode, Aider, etc.)
32
+ - Per-agent variants based on `--agents`:
33
+ - `claude-code` → `CLAUDE.md` + `.claude/settings.json`
34
+ - `cursor` → `.cursor/rules/fimo.mdc`
35
+ - `windsurf` → `.windsurfrules`
36
+ - `gemini-cli` → `GEMINI.md`
37
+ - codex / opencode / aider / others → no extra file; AGENTS.md is read natively
38
+ - The project-scope `fimo` skill (npm package surface: schemas, forms, `t()`, `fimo/ui` components) copied into each selected agent's skill dir (`.claude/skills/fimo/`, `.cursor/skills/fimo/`, etc.). Version-locked to the bundled `fimo` package — the installed `SKILL.md` is stamped with `fimoVersion`. Refresh later with `fimo skills sync`. See `references/agent-setup.md`.
39
+ - A git repository with `fimo` as the remote, an initial commit, and an initial push
40
+
41
+ Codex, OpenCode, Aider, and similar AGENTS.md-native tools work directly off AGENTS.md without a per-agent variant.
42
+
43
+ `.env` is only the bootstrap app env so local `pnpm dev` works immediately. User-managed public variables are stored with `fimo vars set` and synced into a managed `.env.local` block; hidden values use `fimo secrets set`. See `references/project-vars-and-secrets.md`.
44
+
45
+ ## Right after scaffolding
46
+
47
+ Set a human-readable display name with `fimo rename "<Name>"`. Infer it from what the user is building (e.g. "Acme Coffee" for a café site, "Jane Doe Portfolio" for a personal site). The default post-`fimo create` name is an auto-generated slug; renaming updates the dashboard label and the SEO `siteName`.
48
+
49
+ ```bash
50
+ cd my-site
51
+ fimo rename "Acme Coffee"
52
+ ```
53
+
54
+ **Then kick off a warm-up deploy in the background** so the sandbox provisions and installs the user's deps (which may differ from the template's baked ones) while you generate code. The first deploy of a project's life pays a ~15-30s provisioning cost; doing it in the background means the user's first interactive deploy lands on a warm sandbox and finishes in ~4-10s instead. See `references/deploy.md` § Warm the sandbox early in a session.
55
+
56
+ ```bash
57
+ fimo deploy -m "chore: init" &
58
+ ```
59
+
60
+ `-m` is the commit message for any files `validate` may codegen — use a sensible conventional-commit string (e.g. `chore: init`, `chore: scaffold`).
61
+
62
+ ## Load the project context
63
+
64
+ After `fimo create` completes, the project root has:
65
+
66
+ - `AGENTS.md` (universal) plus the per-agent variant for the AI tool you're running in (e.g. `CLAUDE.md` for Claude Code, `.cursor/rules/fimo.mdc` for Cursor)
67
+ - `.claude/skills/fimo/SKILL.md` (or the equivalent per-agent path) — the project-scope `fimo` skill, version-locked to the installed `fimo` package
68
+
69
+ **These files came into existence mid-session, so they may not be auto-loaded by the harness.** Explicitly:
70
+
71
+ 1. `cd <project-dir>` — switch into the new project.
72
+ 2. `Read` the project rules file: `AGENTS.md` (or `CLAUDE.md` if you're Claude Code).
73
+ 3. `Read` the project skill: `.claude/skills/fimo/SKILL.md` (or `.cursor/skills/fimo/SKILL.md` for Cursor, etc.).
74
+ 4. From the project skill's "load these references up front" section, batch-read `references/content.md`, `references/forms.md`, `references/assets.md`, `references/labels.md`, `references/locales.md`, `references/ui.md` before generating any significant code.
75
+
76
+ Once those are in context, generation respects the project's conventions (schemas, `t()`, `fimo/ui` components, asset handling).
77
+
78
+ ## Adding another AI tool to an existing project later
79
+
80
+ If a teammate joins on a different AI tool (e.g. Cursor) after the project was scaffolded for claude-code only:
81
+
82
+ ```bash
83
+ fimo skills install --agents=cursor # one command — global skill + project skill + rule file
84
+ ```
85
+
86
+ Run from inside the project. The command detects the project context and wires up everything for the new agent in a single shot. See `references/agent-setup.md`.
87
+
88
+ ## Multi-org accounts
89
+
90
+ If you belong to a single organization, `fimo create` defaults to it. If you belong to multiple and don't pass `--org`, the CLI shows an interactive picker. For `fimo create`, pass the org **id** (from the `ID` column of `fimo orgs list`) — not the slug. Use `fimo orgs switch --org <id-or-slug>` to set a default for future `fimo create` invocations. See `references/account.md`.
@@ -0,0 +1,43 @@
1
+ # Translations workflow
2
+
3
+ > For the `t()` helper signature, `useTranslations()` hook, key naming rules, and the "wrap every user-facing string" rule — these are package surface and code conventions. See **`fimo/references/translations.md`**.
4
+
5
+ This file covers the **CLI workflow** for translations.
6
+
7
+ ## What sync does
8
+
9
+ The pre-build step scans `src/**/*.{ts,tsx,js,jsx}` for `t('literal-key', 'literal-default')` calls (skipping `src/ui` and `node_modules`), writes them to `translations/en.json`, and syncs them to the tenant DB. This makes strings editable inline by non-technical users in the Fimo admin and translatable to other locales.
10
+
11
+ Code is the source of truth for English defaults.
12
+
13
+ ## CLI commands
14
+
15
+ ```bash
16
+ fimo validate # run all checks + sync local state to DB (includes translations)
17
+ fimo translations list # inspect what's currently in the DB (read-only)
18
+ fimo translations list --locale en # filter by locale
19
+ ```
20
+
21
+ `fimo validate` is the single entrypoint that extracts `t()` calls, rewrites `translations/en.json`, and pushes the diff to the DB. It's idempotent — run it as often as you like. It also runs automatically inside `fimo deploy`: `fimo validate` runs first (lint + codegen + DB sync), then any `translations/en.json` changes are committed and pushed alongside the rest of your code.
22
+
23
+ Advanced/internal primitives (hidden from `fimo --help`, used for debugging or by the backend sandbox):
24
+
25
+ ```bash
26
+ fimo scripts extract-translations # rewrite translations/en.json locally — no DB push
27
+ fimo scripts inject-translations # rewrite t() defaults in src/ to match translations/en.json
28
+ ```
29
+
30
+ Prefer `fimo validate` — these primitives are for narrow use cases.
31
+
32
+ ## When to run validate
33
+
34
+ - After adding or changing any `t()` calls in code → run `fimo validate` (or rely on `fimo deploy` doing it for you).
35
+ - After renaming/removing `t()` keys → the sync phase removes stale entries from the DB.
36
+
37
+ ## Workflow
38
+
39
+ 1. Write JSX with `t('key', 'Default')` calls. Load `fimo/references/translations.md` for the rules (literal keys, naming, what NOT to translate).
40
+ 2. Run `fimo validate` to push new keys to the DB.
41
+ 3. Editors can now change copy in the Fimo admin without code changes.
42
+
43
+ For the helper signatures, hooks, and code patterns, **always** load `fimo/references/translations.md` before writing code that uses `t()`.
@@ -117,6 +117,8 @@ node <this-skill>/scripts/write-migration-report.mjs <project-root>
117
117
  - `references/wrap.md` - wrap mode: adopt a working JS codebase and wrap its
118
118
  content in place (default for source-backed JS sites).
119
119
  - `references/manifest.md` - canonical migration ledger schema.
120
+ - `references/pages.md` - the page registry (templates, schema bindings,
121
+ instances), Studio preview navigation, and the opt-in per-page SEO recipes.
120
122
  - `references/content.md` - CMS, entries, references, and source value rules.
121
123
  - `references/media.md` - media inventory, upload, dedupe, and replacement rules.
122
124
  - `references/forms.md` - form classification and Fimo replacement rules.
@@ -66,11 +66,22 @@ rebuild migrations:
66
66
  "wrappedMedia": 3,
67
67
  "wrappedRichText": 1
68
68
  },
69
+ "seo": { "decision": "external", "decidedBy": "default" },
69
70
  "planPath": ".fimo/migration/plan.json"
70
71
  }
71
72
  }
72
73
  ```
73
74
 
75
+ `wrap.seo` records who owns the site's SEO metadata — a USER decision, like
76
+ `wrap.dataSources`: `decision` is `"fimo"` (Fimo manages per-page SEO,
77
+ editable in the Studio) or `"external"` (the code / an external SEO tool
78
+ keeps ownership — the DEFAULT, nothing is ever wired on it). `decidedBy` is
79
+ `"default"` until the user actually answered; relay defaulted records and
80
+ rewrite them with `decidedBy: "user"`. Page bindings themselves live in the
81
+ page registry (`fimo-config.json#routes` →
82
+ `params: { "<param>": { "schema", "queryField", "displayField" } }`), not in
83
+ the manifest — see `references/pages.md`.
84
+
74
85
  `coverage` mirrors the final `fimo migrate wrap --check` output: `wrappable`
75
86
  must be 0 (or fully accounted for by `ignored` + `unresolvedItems`, each with a
76
87
  ledger entry). `seeded` counts must match what `fimo labels list` /
@@ -0,0 +1,95 @@
1
+ # Pages — the page registry, Preview navigation, and opt-in SEO
2
+
3
+ A **Page** is a navigable URL surface of the site, in two forms:
4
+
5
+ - **Page template** — what the app's routing declares: `/pricing` (static),
6
+ `/blog/:slug` (dynamic). The app's CODE is the source of truth for page
7
+ structure; `fimo migrate` discovers templates per framework and reconciles
8
+ them into the registry, `fimo-config.json#routes` (manual entries are
9
+ preserved). Param syntax is ALWAYS `:param` / catch-all `:param*` — never
10
+ `[param]`.
11
+ - **Page instance** — a concrete page: template × content entry
12
+ (`/blog/:slug` × `hello-world`). Instances are NEVER stored; they derive
13
+ from entries through the template's binding, so they cannot drift.
14
+
15
+ The registry entry shape:
16
+
17
+ ```json
18
+ "blog-post": {
19
+ "label": "Blog post",
20
+ "path": "/blog/:slug",
21
+ "params": { "slug": { "schema": "BlogPost", "queryField": "slug", "displayField": "title" } },
22
+ "seo": { "title": "{{blog-post.title}} — {{siteName}}" }
23
+ }
24
+ ```
25
+
26
+ - `params[<param>]` is the **binding**: the schema whose entries materialize
27
+ the instances. `queryField` = the entry field substituted into the URL
28
+ (entry `slug` metadata always qualifies); `displayField` = the label the
29
+ Studio's page picker shows.
30
+ - The Studio preview navigates ONLY what the registry describes: static pages
31
+ as-is, dynamic pages once bound. An unbound dynamic page navigates to a
32
+ literal `/blog/:slug` URL — broken by definition.
33
+ - Locale params (`:locale`, `:lang`) are never schema-bound; tooling
34
+ substitutes the project's default locale. (Studio locale switching for
35
+ dynamic pages is a platform follow-up, not your problem.)
36
+
37
+ ## Completing the bindings (agent workflow)
38
+
39
+ `fimo migrate` writes strong-evidence bindings itself (a rewired parent list
40
+ page; a wired CMS with a single page-builder type) and lists candidates +
41
+ unbound pages in the generated prompt. Your job:
42
+
43
+ 1. `npx fimo pages list` — see every template and its status
44
+ (`static | dynamic:bound | dynamic:unbound`).
45
+ 2. For each candidate/unbound page, find the schema whose entries the page
46
+ renders (read the page component's fetch). Apply with
47
+ `npx fimo pages set <id> --params '{"<param>":{"schema":"<Type>","queryField":"slug","displayField":"<field>"}}'`
48
+ — it validates the schema and both fields against the server. Never
49
+ hand-edit the JSON. If drafts are not adopted yet, run
50
+ `schemas adopt-drafts` FIRST and bind with the FINAL names.
51
+ 3. Add pages the scanner missed (SPA relative/wildcard routes):
52
+ `npx fimo pages set <id> --path /the/path --label "Label"`.
53
+ 4. Verify: `npx fimo pages list --expand` materializes every instance —
54
+ each page the site's nav links to must appear as a real URL.
55
+
56
+ ## SEO — a USER decision, never wired by default
57
+
58
+ Who owns SEO metadata is recorded in `.fimo/migration/manifest.json` →
59
+ `wrap.seo` (`{ "decision": "fimo" | "external", "decidedBy": "user" | "default" }`).
60
+ The default is ALWAYS `external` (nothing touched — the site's code or an
61
+ external SEO tool keeps ownership). When the prompt shows the decision as
62
+ DEFAULTED, relay it to the user once; only an explicit user "fimo" unlocks
63
+ wiring. Never migrate, edit, or inject metadata on the default.
64
+
65
+ When the user opted into Fimo-managed SEO, the engine is the neutral
66
+ `fimo/seo` export — pure resolution over the registry, no framework coupling:
67
+
68
+ ```ts
69
+ import { getFimoSeoForPath } from 'fimo/seo';
70
+ const seo = getFimoSeoForPath(pathname, { routeData: { 'blog-post': entry } });
71
+ // seo.title / seo.description / seo.image / seo.robots …
72
+ ```
73
+
74
+ Per-framework injection (map the resolved values to the framework's head API):
75
+
76
+ - **Next (app router)** — in the layout or page, `generateMetadata()` calls
77
+ `getFimoSeoForPath` with the resolved pathname (+ the entry as `routeData`
78
+ on dynamic pages) and returns `{ title, description, openGraph: {...} }`.
79
+ - **React Router** — already shipped: `loadFimoMetaData` / `buildFimoMeta` /
80
+ `fimoHtmlProps` from `fimo/react-router`.
81
+ - **Astro** — a head partial calling `getFimoSeoForPath(Astro.url.pathname)`
82
+ server-side and rendering the `<meta>` tags.
83
+ - **Nuxt** — `useSeoMeta()` fed from a server util wrapping `getFimoSeoForPath`.
84
+ - **SvelteKit** — resolve in `+layout.server` load, render in the root
85
+ layout's `<svelte:head>`.
86
+
87
+ Route-level `seo` blocks support `{{variables}}` resolved against
88
+ `routeData` — per-INSTANCE metadata from one template.
89
+
90
+ ## Sitemap / robots
91
+
92
+ Once bindings exist, `getFimoPaths` (from `fimo/paths`) materializes the full
93
+ instance list for prerender/sitemap use. It reads the API base URL from
94
+ `VITE_API_URL` (a plain env var or `.env` line works outside Vite). The
95
+ `fimo pages list --expand` output is the same list, CLI-side.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "bundled": true,
3
3
  "bundler": "esbuild",
4
- "bundledAt": "2026-07-24T17:59:36.047Z",
4
+ "bundledAt": "2026-07-25T21:00:19.368Z",
5
5
  "cliVersion": "0.8.0",
6
6
  "external": [
7
7
  "oxc-parser",