Compare commits
5 Commits
dev
...
1788b2b5be
| Author | SHA1 | Date | |
|---|---|---|---|
| 1788b2b5be | |||
| 85f0fc0dc5 | |||
| fb9c13fbd5 | |||
| 9895244535 | |||
| df0bcae250 |
@@ -4,7 +4,7 @@ Two hooks, both small, both readable in a minute, both safe to delete. They exis
|
||||
|
||||
| Hook | Event | What it does |
|
||||
| --- | --- | --- |
|
||||
| `session-context.mjs` | `sessionStart` | Injects the session's starting facts: whether the model lanes are bound (and whether the picker drifted from the recorded lead), the handoff's next action, any of the four capped context files currently over its cap, and the standing workflow (commit after each task; `graphify update .` at session end). |
|
||||
| `session-context.mjs` | `sessionStart` | Injects the session's starting facts: whether the model lanes are bound (and whether the picker drifted from the recorded lead), the handoff's next action, and any of the four capped context files currently over its cap. |
|
||||
| `guard-destructive.mjs` | `beforeShellExecution` | Returns `ask` — never `deny` — for force pushes, `rm -rf`, migrations, deploys, infra changes, pipe-to-shell, and friends, with the reason named and the gate quoted back to the agent. Routine `git push` is deliberately not on the list. |
|
||||
|
||||
## Why these two
|
||||
|
||||
@@ -132,10 +132,6 @@ function main(payload) {
|
||||
notes.push(`Context caps EXCEEDED: ${over.join(" · ")}. Run /memory-sync before adding anything.`);
|
||||
}
|
||||
|
||||
notes.push(
|
||||
"Workflow: commit after each landed task (no secrets). Session end: graphify update .",
|
||||
);
|
||||
|
||||
return notes.length
|
||||
? { additional_context: `Project state (from .cursor/hooks/session-context.mjs):\n- ${notes.join("\n- ")}` }
|
||||
: {};
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
description: graphify knowledge graph context
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
This project has a graphify knowledge graph at graphify-out/.
|
||||
|
||||
**MANDATORY: Before using Read, Grep, Glob, or Bash to explore the codebase, you MUST run graphify first:**
|
||||
- `graphify query "<question>"` — scoped subgraph for any codebase or architecture question
|
||||
- `graphify path "<A>" "<B>"` — dependency path between two symbols
|
||||
- `graphify explain "<concept>"` — all nodes related to a concept
|
||||
|
||||
This applies to YOU and to every subagent you spawn. Include this rule explicitly in every subagent prompt that involves code exploration. Do not skip graphify because files are "already known" or because you are executing a plan — the graph surfaces cross-file dependencies and INFERRED edges that grep and Read cannot find.
|
||||
|
||||
Only use Read/Grep/Glob directly when:
|
||||
1. graphify has already oriented you and you need to modify or debug specific lines
|
||||
2. `graphify-out/graph.json` does not exist yet
|
||||
|
||||
- If `graphify-out/wiki/index.md` exists, navigate it instead of reading raw files
|
||||
- Read `graphify-out/GRAPH_REPORT.md` only for broad architecture review when query/path/explain do not surface enough context
|
||||
- After modifying code files, run `graphify update .` to keep the graph current (AST-only, no API cost).
|
||||
- At **session end**, always run `graphify update .` even if the last edits were docs-only, so the next session's queries match the tree.
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -11,6 +11,3 @@ dist/
|
||||
# Playwright
|
||||
test-results/
|
||||
.tmp-*
|
||||
|
||||
# Graphify knowledge graph (local, regenerable)
|
||||
graphify-out/
|
||||
|
||||
@@ -200,7 +200,6 @@ When the user corrects you, a test or review proves a mistake, or you discover a
|
||||
## Lessons
|
||||
|
||||
- Website Coolify deploys must `npm start` (`server.mjs`) or the Dockerfile; Vite preview is not production.
|
||||
- After each landed task, commit (no secrets); session end run `graphify update .`.
|
||||
|
||||
## Memory protocol
|
||||
|
||||
@@ -209,8 +208,8 @@ Subagents have no memory and cannot see this conversation — durable knowledge
|
||||
## State continuity and proactive improvement
|
||||
|
||||
- Do not rely on the transcript for project state. At the start of a resumed or fresh conversation, invoke `/resume-project` before planning or editing.
|
||||
- Before ending a substantial task, update `docs/HANDOFF.md` with the verified state, changed paths, checks run, risks, and next smallest action, and promote durable knowledge per the memory protocol. Then **commit** the landed files (no secrets, no `graphify-out/`); do not push unless asked.
|
||||
- At every phase seal and session end, refresh `docs/PROGRESS.md` for the owner in plain language. Then run `graphify update .` (AST-only) so `graphify-out/` matches the tree. `HANDOFF.md` speaks to the next agent; `PROGRESS.md` speaks to the owner.
|
||||
- Before ending a substantial task, update `docs/HANDOFF.md` with the verified state, changed paths, checks run, risks, and next smallest action, and promote durable knowledge per the memory protocol.
|
||||
- At every phase seal and session end, refresh `docs/PROGRESS.md` for the owner in plain language. `HANDOFF.md` speaks to the next agent; `PROGRESS.md` speaks to the owner.
|
||||
- After a verified recurring mistake, correction, or workflow gap, invoke `/continuous-improvement` and delegate changes to `system-steward`.
|
||||
- Improve agents, skills, and rules only from concrete evidence. Keep changes small and testable; never silently change lane routing, tool permissions, hooks, safety rules, or external-action authority.
|
||||
|
||||
|
||||
@@ -337,7 +337,6 @@ Keep rules general, evidence-backed, and under 20 words. Never add secrets, pers
|
||||
- Style inline; Tailwind classes do nothing until PostCSS is wired.
|
||||
- Verify CHANGELOG entries against code, not commit subjects or handoff summaries.
|
||||
- Website Coolify deploys must `npm start` (`server.mjs`) or the Dockerfile; Vite preview is not production.
|
||||
- After each landed task, commit (no secrets); session end run `graphify update .`.
|
||||
|
||||
## Memory protocol
|
||||
|
||||
@@ -355,8 +354,8 @@ Run `/memory-sync` at a phase change, before ending a long run, or when any capp
|
||||
## State continuity and proactive improvement
|
||||
|
||||
- Do not rely on the chat transcript for project state. At the start of a resumed, compacted, or fresh session, invoke `/resume-project` before planning or editing.
|
||||
- Before ending a substantial task, update `docs/HANDOFF.md` with the verified state, changed paths, checks run, risks, and the next smallest action, and promote any durable new knowledge per the memory protocol. Then **commit** the landed files (no secrets, no `graphify-out/`); do not push unless asked.
|
||||
- At every phase seal and session end, refresh `docs/PROGRESS.md` for the owner in plain language: what newly works and how to see it, the *Waiting on you* queue, and what proceeds without them. Then run `graphify update .`. `HANDOFF.md` speaks to the next agent; `PROGRESS.md` speaks to the owner.
|
||||
- Before ending a substantial task, update `docs/HANDOFF.md` with the verified state, changed paths, checks run, risks, and the next smallest action, and promote any durable new knowledge per the memory protocol.
|
||||
- At every phase seal and session end, refresh `docs/PROGRESS.md` for the owner in plain language: what newly works and how to see it, the *Waiting on you* queue, and what proceeds without them. `HANDOFF.md` speaks to the next agent; `PROGRESS.md` speaks to the owner.
|
||||
- After a verified recurring mistake, user correction, or workflow gap, invoke `/continuous-improvement`. Opus delegates changes to `system-steward`.
|
||||
- Improve agents and skills only from concrete evidence. Keep changes small, testable, and versioned; never silently change model routing, tool permissions, safety rules, or external-action authority.
|
||||
|
||||
|
||||
@@ -22,16 +22,6 @@ Add an entry when a choice is hard to reverse, shapes future work, or a future m
|
||||
|
||||
## Log
|
||||
|
||||
### D-2026-09-04-01 — Commit each landed task; graphify update at session end
|
||||
|
||||
- **Status:** accepted
|
||||
- **Context:** Graph and git lagged behind work (SEO sat uncommitted; graph needed a manual deep rebuild).
|
||||
- **Decision:** After every completed task, commit landed files (never secrets or `graphify-out/`). Do not push unless asked. At session end, run `graphify update .`.
|
||||
- **Alternatives considered:** Commit only when asked (lost work); full `/graphify --mode deep` every session (too expensive).
|
||||
- **Consequences:** More small commits on `dev`. Graph stays AST-current; docs/images still need an occasional deep extract.
|
||||
- **Verification:** Session-start hook reminds the workflow; `graphify-out/` stays gitignored.
|
||||
- **Owner / date:** John Kevin, 2026-09-04
|
||||
|
||||
### D-2026-09-03-01 — Website path URLs for Google indexing
|
||||
|
||||
- **Status:** accepted
|
||||
|
||||
@@ -32,5 +32,3 @@ Tap targets ≥ 40px; body text contrast AA.
|
||||
| PackageCard | Chrome / VS Code / CLI |
|
||||
| DocsSection | Package docs blocks |
|
||||
| Not found | Unknown path: H1 + Home / Docs |
|
||||
| Breadcrumbs | Inner pages: Home / current |
|
||||
| FAQ | Home `<details>` list; copy must match FAQ JSON-LD |
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## Current state
|
||||
|
||||
- **Outcome:** Standing workflow: commit after each landed task; session end `graphify update .`.
|
||||
- **Shipped:** `AGENTS.md` / `CLAUDE.md` lessons + state continuity; `.cursor/rules/graphify.mdc`; session-start reminder; `D-2026-09-04-01`.
|
||||
- **Verified:** docs/rules updated; graphify-out gitignored.
|
||||
- **Next smallest action:** Continue product work; this session will commit this change and run `graphify update .`.
|
||||
- **Outcome:** Website prepared for Google indexing (path URLs, robots, sitemap, unique titles).
|
||||
- **Shipped:** BrowserRouter; hash `#/…` rewrite; `public/robots.txt` + `sitemap.xml`; `src/seo.ts` + `SeoHead`; `inject-seo.mjs`; HTTP 404 + `NotFoundPage`; origin `https://lexai.juankibin.space`.
|
||||
- **Verified:** `website:build` OK. Local `node server.mjs`: `/` `/docs` `/robots.txt` `/sitemap.xml` 200 (docs title unique; sitemap `application/xml`); `/no-such-page` 404.
|
||||
- **Next smallest action:** Redeploy Coolify, then Search Console: verify property, submit sitemap, request index of `/` and `/docs`.
|
||||
|
||||
@@ -6,16 +6,6 @@ This is the project’s shared, evidence-backed memory of mistakes worth prevent
|
||||
|
||||
`AGENTS.md` → `## Lessons` is the canonical active rule list loaded every session. Keep supporting evidence here; mirror an active rule here only when its detail is useful for maintenance. Keep at most 12 active rules, each short and imperative.
|
||||
|
||||
### L-2026-09-04-01 — Commit each landed task; session end `graphify update .`
|
||||
|
||||
- **Status:** active
|
||||
- **Trigger:** Owner asked to stop leaving finished work uncommitted and the knowledge graph stale.
|
||||
- **Root cause / failure boundary:** Commit-on-request plus no session-end graph refresh.
|
||||
- **Prevention:** Commit after each landed task (no secrets). Session end: `graphify update .`.
|
||||
- **Evidence:** Owner instruction 2026-09-04; graphify CLI `update`.
|
||||
- **Eval:** manual guardrail — git status + graphify-out mtime
|
||||
- **Owner / review:** lead / if auto-commit becomes noisy
|
||||
|
||||
### L-2026-08-17-01 — Serve website `dist/` on `0.0.0.0:$PORT`; do not use Vite preview in production
|
||||
|
||||
- **Status:** active
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
- Prefer encrypted Chrome key path; keep plaintext `apiKey` fallback until migration.
|
||||
- VS Code / CLI must not import `@lib/crypto` or `@lib/messaging`.
|
||||
- Root scripts: `install:all`, `chrome:*`, `vscode:*`, `cli:*`, `website:*`.
|
||||
- After each landed task: git commit (no secrets; `graphify-out/` ignored). Session end: `graphify update .`. Do not push unless asked.
|
||||
|
||||
## Environment quirks
|
||||
|
||||
|
||||
@@ -2,28 +2,34 @@
|
||||
|
||||
> For the owner. What works, how to see it, and what's waiting on you — plain language.
|
||||
|
||||
**Updated:** 2026-09-04 · **Overall:** Search Console is live; agents now commit after each task and refresh the code graph at session end
|
||||
**Updated:** 2026-09-03 · **Overall:** Website is ready for Google Search; needs a Coolify redeploy, then Search Console
|
||||
|
||||
## What works now
|
||||
|
||||
- Chrome extension: select on any page → AI action → Replace/Copy
|
||||
- VS Code extension: select in editor → LexAI context menu / Command Palette → selection replaced
|
||||
- Website: real paths, sitemap accepted by Google. Repo now has stronger titles, a home FAQ, and richer structured data (not live until you deploy)
|
||||
- Website (`packages/website`): Home, Docs, Contribute, Community at real paths (`/docs`, not `/#/docs`). `/robots.txt` and `/sitemap.xml` are in the build.
|
||||
|
||||
## See it yourself
|
||||
## See it yourself — website on Coolify
|
||||
|
||||
1. Merge/push this change, Coolify on **main**, redeploy `packages/website`.
|
||||
2. Home should end with an FAQ. Docs should show Home / Docs crumbs.
|
||||
3. Search Console → URL inspection → request indexing for `https://lexai.juankibin.space/` and `/docs`.
|
||||
1. Pull/push this change, then in Coolify set **Base Directory** to `packages/website` (no leading slash).
|
||||
2. Turn **Is it a static site?** **off**.
|
||||
3. Either **Build Pack: Dockerfile** and port **80**, or **Nixpacks** and port **3000**.
|
||||
4. Redeploy, then open `https://lexai.juankibin.space/` — docs at `/docs`.
|
||||
5. In [Google Search Console](https://search.google.com/search-console): add that URL, verify ownership, submit `https://lexai.juankibin.space/sitemap.xml`, request indexing for Home and Docs.
|
||||
|
||||
Local check: `npm run website:build` then `npm run website:start` and visit http://127.0.0.1:3000/
|
||||
|
||||
## Waiting on you
|
||||
|
||||
| # | Decision | Options (recommended bold) | What it unblocks |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | Deploy this SEO pass to production | **Redeploy Coolify from main** / wait | Google seeing the new titles/FAQ |
|
||||
| 2 | Request indexing for Home and Docs | **Do after deploy** / later | Faster first index |
|
||||
| 3 | Grammarly reference screenshots? | **Later** / capture into `docs/reference/` | Chrome UX gauntlet |
|
||||
| 1 | Redeploy the Coolify website with the settings above | **Dockerfile + port 80** / Nixpacks + port 3000 | public LexAI site + crawlable URLs |
|
||||
| 2 | Google Search Console: verify + submit sitemap | **Do this after redeploy** / later | Google indexing |
|
||||
| 3 | Confirm Cursor mid/strong model picker IDs | **Keep written IDs** / send exact picker strings | reliable Task dispatch |
|
||||
| 4 | Grammarly reference screenshots? | **Later** / capture into `docs/reference/` | Chrome UX gauntlet |
|
||||
| 5 | Publish VS Code extension to Marketplace? | **Stay local for now** / set publisher + publish | public install |
|
||||
|
||||
## Next up — proceeds without you
|
||||
|
||||
- Rankings take days to weeks; no extra code until the new HTML is live
|
||||
- Optional: VS Code Prompt Builder / style quick-pick / Marketplace packaging when you ask
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
# Home FAQ (SEO)
|
||||
|
||||
## User + job
|
||||
|
||||
Someone comparing LexAI to Grammarly or Copilot gets a direct answer without leaving Home. Success: they install or open Docs.
|
||||
|
||||
## Flow
|
||||
|
||||
Home → FAQ section → expand one question → Docs or store CTA already on the page. Target: read in place (0 extra pages).
|
||||
|
||||
## Screen states
|
||||
|
||||
| State | What |
|
||||
| --- | --- |
|
||||
| Success | Five `<details>`; first collapsed |
|
||||
| Empty / loading / error / offline | Static copy; no extra states |
|
||||
|
||||
## Copy
|
||||
|
||||
Questions and answers live in `packages/website/src/faq.json` only (visible text = JSON-LD).
|
||||
|
||||
## Accessibility
|
||||
|
||||
`summary` min-height 44px; native details/summary keyboard. Breadcrumbs: `nav aria-label="Breadcrumb"` on Docs, Contribute, Community.
|
||||
|
||||
## Acceptance
|
||||
|
||||
1. Home `#faq` shows the five Q&As from `faq.json`.
|
||||
2. Built `dist/index.html` includes `FAQPage` JSON-LD with the same questions.
|
||||
3. Docs/Contribute/Community show Home / current breadcrumbs.
|
||||
@@ -6,31 +6,23 @@
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Free Chrome extension and VS Code assist. Bring your own OpenAI, Anthropic, Groq, or OpenRouter key. No LexAI servers, account, or subscription."
|
||||
content="LexAI — bring-your-own-LLM writing and code assist. Chrome, VS Code, and CLI. No LexAI servers, no subscription."
|
||||
/>
|
||||
<link rel="canonical" href="https://lexai.juankibin.space/" />
|
||||
<meta name="theme-color" content="#f7f7f5" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="LexAI" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="LexAI — Grammarly-style writing help with your own API key"
|
||||
/>
|
||||
<meta property="og:title" content="LexAI — BYO-LLM writing & code assist" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Free Chrome extension and VS Code assist. Bring your own OpenAI, Anthropic, Groq, or OpenRouter key. No LexAI servers, account, or subscription."
|
||||
content="LexAI — bring-your-own-LLM writing and code assist. Chrome, VS Code, and CLI. No LexAI servers, no subscription."
|
||||
/>
|
||||
<meta property="og:url" content="https://lexai.juankibin.space/" />
|
||||
<meta property="og:image" content="https://lexai.juankibin.space/icon.svg" />
|
||||
<meta property="og:image:alt" content="LexAI logo" />
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="LexAI — Grammarly-style writing help with your own API key"
|
||||
/>
|
||||
<meta name="twitter:title" content="LexAI — BYO-LLM writing & code assist" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Free Chrome extension and VS Code assist. Bring your own OpenAI, Anthropic, Groq, or OpenRouter key. No LexAI servers, account, or subscription."
|
||||
content="LexAI — bring-your-own-LLM writing and code assist. Chrome, VS Code, and CLI. No LexAI servers, no subscription."
|
||||
/>
|
||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
@@ -39,7 +31,7 @@
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>LexAI — Grammarly-style writing help with your own API key</title>
|
||||
<title>LexAI — BYO-LLM writing & code assist</title>
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
|
||||
@@ -7,64 +7,8 @@ const distHtml = join(root, 'dist', 'index.html');
|
||||
const { origin, routes } = JSON.parse(
|
||||
readFileSync(join(root, 'src', 'seo.routes.json'), 'utf8'),
|
||||
);
|
||||
const faq = JSON.parse(readFileSync(join(root, 'src', 'faq.json'), 'utf8'));
|
||||
|
||||
const CHROME_STORE =
|
||||
'https://chromewebstore.google.com/detail/bagpcheidbkfgijnnmolnkgagibbjfnk';
|
||||
const VSCODE_MARKETPLACE =
|
||||
'https://marketplace.visualstudio.com/items?itemName=JuanKibin.lexai-vscode';
|
||||
const REPO = 'https://git.juankibin.space/kibin/LexAI';
|
||||
|
||||
const homeDescription = routes.find((r) => r.path === '/')?.description ?? '';
|
||||
|
||||
function softwareGraph() {
|
||||
return {
|
||||
'@type': 'SoftwareApplication',
|
||||
name: 'LexAI',
|
||||
applicationCategory: 'BrowserApplication',
|
||||
applicationSubCategory: 'Writing assistance',
|
||||
operatingSystem: 'Chrome, VS Code, Windows, macOS, Linux',
|
||||
url: `${origin}/`,
|
||||
downloadUrl: [CHROME_STORE, VSCODE_MARKETPLACE],
|
||||
license: `${REPO}/src/branch/main/LICENSE`,
|
||||
sameAs: [REPO, CHROME_STORE, VSCODE_MARKETPLACE],
|
||||
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
|
||||
};
|
||||
}
|
||||
|
||||
function jsonLd(route) {
|
||||
const url = route.path === '/' ? `${origin}/` : `${origin}${route.path}`;
|
||||
const graph = [
|
||||
{
|
||||
'@type': 'WebSite',
|
||||
name: 'LexAI',
|
||||
url: `${origin}/`,
|
||||
description: homeDescription,
|
||||
},
|
||||
softwareGraph(),
|
||||
];
|
||||
if (route.path === '/') {
|
||||
graph.push({
|
||||
'@type': 'FAQPage',
|
||||
mainEntity: faq.map((item) => ({
|
||||
'@type': 'Question',
|
||||
name: item.q,
|
||||
acceptedAnswer: { '@type': 'Answer', text: item.a },
|
||||
})),
|
||||
});
|
||||
} else {
|
||||
graph.push({
|
||||
'@type': 'BreadcrumbList',
|
||||
itemListElement: [
|
||||
{ '@type': 'ListItem', position: 1, name: 'Home', item: `${origin}/` },
|
||||
{ '@type': 'ListItem', position: 2, name: route.title, item: url },
|
||||
],
|
||||
});
|
||||
}
|
||||
return JSON.stringify({ '@context': 'https://schema.org', '@graph': graph });
|
||||
}
|
||||
|
||||
function apply(html, { title, description, url, robots, ld }) {
|
||||
function apply(html, { title, description, url, robots }) {
|
||||
let out = html;
|
||||
out = out.replace(/<title>[\s\S]*?<\/title>/, `<title>${title}</title>`);
|
||||
out = out.replace(
|
||||
@@ -84,10 +28,6 @@ function apply(html, { title, description, url, robots, ld }) {
|
||||
`$1${description}$2`,
|
||||
);
|
||||
out = out.replace(/(<link\s+rel="canonical"\s+href=")[^"]*(")/, `$1${url}$2`);
|
||||
out = out.replace(
|
||||
/<script type="application\/ld\+json">[\s\S]*?<\/script>/,
|
||||
`<script type="application/ld+json">${ld}</script>`,
|
||||
);
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -110,7 +50,6 @@ for (const route of routes) {
|
||||
description: route.description,
|
||||
url,
|
||||
robots: 'index, follow',
|
||||
ld: jsonLd(route),
|
||||
}),
|
||||
);
|
||||
written.push(file);
|
||||
@@ -123,30 +62,8 @@ writeFileSync(
|
||||
description: 'That address is not a LexAI page.',
|
||||
url: `${origin}/`,
|
||||
robots: 'noindex, follow',
|
||||
ld: JSON.stringify({
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [{ '@type': 'WebSite', name: 'LexAI', url: `${origin}/` }, softwareGraph()],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
written.push('404.html');
|
||||
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${routes
|
||||
.map(
|
||||
(r, i) => ` <url>
|
||||
<loc>${r.path === '/' ? `${origin}/` : `${origin}${r.path}`}</loc>
|
||||
<lastmod>${today}</lastmod>
|
||||
<changefreq>${i < 2 ? 'weekly' : 'monthly'}</changefreq>
|
||||
<priority>${r.path === '/' ? '1.0' : r.path === '/docs' ? '0.8' : '0.5'}</priority>
|
||||
</url>`,
|
||||
)
|
||||
.join('\n')}
|
||||
</urlset>
|
||||
`;
|
||||
writeFileSync(join(root, 'dist', 'sitemap.xml'), sitemap);
|
||||
written.push('sitemap.xml');
|
||||
|
||||
console.log('SEO HTML written for', written.join(', '));
|
||||
|
||||
@@ -11,13 +11,11 @@ server {
|
||||
}
|
||||
|
||||
location = /robots.txt {
|
||||
add_header Cache-Control "public, max-age=300";
|
||||
try_files /robots.txt =404;
|
||||
}
|
||||
|
||||
location = /sitemap.xml {
|
||||
default_type application/xml;
|
||||
add_header Cache-Control "public, max-age=300";
|
||||
try_files /sitemap.xml =404;
|
||||
}
|
||||
|
||||
|
||||
@@ -82,12 +82,7 @@ const server = createServer((req, res) => {
|
||||
}
|
||||
|
||||
const type = MIME[extname(file).toLowerCase()] ?? 'application/octet-stream';
|
||||
const headers = { 'Content-Type': type };
|
||||
const base = file.replace(/\\/g, '/');
|
||||
if (base.endsWith('/robots.txt') || base.endsWith('/sitemap.xml')) {
|
||||
headers['Cache-Control'] = 'public, max-age=300';
|
||||
}
|
||||
res.writeHead(status, headers);
|
||||
res.writeHead(status, { 'Content-Type': type });
|
||||
if (req.method === 'HEAD') {
|
||||
res.end();
|
||||
return;
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
type Crumb = { label: string; to?: string };
|
||||
|
||||
export function Breadcrumbs({ items }: { items: Crumb[] }) {
|
||||
return (
|
||||
<nav className="crumbs" aria-label="Breadcrumb">
|
||||
<ol>
|
||||
{items.map((item, i) => {
|
||||
const last = i === items.length - 1;
|
||||
return (
|
||||
<li key={item.label}>
|
||||
{last || !item.to ? (
|
||||
<span aria-current={last ? 'page' : undefined}>{item.label}</span>
|
||||
) : (
|
||||
<Link to={item.to}>{item.label}</Link>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { canonicalUrl, jsonLdForPath, seoForPath, SITE_ORIGIN } from '../seo';
|
||||
import { canonicalUrl, seoForPath, SITE_ORIGIN } from '../seo';
|
||||
|
||||
function upsertMeta(selector: string, attrs: Record<string, string>, content: string) {
|
||||
let el = document.head.querySelector(selector) as HTMLMetaElement | null;
|
||||
@@ -41,15 +41,6 @@ export function SeoHead() {
|
||||
upsertMeta('meta[property="og:url"]', { property: 'og:url' }, url);
|
||||
upsertMeta('meta[name="twitter:title"]', { name: 'twitter:title' }, seo.title);
|
||||
upsertMeta('meta[name="twitter:description"]', { name: 'twitter:description' }, seo.description);
|
||||
let ld = document.head.querySelector(
|
||||
'script[type="application/ld+json"]',
|
||||
) as HTMLScriptElement | null;
|
||||
if (!ld) {
|
||||
ld = document.createElement('script');
|
||||
ld.type = 'application/ld+json';
|
||||
document.head.appendChild(ld);
|
||||
}
|
||||
ld.textContent = JSON.stringify(jsonLdForPath(pathname));
|
||||
}, [pathname]);
|
||||
|
||||
return null;
|
||||
|
||||
@@ -8,7 +8,6 @@ export function SiteFooter() {
|
||||
<p style={{ margin: 0 }}>© {new Date().getFullYear()} LexAI · MIT</p>
|
||||
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
|
||||
<Link to="/docs">Docs</Link>
|
||||
<Link to={{ pathname: '/', hash: 'faq' }}>FAQ</Link>
|
||||
<Link to="/contribute">Contribute</Link>
|
||||
<Link to="/community">Community</Link>
|
||||
<a href={REPO} target="_blank" rel="noreferrer">
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
[
|
||||
{
|
||||
"q": "Is LexAI free?",
|
||||
"a": "Yes. LexAI has no subscription and no account. You only pay the LLM provider you choose. Groq and OpenRouter have free-tier keys you can use to start."
|
||||
},
|
||||
{
|
||||
"q": "Do I need my own API key?",
|
||||
"a": "Yes. LexAI is bring-your-own-LLM: paste a key for OpenAI, Anthropic, Groq, or OpenRouter in Chrome Options or VS Code settings. There is no LexAI backend."
|
||||
},
|
||||
{
|
||||
"q": "Is LexAI a Grammarly alternative?",
|
||||
"a": "For writing help, yes: select text on any webpage and Fix, Rephrase, Shorten, Expand, or Explain. Unlike Grammarly, your key stays on your device and text goes only to the provider you pick."
|
||||
},
|
||||
{
|
||||
"q": "How is LexAI different from GitHub Copilot?",
|
||||
"a": "In VS Code or Cursor you select code, give an instruction, and apply a suggestion with file context. It is not tab-complete autocomplete. You use your own provider key instead of a Copilot subscription."
|
||||
},
|
||||
{
|
||||
"q": "Does LexAI store my text or API key?",
|
||||
"a": "No LexAI servers sit in the path. The Chrome key is stored on your device; VS Code uses Secret Storage. Text is sent only to the provider endpoint you configure. There is no LexAI telemetry."
|
||||
}
|
||||
]
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Breadcrumbs } from '../components/Breadcrumbs';
|
||||
import {
|
||||
CHROME_STORE,
|
||||
REPO,
|
||||
@@ -13,7 +12,6 @@ export function CommunityPage() {
|
||||
return (
|
||||
<div className="wrap page">
|
||||
<article className="docs-content">
|
||||
<Breadcrumbs items={[{ label: 'Home', to: '/' }, { label: 'Community' }]} />
|
||||
<h1>Community</h1>
|
||||
<p className="intro">
|
||||
LexAI is maintained in the open. Use these links to install, report issues, and follow the
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { Breadcrumbs } from '../components/Breadcrumbs';
|
||||
import { REPO, REPO_CONDUCT, REPO_CONTRIBUTING } from '../links';
|
||||
|
||||
export function ContributePage() {
|
||||
return (
|
||||
<div className="wrap page">
|
||||
<article className="docs-content">
|
||||
<Breadcrumbs items={[{ label: 'Home', to: '/' }, { label: 'Contribute' }]} />
|
||||
<h1>Contribute</h1>
|
||||
<p className="intro">
|
||||
LexAI is a monorepo: Chrome, VS Code, CLI, website, and a shared library. Please follow
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Breadcrumbs } from '../components/Breadcrumbs';
|
||||
import { FREE_PROVIDERS } from '../freeProviders';
|
||||
import { CHROME_STORE, REPO, VSCODE_MARKETPLACE } from '../links';
|
||||
|
||||
@@ -39,7 +38,6 @@ export function DocsPage() {
|
||||
</aside>
|
||||
|
||||
<article className="docs-content">
|
||||
<Breadcrumbs items={[{ label: 'Home', to: '/' }, { label: 'Docs' }]} />
|
||||
<h1>Documentation</h1>
|
||||
<p className="intro">
|
||||
LexAI is an open-source, bring-your-own-LLM assistant. This page covers every package and
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { LogoMark } from '../components/LogoMark';
|
||||
import { FREE_PROVIDERS, FREE_PROVIDER_BADGES, LEXAI_FREE_HIGHLIGHTS } from '../freeProviders';
|
||||
import faq from '../faq.json';
|
||||
import { CHROME_STORE, REPO, VSCODE_MARKETPLACE } from '../links';
|
||||
|
||||
function openDocsSection(navigate: ReturnType<typeof useNavigate>, id: string) {
|
||||
@@ -233,24 +232,6 @@ export function HomePage() {
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section" id="faq">
|
||||
<div className="wrap">
|
||||
<p className="section-kicker">FAQ</p>
|
||||
<h2>Questions</h2>
|
||||
<p className="section-lead">
|
||||
Short answers for people comparing LexAI to Grammarly, Copilot, and hosted writing tools.
|
||||
</p>
|
||||
<div className="faq-list">
|
||||
{faq.map((item) => (
|
||||
<details key={item.q} className="faq-item">
|
||||
<summary>{item.q}</summary>
|
||||
<p>{item.a}</p>
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,23 +3,23 @@
|
||||
"routes": [
|
||||
{
|
||||
"path": "/",
|
||||
"title": "LexAI — Grammarly-style writing help with your own API key",
|
||||
"description": "Free Chrome extension and VS Code assist. Bring your own OpenAI, Anthropic, Groq, or OpenRouter key. No LexAI servers, account, or subscription."
|
||||
"title": "LexAI — BYO-LLM writing & code assist",
|
||||
"description": "LexAI — bring-your-own-LLM writing and code assist. Chrome, VS Code, and CLI. No LexAI servers, no subscription."
|
||||
},
|
||||
{
|
||||
"path": "/docs",
|
||||
"title": "LexAI docs — Chrome, VS Code, CLI, and free LLM setup",
|
||||
"description": "Install LexAI, add a free Groq or OpenRouter key, and use writing actions in Chrome or code assist in VS Code. Privacy and shared-core notes included."
|
||||
"title": "Docs — LexAI",
|
||||
"description": "Install and use LexAI in Chrome, VS Code, and the CLI. Free LLM setup, privacy, and the shared core."
|
||||
},
|
||||
{
|
||||
"path": "/contribute",
|
||||
"title": "Contribute to LexAI — open-source BYO-LLM assistant",
|
||||
"description": "Report bugs, set up the LexAI monorepo (Chrome, VS Code, CLI, website), and send focused pull requests. MIT license, Code of Conduct."
|
||||
"title": "Contribute — LexAI",
|
||||
"description": "How to report bugs, set up the LexAI monorepo, and send focused pull requests."
|
||||
},
|
||||
{
|
||||
"path": "/community",
|
||||
"title": "LexAI community — source, issues, and installs",
|
||||
"description": "Chrome Web Store, VS Code Marketplace, Gitea repository, issue tracker, and Code of Conduct for the LexAI project."
|
||||
"title": "Community — LexAI",
|
||||
"description": "LexAI source, issues, Chrome Web Store, VS Code Marketplace, and Code of Conduct."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import faq from './faq.json';
|
||||
import routesFile from './seo.routes.json';
|
||||
import { CHROME_STORE, REPO, VSCODE_MARKETPLACE } from './links';
|
||||
|
||||
export const SITE_ORIGIN = routesFile.origin;
|
||||
|
||||
@@ -34,53 +32,3 @@ export function canonicalUrl(path: string): string {
|
||||
if (!path || path === '/') return `${SITE_ORIGIN}/`;
|
||||
return `${SITE_ORIGIN}${path.startsWith('/') ? path : `/${path}`}`;
|
||||
}
|
||||
|
||||
function softwareGraph() {
|
||||
return {
|
||||
'@type': 'SoftwareApplication',
|
||||
name: 'LexAI',
|
||||
applicationCategory: 'BrowserApplication',
|
||||
applicationSubCategory: 'Writing assistance',
|
||||
operatingSystem: 'Chrome, VS Code, Windows, macOS, Linux',
|
||||
url: `${SITE_ORIGIN}/`,
|
||||
downloadUrl: [CHROME_STORE, VSCODE_MARKETPLACE],
|
||||
license: `${REPO}/src/branch/main/LICENSE`,
|
||||
sameAs: [REPO, CHROME_STORE, VSCODE_MARKETPLACE],
|
||||
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
|
||||
};
|
||||
}
|
||||
|
||||
export function jsonLdForPath(pathname: string): Record<string, unknown> {
|
||||
const seo = seoForPath(pathname);
|
||||
const url = seo.index ? canonicalUrl(seo.path) : `${SITE_ORIGIN}${pathname}`;
|
||||
const graph: unknown[] = [
|
||||
{
|
||||
'@type': 'WebSite',
|
||||
name: 'LexAI',
|
||||
url: `${SITE_ORIGIN}/`,
|
||||
description: seoForPath('/').description,
|
||||
},
|
||||
softwareGraph(),
|
||||
];
|
||||
|
||||
if (seo.path === '/') {
|
||||
graph.push({
|
||||
'@type': 'FAQPage',
|
||||
mainEntity: faq.map((item) => ({
|
||||
'@type': 'Question',
|
||||
name: item.q,
|
||||
acceptedAnswer: { '@type': 'Answer', text: item.a },
|
||||
})),
|
||||
});
|
||||
} else if (seo.index) {
|
||||
graph.push({
|
||||
'@type': 'BreadcrumbList',
|
||||
itemListElement: [
|
||||
{ '@type': 'ListItem', position: 1, name: 'Home', item: `${SITE_ORIGIN}/` },
|
||||
{ '@type': 'ListItem', position: 2, name: seo.title, item: url },
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
return { '@context': 'https://schema.org', '@graph': graph };
|
||||
}
|
||||
|
||||
@@ -264,60 +264,6 @@ main {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.faq-list {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.faq-item {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.faq-item summary {
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
min-height: 44px;
|
||||
padding: 0.75rem 1rem;
|
||||
font-weight: 560;
|
||||
}
|
||||
|
||||
.faq-item summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.faq-item p {
|
||||
margin: 0;
|
||||
padding: 0 1rem 1rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.crumbs {
|
||||
margin: 0 0 1rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.crumbs ol {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.crumbs li:not(:last-child)::after {
|
||||
content: '/';
|
||||
margin-left: 0.35rem;
|
||||
color: var(--line);
|
||||
}
|
||||
|
||||
.crumbs a {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
display: grid;
|
||||
gap: 0;
|
||||
|
||||
Reference in New Issue
Block a user