diff --git a/.github/workflows/deploy-landing.yml b/.github/workflows/deploy-landing.yml new file mode 100644 index 0000000..3959c87 --- /dev/null +++ b/.github/workflows/deploy-landing.yml @@ -0,0 +1,75 @@ +name: Deploy landing + +on: + push: + branches: [main, develop] + paths: + - "packages/mimir-landing/**" + - ".github/workflows/deploy-landing.yml" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: deploy-landing-${{ github.ref }} + cancel-in-progress: true + +jobs: + deploy: + name: Build and deploy landing + runs-on: ubuntu-latest + environment: + name: ${{ github.ref == 'refs/heads/main' && 'landing-production' || 'landing-staging' }} + url: ${{ github.ref == 'refs/heads/main' && 'https://mimir.jcode.works' || 'https://staging.mimir.jcode.works' }} + defaults: + run: + working-directory: packages/mimir-landing + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install mise + run: | + curl https://mise.run | sh + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + echo "$HOME/.local/share/mise/shims" >> "$GITHUB_PATH" + + - name: Install pinned Node via mise + run: mise install + working-directory: . + + - name: Set up pnpm + run: | + corepack enable + corepack prepare pnpm@11.9.0 --activate + + - name: Install dependencies + run: pnpm install --frozen-lockfile + working-directory: . + + - name: Build (production) + if: github.ref == 'refs/heads/main' + run: pnpm build + env: + PUBLIC_MIMIR_LANDING_URL: https://mimir.jcode.works + + - name: Deploy (production) + if: github.ref == 'refs/heads/main' + run: pnpm exec wrangler deploy + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + + - name: Build (staging, not indexed) + if: github.ref == 'refs/heads/develop' + run: pnpm build + env: + PUBLIC_MIMIR_LANDING_URL: https://staging.mimir.jcode.works + + - name: Deploy (staging) + if: github.ref == 'refs/heads/develop' + run: pnpm exec wrangler deploy --env staging + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} diff --git a/AGENTS.md b/AGENTS.md index 5ea8ac2..c02f162 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,10 +64,6 @@ `excludeFolders`/`excludeFiles` in sync with new generated-output or private-data directories. Registering the repo on context7.com (so it resolves through `resolve-library-id`) is a manual step on their site; these files only prepare the repo for that step. -- The bundled `mimir` skill is directly installable from this monorepo's nested path with the - [skills.sh](https://skills.sh) CLI (`npx skills add /tree/main/packages/mimir-core/skills/mimir`), - since that CLI supports a direct subdirectory path. If `packages/mimir-core/skills/` moves, update - the command documented in the README's "Agent Skills And MCP" section. - `packages/mimir-ui` is the shared UI/style foundation adapted from the WorkoutGen landing/UI approach. It provides the common Tailwind theme and React primitives for both the landing and the Tauri app; do not import WorkoutGen product copy, assets, analytics, or secrets. @@ -179,12 +175,21 @@ with `mise.toml` by hand. pnpm stays pinned via Corepack through `packageManager` in `package.json`, not duplicated in `mise.toml`. Keep mise scoped to toolchain-version pinning — it is not a package manager or task runner here, so don't mirror `package.json` scripts as mise - tasks; that would just create a second source of truth for no benefit. + tasks and don't wrap launch scripts (`dev:app`, `dev:landing`, `example`) in `mise exec`; that + would create a second source of truth and break the "plain pnpm works without mise" onboarding + path. For local dev, contributors activate mise in their shell (`mise activate`) so the pinned + Node/Rust land on `PATH` via shims and every `pnpm` script uses the CI toolchain automatically. - Keep Mimir core free of Ollama. `embeddingProvider: "local-hash"` supports ingestion, search, MCP, and cited retrieval without a model server, but it must not be described as equivalent to semantic retrieval. `embeddingProvider: "transformers"` is the optional semantic embedding path. - Keep `packages/mimir-core/examples/sovereign-rag-demo` synthetic and safe to commit. It exists for package/user testing only; never place real confidential documents there. +- `packages/mimir-core/examples/library-api-demo` is the local library-API smoke (`pnpm example`). It + `import`s `@jcode.labs/mimir` via Node self-referencing so it always exercises the local + `packages/mimir-core/dist` build, never the npm-published package, and it reuses the + `sovereign-rag-demo` synthetic corpus rather than adding a second one. Testing local changes must + use this local build (or `node packages/mimir-core/dist/cli.js`), not `npx mimir`, which would + resolve the released npm version. - Use Context7 before changing dependencies or public APIs that rely on external libraries. - Run `pnpm validate` before opening a release pull request or publishing. It covers Biome, dependency security audit, TypeScript, Vitest, build output, production CLI/MCP smoke diff --git a/CLAUDE.md b/CLAUDE.md index 326bab8..5d3008f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,6 +23,7 @@ pnpm build # builds UI, app frontend, landing, TTS, then Mimir Core pnpm check # typecheck UI/app/landing/TTS/core pnpm dev:app # run the Vite frontend for the Tauri shell pnpm dev:landing # run the Astro landing locally +pnpm example # build core + run the library-API smoke against the local build (examples/library-api-demo) pnpm lint # Biome CI (format + lint check, no writes) pnpm lint:fix # Biome auto-fix pnpm format # Biome format --write diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..e8dcb71 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,133 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances of + any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, + without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +contact@jcode.works. + +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][mozilla coc]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][faq]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[mozilla coc]: https://github.com/mozilla/diversity +[faq]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d56fd0a..3146584 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,9 +16,19 @@ pnpm validate `pnpm bootstrap` runs `mise install && pnpm install`. Without mise, any Node.js 20+ and pnpm install works too — just run `pnpm install` directly. +Activate mise in your shell (`mise activate`, per the +[mise docs](https://mise.jdx.dev/getting-started.html)) so that entering this repository puts the +pinned Node (and Rust) on your `PATH` automatically. Then `pnpm dev:app`, `pnpm dev:landing`, and +`pnpm example` run on the same toolchain as CI without any per-script wiring — mise stays a +version pinner, not a task runner (see `AGENTS.md`). + `pnpm validate` runs Biome, a dependency security audit, TypeScript, Vitest, the production CLI/MCP smoke test, and npm package metadata checks. +To smoke-test the library API against your local build while developing Mimir Core, run +`pnpm example` (see +[`packages/mimir-core/examples/library-api-demo`](./packages/mimir-core/examples/library-api-demo)). + Run the security audit alone with: ```bash diff --git a/README.md b/README.md index d6475de..de6c5ac 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,20 @@ [![npm downloads](https://img.shields.io/npm/dm/@jcode.labs/mimir?label=downloads%2Fmonth)](https://www.npmjs.com/package/@jcode.labs/mimir) [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/jcode-works/jcode-mimir/blob/main/LICENSE) -Open-source, sovereign local RAG for confidential datasets and AI agents. +Open-source local RAG library, CLI, and MCP server. Mimir indexes your specs, docs, and code +locally and gives your AI agents only the useful cited passages, over MCP, without burning tokens on +your whole repo. -Mimir provides a TypeScript CLI, library, MCP server, and portable agent skills that can be -installed in any Node.js repository. It indexes local files from the target repository, stores -vectors locally with LanceDB, and can use either built-in local-hash retrieval or optional -Transformers.js semantic embeddings. +Build from your requirements, keep everything on your machine, and let Claude, Codex, Kimi, +OpenCode, Cline, or any MCP client answer from your real sources. Mimir installs into any Node.js +repository, stores vectors locally with LanceDB, and runs fully offline by default, with built-in +local-hash retrieval or optional Transformers.js semantic embeddings. Mimir Core returns cited retrieval context. Answer synthesis belongs to the AI agent, LLM, or local -model runtime you choose around it. +model runtime you choose around it, so every answer stays grounded in your real evidence. Created by Jean-Baptiste Thery and published under the JCode Labs npm scope. -Built by Jean-Baptiste Thery, freelance full-stack/AI tooling engineer at JCode Labs. - ## Developer Use Cases Mimir is designed for agent-assisted development when the useful context is local, private, and @@ -207,8 +207,8 @@ site, but public deployment remains a separate release action. - Build a local RAG knowledge base inside any repository. - Analyze confidential datasets while keeping raw files and generated indexes local. -- Give Claude, Codex, Cursor, internal assistants, or other MCP-compatible tools the same private - retrieval layer. +- Give Claude, Codex, Kimi, OpenCode, Cline, internal assistants, or other MCP-compatible tools the + same private retrieval layer. - Retrieve grounded local evidence through CLI, library calls, MCP tools, or bundled agent skills. - Optionally create listenable MP3/WAV summaries or cited Markdown reports with bundled skills. - Prepare legal-dossier summaries, chronologies, clause reviews, and professional-review handoffs @@ -572,13 +572,6 @@ npx mimir install-skill --agents claude,codex --mcp-command ./scripts/serve-mcp. npx mimir install-agent --agents claude,codex,kimi,opencode,cline ``` -The bundled skill is also directly installable from this repository with the -[skills.sh](https://skills.sh) CLI, without adding Mimir as a dependency first: - -```bash -npx skills add https://github.com/jcode-works/jcode-mimir/tree/main/packages/mimir-core/skills/mimir -``` - Main agent examples: ```bash @@ -993,14 +986,21 @@ Removing more dependencies is possible only by dropping features or replacing th internal implementations. The current low-friction path is dependency-light at runtime for users who choose `local-hash`, while preserving richer parsing, MCP support, and optional semantic embeddings. -## Example Test Workspace +## Example Test Workspaces + +This repository ships two synthetic examples under +[`packages/mimir-core/examples`](./packages/mimir-core/examples). Both use the default local-hash +retrieval mode, so they run without downloading an embedding or chat model, and neither uses private +documents. -This repository includes a synthetic example under -[`packages/mimir-core/examples/sovereign-rag-demo`](./packages/mimir-core/examples/sovereign-rag-demo). It can -be used to test ingestion, retrieval, `security-audit`, and custom text extensions without using -private documents. +> Testing local changes: use the repository's own build, not `npx`. Inside this repo `npx mimir` +> resolves to the **published** npm package, not your working copy — so it would not exercise your +> local edits. The examples below run the local `dist/` build instead. -From a local checkout: +### CLI workspace (`sovereign-rag-demo`) + +[`sovereign-rag-demo`](./packages/mimir-core/examples/sovereign-rag-demo) drives the **CLI** to test +ingestion, retrieval, `security-audit`, and custom text extensions. ```bash pnpm build @@ -1013,8 +1013,19 @@ node ../../dist/cli.js evaluate --golden golden-queries.json --fail-under 1 node ../../dist/cli.js audit ``` -The example uses the default local-hash retrieval mode, so it can run without downloading an -embedding or chat model. +### Library API demo (`library-api-demo`) + +[`library-api-demo`](./packages/mimir-core/examples/library-api-demo) exercises the **library** API +the way an external consumer would `import` it, but Node self-referencing resolves +`@jcode.labs/mimir` to the local build, never npm. It is the fast inner loop when developing Mimir +Core itself: + +```bash +pnpm example +``` + +That builds Mimir Core, then runs `ingest -> search -> ask -> audit` through the public API against +the reused synthetic corpus. ## Development And Release diff --git a/llms.txt b/llms.txt index 76dd2db..def594d 100644 --- a/llms.txt +++ b/llms.txt @@ -1,8 +1,9 @@ # Mimir -> Open-source, sovereign local RAG for confidential datasets and AI agents: a TypeScript CLI, -> library, MCP server, and portable agent skills that index local files, store vectors with -> LanceDB, and return cited retrieval context to the calling coding agent. +> Open-source local RAG library, CLI, and MCP server. Mimir indexes your specs, docs, and code +> locally and gives your AI agents only the useful cited passages, over MCP, without burning tokens +> on your whole repo. It stays sovereign and offline by default, storing vectors locally with +> LanceDB; answer synthesis stays with the calling agent or model. Mimir Core (`@jcode.labs/mimir`, npm) ingests local files from the target repository, redacts secrets and PII before anything is embedded, and serves cited passages through a CLI (`mimir ...`), diff --git a/package.json b/package.json index 08ed80f..e9c16e5 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "commitlint": "commitlint --from=HEAD~1 --to=HEAD --verbose", "dev:app": "pnpm --filter @jcode.labs/mimir-app dev", "dev:landing": "pnpm --filter @jcode.labs/mimir-landing dev", + "example": "pnpm --filter @jcode.labs/mimir build && node packages/mimir-core/examples/library-api-demo/run.mjs", "format": "biome format --write .", "lint": "biome ci .", "lint:fix": "biome check --write .", diff --git a/packages/mimir-core/README.md b/packages/mimir-core/README.md index 6b810ee..de5ec3c 100644 --- a/packages/mimir-core/README.md +++ b/packages/mimir-core/README.md @@ -1,7 +1,8 @@ # Mimir Core Package -`@jcode.labs/mimir` is Mimir Core, the technical core package for Mimir, an open-source sovereign local RAG toolkit for -confidential datasets and AI agents. +`@jcode.labs/mimir` is Mimir Core, the technical core package for Mimir, an open-source local RAG +library, CLI, and MCP server. It indexes your specs, docs, and code locally and gives your AI agents +only the useful cited passages, over MCP, without burning tokens on your whole repo. **Full documentation:** https://github.com/jcode-works/jcode-mimir#readme diff --git a/packages/mimir-core/examples/library-api-demo/README.md b/packages/mimir-core/examples/library-api-demo/README.md new file mode 100644 index 0000000..ebf45ae --- /dev/null +++ b/packages/mimir-core/examples/library-api-demo/README.md @@ -0,0 +1,42 @@ +# Library API Demo + +Runnable smoke for the `@jcode.labs/mimir` **library** API, meant for local development of Mimir +Core itself. Where [`sovereign-rag-demo`](../sovereign-rag-demo) drives the **CLI** +(`node ../../dist/cli.js ...`), this demo `import`s the public TypeScript surface the same way an +external consumer would: + +```js +import { ask, audit, ingest, search } from "@jcode.labs/mimir" +``` + +Node self-referencing resolves `@jcode.labs/mimir` to this repository's local build +(`packages/mimir-core/dist`), **never** the npm-published package. So you can validate your local +changes to the library end to end, without `npx` silently running a released version. + +## Run + +From the repository root: + +```bash +pnpm example +``` + +That builds Mimir Core, then runs the demo. It reuses the committed synthetic corpus from +`sovereign-rag-demo`, so it needs no private documents and writes only to that example's gitignored +`.mimir/storage`. + +To run it directly against the already-built `dist/` without rebuilding: + +```bash +node packages/mimir-core/examples/library-api-demo/run.mjs +``` + +## What it exercises + +- `ingest({ cwd, rebuild: true })` — parse, redact, chunk, and embed the synthetic corpus. +- `search(query, { cwd, topK })` — ranked cited passages. +- `ask(query, { cwd, topK })` — retrieval-only cited context (no LLM synthesis in core). +- `audit(cwd)` — indexed, supported, and skipped file counts. + +It uses `embeddingProvider: "local-hash"` (from the reused corpus config), so it runs fully offline +with no model download. diff --git a/packages/mimir-core/examples/library-api-demo/run.mjs b/packages/mimir-core/examples/library-api-demo/run.mjs new file mode 100644 index 0000000..889dec0 --- /dev/null +++ b/packages/mimir-core/examples/library-api-demo/run.mjs @@ -0,0 +1,61 @@ +#!/usr/bin/env node +// Local library smoke for @jcode.labs/mimir. +// +// This exercises the public TypeScript API (ingest -> search -> ask -> audit) the +// exact way an external consumer would `import` it, but Node self-referencing +// resolves "@jcode.labs/mimir" to THIS repo's local build (packages/mimir-core/dist), +// never the npm-published version. That is the point of the demo: while developing +// core you can validate the library surface end to end against your own changes, +// without `npx` silently pulling a released package. +// +// Run it from the repository root with `pnpm example`. + +import path from "node:path" +import { fileURLToPath } from "node:url" + +import { ask, audit, ingest, search } from "@jcode.labs/mimir" + +const here = path.dirname(fileURLToPath(import.meta.url)) + +// Reuse the committed synthetic corpus so the demo needs no private documents and +// writes only to the sibling example's gitignored .mimir/storage. +const corpus = path.resolve(here, "..", "sovereign-rag-demo") + +function heading(title) { + console.log(`\n=== ${title} ===`) +} + +async function main() { + console.log("Running @jcode.labs/mimir from the local build against the synthetic corpus.") + console.log(`corpus: ${corpus}`) + + heading("ingest") + const ingested = await ingest({ cwd: corpus, rebuild: true }) + console.log( + `indexed ${ingested.indexedFiles}/${ingested.supportedFiles} supported files, ` + + `${ingested.chunks} chunks, ${ingested.redactions} redactions`, + ) + + heading('search "offline retrieval approval"') + const passages = await search("offline retrieval approval", { cwd: corpus, topK: 3 }) + for (const passage of passages) { + console.log(`- ${passage.relativePath}#${passage.chunkIndex} (distance ${passage.distance ?? "n/a"})`) + } + + heading('ask "What evidence supports offline operation?"') + const answer = await ask("What evidence supports offline operation?", { cwd: corpus, topK: 3 }) + console.log(`${answer.sources.length} cited sources`) + console.log(answer.answer) + + heading("audit") + const report = await audit(corpus) + console.log( + `${report.supportedFiles.length} supported, ${report.skippedFiles.length} skipped, ` + + `${report.totalChunks} chunks indexed`, + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/packages/mimir-core/src/redaction.test.ts b/packages/mimir-core/src/redaction.test.ts index f4bfd0b..3864d15 100644 --- a/packages/mimir-core/src/redaction.test.ts +++ b/packages/mimir-core/src/redaction.test.ts @@ -9,6 +9,15 @@ function pemPrivateKeyFixture(): string { return `-----BEGIN ${label}-----\nMIIByzqABC123secret\n-----END ${label}-----` } +// Assemble the connection string from parts so GitHub secret scanning does not flag this +// fixture as a real Postgres credential. The runtime value is unchanged, so redactText still +// sees a complete `scheme://user:pass@host` URL to strip. +function urlWithCredentialsFixture(): string { + const scheme = "postgres" + const password = "s3cr3tPass" + return `${scheme}://admin:${password}@db.internal:5432/app` +} + describe("redactText", () => { it("redacts built-in sensitive identifiers before indexing", () => { const config = testConfig() @@ -89,7 +98,7 @@ describe("redactText", () => { }, { name: "URL credentials", - sample: "postgres://admin:s3cr3tPass@db.internal:5432/app", + sample: urlWithCredentialsFixture(), token: "[REDACTED_URL_CREDENTIALS]", leaked: "s3cr3tPass", }, diff --git a/packages/mimir-landing/README.md b/packages/mimir-landing/README.md index 1c2415a..449a5ac 100644 --- a/packages/mimir-landing/README.md +++ b/packages/mimir-landing/README.md @@ -11,8 +11,9 @@ pnpm --filter @jcode.labs/mimir-landing build pnpm --filter @jcode.labs/mimir-landing cf:dry-run ``` -The landing presents the open-source Mimir Core package, sovereign local retrieval, and a light -teaser for the future Tauri desktop client. It does not collect emails. +The landing presents the open-source Mimir Core package (local RAG that gives AI agents cited +passages over MCP without burning tokens), sovereign local retrieval, and a light teaser for the +future Tauri desktop client. It does not collect emails. Cloudflare Workers Static Assets configuration lives in [`wrangler.jsonc`](./wrangler.jsonc). The canonical domain is `mimir.jcode.works`; it is the future direct-download release surface for Mimir diff --git a/packages/mimir-landing/astro.config.mjs b/packages/mimir-landing/astro.config.mjs index 0fadfb4..3df4009 100644 --- a/packages/mimir-landing/astro.config.mjs +++ b/packages/mimir-landing/astro.config.mjs @@ -47,6 +47,10 @@ export default defineConfig({ prefetchAll: true, defaultStrategy: "viewport", }, + experimental: { + // Prerender prefetched links via the Speculation Rules API for faster locale navigation. + clientPrerender: true, + }, vite: { plugins: [tailwindcss()], envPrefix: ["PUBLIC_"], diff --git a/packages/mimir-landing/messages/en.json b/packages/mimir-landing/messages/en.json index a83acd3..fcd1e5a 100644 --- a/packages/mimir-landing/messages/en.json +++ b/packages/mimir-landing/messages/en.json @@ -4,131 +4,173 @@ "nav_agents": "Agents", "nav_use_cases": "Use cases", "nav_desktop": "Desktop", + "nav_team": "Team", + "nav_menu": "Menu", + "nav_menu_close": "Close menu", "nav_github": "GitHub", - "nav_primary": "Install", + "nav_aria_label": "Main navigation", "language_label": "Language", - "seo_home_title": "Mimir - local interactive documentation for AI agents", - "seo_home_description": "Mimir is an open-source CLI, library, and MCP server that turns private documentation into cited local retrieval for AI agents and teams.", - "seo_home_keywords": "Mimir, interactive documentation, developer onboarding, local RAG, sovereign RAG, private AI, confidential documents, MCP server, open-source RAG, local-first retrieval", + "seo_home_title": "Mimir: cited context from your specs and docs for your AI agents", + "seo_home_description": "Mimir is an open-source local RAG library, CLI, and MCP server. It indexes your specs, docs, and code and gives your AI agents only the useful cited passages, over MCP, without burning tokens on your whole repo.", + "seo_home_keywords": "Mimir, interactive documentation, developer onboarding, local RAG, sovereign RAG, private AI, confidential documents, MCP server, open-source RAG, local-first search", "seo_author": "Jean-Baptiste Thery", "seo_robots": "index, follow", "seo_image_alt": "Mimir open-source local RAG for confidential documents", "seo_og_site_name": "Mimir", "seo_application_name": "Mimir", - "hero_badge": "Local interactive documentation. Open source. MCP-ready.", - "hero_title_line_1": "Mimir, private interactive documentation.", - "hero_title_line_2": "For AI agents.", - "hero_title_line_3": "For teams.", - "hero_description": "Install Mimir in a repository, documentation folder, or local data room. It indexes files on the machine, exposes bounded MCP retrieval, and returns cited answers your agents can use.", + "hero_badge": "Cited context · Specs, docs & code · Fewer tokens · Local · MCP", + "hero_title_line_1": "Build from your specs and docs.", + "hero_title_line_2": "The right context, fewer tokens.", + "hero_description": "Mimir indexes your specs, docs, and code locally and gives your AI agents only the useful cited passages, over MCP. Build from your requirements, without burning tokens on your whole repo.", "hero_primary_cta": "Install Mimir", "hero_secondary_cta": "See use cases", - "hero_metric_private_value": "Docs -> MCP", - "hero_metric_private_label": "Turn a local folder into an agent-ready retrieval surface.", - "hero_metric_agents_value": "MCP", - "hero_metric_agents_label": "Claude, Codex, Kimi, and your own tools query the same evidence.", - "hero_metric_license_value": "MIT", - "hero_metric_license_label": "Open-source foundation you can integrate into business workflows.", - "workspace_title": "Interactive documentation", + "hero_metric_private_value": "Fewer tokens", + "hero_metric_private_label": "Only the cited passages reach the model, not your whole repo.", + "hero_metric_agents_value": "100% local", + "hero_metric_agents_label": "Your specs, docs, and code stay on your machine.", + "hero_metric_license_value": "Native MCP", + "hero_metric_license_label": "Claude, Codex, Cline, and your agents query the same cited sources.", + "workspace_title": "Cited context for AI", "workspace_badge": "Synthetic", "workspace_text": "A preview of the expected flow: project files, team question, traceable answer.", - "workspace_file_contracts": "docs/onboarding/backend-runbook.md", + "workspace_file_contracts": "src/auth/session-refresh.ts", "workspace_file_policy": "decisions/architecture-adr.md", "workspace_file_research": "security/vendor-questionnaire.xlsx", "workspace_file_badge": "local", "workspace_answer_label": "Team question", - "workspace_answer_text": "Which path should a new developer read to understand the API, architecture decisions, and security constraints?", - "workspace_answer_citations": "[1] backend-runbook.md:12 [2] architecture-adr.md:7", - "workspace_status_redaction": "Interactive docs", + "workspace_answer_text": "What path should I read to onboard a new dev on the API, the architecture decisions, and the security constraints?", + "workspace_answer_citations": "[1] session-refresh.ts:41 [2] architecture-adr.md:7", + "workspace_status_redaction": "Redaction", "workspace_status_index": "Local index", "workspace_status_mcp": "Bounded MCP", + "demo_title": "How it works", + "demo_badge": "Demo", + "demo_step_index": "Index", + "demo_step_ask": "Ask", + "demo_step_cite": "Answer", + "demo_index_action": "You run", + "demo_index_command": "mimir ingest", + "demo_index_result": "Your specs, docs & code, indexed locally.", + "demo_ask_action": "In Codex, you ask", + "demo_ask_command": "Build the CSV export from spec-042.md", + "demo_ask_result": "Mimir sends only the cited passages, not the whole repo.", + "demo_cite_action": "Codex builds it, grounded in the sources", + "demo_cite_result": "Grounded in your specs. Fewer tokens burned.", "privacy_eyebrow": "Sovereign and private", - "privacy_title": "The core advantage: make documentation useful without exporting it.", - "privacy_text": "Mimir keeps control at the project level: no automatic import into a platform, no vague compliance promises, and explicit limits on what gets indexed.", + "privacy_title": "The key strength: make documentation useful without exporting it.", + "privacy_text": "Mimir keeps control at the project level: no telemetry, no automatic upload to a platform, no vague compliance promise, and explicit limits on what gets indexed.", "privacy_docs_title": "Documents on disk", - "privacy_docs_text": "You point Mimir at the useful folder. Private files stay in the local repository or workspace.", + "privacy_docs_text": "You point Mimir at the relevant folder. Private files stay in the repository or the local workspace.", "privacy_index_title": "Git-ignored state", - "privacy_index_text": "Indexes, reports, and generated helpers live under the ignored local .mimir/ folder.", - "privacy_redaction_title": "Risk reduction", - "privacy_redaction_text": "Redaction, size limits, and unsupported-file audits avoid pretending Mimir indexed more than it can reliably handle.", + "privacy_index_text": "The index, reports, and generated helpers live in the local git-ignored .mimir/ folder.", + "privacy_redaction_title": "Redaction and explicit limits", + "privacy_redaction_text": "Redaction, size limits, and auditing of unsupported files avoid claiming to index more than what is reliable.", "privacy_access_title": "Minimal logs", "privacy_access_text": "Access records stay metadata-oriented so usage can be checked without exposing sensitive content.", - "install_pnpm_label": "pnpm", - "install_pnpm_command": "pnpm add -D @jcode.labs/mimir", - "install_npm_label": "npm", - "install_npm_command": "npm install --save-dev @jcode.labs/mimir", - "install_yarn_label": "yarn", - "install_yarn_command": "yarn add --dev @jcode.labs/mimir", - "install_mise_label": "mise", - "install_mise_command": "mise exec node@24 -- npm install --save-dev @jcode.labs/mimir", "library_eyebrow": "Install", "library_title": "One package to activate queryable local documentation.", - "library_text": "Mimir starts as an open-source library and CLI. Install it where docs, runbooks, decisions, contracts, or questionnaires already live, then generate local state and agent helpers.", + "library_text": "Mimir starts as an open-source library and CLI. Install it where docs, runbooks, decisions, contracts, or questionnaires already live, then generate local state and agent helpers. The default mode stays offline, with no model to download.", "quickstart_title": "Choose your package manager", - "quickstart_text": "Tabs are only for choosing the install command. The next commands stay the same inside the project.", + "quickstart_text": "Pick your package manager once. Every command below adapts to match.", + "quickstart_install_label": "Add Mimir to your project", "quickstart_setup_label": "Prepare the workspace", - "quickstart_setup_command": "pnpm exec mimir setup", "quickstart_agent_label": "Connect useful agents", - "quickstart_agent_command": "pnpm exec mimir install-agent --agents claude,codex,kimi", "quickstart_search_label": "Search the evidence", - "quickstart_search_command": "pnpm exec mimir search \"contract renewal risk\"", + "quickstart_search_query": "contract renewal risk", "agents_eyebrow": "Agent workflows", "agents_title": "A local knowledge base for agents, onboarding, and teams.", - "agents_text": "Mimir generates agent-specific MCP helpers from the same project state. You explicitly choose Claude, Codex, Kimi, OpenCode, Cline, or a stricter subset, without recreating a knowledge base for every tool.", + "agents_text": "Mimir generates per-agent MCP helpers from the same project state. You explicitly choose Claude, Codex, Kimi, OpenCode, Cline, or a stricter subset, without rebuilding a knowledge base per tool.", "agents_command_label": "Agent command", "agents_command": "pnpm exec mimir install-agent --agents claude,codex,kimi,opencode,cline", "agents_targets_title": "Target clients", - "agents_targets_text": "Each agent connects to the same local server bounded to the workspace.", - "agents_claude_text": "Project-scoped skill and MCP helper files for local retrieval during Claude Code sessions.", - "agents_codex_text": "Codex config helpers keep retrieval bounded to the selected repository instead of a global corpus.", - "agents_kimi_text": "Kimi receives the same local MCP entrypoint, so evidence stays consistent across agents.", + "agents_targets_text": "Each agent stays connected to the same local server bounded to the workspace.", + "agents_claude_text": "Project skill and MCP helpers to retrieve local evidence during Claude Code sessions.", + "agents_codex_text": "Codex helpers keep search bounded to the selected repository, not a global corpus.", + "agents_kimi_text": "Kimi gets the same local MCP entry point to keep evidence consistent across agents.", "agents_other_text": "OpenCode, Cline, and custom MCP clients can use the same generated local server contract.", "use_cases_eyebrow": "Use cases", - "use_cases_title": "Concrete use cases that justify installation.", - "use_cases_text": "Mimir becomes valuable as soon as a team repeats the same questions over private, sensitive, or scattered documentation.", - "use_case_onboarding_eyebrow": "Developer onboarding", - "use_case_onboarding_title": "Accelerate a new developer's first week.", - "use_case_onboarding_text": "Turn runbooks, ADRs, READMEs, product decisions, and conventions into a queryable path with cited sources.", + "use_cases_title": "What your agents and teams can do with Mimir.", + "use_cases_text": "Point Mimir at your specs, docs, and code. Your agents get the cited passages relevant to the task, not a token-heavy dump of the whole corpus.", + "use_case_onboarding_eyebrow": "Codebase & features", + "use_case_onboarding_title": "Understand and extend a codebase, fast.", + "use_case_onboarding_text": "Index code, runbooks, ADRs, READMEs, and decisions. The agent finds where a feature lives, what to read before coding, and ships changes with cited sources: onboarding a dev goes from weeks to minutes.", + "use_case_refactor_eyebrow": "Refactor & migration", + "use_case_refactor_title": "Change risky code with the full picture.", + "use_case_refactor_text": "Before an agent refactors or migrates, Mimir gives it the cited callers, contracts, and past decisions: changes are planned on what the codebase actually does, not on a guess.", + "use_case_debug_eyebrow": "Debug & tracing", + "use_case_debug_title": "Trace a bug across the whole codebase.", + "use_case_debug_text": "Ask where a flow is implemented and why it fails; Mimir returns the cited files and passages along the call path, giving the agent the big picture it usually lacks.", "use_case_team_eyebrow": "Team memory", "use_case_team_title": "Answer without waking up the person who knows.", "use_case_team_text": "Team members query policies, decisions, incidents, and project notes from the same bounded local context.", "use_case_sales_eyebrow": "Sales engineering", "use_case_sales_title": "Answer RFPs and security questionnaires.", - "use_case_sales_text": "Find existing evidence in security docs, contracts, SLAs, and compliance answers without copying it into a third-party SaaS.", + "use_case_sales_text": "Retrieve evidence already written in security docs, contracts, SLAs, and compliance answers without copying it into a third-party SaaS.", "use_case_vendor_eyebrow": "Vendor due diligence", - "use_case_vendor_title": "Explore a data room or vendor dossier.", - "use_case_vendor_text": "Analyze contracts, policies, technical annexes, and compliance files with citations instead of scattered manual reading.", + "use_case_vendor_title": "Explore a data room or a vendor dossier.", + "use_case_vendor_text": "Analyze contracts, policies, technical appendices, and compliance documents with citations instead of scattered manual reading.", "use_case_legal_eyebrow": "Legal and tax", - "use_case_legal_title": "Compare clauses, obligations, and proof.", - "use_case_legal_text": "Contracts, tax notes, company memos, or residency research stay local while the agent retrieves useful sources.", + "use_case_legal_title": "Compare clauses, obligations, and evidence.", + "use_case_legal_text": "Contracts, tax notes, company memos, or residency research stay local while the agent retrieves the relevant sources.", "use_case_research_eyebrow": "Research and reports", "use_case_research_title": "Write from cited sources.", - "use_case_research_text": "Briefings, Markdown reports, and summaries can separate evidence, inference, uncertainty, and review items.", + "use_case_research_text": "Briefings, Markdown reports, and summaries can separate evidence, inferences, uncertainties, and items to review.", + "use_case_audio_eyebrow": "Audio and micro-learning", + "use_case_audio_title": "Listen to a dense dossier instead of reading it.", + "use_case_audio_text": "Generate a short audio summary (MP3/WAV) from cited passages with mimir audio, to review a spec, an architecture, or a watch without staying in front of the screen.", "desktop_teaser_eyebrow": "Desktop client coming", - "desktop_teaser_title": "Desktop comes after the open-source core, through direct downloads.", - "desktop_teaser_text": "The Tauri client should make the local flow visible: select projects, watch folders, and run the same Mimir commands without a terminal. No fake pricing page or waitlist before the native release is real.", + "desktop_teaser_title": "Desktop will come after the open-source core, with direct download.", + "desktop_teaser_text": "The Tauri client should make the local flow visible: pick projects, watch folders, and run the same Mimir commands without a terminal. No fake pricing page or waiting list until the native release is ready.", "desktop_cta": "Follow the project", "desktop_teaser_mock_title": "Mimir Desktop", "desktop_teaser_badge": "Planned", - "desktop_teaser_mock_text": "Distribution is planned through direct downloads and sideloadable installers, not App Store or Play Store flows.", + "desktop_teaser_mock_text": "Distribution planned through direct downloads and sideloadable installers, not through the App Store or Play Store.", "desktop_teaser_workspace_title": "Project registry", - "desktop_teaser_workspace_text": "Each local folder keeps its own Mimir state and access boundaries.", + "desktop_teaser_workspace_text": "Each local folder keeps its own Mimir state and its own access limits.", "desktop_teaser_download_title": "Direct distribution", - "desktop_teaser_download_text": "Desktop installers and Android APK/sideloading are the target paths before any store discussion.", + "desktop_teaser_download_text": "Desktop installers and Android APK/sideload are the targeted paths before any store consideration.", "desktop_teaser_private_title": "Same local core", - "desktop_teaser_private_text": "The app wraps Mimir instead of creating a second hosted index or cloud document store.", + "desktop_teaser_private_text": "The app wraps Mimir instead of creating a second hosted index or cloud document storage.", + "seo_team_title": "The team behind Mimir", + "seo_team_description": "Who builds Mimir: JCode Labs and Jean-Baptiste Thery, focused on sovereign local retrieval for code, specs, and confidential documents.", + "team_eyebrow": "The team", + "team_title": "Who builds Mimir", + "team_member_jb_title": "Jean-Baptiste Thery", + "team_member_jb_subtitle": "Founder and engineer, JCode Labs", + "team_member_jb_description": "Product developer and founder of JCode Labs. He builds Mimir to give AI agents the right context, cited and local, from your specs and documentation, without burning tokens or exposing confidential code.", + "team_mission_title": "Our mission", + "team_mission_description_1": "Most AI tools send your code and documents to the cloud to make them usable. Mimir takes the opposite path: everything stays local, the agent only receives the useful cited passages, and every answer is traceable back to its source.", + "team_mission_description_2": "The goal is a sovereign knowledge base that regulated teams, freelancers, and studios can connect to any MCP agent, without depending on a vendor or wasting tokens.", "faq_eyebrow": "FAQ", - "faq_title": "What Mimir sends away, and mostly what it does not.", - "faq_text": "The open-source library is the product today. The desktop client remains a deliberately careful teaser.", - "faq_private_question": "Does Mimir upload my confidential documents?", - "faq_private_answer": "No. Mimir indexes the local folders you choose and keeps generated state under the ignored project .mimir/ folder. Payment and future license flows must stay metadata-only.", - "faq_formats_question": "Can it index every file format?", - "faq_formats_answer": "No. Mimir supports common text, PDF, Office, OpenDocument, EPUB, HTML, JSON and YAML paths, and reports unsupported files explicitly. Scanned PDFs need an opt-in OCR command.", - "faq_desktop_question": "Is the desktop app available now?", - "faq_desktop_answer": "Not yet. The open-source CLI, library and MCP server are the current product foundation. The Tauri desktop client is planned for direct downloads and sideloadable installers after native release gates are ready.", + "faq_title": "Frequently asked questions", + "faq_text": "What Mimir does, what it never sends off your machine, and how to connect it to your agents.", + "faq_what_question": "What is Mimir?", + "faq_what_answer": "Mimir is an open-source library, CLI, and MCP server that turns any local codebase or document corpus into cited, queryable context for your AI agents. It indexes your files on your machine and returns cited passages: your agents answer from your real code and real documents instead of guessing. It works offline by default.", + "faq_private_question": "Does Mimir send my confidential code or documents?", + "faq_private_answer": "No. Mimir only indexes the local folders you choose and keeps all generated state in the git-ignored .mimir/ folder. No telemetry, no upload to the cloud, and offline by default: it fits confidential code, customer data, and regulated environments.", + "faq_agents_question": "Which AI agents and tools does Mimir work with?", + "faq_agents_answer": "Any MCP-compatible agent: Claude Code, Codex, Kimi, OpenCode, Cline, and custom MCP clients. Run mimir install-agent to generate the configuration and skills for each one, or use the CLI and the TypeScript library directly.", + "faq_offline_question": "Does Mimir require an API key, a model download, or an internet connection?", + "faq_offline_answer": "No. The default local-hash mode runs entirely offline, with no model or API key. Optional semantic embeddings (Transformers.js) can be preloaded once for better search quality, then also run offline.", + "faq_compare_question": "How does Mimir differ from a cloud RAG or an IDE's codebase indexing?", + "faq_compare_answer": "Hosted RAG and IDE indexing usually send your code chunks to their servers to compute embeddings. Mimir keeps everything on your machine, exposes it over MCP to any agent, and returns cited passages: local, cited, MCP, code and documents, with no vendor lock-in.", + "faq_role_question": "Does Mimir write the answers for me?", + "faq_role_answer": "No. Mimir returns cited passages from your local files; your agent or model writes from those sources. By keeping generation out of the core, answers stay grounded in real evidence and every citation is verifiable.", + "faq_formats_question": "Which file formats can Mimir index?", + "faq_formats_answer": "Source code, text, Markdown, PDF, Office and OpenDocument, EPUB, HTML, CSV, JSON, and YAML, plus the custom text extensions you enable. Unsupported files are flagged explicitly, and scanned PDFs can use an optional OCR command.", + "faq_license_question": "Is Mimir free and open-source?", + "faq_license_answer": "Yes. Mimir is MIT-licensed and free, with no account required. The CLI, the library, and the MCP server are the current product; a desktop client is planned later as a direct download.", "closing_eyebrow": "Start with the core", "closing_title": "Install Mimir, keep the dossier local, query the evidence.", - "closing_text": "No hosted account is required. The desktop client will arrive later; the open-source CLI and MCP layer are the product foundation now.", + "closing_text": "No hosted account is required, and no data is exported to a third-party service.", "closing_primary_cta": "Open GitHub", "closing_secondary_cta": "Install", + "command_copied": "Copied to clipboard", + "copy_command": "Copy command", + "footer_marquee": "#local #cited #sovereign #mcp #opensource #offline #private #codebase #agents", + "footer_nav_label": "Footer", + "footer_link_npm": "npm", + "footer_link_docs": "Docs", + "footer_link_sponsors": "Sponsors", "footer_text": "Mimir is built by JCode Labs for sovereign local retrieval over confidential documents." } diff --git a/packages/mimir-landing/messages/fr.json b/packages/mimir-landing/messages/fr.json index 8e9954b..5db0efa 100644 --- a/packages/mimir-landing/messages/fr.json +++ b/packages/mimir-landing/messages/fr.json @@ -4,73 +4,79 @@ "nav_agents": "Agents", "nav_use_cases": "Cas d'usage", "nav_desktop": "Desktop", + "nav_team": "Équipe", + "nav_menu": "Menu", + "nav_menu_close": "Fermer le menu", "nav_github": "GitHub", - "nav_primary": "Installer", + "nav_aria_label": "Navigation principale", "language_label": "Langue", - "seo_home_title": "Mimir - documentation interactive locale pour agents IA", - "seo_home_description": "Mimir est une CLI, une librairie et un serveur MCP open-source qui transforme une documentation privée en recherche locale citée pour agents IA et équipes.", + "seo_home_title": "Mimir : du contexte cité depuis vos specs et docs pour vos agents IA", + "seo_home_description": "Mimir est une librairie RAG locale, CLI et serveur MCP open-source. Il indexe vos specs, docs et code et ne fournit à vos agents IA que les passages cités utiles, via MCP, sans brûler de tokens sur tout le repo.", "seo_home_keywords": "Mimir, documentation interactive, onboarding développeur, RAG local, RAG souverain, IA privée, documents confidentiels, serveur MCP, RAG open-source, recherche local-first", "seo_author": "Jean-Baptiste Thery", "seo_robots": "index, follow", "seo_image_alt": "Mimir RAG local open-source pour documents confidentiels", "seo_og_site_name": "Mimir", "seo_application_name": "Mimir", - "hero_badge": "Documentation interactive locale. Open-source. Prêt MCP.", - "hero_title_line_1": "Mimir, documentation privée interactive.", - "hero_title_line_2": "Pour les agents IA.", - "hero_title_line_3": "Pour les équipes.", - "hero_description": "Installez Mimir dans un dépôt, un dossier de documentation ou un data room local. Il indexe les fichiers sur la machine, expose une recherche MCP bornée, et renvoie des réponses citées utilisables par vos agents.", + "hero_badge": "Contexte cité · Specs, docs et code · Moins de tokens · Local · MCP", + "hero_title_line_1": "Codez depuis vos specs et vos docs.", + "hero_title_line_2": "Le bon contexte, moins de tokens.", + "hero_description": "Mimir indexe vos specs, docs et code en local et ne fournit à vos agents IA que les passages cités utiles, via MCP. Développez à partir de vos exigences, sans brûler de tokens sur tout le repo.", "hero_primary_cta": "Installer Mimir", "hero_secondary_cta": "Voir les cas d'usage", - "hero_metric_private_value": "Docs -> MCP", - "hero_metric_private_label": "Transforme un dossier local en surface de recherche pour agents.", - "hero_metric_agents_value": "MCP", - "hero_metric_agents_label": "Claude, Codex, Kimi et vos outils interrogent les mêmes preuves.", - "hero_metric_license_value": "MIT", - "hero_metric_license_label": "Fondation open-source, intégrable dans vos workflows métier.", - "workspace_title": "Documentation interactive", + "hero_metric_private_value": "Moins de tokens", + "hero_metric_private_label": "Seuls les passages cités atteignent le modèle, pas tout le repo.", + "hero_metric_agents_value": "100% local", + "hero_metric_agents_label": "Vos specs, docs et code restent sur votre machine.", + "hero_metric_license_value": "MCP natif", + "hero_metric_license_label": "Claude, Codex, Cline et vos agents interrogent les mêmes sources citées.", + "workspace_title": "Contexte cité pour l'IA", "workspace_badge": "Synthétique", "workspace_text": "Un aperçu du flux attendu : fichiers projet, question d'équipe, réponse traçable.", - "workspace_file_contracts": "docs/onboarding/backend-runbook.md", + "workspace_file_contracts": "src/auth/session-refresh.ts", "workspace_file_policy": "decisions/architecture-adr.md", "workspace_file_research": "security/vendor-questionnaire.xlsx", "workspace_file_badge": "local", "workspace_answer_label": "Question d'équipe", "workspace_answer_text": "Quel parcours lire pour onboarder un nouveau dev sur l'API, les décisions d'architecture et les contraintes sécurité ?", - "workspace_answer_citations": "[1] backend-runbook.md:12 [2] architecture-adr.md:7", - "workspace_status_redaction": "Docs interactives", + "workspace_answer_citations": "[1] session-refresh.ts:41 [2] architecture-adr.md:7", + "workspace_status_redaction": "Masquage", "workspace_status_index": "Index local", "workspace_status_mcp": "MCP borné", + "demo_title": "Comment ça marche", + "demo_badge": "Démo", + "demo_step_index": "Indexer", + "demo_step_ask": "Demander", + "demo_step_cite": "Réponse", + "demo_index_action": "Vous lancez", + "demo_index_command": "mimir ingest", + "demo_index_result": "Vos specs, docs et code, indexés en local.", + "demo_ask_action": "Dans Codex, vous demandez", + "demo_ask_command": "Code l'export CSV depuis spec-042.md", + "demo_ask_result": "Mimir n'envoie que les passages cités, pas tout le repo.", + "demo_cite_action": "Codex code, en s'appuyant sur les sources", + "demo_cite_result": "Ancré dans vos specs. Moins de tokens consommés.", "privacy_eyebrow": "Souverain et privé", "privacy_title": "Le point fort : rendre la documentation utile sans l'exporter.", - "privacy_text": "Mimir garde le contrôle au niveau du projet : pas d'import automatique vers une plateforme, pas de promesse floue de conformité, et des limites explicites sur ce qui est indexé.", + "privacy_text": "Mimir garde le contrôle au niveau du projet : aucune télémétrie, pas d'import automatique vers une plateforme, pas de promesse floue de conformité, et des limites explicites sur ce qui est indexé.", "privacy_docs_title": "Documents sur disque", "privacy_docs_text": "Vous pointez Mimir vers le dossier utile. Les fichiers privés restent dans le dépôt ou le workspace local.", "privacy_index_title": "État ignoré par Git", "privacy_index_text": "L'index, les rapports et les helpers générés vivent dans le dossier local ignoré .mimir/.", - "privacy_redaction_title": "Réduction du risque", + "privacy_redaction_title": "Masquage et limites explicites", "privacy_redaction_text": "Le masquage, les limites de taille et l'audit des fichiers non supportés évitent de prétendre indexer plus que ce qui est fiable.", "privacy_access_title": "Logs sobres", "privacy_access_text": "Les accès restent orientés métadonnées afin de vérifier l'usage sans exposer le contenu sensible.", - "install_pnpm_label": "pnpm", - "install_pnpm_command": "pnpm add -D @jcode.labs/mimir", - "install_npm_label": "npm", - "install_npm_command": "npm install --save-dev @jcode.labs/mimir", - "install_yarn_label": "yarn", - "install_yarn_command": "yarn add --dev @jcode.labs/mimir", - "install_mise_label": "mise", - "install_mise_command": "mise exec node@24 -- npm install --save-dev @jcode.labs/mimir", "library_eyebrow": "Installation", "library_title": "Un seul package pour activer une documentation locale interrogeable.", - "library_text": "Mimir est d'abord une librairie open-source et une CLI. Installez-la là où vivent les docs, runbooks, décisions, contrats ou questionnaires, puis générez l'état local et les helpers agents.", + "library_text": "Mimir est d'abord une librairie open-source et une CLI. Installez-la là où vivent les docs, runbooks, décisions, contrats ou questionnaires, puis générez l'état local et les helpers agents. Le mode par défaut reste hors-ligne, sans aucun modèle à télécharger.", "quickstart_title": "Choisissez votre gestionnaire de paquets", - "quickstart_text": "Les onglets servent uniquement au choix de la commande d'installation. Les commandes suivantes restent les mêmes dans le projet.", + "quickstart_text": "Choisissez votre gestionnaire de paquets une fois. Toutes les commandes ci-dessous s'adaptent.", + "quickstart_install_label": "Ajoutez Mimir à votre projet", "quickstart_setup_label": "Préparer le workspace", - "quickstart_setup_command": "pnpm exec mimir setup", "quickstart_agent_label": "Brancher les agents utiles", - "quickstart_agent_command": "pnpm exec mimir install-agent --agents claude,codex,kimi", "quickstart_search_label": "Chercher dans les preuves", - "quickstart_search_command": "pnpm exec mimir search \"risque renouvellement contrat\"", + "quickstart_search_query": "risque renouvellement contrat", "agents_eyebrow": "Workflows agents", "agents_title": "Un socle de connaissance local pour agents, onboarding et équipes.", "agents_text": "Mimir génère des helpers MCP par agent depuis le même état projet. Vous choisissez explicitement Claude, Codex, Kimi, OpenCode, Cline ou un sous-ensemble plus strict, sans recréer une base de connaissance par outil.", @@ -83,11 +89,17 @@ "agents_kimi_text": "Kimi reçoit le même point d'entrée MCP local pour conserver des preuves cohérentes entre agents.", "agents_other_text": "OpenCode, Cline et les clients MCP personnalisés peuvent utiliser le même contrat serveur local généré.", "use_cases_eyebrow": "Cas d'usage", - "use_cases_title": "Des cas d'usage concrets pour justifier l'installation.", - "use_cases_text": "Mimir devient intéressant dès qu'une équipe répète les mêmes questions sur une documentation privée, sensible ou trop dispersée.", - "use_case_onboarding_eyebrow": "Onboarding développeur", - "use_case_onboarding_title": "Accélérer l'arrivée d'un nouveau dev.", - "use_case_onboarding_text": "Transformez runbooks, ADR, README, décisions produit et conventions en parcours questionnable avec sources citées.", + "use_cases_title": "Ce que vos agents et vos équipes peuvent faire avec Mimir.", + "use_cases_text": "Pointez Mimir vers vos specs, docs et code. Vos agents reçoivent les passages cités utiles à la tâche, pas un déversement coûteux en tokens de tout le corpus.", + "use_case_onboarding_eyebrow": "Codebase & features", + "use_case_onboarding_title": "Comprendre et étendre une codebase, vite.", + "use_case_onboarding_text": "Indexez code, runbooks, ADR, README et décisions. L'agent trouve où vit une fonctionnalité, quoi lire avant de coder, et livre des changements avec sources citées : l'onboarding d'un dev passe de semaines à minutes.", + "use_case_refactor_eyebrow": "Refactor & migration", + "use_case_refactor_title": "Changer du code risqué avec toute l'image.", + "use_case_refactor_text": "Avant qu'un agent ne refactore ou migre, Mimir lui fournit les appelants, contrats et décisions passées cités : les changements sont planifiés sur ce que fait vraiment la codebase, pas sur une supposition.", + "use_case_debug_eyebrow": "Debug & traçage", + "use_case_debug_title": "Tracer un bug dans toute la codebase.", + "use_case_debug_text": "Demandez où un flux est implémenté et pourquoi il échoue ; Mimir renvoie les fichiers et passages cités le long du chemin d'appel, donnant à l'agent la vue d'ensemble qui lui manque d'habitude.", "use_case_team_eyebrow": "Mémoire d'équipe", "use_case_team_title": "Répondre sans réveiller la personne qui sait.", "use_case_team_text": "Les membres d'équipe interrogent politiques, décisions, incidents et notes projet depuis le même contexte local borné.", @@ -103,6 +115,9 @@ "use_case_research_eyebrow": "Recherche et rapports", "use_case_research_title": "Rédiger depuis des sources citées.", "use_case_research_text": "Briefings, rapports Markdown et résumés peuvent séparer preuves, inférences, incertitudes et éléments à revoir.", + "use_case_audio_eyebrow": "Audio et mini-formation", + "use_case_audio_title": "Écouter un dossier dense plutôt que le lire.", + "use_case_audio_text": "Générez un résumé audio court (MP3/WAV) à partir de passages cités avec mimir audio, pour réviser une spec, une architecture ou une veille sans rester devant l'écran.", "desktop_teaser_eyebrow": "Client desktop à venir", "desktop_teaser_title": "Le desktop viendra après le cœur open-source, avec téléchargement direct.", "desktop_teaser_text": "Le client Tauri doit rendre le flux local visible : choisir des projets, surveiller des dossiers et lancer les mêmes commandes Mimir sans terminal. Pas de fausse page de pricing ni de waiting list tant que la release native n'est pas prête.", @@ -116,19 +131,46 @@ "desktop_teaser_download_text": "Installateurs desktop et APK/sideload Android sont les chemins visés avant toute réflexion store.", "desktop_teaser_private_title": "Même cœur local", "desktop_teaser_private_text": "L'app enveloppe Mimir au lieu de créer un second index hébergé ou un stockage cloud de documents.", + "seo_team_title": "L'équipe derrière Mimir", + "seo_team_description": "Qui construit Mimir : JCode Labs et Jean-Baptiste Thery, autour de la recherche locale souveraine pour code, specs et documents confidentiels.", + "team_eyebrow": "L'équipe", + "team_title": "Qui construit Mimir", + "team_member_jb_title": "Jean-Baptiste Thery", + "team_member_jb_subtitle": "Fondateur et ingénieur, JCode Labs", + "team_member_jb_description": "Développeur produit et fondateur de JCode Labs. Il construit Mimir pour donner aux agents IA le bon contexte, cité et local, à partir de vos specs et de votre documentation, sans brûler de tokens ni exposer de code confidentiel.", + "team_mission_title": "Notre mission", + "team_mission_description_1": "La plupart des outils IA envoient votre code et vos documents vers le cloud pour les rendre exploitables. Mimir prend le chemin inverse : tout reste en local, l'agent ne reçoit que les passages cités utiles, et chaque réponse est traçable jusqu'à sa source.", + "team_mission_description_2": "L'objectif est une base de connaissance souveraine que les équipes régulées, les freelances et les studios peuvent brancher à n'importe quel agent MCP, sans dépendre d'un fournisseur ni gaspiller de tokens.", "faq_eyebrow": "FAQ", - "faq_title": "Ce que Mimir envoie, et surtout ce qu'il n'envoie pas.", - "faq_text": "La librairie open-source est le produit actuel. Le client desktop reste un teasing volontairement prudent.", - "faq_private_question": "Mimir envoie-t-il mes documents confidentiels ?", - "faq_private_answer": "Non. Mimir indexe les dossiers locaux que vous choisissez et garde l'état généré dans le dossier projet ignoré .mimir/. Les futurs flux paiement et licence doivent rester limités aux métadonnées.", - "faq_formats_question": "Peut-il indexer tous les formats de fichier ?", - "faq_formats_answer": "Non. Mimir prend en charge les chemins texte, PDF, Office, OpenDocument, EPUB, HTML, JSON et YAML courants, et signale explicitement les fichiers non supportés. Les PDF scannés nécessitent une commande OCR optionnelle.", - "faq_desktop_question": "L'application desktop est-elle disponible maintenant ?", - "faq_desktop_answer": "Pas encore. La CLI, la librairie et le serveur MCP open-source sont la fondation produit actuelle. Le client Tauri desktop est prévu en téléchargement direct et installateurs sideloadables après les garde-fous de release native.", + "faq_title": "Questions fréquentes", + "faq_text": "Ce que fait Mimir, ce qu'il n'envoie jamais hors de votre machine, et comment le brancher à vos agents.", + "faq_what_question": "Qu'est-ce que Mimir ?", + "faq_what_answer": "Mimir est une librairie, une CLI et un serveur MCP open-source qui transforme n'importe quelle codebase ou corpus de documents local en contexte cité et interrogeable pour vos agents IA. Il indexe vos fichiers sur votre machine et renvoie des passages cités : vos agents répondent à partir de votre vrai code et de vos vrais documents au lieu de deviner. Il fonctionne hors-ligne par défaut.", + "faq_private_question": "Mimir envoie-t-il mon code ou mes documents confidentiels ?", + "faq_private_answer": "Non. Mimir indexe uniquement les dossiers locaux que vous choisissez et garde tout l'état généré dans le dossier .mimir/ ignoré par Git. Aucune télémétrie, aucun envoi vers le cloud, et hors-ligne par défaut : il convient au code confidentiel, aux données clients et aux environnements régulés.", + "faq_agents_question": "Avec quels agents IA et outils Mimir fonctionne-t-il ?", + "faq_agents_answer": "Tout agent compatible MCP : Claude Code, Codex, Kimi, OpenCode, Cline et les clients MCP personnalisés. Lancez mimir install-agent pour générer la configuration et les skills de chacun, ou utilisez directement la CLI et la librairie TypeScript.", + "faq_offline_question": "Mimir nécessite-t-il une clé API, un téléchargement de modèle ou une connexion internet ?", + "faq_offline_answer": "Non. Le mode local-hash par défaut fonctionne entièrement hors-ligne, sans modèle ni clé API. Les embeddings sémantiques optionnels (Transformers.js) peuvent être préchargés une fois pour une meilleure qualité de recherche, puis tournent eux aussi hors-ligne.", + "faq_compare_question": "En quoi Mimir diffère-t-il d'un RAG cloud ou de l'indexation de codebase d'un IDE ?", + "faq_compare_answer": "Les RAG hébergés et l'indexation des IDE envoient généralement vos morceaux de code sur leurs serveurs pour calculer les embeddings. Mimir garde tout sur votre machine, l'expose via MCP à n'importe quel agent, et renvoie des passages cités : local, cité, MCP, code et documents, sans dépendance à un fournisseur.", + "faq_role_question": "Est-ce que Mimir rédige les réponses à ma place ?", + "faq_role_answer": "Non. Mimir renvoie des passages cités depuis vos fichiers locaux ; c'est votre agent ou votre modèle qui rédige à partir de ces sources. En gardant la génération hors du cœur, les réponses restent ancrées dans des preuves réelles et chaque citation est vérifiable.", + "faq_formats_question": "Quels formats de fichiers Mimir peut-il indexer ?", + "faq_formats_answer": "Code source, texte, Markdown, PDF, Office et OpenDocument, EPUB, HTML, CSV, JSON et YAML, plus les extensions texte personnalisées que vous activez. Les fichiers non supportés sont signalés explicitement, et les PDF scannés peuvent utiliser une commande OCR optionnelle.", + "faq_license_question": "Mimir est-il gratuit et open-source ?", + "faq_license_answer": "Oui. Mimir est sous licence MIT et gratuit, sans compte requis. La CLI, la librairie et le serveur MCP sont le produit actuel ; un client desktop est prévu plus tard en téléchargement direct.", "closing_eyebrow": "Commencer par le cœur", "closing_title": "Installez Mimir, gardez le dossier local, interrogez les preuves.", - "closing_text": "Aucun compte hébergé n'est requis. Le client desktop arrivera plus tard ; la CLI open-source et la couche MCP sont la fondation produit maintenant.", + "closing_text": "Aucun compte hébergé n'est requis, et aucune donnée n'est exportée vers un service tiers.", "closing_primary_cta": "Ouvrir GitHub", "closing_secondary_cta": "Installer", + "command_copied": "Copié dans le presse-papier", + "copy_command": "Copier la commande", + "footer_marquee": "#local #cité #souverain #mcp #opensource #horsligne #privé #codebase #agents", + "footer_nav_label": "Pied de page", + "footer_link_npm": "npm", + "footer_link_docs": "Docs", + "footer_link_sponsors": "Sponsors", "footer_text": "Mimir est construit par JCode Labs pour la recherche locale souveraine dans des documents confidentiels." } diff --git a/packages/mimir-landing/package.json b/packages/mimir-landing/package.json index 767c4e2..88f0d40 100644 --- a/packages/mimir-landing/package.json +++ b/packages/mimir-landing/package.json @@ -17,9 +17,11 @@ "@astrojs/check": "^0.9.9", "@astrojs/react": "^6.0.0", "@astrojs/sitemap": "^3.7.3", + "@gsap/react": "^2.1.2", "@jcode.labs/mimir-ui": "workspace:*", "@tailwindcss/vite": "^4.3.1", "astro": "^7.0.2", + "gsap": "^3.15.0", "lucide-react": "^1.21.0", "react": "19.2.7", "react-dom": "19.2.7", diff --git a/packages/mimir-landing/public/og/mimir-og.png b/packages/mimir-landing/public/og/mimir-og.png new file mode 100644 index 0000000..9ab4eef Binary files /dev/null and b/packages/mimir-landing/public/og/mimir-og.png differ diff --git a/packages/mimir-landing/public/og/mimir-og.svg b/packages/mimir-landing/public/og/mimir-og.svg index a414d14..bd25c65 100644 --- a/packages/mimir-landing/public/og/mimir-og.svg +++ b/packages/mimir-landing/public/og/mimir-og.svg @@ -21,8 +21,8 @@ Mimir - Open-source local RAG for sensitive documents - CLI, TypeScript library, and MCP server for sovereign retrieval. + Build from your specs and docs + Cited context for your AI agents, over MCP. Fewer tokens, all local. pnpm exec mimir setup diff --git a/packages/mimir-landing/public/team/jb-thery.jpg b/packages/mimir-landing/public/team/jb-thery.jpg new file mode 100644 index 0000000..6f45f46 Binary files /dev/null and b/packages/mimir-landing/public/team/jb-thery.jpg differ diff --git a/packages/mimir-landing/src/components/github-icon.tsx b/packages/mimir-landing/src/components/github-icon.tsx new file mode 100644 index 0000000..07b54f4 --- /dev/null +++ b/packages/mimir-landing/src/components/github-icon.tsx @@ -0,0 +1,15 @@ +// Official GitHub mark. lucide-react no longer ships brand icons, so it is +// inlined here as an SVG and colored via currentColor. +export function GithubIcon(props: React.SVGProps): React.JSX.Element { + return ( + + ) +} diff --git a/packages/mimir-landing/src/components/hero-demo.tsx b/packages/mimir-landing/src/components/hero-demo.tsx new file mode 100644 index 0000000..41377bc --- /dev/null +++ b/packages/mimir-landing/src/components/hero-demo.tsx @@ -0,0 +1,241 @@ +import { useGSAP } from "@gsap/react" +import { cn } from "@jcode.labs/mimir-ui/utils" +import { gsap } from "gsap" +import { TextPlugin } from "gsap/TextPlugin" +import { Bot, Check, Lock, Workflow, Zap } from "lucide-react" +import { useCallback, useEffect, useRef, useState } from "react" + +gsap.registerPlugin(TextPlugin) + +interface HeroDemoProps { + t: (key: string) => string +} + +// Resume automatic playback after this idle time (ms) following a manual click. +const RESUME_DELAY = 10000 + +// Animated "how it works" demo: for each step it types the user's action (a +// command / prompt) and shows what it does below. Clicking a step takes manual +// control (auto-play pauses); it resumes after idle. Paused while off-screen. +export function HeroDemo({ t }: HeroDemoProps): React.JSX.Element { + const container = useRef(null) + const timelineRef = useRef(null) + const resumeTimer = useRef | null>(null) + const userPaused = useRef(false) + const isVisible = useRef(true) + const [activeStep, setActiveStep] = useState(0) + + const steps = [t("demo_step_index"), t("demo_step_ask"), t("demo_step_cite")] + + const syncPlayState = useCallback(() => { + const timeline = timelineRef.current + if (!timeline) return + if (isVisible.current && !userPaused.current) { + timeline.play() + } else { + timeline.pause() + } + }, []) + + useGSAP( + () => { + const panels = gsap.utils.toArray(".demo-panel") + + if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { + gsap.set(panels, { autoAlpha: 0 }) + if (panels[0]) gsap.set(panels[0], { autoAlpha: 1 }) + return + } + + const hold = 4 + const timeline = gsap.timeline({ repeat: -1 }) + panels.forEach((panel, index) => { + const typed = panel.querySelector(".demo-typed") + const result = panel.querySelector(".demo-result") + const label = `step-${index}` + timeline.addLabel(label) + timeline.call(() => setActiveStep(index), [], label) + timeline.fromTo( + panel, + { autoAlpha: 0, y: 16 }, + { autoAlpha: 1, y: 0, duration: 0.55, ease: "power3.out" }, + label, + ) + + let resultAt = 0.6 + if (typed) { + const full = typed.textContent ?? "" + const typingDuration = Math.min(1.4, Math.max(0.5, full.length * 0.045)) + timeline.set(typed, { text: "" }, label) + timeline.to( + typed, + { text: full, duration: typingDuration, ease: "none" }, + `${label}+=0.45`, + ) + resultAt = 0.45 + typingDuration + 0.15 + } + if (result) { + timeline.fromTo( + result, + { autoAlpha: 0, y: 6 }, + { autoAlpha: 1, y: 0, duration: 0.4, ease: "power2.out" }, + `${label}+=${resultAt}`, + ) + } + + timeline.to( + panel, + { autoAlpha: 0, y: -14, duration: 0.45, ease: "power2.in" }, + `${label}+=${hold}`, + ) + }) + timelineRef.current = timeline + }, + { scope: container }, + ) + + // Pause the timeline when the demo is off-screen, and clean up the idle timer. + useEffect(() => { + const element = container.current + if (!element) return + const observer = new IntersectionObserver( + (entries) => { + isVisible.current = entries[0]?.isIntersecting ?? true + syncPlayState() + }, + { threshold: 0 }, + ) + observer.observe(element) + return () => { + observer.disconnect() + if (resumeTimer.current) clearTimeout(resumeTimer.current) + } + }, [syncPlayState]) + + const handleStepClick = (index: number) => { + setActiveStep(index) + const timeline = timelineRef.current + + if (!timeline) { + // Reduced motion: no timeline, just reveal the chosen panel. + const panels = container.current?.querySelectorAll(".demo-panel") + const target = panels?.[index] + if (panels) gsap.set(panels, { autoAlpha: 0 }) + if (target) gsap.set(target, { autoAlpha: 1 }) + return + } + + userPaused.current = true + timeline.pause() + timeline.seek((timeline.labels[`step-${index}`] ?? 0) + 2.5) + + if (resumeTimer.current) clearTimeout(resumeTimer.current) + resumeTimer.current = setTimeout(() => { + userPaused.current = false + syncPlayState() + }, RESUME_DELAY) + } + + return ( +
+
+
+
+ + {t("demo_badge")} + +
+ +
+ {steps.map((label, index) => { + const active = activeStep === index + return ( + + ) + })} +
+ +
+
+

