fimo 2.8.0-staging.8 → 2.8.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/assets/skills/fimo/SKILL.md +0 -1
- package/assets/skills/fimo-cli/SKILL.md +6 -7
- package/assets/skills/fimo-cli/references/assets.md +2 -0
- package/assets/skills/fimo-cli/references/deploy.md +1 -1
- package/assets/skills/fimo-cli/references/init.md +1 -2
- package/assets/skills/fimo-cli/references/integrations/firecrawl.md +1 -41
- package/assets/skills/fimo-cli/references/integrations.md +2 -38
- package/assets/skills/fimo-cli/references/knowledge.md +1 -1
- package/assets/skills/fimo-cli/references/mcp.md +5 -6
- package/assets/skills/fimo-cli/references/preview.md +1 -1
- package/assets/skills/fimo-cli/references/project-vars-and-secrets.md +0 -16
- package/assets/skills/fimo-cli/references/webhooks.md +0 -2
- package/assets/skills/fimo-code/SKILL.md +1 -0
- package/assets/skills/fimo-code/references/assets.md +5 -5
- package/assets/skills/fimo-code/references/design.md +1 -1
- package/assets/skills/fimo-code/references/ui.md +1 -1
- package/assets/skills/fimo-studio/SKILL.md +13 -7
- package/assets/skills/fimo-studio/references/ai-chat.md +15 -2
- package/assets/skills/fimo-studio/references/billing-and-credits.md +4 -6
- package/assets/skills/fimo-studio/references/cms.md +1 -1
- package/assets/skills/fimo-studio/references/command-palette.md +1 -2
- package/assets/skills/fimo-studio/references/connections.md +3 -4
- package/assets/skills/fimo-studio/references/custom-domains.md +2 -2
- package/assets/skills/fimo-studio/references/media-library.md +1 -2
- package/assets/skills/fimo-studio/references/preview-and-responsive.md +4 -9
- package/assets/skills/fimo-studio/references/project-creation.md +24 -6
- package/assets/skills/fimo-studio/references/publish-and-deploy.md +2 -2
- package/assets/skills/fimo-studio/references/schedules.md +2 -3
- package/assets/skills/fimo-studio/references/seo-settings.md +2 -2
- package/assets/skills/fimo-studio/references/sharing-and-collaboration.md +2 -3
- package/assets/skills/fimo-studio/references/visual-editing.md +1 -1
- package/assets/skills/fimo-studio/references/webhooks.md +7 -8
- package/dist/cli/bundle.json +2 -2
- package/dist/cli/index.js +153 -162
- package/dist/runtime/paths/get-fimo-paths.js +2 -24
- package/dist/runtime/project-layout.d.ts +0 -1
- package/dist/runtime/project-layout.js +1 -5
- package/dist/runtime/shared/local-runtime-env.js +2 -14
- package/dist/runtime/templates.d.ts +0 -1
- package/dist/runtime/templates.js +0 -3
- package/package.json +10 -10
- package/release.json +2 -2
- package/templates/astro/.fimo/config.json +0 -14
- package/templates/astro/AGENTS.md +2 -1
- package/templates/astro/_gitignore +3 -0
- package/templates/astro/package.json +3 -3
- package/templates/astro/src/layouts/Base.astro +0 -48
- package/templates/nextjs/AGENTS.md +1 -1
- package/templates/nextjs/_gitignore +3 -0
- package/templates/nextjs/package.json +5 -5
- package/templates/react-router/AGENTS.md +1 -1
- package/templates/react-router/_gitignore +3 -0
- package/templates/react-router/package.json +5 -5
- package/templates/sveltekit/AGENTS.md +1 -0
- package/templates/sveltekit/_gitignore +3 -0
- package/templates/sveltekit/package.json +3 -3
- package/templates/vite/AGENTS.md +2 -0
- package/templates/vite/_gitignore +3 -0
- package/templates/vite/package.json +5 -5
- package/assets/agent-templates/_default/GOAL.md +0 -21
- package/assets/agent-templates/_default/capabilities.yaml +0 -11
- package/assets/agent-templates/_default/config.yaml +0 -13
- package/assets/agent-templates/broken-link-sweeper/GOAL.md +0 -51
- package/assets/agent-templates/broken-link-sweeper/capabilities.yaml +0 -1
- package/assets/agent-templates/broken-link-sweeper/config.yaml +0 -13
- package/assets/agent-templates/content-translator/GOAL.md +0 -33
- package/assets/agent-templates/content-translator/capabilities.yaml +0 -5
- package/assets/agent-templates/content-translator/config.yaml +0 -12
- package/assets/agent-templates/content-translator/scripts/translate-entries.ts +0 -69
- package/assets/agent-templates/post-deploy-check/GOAL.md +0 -23
- package/assets/agent-templates/post-deploy-check/capabilities.yaml +0 -1
- package/assets/agent-templates/post-deploy-check/config.yaml +0 -10
- package/assets/agent-templates/post-deploy-check/scripts/check-logs.ts +0 -39
- package/assets/agent-templates/post-deploy-check/scripts/probe-routes.ts +0 -53
- package/assets/agent-templates/seo-audit/GOAL.md +0 -43
- package/assets/agent-templates/seo-audit/capabilities.yaml +0 -3
- package/assets/agent-templates/seo-audit/config.yaml +0 -14
- package/assets/agent-templates/seo-audit/references/report-template.md +0 -28
- package/assets/agent-templates/seo-audit/references/scoring-methodology.md +0 -30
- package/assets/agent-templates/seo-audit/scripts/analyze-html.ts +0 -82
- package/assets/agent-templates/seo-audit/scripts/fetch-page.ts +0 -39
- package/assets/agent-templates/seo-audit/scripts/list-routes.ts +0 -46
- package/assets/agent-templates/seo-audit/scripts/report.ts +0 -82
- package/assets/agent-templates/seo-audit/scripts/score.ts +0 -131
- package/assets/skills/fimo-cli/references/agents.md +0 -298
- package/assets/skills/fimo-studio/references/agents.md +0 -33
- package/assets/skills/fimo-studio/references/drawing-and-wireframing.md +0 -61
- package/templates/astro/src/components/ProjectOverview.astro +0 -45
- package/templates/astro/src/pages/[locale]/projects/[slug].astro +0 -38
- package/templates/astro/src/pages/[locale]/projects/index.astro +0 -19
- package/templates/astro/src/pages/projects/[slug].astro +0 -31
- package/templates/astro/src/pages/projects/index.astro +0 -13
- package/templates/astro/src/schemas/Project.json +0 -9
- package/templates/astro/src/schemas/Project.ts +0 -29
- package/templates/astro/src/schemas/Project.types.ts +0 -70
- package/templates/astro/src/schemas/SiteSettings.json +0 -10
- package/templates/astro/src/schemas/SiteSettings.ts +0 -31
- package/templates/astro/src/schemas/SiteSettings.types.ts +0 -69
- package/templates/react-router/src/pages/Privacy.tsx +0 -79
- package/templates/react-router/src/pages/Terms.tsx +0 -48
- package/templates/react-router/src/pages/legal/LegalPage.tsx +0 -42
- package/templates/welcome/manifest.json +0 -655
- package/templates/welcome/media/analytics-overview.avif +0 -0
- package/templates/welcome/media/autonomous-loop.avif +0 -0
- package/templates/welcome/media/editable-preview.avif +0 -0
- package/templates/welcome/media/media-library.avif +0 -0
- package/templates/welcome/media/slack-teams.avif +0 -0
- package/templates/welcome/template/.fimo/agents/welcome-guide/GOAL.md +0 -12
- package/templates/welcome/template/.fimo/agents/welcome-guide/capabilities.yaml +0 -3
- package/templates/welcome/template/.fimo/agents/welcome-guide/config.yaml +0 -8
- package/templates/welcome/template/.fimo/config.json +0 -52
- package/templates/welcome/template/src/font.css +0 -60
- package/templates/welcome/template/src/pages/ArticleDetail.tsx +0 -104
- package/templates/welcome/template/src/pages/Index.tsx +0 -36
- package/templates/welcome/template/src/pages/SiteContent.tsx +0 -674
- package/templates/welcome/template/src/pages/fallback-content.ts +0 -152
- package/templates/welcome/template/src/routes.ts +0 -9
- package/templates/welcome/template/src/schemas/Article.json +0 -34
- package/templates/welcome/template/src/schemas/Article.ts +0 -54
- package/templates/welcome/template/src/schemas/Article.types.ts +0 -93
- package/templates/welcome/template/src/schemas/Author.json +0 -17
- package/templates/welcome/template/src/schemas/Author.ts +0 -51
- package/templates/welcome/template/src/schemas/Author.types.ts +0 -54
- package/templates/welcome/template/src/schemas/Category.json +0 -17
- package/templates/welcome/template/src/schemas/Category.ts +0 -51
- package/templates/welcome/template/src/schemas/Category.types.ts +0 -54
- package/templates/welcome/template/src/schemas/HomePage.json +0 -110
- package/templates/welcome/template/src/schemas/HomePage.ts +0 -22
- package/templates/welcome/template/src/schemas/HomePage.types.ts +0 -116
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool: translate-entries
|
|
3
|
-
*
|
|
4
|
-
* Walk every default-locale CMS entry, find missing non-default-locale
|
|
5
|
-
* document rows, call the configured translation provider, and write back
|
|
6
|
-
* through the Fimo API. Skips fields marked `translate: false` in the schema.
|
|
7
|
-
*
|
|
8
|
-
* Provider is configured via env (FIMO_AGENT_TRANSLATE_PROVIDER) — the
|
|
9
|
-
* runtime threads project secrets through when the v1.x secrets API
|
|
10
|
-
* lands. For now this template uses the OpenAI fallback.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
interface Entry {
|
|
14
|
-
id: string;
|
|
15
|
-
documentId: string;
|
|
16
|
-
locale: string;
|
|
17
|
-
type: string;
|
|
18
|
-
data: Record<string, unknown>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface TranslateOptions {
|
|
22
|
-
fromLocale: string;
|
|
23
|
-
toLocales: string[];
|
|
24
|
-
api: { base: string; projectId: string; env: string };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function listMissing(_opts: TranslateOptions): Promise<Entry[]> {
|
|
28
|
-
// Stub — wired to the Fimo API by the runtime. Returns entries with
|
|
29
|
-
// at least one missing target-locale document row while the default-locale
|
|
30
|
-
// row is populated.
|
|
31
|
-
return [];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async function translate(text: string, _to: string): Promise<string> {
|
|
35
|
-
// Stub — call the configured provider. Returns the translated string.
|
|
36
|
-
return text;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function writeBack(_entry: Entry, _locale: string, _value: string): Promise<void> {
|
|
40
|
-
// Stub — create/update the target locale entry via
|
|
41
|
-
// `/api/tenant/:projectId/branches/:branch/entries/:type`.
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function main(): Promise<void> {
|
|
45
|
-
const fromLocale = process.env.FIMO_DEFAULT_LOCALE ?? 'en';
|
|
46
|
-
const toLocales = (process.env.FIMO_TARGET_LOCALES ?? '').split(',').filter(Boolean);
|
|
47
|
-
const api = {
|
|
48
|
-
base: process.env.FIMO_API_URL ?? 'http://localhost:3000',
|
|
49
|
-
projectId: process.env.FIMO_PROJECT_ID ?? '',
|
|
50
|
-
env: process.env.FIMO_ENV ?? 'main',
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const entries = await listMissing({ fromLocale, toLocales, api });
|
|
54
|
-
for (const entry of entries) {
|
|
55
|
-
for (const locale of toLocales) {
|
|
56
|
-
const sourceValue = typeof entry.data.title === 'string' ? entry.data.title : undefined;
|
|
57
|
-
if (!sourceValue) {
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
const translated = await translate(sourceValue, locale);
|
|
61
|
-
await writeBack(entry, locale, translated);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
main().catch((err) => {
|
|
67
|
-
console.error(err);
|
|
68
|
-
process.exit(1);
|
|
69
|
-
});
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# Post-deploy check
|
|
2
|
-
|
|
3
|
-
Run a quick health probe every time the project deploys, and post a
|
|
4
|
-
summary back to the deploy log. Triggered automatically by the
|
|
5
|
-
`deploy.succeeded` event.
|
|
6
|
-
|
|
7
|
-
## What to do
|
|
8
|
-
|
|
9
|
-
1. Read the routes the deploy declares as critical (`probe-routes.ts`).
|
|
10
|
-
2. For each one, fetch and assert that the response is 2xx and renders
|
|
11
|
-
the expected key element (a known DOM selector).
|
|
12
|
-
3. Read the last 5 minutes of production logs (`check-logs.ts`) and
|
|
13
|
-
surface any errors emitted during the deploy window.
|
|
14
|
-
4. Write a single-line summary to stdout that the deploy notification
|
|
15
|
-
includes verbatim. Examples:
|
|
16
|
-
- `✅ 12 routes pass · 0 errors`
|
|
17
|
-
- `❌ /pricing failed (500) · 3 errors in last 5min`
|
|
18
|
-
|
|
19
|
-
## What NOT to do
|
|
20
|
-
|
|
21
|
-
- Don't roll back the deploy — that's a separate human-in-the-loop
|
|
22
|
-
decision; this agent only reports.
|
|
23
|
-
- Don't write to CMS or files. Read-only probes plus log reads.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
- fimo:logs:read
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool: check-logs
|
|
3
|
-
*
|
|
4
|
-
* Query the project's production log stream for the last N minutes and
|
|
5
|
-
* count errors. Used by the post-deploy-check summary line.
|
|
6
|
-
*
|
|
7
|
-
* Inputs (argv):
|
|
8
|
-
* 1. Minutes to look back (default 5)
|
|
9
|
-
*
|
|
10
|
-
* Outputs (stdout JSON): { errors: number; first?: { ts: string; message: string } }
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
async function main(): Promise<void> {
|
|
14
|
-
const minutes = Number(process.argv[2] ?? '5');
|
|
15
|
-
const projectId = process.env.FIMO_PROJECT_ID ?? '';
|
|
16
|
-
const env = process.env.FIMO_ENV ?? 'main';
|
|
17
|
-
const base = process.env.FIMO_API_URL ?? 'http://localhost:3000';
|
|
18
|
-
|
|
19
|
-
const since = new Date(Date.now() - minutes * 60_000).toISOString();
|
|
20
|
-
const url = `${base}/api/management/projects/${projectId}/branches/${encodeURIComponent(env)}/logs?since=${encodeURIComponent(since)}&level=error`;
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
const res = await fetch(url, { credentials: 'include' });
|
|
24
|
-
if (!res.ok) {
|
|
25
|
-
process.stdout.write(JSON.stringify({ errors: 0, note: `log endpoint returned ${res.status}` }));
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
const payload = (await res.json()) as { data?: { entries?: Array<{ ts: string; message: string }> } };
|
|
29
|
-
const entries = payload.data?.entries ?? [];
|
|
30
|
-
process.stdout.write(JSON.stringify({ errors: entries.length, first: entries[0] }));
|
|
31
|
-
} catch (err) {
|
|
32
|
-
process.stdout.write(JSON.stringify({ errors: 0, note: `log fetch failed: ${(err as Error).message}` }));
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
main().catch((err) => {
|
|
37
|
-
console.error(err);
|
|
38
|
-
process.exit(1);
|
|
39
|
-
});
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool: probe-routes
|
|
3
|
-
*
|
|
4
|
-
* Fetch a list of critical routes and assert that each one responds
|
|
5
|
-
* with a 2xx. Optionally checks that a known selector is present.
|
|
6
|
-
*
|
|
7
|
-
* Inputs (argv): JSON array of `{ url, selector? }`
|
|
8
|
-
* Outputs (stdout JSON): { ok: number; failed: { url: string; status: number; reason: string }[] }
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
interface Probe {
|
|
12
|
-
url: string;
|
|
13
|
-
selector?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface Result {
|
|
17
|
-
ok: number;
|
|
18
|
-
failed: { url: string; status: number; reason: string }[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function probe(p: Probe): Promise<{ url: string; status: number; reason: string } | null> {
|
|
22
|
-
const res = await fetch(p.url, { redirect: 'manual' });
|
|
23
|
-
if (res.status < 200 || res.status >= 300) {
|
|
24
|
-
return { url: p.url, status: res.status, reason: `non-2xx status ${res.status}` };
|
|
25
|
-
}
|
|
26
|
-
if (p.selector) {
|
|
27
|
-
const body = await res.text();
|
|
28
|
-
if (!body.includes(p.selector)) {
|
|
29
|
-
return { url: p.url, status: res.status, reason: `expected selector "${p.selector}" not found` };
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function main(): Promise<void> {
|
|
36
|
-
const raw = process.argv[2] ?? '[]';
|
|
37
|
-
const probes = JSON.parse(raw) as Probe[];
|
|
38
|
-
const result: Result = { ok: 0, failed: [] };
|
|
39
|
-
for (const p of probes) {
|
|
40
|
-
const failure = await probe(p);
|
|
41
|
-
if (failure) {
|
|
42
|
-
result.failed.push(failure);
|
|
43
|
-
} else {
|
|
44
|
-
result.ok += 1;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
process.stdout.write(JSON.stringify(result));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
main().catch((err) => {
|
|
51
|
-
console.error(err);
|
|
52
|
-
process.exit(1);
|
|
53
|
-
});
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# SEO audit
|
|
2
|
-
|
|
3
|
-
Audit the project's pages for SEO health and deliver the findings as your session report.
|
|
4
|
-
|
|
5
|
-
## Inputs
|
|
6
|
-
|
|
7
|
-
- The project's published routes (via `scripts/list-routes.ts`)
|
|
8
|
-
- Each page's HTML + response headers (via `scripts/fetch-page.ts`)
|
|
9
|
-
- The current analytics data (read-only, via `fimo:analytics:read`)
|
|
10
|
-
|
|
11
|
-
## What to check (per page)
|
|
12
|
-
|
|
13
|
-
1. **Title tag** — present, 30–60 chars, unique across routes
|
|
14
|
-
2. **Meta description** — present, 50–160 chars, unique
|
|
15
|
-
3. **Open Graph** — `og:title`, `og:description`, `og:image`, `og:url`
|
|
16
|
-
4. **Schema.org** — at minimum `WebPage`; `Article` on blog posts;
|
|
17
|
-
`BreadcrumbList` on nested routes
|
|
18
|
-
5. **Headings** — exactly one `h1`; `h2`/`h3` nest correctly
|
|
19
|
-
6. **Canonical URL** — `<link rel="canonical">` present and self-referential
|
|
20
|
-
7. **Robots / X-Robots-Tag** — not `noindex` on routes that should be indexed
|
|
21
|
-
8. **Sitemap** — every route is present in `/sitemap.xml`
|
|
22
|
-
|
|
23
|
-
## What to produce
|
|
24
|
-
|
|
25
|
-
Deliver the audit as your final session report — Fimo renders your last
|
|
26
|
-
message as the report, so you do not have (and do not need) write access.
|
|
27
|
-
Structure it as:
|
|
28
|
-
|
|
29
|
-
- An executive summary (score / 100, top 3 issues)
|
|
30
|
-
- A per-page table with status chips (✅ / ⚠️ / ❌)
|
|
31
|
-
- A "fix recipes" appendix linking each failure to the smallest change
|
|
32
|
-
that would resolve it
|
|
33
|
-
- A diff-friendly machine-readable footer (`<!-- agent-summary: ... -->`)
|
|
34
|
-
so subsequent runs can show the trend
|
|
35
|
-
|
|
36
|
-
## What NOT to do
|
|
37
|
-
|
|
38
|
-
- This audit is strictly read-only. Don't attempt to write files, create a
|
|
39
|
-
report file, or commit — and don't mention capabilities or write access in
|
|
40
|
-
the report itself. The report is your final message, nothing more.
|
|
41
|
-
- Don't fetch external URLs other than the project's own domain.
|
|
42
|
-
- Don't include traffic / conversion data in the report — it lives
|
|
43
|
-
elsewhere in the Fimo dashboard.
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
version: 1
|
|
2
|
-
description: "Audit the project's pages for SEO health and write a markdown report."
|
|
3
|
-
model: openai/gpt-5.6-luna
|
|
4
|
-
# Read from main env. Switch to prod when running the audit against the
|
|
5
|
-
# live site (M13 keeps data_sources here so M16's sandbox adapter can
|
|
6
|
-
# bind the right Fimo API base URL).
|
|
7
|
-
data_sources:
|
|
8
|
-
- main
|
|
9
|
-
|
|
10
|
-
# Manual-only in v1. Add a schedule trigger when you're ready to run
|
|
11
|
-
# this nightly — make sure .fimo/config.json has
|
|
12
|
-
# `agents.notifications.email_recipients` set before activating.
|
|
13
|
-
triggers:
|
|
14
|
-
- type: manual
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# Report template
|
|
2
|
-
|
|
3
|
-
The agent renders `reports/seo-audit-<YYYY-MM-DD>.md` using the shape:
|
|
4
|
-
|
|
5
|
-
```markdown
|
|
6
|
-
# SEO audit · 2026-05-26
|
|
7
|
-
|
|
8
|
-
**Overall score:** 78 / 100
|
|
9
|
-
|
|
10
|
-
## Per-page summary
|
|
11
|
-
|
|
12
|
-
| Path | Score | Status |
|
|
13
|
-
| -------- | ----- | ------ |
|
|
14
|
-
| `/` | 95 | OK |
|
|
15
|
-
| `/about` | 70 | WARN |
|
|
16
|
-
|
|
17
|
-
## Findings
|
|
18
|
-
|
|
19
|
-
### `/about`
|
|
20
|
-
|
|
21
|
-
- ⚠️ **title-length** — Title length 22 chars — recommend 30–60
|
|
22
|
-
- ❌ **h1-present** — No <h1> on the page
|
|
23
|
-
|
|
24
|
-
<!-- agent-summary: { "score": 78, "pages": 12 } -->
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
The `<!-- agent-summary -->` comment is parsed by subsequent runs to
|
|
28
|
-
plot a trend; keep its shape stable.
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# Scoring methodology
|
|
2
|
-
|
|
3
|
-
A page starts at 100 points and loses:
|
|
4
|
-
|
|
5
|
-
- **-15** per `fail` finding (missing title, missing `<h1>`, `noindex` on a public page, etc.)
|
|
6
|
-
- **-5** per `warn` finding (length out of range, missing OG tag, no canonical, etc.)
|
|
7
|
-
|
|
8
|
-
The overall project score is the arithmetic mean of per-page scores.
|
|
9
|
-
|
|
10
|
-
## Severity guide
|
|
11
|
-
|
|
12
|
-
| Rule | Severity | Why |
|
|
13
|
-
| ---------------- | --------- | -------------------------------------------------------------------- |
|
|
14
|
-
| title-present | fail | Without a title, search snippets fall back to the URL — costly |
|
|
15
|
-
| title-length | warn | Outside 30–60 chars, Google truncates or pads — still ranks |
|
|
16
|
-
| description-\* | warn/fail | Less impact on rank but huge impact on CTR |
|
|
17
|
-
| og-{title,image} | warn | Affects social shares only; no SEO rank impact |
|
|
18
|
-
| h1-present | fail | Page has no semantic root — both screen-readers and search hurt |
|
|
19
|
-
| h1-single | warn | Multiple h1s — accessibility lint, not a ranking penalty |
|
|
20
|
-
| canonical | warn | Self-referential canonical is best practice; rarely required |
|
|
21
|
-
| robots-noindex | fail | Page explicitly excluded from index — review and remove if a mistake |
|
|
22
|
-
| schema-org | warn | Missing rich-result eligibility — won't hurt rank but loses CTR |
|
|
23
|
-
|
|
24
|
-
## How the agent should act on findings
|
|
25
|
-
|
|
26
|
-
Don't write to project files in v1. Instead, the report's "fix recipes"
|
|
27
|
-
appendix should link to the file the agent suspects is responsible (e.g.
|
|
28
|
-
"fix this in `src/routes/[slug].tsx`'s `meta` export"). A follow-up
|
|
29
|
-
agent — the one that resolves recipes into actual PRs — is the
|
|
30
|
-
appropriate writer.
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool: analyze-html
|
|
3
|
-
*
|
|
4
|
-
* Extract SEO signals from HTML — title, meta description, OG tags,
|
|
5
|
-
* schema.org JSON-LD, heading structure, canonical, robots directive.
|
|
6
|
-
* Returns a structured shape `score.ts` consumes.
|
|
7
|
-
*
|
|
8
|
-
* Inputs (stdin): HTML body
|
|
9
|
-
* Outputs (stdout JSON): SEOSignals
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
interface SEOSignals {
|
|
13
|
-
title: string | null;
|
|
14
|
-
description: string | null;
|
|
15
|
-
canonical: string | null;
|
|
16
|
-
robots: string | null;
|
|
17
|
-
openGraph: Record<string, string>;
|
|
18
|
-
schemaOrg: unknown[];
|
|
19
|
-
headings: { h1: string[]; h2: string[]; h3: string[] };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function getMatch(html: string, regex: RegExp): string | null {
|
|
23
|
-
const m = html.match(regex);
|
|
24
|
-
return m && m[1] ? m[1].trim() : null;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function getAllMatches(html: string, regex: RegExp): string[] {
|
|
28
|
-
const out: string[] = [];
|
|
29
|
-
for (const m of html.matchAll(regex)) {
|
|
30
|
-
if (m[1]) {
|
|
31
|
-
out.push(m[1].trim());
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return out;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function readStdin(): Promise<string> {
|
|
38
|
-
const chunks: Buffer[] = [];
|
|
39
|
-
for await (const chunk of process.stdin) {
|
|
40
|
-
chunks.push(Buffer.from(chunk));
|
|
41
|
-
}
|
|
42
|
-
return Buffer.concat(chunks).toString('utf8');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async function main(): Promise<void> {
|
|
46
|
-
const html = await readStdin();
|
|
47
|
-
|
|
48
|
-
const openGraph: Record<string, string> = {};
|
|
49
|
-
for (const m of html.matchAll(/<meta\s+property=["']og:([^"']+)["']\s+content=["']([^"']*)["']/gi)) {
|
|
50
|
-
openGraph[m[1]!] = m[2] ?? '';
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const schemaOrg: unknown[] = [];
|
|
54
|
-
for (const m of html.matchAll(/<script\s+type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi)) {
|
|
55
|
-
try {
|
|
56
|
-
schemaOrg.push(JSON.parse(m[1]!.trim()));
|
|
57
|
-
} catch {
|
|
58
|
-
// skip invalid JSON-LD blocks
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const signals: SEOSignals = {
|
|
63
|
-
title: getMatch(html, /<title>([\s\S]*?)<\/title>/i),
|
|
64
|
-
description: getMatch(html, /<meta\s+name=["']description["']\s+content=["']([^"']*)["']/i),
|
|
65
|
-
canonical: getMatch(html, /<link\s+rel=["']canonical["']\s+href=["']([^"']*)["']/i),
|
|
66
|
-
robots: getMatch(html, /<meta\s+name=["']robots["']\s+content=["']([^"']*)["']/i),
|
|
67
|
-
openGraph,
|
|
68
|
-
schemaOrg,
|
|
69
|
-
headings: {
|
|
70
|
-
h1: getAllMatches(html, /<h1[^>]*>([\s\S]*?)<\/h1>/gi).map((s) => s.replace(/<[^>]+>/g, '').trim()),
|
|
71
|
-
h2: getAllMatches(html, /<h2[^>]*>([\s\S]*?)<\/h2>/gi).map((s) => s.replace(/<[^>]+>/g, '').trim()),
|
|
72
|
-
h3: getAllMatches(html, /<h3[^>]*>([\s\S]*?)<\/h3>/gi).map((s) => s.replace(/<[^>]+>/g, '').trim()),
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
process.stdout.write(JSON.stringify(signals, null, 2));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
main().catch((err) => {
|
|
80
|
-
console.error(err);
|
|
81
|
-
process.exit(1);
|
|
82
|
-
});
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool: fetch-page
|
|
3
|
-
*
|
|
4
|
-
* Fetch HTML + headers for a given URL. Used by the audit to walk every
|
|
5
|
-
* route returned by `list-routes` and feed the bytes to `analyze-html`.
|
|
6
|
-
*
|
|
7
|
-
* Inputs (argv):
|
|
8
|
-
* 1. The URL to fetch (must be the project's own domain)
|
|
9
|
-
*
|
|
10
|
-
* Outputs (stdout JSON):
|
|
11
|
-
* {
|
|
12
|
-
* "url": "...",
|
|
13
|
-
* "status": 200,
|
|
14
|
-
* "headers": { "content-type": "text/html; charset=utf-8", ... },
|
|
15
|
-
* "body": "<!doctype html>..."
|
|
16
|
-
* }
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
async function main(): Promise<void> {
|
|
20
|
-
const url = process.argv[2];
|
|
21
|
-
if (!url) {
|
|
22
|
-
console.error('Usage: fetch-page <url>');
|
|
23
|
-
process.exit(1);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const res = await fetch(url, { redirect: 'manual' });
|
|
27
|
-
const headers: Record<string, string> = {};
|
|
28
|
-
res.headers.forEach((value, key) => {
|
|
29
|
-
headers[key] = value;
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const body = await res.text();
|
|
33
|
-
process.stdout.write(JSON.stringify({ url, status: res.status, headers, body }, null, 2));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
main().catch((err) => {
|
|
37
|
-
console.error(err);
|
|
38
|
-
process.exit(1);
|
|
39
|
-
});
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool: list-routes
|
|
3
|
-
*
|
|
4
|
-
* Enumerate every public route in the project via the Fimo management
|
|
5
|
-
* API. Returns paths the audit walks one-by-one through `fetch-page`.
|
|
6
|
-
*
|
|
7
|
-
* Inputs (branch):
|
|
8
|
-
* - FIMO_PROJECT_ID — provided by the runtime (M16 sandbox adapter)
|
|
9
|
-
* - FIMO_ENV — provided by the runtime (legacy runtime selector)
|
|
10
|
-
* - FIMO_API_URL — provided by the runtime
|
|
11
|
-
*
|
|
12
|
-
* Outputs (stdout JSON):
|
|
13
|
-
* { "routes": [{ "path": "/", "label": "Home" }, ...] }
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
interface Route {
|
|
17
|
-
path: string;
|
|
18
|
-
label: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function main(): Promise<void> {
|
|
22
|
-
const projectId = process.env.FIMO_PROJECT_ID;
|
|
23
|
-
const env = process.env.FIMO_ENV ?? 'main';
|
|
24
|
-
const base = process.env.FIMO_API_URL ?? 'http://localhost:3000';
|
|
25
|
-
|
|
26
|
-
if (!projectId) {
|
|
27
|
-
console.error('FIMO_PROJECT_ID is required');
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const url = `${base}/api/management/projects/${projectId}/branches/${encodeURIComponent(env)}/routes`;
|
|
32
|
-
const res = await fetch(url, { credentials: 'include' });
|
|
33
|
-
if (!res.ok) {
|
|
34
|
-
console.error(`Failed to list routes: ${res.status} ${res.statusText}`);
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const payload = (await res.json()) as { data?: { routes?: Record<string, Route> } };
|
|
39
|
-
const routes = Object.values(payload.data?.routes ?? {});
|
|
40
|
-
process.stdout.write(JSON.stringify({ routes }, null, 2));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
main().catch((err) => {
|
|
44
|
-
console.error(err);
|
|
45
|
-
process.exit(1);
|
|
46
|
-
});
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool: report
|
|
3
|
-
*
|
|
4
|
-
* Render a markdown report from the per-page audit results.
|
|
5
|
-
*
|
|
6
|
-
* Inputs (stdin JSON): { score: number, pages: PageReport[] }
|
|
7
|
-
* Outputs (stdout): markdown
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
interface Finding {
|
|
11
|
-
rule: string;
|
|
12
|
-
severity: 'ok' | 'warn' | 'fail';
|
|
13
|
-
message: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface PageReport {
|
|
17
|
-
path: string;
|
|
18
|
-
score: number;
|
|
19
|
-
findings: Finding[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function statusChip(score: number): string {
|
|
23
|
-
if (score >= 80) {
|
|
24
|
-
return 'OK';
|
|
25
|
-
}
|
|
26
|
-
if (score >= 60) {
|
|
27
|
-
return 'WARN';
|
|
28
|
-
}
|
|
29
|
-
return 'FAIL';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function readStdin(): Promise<string> {
|
|
33
|
-
const chunks: Buffer[] = [];
|
|
34
|
-
for await (const chunk of process.stdin) {
|
|
35
|
-
chunks.push(Buffer.from(chunk));
|
|
36
|
-
}
|
|
37
|
-
return Buffer.concat(chunks).toString('utf8');
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function main(): Promise<void> {
|
|
41
|
-
const text = await readStdin();
|
|
42
|
-
const data = JSON.parse(text) as { score: number; pages: PageReport[] };
|
|
43
|
-
|
|
44
|
-
const date = new Date().toISOString().slice(0, 10);
|
|
45
|
-
const lines: string[] = [];
|
|
46
|
-
lines.push(`# SEO audit · ${date}`);
|
|
47
|
-
lines.push('');
|
|
48
|
-
lines.push(`**Overall score:** ${data.score} / 100`);
|
|
49
|
-
lines.push('');
|
|
50
|
-
lines.push('## Per-page summary');
|
|
51
|
-
lines.push('');
|
|
52
|
-
lines.push('| Path | Score | Status |');
|
|
53
|
-
lines.push('|------|-------|--------|');
|
|
54
|
-
for (const page of data.pages) {
|
|
55
|
-
lines.push(`| \`${page.path}\` | ${page.score} | ${statusChip(page.score)} |`);
|
|
56
|
-
}
|
|
57
|
-
lines.push('');
|
|
58
|
-
lines.push('## Findings');
|
|
59
|
-
lines.push('');
|
|
60
|
-
for (const page of data.pages) {
|
|
61
|
-
const failures = page.findings.filter((f) => f.severity !== 'ok');
|
|
62
|
-
if (failures.length === 0) {
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
lines.push(`### \`${page.path}\``);
|
|
66
|
-
lines.push('');
|
|
67
|
-
for (const finding of failures) {
|
|
68
|
-
const sev = finding.severity === 'fail' ? '❌' : '⚠️';
|
|
69
|
-
lines.push(`- ${sev} **${finding.rule}** — ${finding.message}`);
|
|
70
|
-
}
|
|
71
|
-
lines.push('');
|
|
72
|
-
}
|
|
73
|
-
lines.push('');
|
|
74
|
-
lines.push(`<!-- agent-summary: { "score": ${data.score}, "pages": ${data.pages.length} } -->`);
|
|
75
|
-
|
|
76
|
-
process.stdout.write(lines.join('\n'));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
main().catch((err) => {
|
|
80
|
-
console.error(err);
|
|
81
|
-
process.exit(1);
|
|
82
|
-
});
|