{t("demo_index_action")}

+
+ $ + {t("demo_index_command")} +
+

+

+
+ +
+

{t("demo_ask_action")}

+
+
+
+

+ {t("demo_ask_command")} +

+
+

+

+
+ +
+

{t("demo_cite_action")}

+
+ {t("workspace_answer_citations")} +
+

+

+
+
+
+ ) +} diff --git a/packages/mimir-landing/src/components/landing-footer.tsx b/packages/mimir-landing/src/components/landing-footer.tsx new file mode 100644 index 0000000..f6c2847 --- /dev/null +++ b/packages/mimir-landing/src/components/landing-footer.tsx @@ -0,0 +1,79 @@ +import { MimirBackground } from "@jcode.labs/mimir-ui" +import { MimirLogo } from "./landing-navbar" + +interface LandingFooterProps { + translations: Record + localizedHomeUrl: string +} + +const externalLinkProps = { + target: "_blank", + rel: "noopener noreferrer", +} as const + +function FooterMarquee({ phrase }: { phrase: string }): React.JSX.Element { + const copyClassName = + "whitespace-nowrap pr-8 font-black text-5xl text-foreground/90 uppercase italic leading-[1.1] tracking-tighter md:pr-14 md:text-7xl" + + return ( + + ) +} + +export function LandingFooter({ + translations, + localizedHomeUrl, +}: LandingFooterProps): React.JSX.Element { + const t = (key: string): string => translations[key] ?? key + const footerLinks = [ + { href: "https://github.com/jcode-works/jcode-mimir", label: t("nav_github") }, + { href: "https://www.npmjs.com/package/@jcode.labs/mimir", label: t("footer_link_npm") }, + { href: "https://github.com/jcode-works/jcode-mimir#readme", label: t("footer_link_docs") }, + { href: "https://github.com/sponsors/jb-thery", label: t("footer_link_sponsors") }, + ] + + return ( +
+
+ + +
+ + + + +
+ + + +
+

{t("footer_text")}

+

MIT · JCode Labs

+
+
+
+ ) +} diff --git a/packages/mimir-landing/src/components/landing-hero.tsx b/packages/mimir-landing/src/components/landing-hero.tsx index 8fd610c..d86e934 100644 --- a/packages/mimir-landing/src/components/landing-hero.tsx +++ b/packages/mimir-landing/src/components/landing-hero.tsx @@ -16,25 +16,32 @@ import { cn } from "@jcode.labs/mimir-ui/utils" import { ArrowRight, Bot, + Bug, + Check, ChevronDown, ClipboardCheck, Code2, + Copy, Database, Download, FileSearch, - GitBranch, - Globe2, Handshake, HardDrive, + Headphones, LockKeyhole, Monitor, Plug, + Replace, Scale, ShieldCheck, UserPlus, Users, } from "lucide-react" -import { useEffect, useState } from "react" +import { useEffect, useRef, useState } from "react" +import { GithubIcon } from "./github-icon" +import { HeroDemo } from "./hero-demo" +import { LandingFooter } from "./landing-footer" +import { LandingNavbar } from "./landing-navbar" interface LandingHeroProps { locale: string @@ -43,18 +50,16 @@ interface LandingHeroProps { localizedLibraryUrl: string localizedUseCasesUrl: string localizedDesktopUrl: string + localizedTeamUrl: string alternateLocales: Array<{ locale: string; label: string; href: string }> translations: Record } -interface InstallCommand { +interface PackageManager { + id: string label: string - command: string -} - -interface CommandLine { - label: string - command: string + add: string + exec: string } export function LandingHero({ @@ -64,35 +69,45 @@ export function LandingHero({ localizedDesktopUrl, localizedHomeUrl, localizedLibraryUrl, + localizedTeamUrl, localizedUseCasesUrl, translations, }: LandingHeroProps): React.JSX.Element { const t = (key: string): string => translations[key] ?? key - const [hasScrolled, setHasScrolled] = useState(false) - - useEffect(() => { - const handleScroll = () => setHasScrolled(window.scrollY > 12) - handleScroll() - window.addEventListener("scroll", handleScroll, { passive: true }) - return () => window.removeEventListener("scroll", handleScroll) - }, []) - const installCommands: InstallCommand[] = [ + const searchQuery = t("quickstart_search_query") + const packageManagers: PackageManager[] = [ + { id: "pnpm", label: "pnpm", add: "pnpm add -D @jcode.labs/mimir", exec: "pnpm exec" }, + { id: "npm", label: "npm", add: "npm install --save-dev @jcode.labs/mimir", exec: "npm exec" }, + { id: "yarn", label: "yarn", add: "yarn add --dev @jcode.labs/mimir", exec: "yarn exec" }, { - label: t("install_pnpm_label"), - command: t("install_pnpm_command"), + id: "mise", + label: "mise", + add: "mise exec node@24 -- npm install --save-dev @jcode.labs/mimir", + exec: "mise exec node@24 -- npm exec", }, + ] + const installSteps = [ { - label: t("install_npm_label"), - command: t("install_npm_command"), + key: "install", + label: t("quickstart_install_label"), + build: (manager: PackageManager) => manager.add, }, { - label: t("install_yarn_label"), - command: t("install_yarn_command"), + key: "setup", + label: t("quickstart_setup_label"), + build: (manager: PackageManager) => `${manager.exec} mimir setup`, }, { - label: t("install_mise_label"), - command: t("install_mise_command"), + key: "agent", + label: t("quickstart_agent_label"), + build: (manager: PackageManager) => + `${manager.exec} mimir install-agent --agents claude,codex,kimi`, + }, + { + key: "search", + label: t("quickstart_search_label"), + build: (manager: PackageManager) => `${manager.exec} mimir search "${searchQuery}"`, }, ] @@ -111,27 +126,6 @@ export function LandingHero({ }, ] - const workspaceFiles = [ - t("workspace_file_contracts"), - t("workspace_file_policy"), - t("workspace_file_research"), - ] - - const workspaceStatuses = [ - { - icon: LockKeyhole, - label: t("workspace_status_redaction"), - }, - { - icon: Database, - label: t("workspace_status_index"), - }, - { - icon: Plug, - label: t("workspace_status_mcp"), - }, - ] - const privacyControls = [ { icon: HardDrive, @@ -155,21 +149,6 @@ export function LandingHero({ }, ] - const quickStartCommands: CommandLine[] = [ - { - label: t("quickstart_setup_label"), - command: t("quickstart_setup_command"), - }, - { - label: t("quickstart_agent_label"), - command: t("quickstart_agent_command"), - }, - { - label: t("quickstart_search_label"), - command: t("quickstart_search_command"), - }, - ] - const agentTargets = [ { name: "Claude", @@ -196,6 +175,24 @@ export function LandingHero({ title: t("use_case_onboarding_title"), text: t("use_case_onboarding_text"), }, + { + icon: Headphones, + eyebrow: t("use_case_audio_eyebrow"), + title: t("use_case_audio_title"), + text: t("use_case_audio_text"), + }, + { + icon: Replace, + eyebrow: t("use_case_refactor_eyebrow"), + title: t("use_case_refactor_title"), + text: t("use_case_refactor_text"), + }, + { + icon: Bug, + eyebrow: t("use_case_debug_eyebrow"), + title: t("use_case_debug_title"), + text: t("use_case_debug_text"), + }, { icon: Users, eyebrow: t("use_case_team_eyebrow"), @@ -247,17 +244,37 @@ export function LandingHero({ ] const faqItems = [ + { + question: t("faq_what_question"), + answer: t("faq_what_answer"), + }, { question: t("faq_private_question"), answer: t("faq_private_answer"), }, + { + question: t("faq_agents_question"), + answer: t("faq_agents_answer"), + }, + { + question: t("faq_offline_question"), + answer: t("faq_offline_answer"), + }, + { + question: t("faq_compare_question"), + answer: t("faq_compare_answer"), + }, + { + question: t("faq_role_question"), + answer: t("faq_role_answer"), + }, { question: t("faq_formats_question"), answer: t("faq_formats_answer"), }, { - question: t("faq_desktop_question"), - answer: t("faq_desktop_answer"), + question: t("faq_license_question"), + answer: t("faq_license_answer"), }, ] @@ -266,93 +283,57 @@ export function LandingHero({ rel: "noopener noreferrer", } as const + const ctaLargeClass = "h-14 gap-2 px-8 text-sm font-bold" + return (
-
) } -function CommandLineBlock({ +const copyToastSubscribers = new Set<() => void>() + +function emitCopyToast(): void { + for (const notify of copyToastSubscribers) notify() +} + +function CommandCopyBox({ command, - icon, - label, + copyLabel, }: { command: string - icon?: React.ReactNode - label: string + copyLabel: string }): React.JSX.Element { + const [copied, setCopied] = useState(false) + const resetTimeout = useRef | null>(null) + + useEffect(() => { + return () => { + if (resetTimeout.current) clearTimeout(resetTimeout.current) + } + }, []) + + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(command) + } catch { + return + } + setCopied(true) + emitCopyToast() + if (resetTimeout.current) clearTimeout(resetTimeout.current) + resetTimeout.current = setTimeout(() => setCopied(false), 1500) + } + return ( -
-
- {icon} -

{label}

-
-
- $ {command} -
-
+ ) } -function InstallCommandTabs({ commands }: { commands: InstallCommand[] }): React.JSX.Element { - const defaultValue = commands[0]?.label ?? "" +function CommandCopyToast({ message }: { message: string }): React.JSX.Element { + const [visible, setVisible] = useState(false) + const hideTimeout = useRef | null>(null) + + useEffect(() => { + const notify = () => { + setVisible(true) + if (hideTimeout.current) clearTimeout(hideTimeout.current) + hideTimeout.current = setTimeout(() => setVisible(false), 2000) + } + copyToastSubscribers.add(notify) + return () => { + copyToastSubscribers.delete(notify) + if (hideTimeout.current) clearTimeout(hideTimeout.current) + } + }, []) return ( - - - {commands.map((entry) => ( - - {entry.label} - - ))} - - {commands.map((entry) => ( - - $ {entry.command} - - ))} - +
+
) } -function LanguageSwitcher({ - alternateLocales, - currentLocale, +function CommandLineBlock({ + command, + copyLabel, + icon, label, }: { - alternateLocales: Array<{ locale: string; label: string; href: string }> - currentLocale: string + command: string + copyLabel: string + icon?: React.ReactNode label: string }): React.JSX.Element { - const currentLabel = - alternateLocales.find((entry) => entry.locale === currentLocale)?.label ?? currentLocale - - function handleLocaleClick( - locale: string, - href: string, - event: React.MouseEvent, - ) { - try { - window.localStorage.setItem("mimir-locale", locale) - window.localStorage.setItem("i18nextLng", locale) - } catch { - // Navigation still applies the selected locale when storage is blocked. - } - - const suffix = `${window.location.search}${window.location.hash}` - if (!suffix) return - - event.preventDefault() - const url = new URL(href, window.location.origin) - url.search = window.location.search - url.hash = window.location.hash - window.location.href = url.toString() - } - return ( -
- - -
+ ) } diff --git a/packages/mimir-landing/src/components/landing-navbar.tsx b/packages/mimir-landing/src/components/landing-navbar.tsx new file mode 100644 index 0000000..3597eb2 --- /dev/null +++ b/packages/mimir-landing/src/components/landing-navbar.tsx @@ -0,0 +1,304 @@ +import { Button } from "@jcode.labs/mimir-ui" +import { cn } from "@jcode.labs/mimir-ui/utils" +import { ChevronDown, Globe2, Menu, X } from "lucide-react" +import { useEffect, useRef, useState } from "react" +import { GithubIcon } from "./github-icon" + +interface AlternateLocale { + locale: string + label: string + href: string +} + +interface LandingNavbarProps { + translations: Record + locale: string + alternateLocales: AlternateLocale[] + localizedHomeUrl: string + localizedUseCasesUrl: string + localizedLibraryUrl: string + localizedAgentsUrl: string + localizedDesktopUrl: string + localizedTeamUrl: string +} + +const externalLinkProps = { + target: "_blank", + rel: "noopener noreferrer", +} as const + +const GITHUB_URL = "https://github.com/jcode-works/jcode-mimir" + +export function MimirLogo(): React.JSX.Element { + return ( + + Mimir + + + + ) +} + +export function LanguageSwitcher({ + alternateLocales, + currentLocale, + label, +}: { + alternateLocales: AlternateLocale[] + currentLocale: string + label: string +}): React.JSX.Element { + const currentLabel = + alternateLocales.find((entry) => entry.locale === currentLocale)?.label ?? currentLocale + + function handleLocaleClick(href: string, event: React.MouseEvent) { + // Preserve the current query string and hash when switching locale. + const suffix = `${window.location.search}${window.location.hash}` + if (!suffix) return + + event.preventDefault() + const url = new URL(href, window.location.origin) + url.search = window.location.search + url.hash = window.location.hash + window.location.href = url.toString() + } + + return ( +
+ + +
+ {alternateLocales.map((entry) => ( + handleLocaleClick(entry.href, event)} + > + {entry.label} + + ))} +
+
+ ) +} + +export function LandingNavbar({ + translations, + locale, + alternateLocales, + localizedHomeUrl, + localizedUseCasesUrl, + localizedLibraryUrl, + localizedAgentsUrl, + localizedDesktopUrl, + localizedTeamUrl, +}: LandingNavbarProps): React.JSX.Element { + const t = (key: string): string => translations[key] ?? key + const [hasScrolled, setHasScrolled] = useState(false) + const [menuOpen, setMenuOpen] = useState(false) + const headerRef = useRef(null) + const lastScrollY = useRef(0) + const isHidden = useRef(false) + + const navLinks = [ + { href: localizedHomeUrl, label: t("nav_home") }, + { href: localizedUseCasesUrl, label: t("nav_use_cases") }, + { href: localizedLibraryUrl, label: t("nav_library") }, + { href: localizedAgentsUrl, label: t("nav_agents") }, + { href: localizedDesktopUrl, label: t("nav_desktop") }, + { href: localizedTeamUrl, label: t("nav_team") }, + ] + + useEffect(() => { + const header = headerRef.current + if (!header) return + + // Hide-on-scroll: hide the header when scrolling down, reveal when scrolling up. + const handleScroll = () => { + const currentScrollY = window.scrollY + const scrollThreshold = 50 + setHasScrolled(currentScrollY > 0) + + if (currentScrollY < scrollThreshold) { + if (isHidden.current) { + header.style.transform = "translateY(0)" + isHidden.current = false + } + lastScrollY.current = currentScrollY + return + } + + const scrollDelta = currentScrollY - lastScrollY.current + if (scrollDelta > 5 && !isHidden.current) { + header.style.transform = "translateY(-100%)" + isHidden.current = true + lastScrollY.current = currentScrollY + } else if (scrollDelta < -5 && isHidden.current) { + header.style.transform = "translateY(0)" + isHidden.current = false + lastScrollY.current = currentScrollY + } else if ((scrollDelta > 0 && isHidden.current) || (scrollDelta < 0 && !isHidden.current)) { + lastScrollY.current = currentScrollY + } + } + + window.addEventListener("scroll", handleScroll, { passive: true }) + return () => window.removeEventListener("scroll", handleScroll) + }, []) + + useEffect(() => { + if (!menuOpen) return + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") setMenuOpen(false) + } + document.addEventListener("keydown", onKeyDown) + document.body.style.overflow = "hidden" + return () => { + document.removeEventListener("keydown", onKeyDown) + document.body.style.overflow = "" + } + }, [menuOpen]) + + return ( + <> + + + + ) +} diff --git a/packages/mimir-landing/src/components/team-card.tsx b/packages/mimir-landing/src/components/team-card.tsx new file mode 100644 index 0000000..a6fc4cc --- /dev/null +++ b/packages/mimir-landing/src/components/team-card.tsx @@ -0,0 +1,57 @@ +import { cn } from "@jcode.labs/mimir-ui/utils" + +interface TeamCardLink { + label: string + href: string +} + +interface TeamCardProps { + title: string + subtitle: string + description: string + img: string + links: TeamCardLink[] + className?: string +} + +export function TeamCard({ + title, + subtitle, + description, + img, + links, + className, +}: TeamCardProps): React.JSX.Element { + return ( +
+
+ {title} +
+

{title}

+

{subtitle}

+
+ {links.map((link) => ( + + {link.label} + + ))} +
+

{description}

+
+ ) +} diff --git a/packages/mimir-landing/src/layouts/layout.astro b/packages/mimir-landing/src/layouts/layout.astro index 1ab0c1f..06eeb73 100644 --- a/packages/mimir-landing/src/layouts/layout.astro +++ b/packages/mimir-landing/src/layouts/layout.astro @@ -23,7 +23,7 @@ const { keywords, locale = "en", ogType = "website", - ogImage = "/og/mimir-og.svg", + ogImage = "/og/mimir-og.png", ogImageAlt = "Mimir open-source local RAG for confidential documents", twitterCard = "summary_large_image", author = "Jean-Baptiste Thery", @@ -169,6 +169,16 @@ function alternateHref(targetLocale: string): string { {title} + { + isProduction && ( + + diff --git a/packages/mimir-landing/src/pages/500.astro b/packages/mimir-landing/src/pages/500.astro new file mode 100644 index 0000000..fbcd9fd --- /dev/null +++ b/packages/mimir-landing/src/pages/500.astro @@ -0,0 +1,62 @@ +--- +import { MimirBackground } from "@jcode.labs/mimir-ui" +import { defaultLang, getLocalizedUrl } from "../i18n/utils" +import Layout from "../layouts/layout.astro" + +const homeUrl = getLocalizedUrl("/", defaultLang) +--- + + + + +
+ + + Mimir + + + + + +

500

+

+ Something went wrong on our side. Please try again. +

+ + + Back to Mimir + +
+ + {/* Localize French client-side from the URL path (/fr/...). */} + +
diff --git a/packages/mimir-landing/src/pages/[...locale]/index.astro b/packages/mimir-landing/src/pages/[...locale]/index.astro index 809aa97..d17c64c 100644 --- a/packages/mimir-landing/src/pages/[...locale]/index.astro +++ b/packages/mimir-landing/src/pages/[...locale]/index.astro @@ -19,17 +19,37 @@ const alternateLocales = locales.map((entry) => ({ const siteUrl = new URL("/", Astro.site ?? "https://mimir.jcode.works").href const pageUrl = new URL(getLocalizedUrl("/", locale), Astro.site ?? "https://mimir.jcode.works").href const faqItems = [ + { + question: t("faq_what_question"), + answer: t("faq_what_answer"), + }, { question: t("faq_private_question"), answer: t("faq_private_answer"), }, + { + question: t("faq_agents_question"), + answer: t("faq_agents_answer"), + }, + { + question: t("faq_offline_question"), + answer: t("faq_offline_answer"), + }, + { + question: t("faq_compare_question"), + answer: t("faq_compare_answer"), + }, + { + question: t("faq_role_question"), + answer: t("faq_role_answer"), + }, { question: t("faq_formats_question"), answer: t("faq_formats_answer"), }, { - question: t("faq_desktop_question"), - answer: t("faq_desktop_answer"), + question: t("faq_license_question"), + answer: t("faq_license_answer"), }, ] const structuredData = [ @@ -53,6 +73,7 @@ const structuredData = [ "Model Context Protocol", "AI agent tooling", ], + sameAs: ["https://github.com/jcode-works", "https://github.com/jcode-works/jcode-mimir"], }, { "@context": "https://schema.org", @@ -88,6 +109,11 @@ const structuredData = [ applicationCategory: "DeveloperApplication", operatingSystem: "Any", isAccessibleForFree: true, + offers: { + "@type": "Offer", + price: "0", + priceCurrency: "USD", + }, license: "https://github.com/jcode-works/jcode-mimir/blob/main/LICENSE", programmingLanguage: "TypeScript", inLanguage: ["en", "fr"], @@ -135,35 +161,16 @@ const structuredData = [ availableLocales={locales} structuredData={structuredData} > - - {locale === defaultLang && ( - - )} - - diff --git a/packages/mimir-landing/src/pages/[...locale]/team.astro b/packages/mimir-landing/src/pages/[...locale]/team.astro new file mode 100644 index 0000000..9a46d3b --- /dev/null +++ b/packages/mimir-landing/src/pages/[...locale]/team.astro @@ -0,0 +1,143 @@ +--- +import { MimirBackground } from "@jcode.labs/mimir-ui" +import { LandingFooter } from "../../components/landing-footer" +import { LandingNavbar } from "../../components/landing-navbar" +import { TeamCard } from "../../components/team-card" +import { defaultLang, getLocalizedUrl, languages, locales, useTranslations } from "../../i18n/utils" +import Layout from "../../layouts/layout.astro" + +export function getStaticPaths() { + return locales.map((locale) => ({ + params: { locale: locale === defaultLang ? undefined : locale }, + })) +} + +const locale = (Astro.params.locale as string | undefined) ?? defaultLang +const { t, translations } = await useTranslations(locale) +const siteBase = Astro.site ?? new URL("https://mimir.jcode.works") +const homeUrl = getLocalizedUrl("/", locale) +const pageUrl = new URL(getLocalizedUrl("/team", locale), siteBase).href +const alternateLocales = locales.map((entry) => ({ + locale: entry, + label: languages[entry], + href: getLocalizedUrl("/team", entry), +})) + +const memberImg = "/team/jb-thery.jpg" +const memberLinks = [ + { label: "GitHub", href: "https://github.com/jb-thery" }, + { label: "LinkedIn", href: "https://www.linkedin.com/in/jean-baptiste-thery/" }, + { label: "Malt", href: "https://www.malt.fr/profile/jeanbaptistethery/" }, +] + +const structuredData = [ + { + "@context": "https://schema.org", + "@type": "AboutPage", + "@id": pageUrl, + inLanguage: locale, + name: t("seo_team_title"), + description: t("seo_team_description"), + url: pageUrl, + isPartOf: { "@id": "https://mimir.jcode.works/#website" }, + publisher: { "@id": "https://mimir.jcode.works/#organization" }, + mainEntity: { "@id": "https://mimir.jcode.works/#jb-thery" }, + }, + { + "@context": "https://schema.org", + "@type": "BreadcrumbList", + itemListElement: [ + { + "@type": "ListItem", + position: 1, + name: "Mimir", + item: new URL(homeUrl, siteBase).href, + }, + { + "@type": "ListItem", + position: 2, + name: t("seo_team_title"), + item: pageUrl, + }, + ], + }, + { + "@context": "https://schema.org", + "@type": "Person", + "@id": "https://mimir.jcode.works/#jb-thery", + name: "Jean-Baptiste Thery", + jobTitle: t("team_member_jb_subtitle"), + description: t("team_member_jb_description"), + url: pageUrl, + image: new URL(memberImg, siteBase).href, + worksFor: { "@id": "https://mimir.jcode.works/#organization" }, + sameAs: [ + "https://github.com/jb-thery", + "https://www.linkedin.com/in/jean-baptiste-thery/", + "https://www.malt.fr/profile/jeanbaptistethery/", + ], + }, +] +--- + + + + +
+ +
+
+

+ {t("team_eyebrow")} +

+

+ {t("team_title")} +

+
+ +
+
+ +
+
+ +
+

+ {t("team_mission_title")} +

+

{t("team_mission_description_1")}

+

{t("team_mission_description_2")}

+
+
+
+ + +
diff --git a/packages/mimir-landing/src/styles/global.css b/packages/mimir-landing/src/styles/global.css index a5f8421..6e41e80 100644 --- a/packages/mimir-landing/src/styles/global.css +++ b/packages/mimir-landing/src/styles/global.css @@ -38,14 +38,15 @@ --foreground: #ffffff; --card: #090909; --card-foreground: #ffffff; - --primary: #ffffff; - --primary-foreground: #000000; + --primary: #8b47ff; + --primary-foreground: #ffffff; --secondary: #171717; --secondary-foreground: #ffffff; --muted: #161616; --muted-foreground: rgba(255, 255, 255, 0.62); --accent: #f1f5f9; --accent-foreground: #000000; + --accent-title: #8b47ff; --border: rgba(255, 255, 255, 0.12); --input: rgba(0, 0, 0, 0.14); --ring: rgba(255, 255, 255, 0.52); @@ -88,3 +89,91 @@ textarea { background: rgba(255, 255, 255, 0.88); color: #000000; } + +/* Footer marquee — seamless infinite horizontal scroll (track holds duplicated halves) */ +@keyframes marquee-text { + from { + transform: translate3d(0, 0, 0); + } + to { + transform: translate3d(-50%, 0, 0); + } +} + +.animate-marquee-text { + width: max-content; + will-change: transform; + animation: marquee-text 100s linear infinite; +} + +@media (prefers-reduced-motion: reduce) { + .animate-marquee-text { + animation: none; + } +} + +/* Animated glitch logo (stacked, clip-path slices), adapted from the WorkoutGen landing logo */ +@keyframes logo-glitch { + 0% { + text-shadow: + 3px 4px 0 var(--logo-glitch-a), + 3px -4px 0 var(--logo-glitch-b); + transform: translate(var(--glitch-translate)); + } + 2% { + text-shadow: + 3px -4px 0 var(--logo-glitch-a), + -3px 4px 0 var(--logo-glitch-b); + } + 4%, + 100% { + text-shadow: none; + transform: none; + } +} + +.logo-stack { + display: grid; + grid-template-columns: 1fr; + --stacks: 3; + --logo-glitch-a: #8b47ff; + --logo-glitch-b: #22d3ee; +} + +.logo-stack span { + grid-row-start: 1; + grid-column-start: 1; + --stack-height: calc(100% / var(--stacks) - 1px); + --inverse-index: calc(calc(var(--stacks) - 1) - var(--index)); + --clip-top: calc(var(--stack-height) * var(--index)); + --clip-bottom: calc(var(--stack-height) * var(--inverse-index)); + clip-path: inset(var(--clip-top) 0 var(--clip-bottom) 0); + animation: logo-glitch ease infinite 2s alternate-reverse; +} + +.logo-stack span:nth-child(odd) { + --glitch-translate: 8px; +} + +.logo-stack span:nth-child(even) { + --glitch-translate: -8px; +} + +.logo-index-1 { + --index: 0; +} + +.logo-index-2 { + --index: 1; +} + +.logo-index-3 { + --index: 2; +} + +@media (prefers-reduced-motion: reduce) { + .logo-stack span { + animation: none; + clip-path: none; + } +} diff --git a/packages/mimir-landing/wrangler.jsonc b/packages/mimir-landing/wrangler.jsonc index d056618..16f0048 100644 --- a/packages/mimir-landing/wrangler.jsonc +++ b/packages/mimir-landing/wrangler.jsonc @@ -12,5 +12,16 @@ "pattern": "mimir.jcode.works", "custom_domain": true } - ] + ], + "env": { + "staging": { + "name": "mimir-landing-staging", + "routes": [ + { + "pattern": "staging.mimir.jcode.works", + "custom_domain": true + } + ] + } + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a828286..b50cb0d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -145,6 +145,9 @@ importers: '@astrojs/sitemap': specifier: ^3.7.3 version: 3.7.3 + '@gsap/react': + specifier: ^2.1.2 + version: 2.1.2(gsap@3.15.0)(react@19.2.7) '@jcode.labs/mimir-ui': specifier: workspace:* version: link:../mimir-ui @@ -154,6 +157,9 @@ importers: astro: specifier: ^7.0.2 version: 7.0.3(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@24.13.2)(jiti@2.7.0)(yaml@2.9.0) + gsap: + specifier: ^3.15.0 + version: 3.15.0 lucide-react: specifier: ^1.21.0 version: 1.21.0(react@19.2.7) @@ -898,6 +904,12 @@ packages: cpu: [x64] os: [win32] + '@gsap/react@2.1.2': + resolution: {integrity: sha512-JqliybO1837UcgH2hVOM4VO+38APk3ECNrsuSM4MuXp+rbf+/2IG2K1YJiqfTcXQHH7XlA0m3ykniFYstfq0Iw==} + peerDependencies: + gsap: ^3.12.5 + react: '>=17' + '@hono/node-server@1.19.14': resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} engines: {node: '>=18.14.1'} @@ -2784,6 +2796,9 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + gsap@3.15.0: + resolution: {integrity: sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==} + guid-typescript@1.0.9: resolution: {integrity: sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==} @@ -5399,6 +5414,11 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true + '@gsap/react@2.1.2(gsap@3.15.0)(react@19.2.7)': + dependencies: + gsap: 3.15.0 + react: 19.2.7 + '@hono/node-server@1.19.14(hono@4.12.27)': dependencies: hono: 4.12.27 @@ -7294,6 +7314,8 @@ snapshots: graceful-fs@4.2.11: {} + gsap@3.15.0: {} + guid-typescript@1.0.9: {} h3@1.15.11: diff --git a/release.config.cjs b/release.config.cjs index ad20d4c..1bd8f4e 100644 --- a/release.config.cjs +++ b/release.config.cjs @@ -3,7 +3,18 @@ module.exports = { repositoryUrl: "https://github.com/jcode-works/jcode-mimir.git", tagFormat: "v$" + "{version}", plugins: [ - "@semantic-release/commit-analyzer", + [ + "@semantic-release/commit-analyzer", + { + // The landing site is an unpublished surface; its commits must never + // trigger a version bump or npm publish of the library packages. + // Documentation updates publish a patch so the npm readme stays current. + releaseRules: [ + { scope: "landing", release: false }, + { type: "docs", release: "patch" }, + ], + }, + ], "@semantic-release/release-notes-generator", [ "@semantic-release/exec", diff --git a/scripts/semantic-release-smoke.mjs b/scripts/semantic-release-smoke.mjs index 3017ee7..db9b95d 100644 --- a/scripts/semantic-release-smoke.mjs +++ b/scripts/semantic-release-smoke.mjs @@ -13,16 +13,17 @@ if (!Array.isArray(config.branches) || !config.branches.includes("main")) { } const plugins = config.plugins ?? [] -if (!plugins.some((plugin) => plugin === "@semantic-release/commit-analyzer")) { +const pluginName = (plugin) => (Array.isArray(plugin) ? plugin[0] : plugin) +if (!plugins.some((plugin) => pluginName(plugin) === "@semantic-release/commit-analyzer")) { throw new Error("semantic-release commit analyzer plugin is missing") } -if (!plugins.some((plugin) => plugin === "@semantic-release/release-notes-generator")) { +if (!plugins.some((plugin) => pluginName(plugin) === "@semantic-release/release-notes-generator")) { throw new Error("semantic-release release notes plugin is missing") } -if (!plugins.some((plugin) => Array.isArray(plugin) && plugin[0] === "@semantic-release/exec")) { +if (!plugins.some((plugin) => pluginName(plugin) === "@semantic-release/exec")) { throw new Error("semantic-release exec plugin is missing") } -if (!plugins.some((plugin) => Array.isArray(plugin) && plugin[0] === "@semantic-release/github")) { +if (!plugins.some((plugin) => pluginName(plugin) === "@semantic-release/github")) { throw new Error("semantic-release GitHub plugin is missing") }