@lattis-dev/cli
Advanced tools
+108
-62
| #!/usr/bin/env node | ||
| import { api } from "./api.js"; | ||
| let agentMode = false; | ||
| function visibilityScore(s) { | ||
| return s.ai_score ?? s.eval_score ?? null; | ||
| } | ||
| // ── Helpers ── | ||
| function getItemType(text) { | ||
| const match = text.match(/^\[(\w+)\]/); | ||
| return match ? match[1] : "other"; | ||
| } | ||
| function getItemText(text) { | ||
| return text.replace(/^\[\w+\]\s*/, ""); | ||
| } | ||
| function json(data) { | ||
@@ -16,9 +12,18 @@ console.log(JSON.stringify(data, null, agentMode ? 0 : 2)); | ||
| // ── Commands ── | ||
| async function cmdSearch(query) { | ||
| async function cmdSearch(query, siteFilter) { | ||
| const body = { q: query }; | ||
| if (siteFilter) | ||
| body.site = siteFilter; | ||
| const data = await api("/search", { | ||
| method: "POST", | ||
| body: { q: query }, | ||
| body, | ||
| }); | ||
| if (agentMode) { | ||
| json(data.results.map((r) => ({ type: r.type, text: r.text, site: r.site_name, domain: r.domain, url: r.url }))); | ||
| json(data.results.map((r) => ({ | ||
| text: r.text, | ||
| site: r.site_name, | ||
| domain: r.domain, | ||
| url: r.url, | ||
| score: r.score, | ||
| }))); | ||
| return; | ||
@@ -30,6 +35,5 @@ } | ||
| } | ||
| console.log(`\n${data.total} results across ${data.sites_searched} sites\n`); | ||
| console.log(`\n${data.total} passages across ${data.sites_searched} sites\n`); | ||
| for (const r of data.results) { | ||
| const tag = r.type.toUpperCase().padEnd(12); | ||
| console.log(`[${tag}] ${r.text.slice(0, 200)}`); | ||
| console.log(`${r.text.slice(0, 240).replace(/\s+/g, " ").trim()}`); | ||
| console.log(` ${r.site_name} (${r.domain}) — ${r.url}`); | ||
@@ -46,3 +50,4 @@ console.log(); | ||
| category: s.analysis?.category ?? "unknown", | ||
| items: s.item_count, | ||
| pages: s.page_count, | ||
| visibility_score: visibilityScore(s), | ||
| status: s.status, | ||
@@ -60,11 +65,15 @@ }))); | ||
| const cat = a?.category ?? ""; | ||
| const score = visibilityScore(s); | ||
| const scoreStr = score != null ? `${score}/100` : "—"; | ||
| console.log(`\n${name} (${s.domain})`); | ||
| if (a?.description) | ||
| console.log(` ${a.description.slice(0, 120)}`); | ||
| console.log(` ${cat} | ${s.item_count} items | ${s.status}`); | ||
| console.log(` ${cat} | ${s.page_count} pages | visibility ${scoreStr} | ${s.status}`); | ||
| } | ||
| } | ||
| async function cmdSite(domainInput) { | ||
| // The detail endpoint historically returned { site, items }. We ignore | ||
| // `items` entirely — it's deprecated and always empty. | ||
| const data = await api(`/sites/${encodeURIComponent(domainInput)}`); | ||
| const { site, items } = data; | ||
| const site = data.site; | ||
| const a = site.analysis; | ||
@@ -77,37 +86,64 @@ if (agentMode) { | ||
| description: a?.description, | ||
| items: items.map((i) => ({ type: getItemType(i.text), text: getItemText(i.text), source: i.source_url })), | ||
| pages: site.page_count, | ||
| visibility_score: visibilityScore(site), | ||
| verified: site.verified ?? false, | ||
| status: site.status, | ||
| }); | ||
| return; | ||
| } | ||
| console.log(`\n# ${a?.name ?? site.domain} (${site.domain})`); | ||
| const score = visibilityScore(site); | ||
| const scoreStr = score != null ? `${score}/100` : "—"; | ||
| const verified = site.verified ? " ✅ Verified" : ""; | ||
| console.log(`\n# ${a?.name ?? site.domain} (${site.domain})${verified}`); | ||
| if (a?.description) | ||
| console.log(a.description); | ||
| console.log(`Category: ${a?.category ?? "?"} | Audience: ${a?.target_audience ?? "?"}`); | ||
| console.log(`${site.item_count} items | ${site.page_count} pages | ${site.status}\n`); | ||
| const byType = {}; | ||
| for (const item of items) { | ||
| const type = getItemType(item.text); | ||
| if (!byType[type]) | ||
| byType[type] = []; | ||
| byType[type].push(item); | ||
| console.log(`Category: ${a?.category ?? "?"}${a?.target_audience ? ` | Audience: ${a.target_audience}` : ""}`); | ||
| console.log(`AI Visibility Score: ${scoreStr} | ${site.page_count} pages | ${site.status}`); | ||
| console.log(`\nRun 'lattis report ${site.domain}' for the full visibility breakdown.\n`); | ||
| } | ||
| async function cmdReport(domainInput) { | ||
| const data = await api(`/report/${encodeURIComponent(domainInput)}`); | ||
| const { site, score } = data; | ||
| if (agentMode) { | ||
| json(data); | ||
| return; | ||
| } | ||
| for (const [type, typeItems] of Object.entries(byType)) { | ||
| console.log(`## ${type.charAt(0).toUpperCase() + type.slice(1)}s (${typeItems.length})`); | ||
| for (const item of typeItems) { | ||
| console.log(` - ${getItemText(item.text)}`); | ||
| } | ||
| console.log(); | ||
| const verified = site.verified ? " ✅ Verified" : ""; | ||
| console.log(`\n ${site.name} — AI Visibility Score${verified}\n`); | ||
| console.log(` ${score.total}/100 ${score.label}\n`); | ||
| console.log(` Questions: ${score.questions.answered}/${score.questions.total} answered (${Math.round(score.questions.coverage * 100)}%)`); | ||
| console.log(` Data quality: ${score.fundamentals.total}/30`); | ||
| console.log(` Actionability: ${score.actionability.total}/20`); | ||
| const disc = site.pages_discovered; | ||
| const pageInfo = disc && disc > site.page_count ? `${site.page_count}/${disc} pages crawled` : `${site.page_count} pages`; | ||
| console.log(` ${pageInfo}\n`); | ||
| for (const q of score.questions.results) { | ||
| const icon = q.status === "answered" ? "✅" : q.status === "partial" ? "⚠️" : "❌"; | ||
| console.log(` ${icon} [${q.importance}] ${q.question}`); | ||
| if (q.evidence) | ||
| console.log(` → ${q.evidence.slice(0, 100)}`); | ||
| if (q.suggestion) | ||
| console.log(` → ${q.suggestion}`); | ||
| } | ||
| if (score.ai_answer_preview) { | ||
| console.log(`\n What AI tells users:`); | ||
| console.log(` "${score.ai_answer_preview.slice(0, 200)}"`); | ||
| } | ||
| console.log(`\n Full report: https://app.lattis.dev/sites/${site.domain}/report\n`); | ||
| } | ||
| async function cmdItems(domainInput, typeFilter) { | ||
| const typeParam = typeFilter ? `?type=${typeFilter}` : ""; | ||
| const data = await api(`/sites/${encodeURIComponent(domainInput)}/items${typeParam}`); | ||
| async function cmdGetPage(domainInput, path) { | ||
| const normalized = path.startsWith("/") ? path : `/${path}`; | ||
| const data = await api(`/sites/${encodeURIComponent(domainInput)}/pages${normalized}`); | ||
| if (data.error || !data.page) { | ||
| const available = data.available?.map((p) => p.path).slice(0, 10).join(", ") ?? "none"; | ||
| console.error(`Page not found: ${normalized}. Available: ${available}`); | ||
| process.exit(1); | ||
| } | ||
| if (agentMode) { | ||
| json(data.items.map((i) => ({ type: getItemType(i.text), text: getItemText(i.text), source: i.source_url }))); | ||
| json({ domain: domainInput, path: normalized, url: data.page.url, title: data.page.title, markdown: data.page.markdown }); | ||
| return; | ||
| } | ||
| for (const item of data.items) { | ||
| console.log(`[${getItemType(item.text)}] ${getItemText(item.text)}`); | ||
| console.log(` ← ${item.source_url}\n`); | ||
| } | ||
| console.log(`# ${data.page.title}`); | ||
| console.log(`URL: ${data.page.url}\n`); | ||
| console.log(data.page.markdown); | ||
| } | ||
@@ -129,25 +165,29 @@ async function cmdAnalyze(urlInput) { | ||
| agentMode = rawArgs.includes("--agent"); | ||
| const typeFlag = rawArgs.find((a) => a.startsWith("--type=")); | ||
| const typeFilter = typeFlag?.split("=")[1]; | ||
| const filteredArgs = rawArgs.filter((a) => a !== "--agent" && !a.startsWith("--type")); | ||
| const siteFlag = rawArgs.find((a) => a.startsWith("--site=")); | ||
| const siteFilter = siteFlag?.split("=")[1]; | ||
| const filteredArgs = rawArgs.filter((a) => a !== "--agent" && !a.startsWith("--site")); | ||
| const [cmd, ...args] = filteredArgs; | ||
| if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") { | ||
| console.log(`lattis — search any website's facts, capabilities, and actions | ||
| console.log(`lattis — search indexed websites and read raw page markdown | ||
| Usage: | ||
| lattis search <query> Search across all indexed sites | ||
| lattis sites List all indexed sites | ||
| lattis site <domain> Full details + items for a site | ||
| lattis items <domain> All items (--type=fact|capability|action) | ||
| lattis search <query> Semantic search across every indexed site | ||
| lattis search <query> --site=dom Scope search to a single site | ||
| lattis sites List every indexed site (with AI Visibility Score) | ||
| lattis site <domain> Metadata + AI Visibility Score for a single site | ||
| lattis page <domain> <path> Full markdown of a specific page | ||
| lattis report <domain> AI Visibility Score + per-question breakdown | ||
| lattis analyze <url> Submit a site for indexing | ||
| lattis mcp Start MCP server (for Claude Code, Cursor) | ||
| lattis mcp Start local MCP server (for Claude Code, Cursor) | ||
| Flags: | ||
| --agent Compact JSON output for LLM consumption | ||
| --type=fact|capability|action Filter items by type | ||
| --site=domain.com Scope search to a single site | ||
| Examples: | ||
| lattis search "compare credit cards" | ||
| lattis site posthog.com | ||
| lattis items cal.com --type=action | ||
| lattis search "posthog free tier limits" | ||
| lattis search "rate limits" --site=openai.com | ||
| lattis site cal.com | ||
| lattis page posthog.com /pricing | ||
| lattis report incident.io | ||
| lattis analyze https://example.com | ||
@@ -162,6 +202,6 @@ | ||
| if (!args[0]) { | ||
| console.error("Usage: lattis search <query>"); | ||
| console.error("Usage: lattis search <query> [--site=domain]"); | ||
| process.exit(1); | ||
| } | ||
| await cmdSearch(args.join(" ")); | ||
| await cmdSearch(args.join(" "), siteFilter); | ||
| break; | ||
@@ -178,8 +218,8 @@ case "sites": | ||
| break; | ||
| case "items": | ||
| if (!args[0]) { | ||
| console.error("Usage: lattis items <domain>"); | ||
| case "page": | ||
| if (!args[0] || !args[1]) { | ||
| console.error("Usage: lattis page <domain> <path>"); | ||
| process.exit(1); | ||
| } | ||
| await cmdItems(args[0], typeFilter); | ||
| await cmdGetPage(args[0], args[1]); | ||
| break; | ||
@@ -193,9 +233,15 @@ case "analyze": | ||
| break; | ||
| case "report": | ||
| if (!args[0]) { | ||
| console.error("Usage: lattis report <domain>"); | ||
| process.exit(1); | ||
| } | ||
| await cmdReport(args[0]); | ||
| break; | ||
| case "mcp": | ||
| // Import and run MCP server | ||
| await import("./mcp.js"); | ||
| break; | ||
| default: | ||
| // Treat unknown command as search | ||
| await cmdSearch([cmd, ...args].join(" ")); | ||
| // Treat unknown command as a search query | ||
| await cmdSearch([cmd, ...args].join(" "), siteFilter); | ||
| } | ||
@@ -202,0 +248,0 @@ } |
+70
-51
| #!/usr/bin/env node | ||
| /** | ||
| * Lattis MCP Server | ||
| * Lattis local stdio MCP server. | ||
| * | ||
| * Exposes Lattis data to AI agents via Model Context Protocol. | ||
| * Works with Claude Code, Cursor, and any MCP client. | ||
| * Mirrors the 4-tool surface of the remote server at | ||
| * https://mcp.lattis.dev/mcp — use this when you want a local process | ||
| * rather than a streamable-HTTP connection. | ||
| * | ||
| * Usage: | ||
| * npx @lattis-dev/cli mcp (via CLI) | ||
| * lattis-mcp (standalone binary) | ||
| * npx @lattis-dev/cli mcp (via CLI) | ||
| * lattis-mcp (standalone binary) | ||
| * | ||
@@ -46,40 +47,29 @@ * Claude Code config (~/.claude/settings.json): | ||
| } | ||
| function asJson(data) { | ||
| return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; | ||
| } | ||
| const server = new McpServer({ | ||
| name: "lattis", | ||
| version: "0.2.0", | ||
| version: "0.3.0", | ||
| description: "Real-time product data for indexed websites — pricing, features, APIs, and how-tos. Semantic search returns raw passages with source URLs so agents can decide for themselves.", | ||
| }); | ||
| server.tool("lattis_list_sites", "List all websites indexed by Lattis. Returns site name, category, item count, and URL for each indexed site.", {}, async () => { | ||
| server.tool("lattis_search", "Semantic search across every indexed website. Returns ranked markdown passages with source URL, heading path, and score so the calling agent can read and reason over them. Pass `site` to scope to a single domain.", { | ||
| query: z | ||
| .string() | ||
| .min(1) | ||
| .describe("What you are trying to find, e.g. 'does PostHog have a SOC2 report' or 'stripe webhook signature verification'"), | ||
| site: z | ||
| .string() | ||
| .optional() | ||
| .describe("Optional: scope to a single domain, e.g. 'posthog.com'"), | ||
| }, async ({ query, site }) => { | ||
| try { | ||
| const data = await api("/sites"); | ||
| const sites = data.sites.map((s) => ({ | ||
| domain: s.domain, | ||
| name: s.analysis?.name ?? s.domain, | ||
| category: s.analysis?.category ?? "unknown", | ||
| items: s.item_count, | ||
| url: s.url, | ||
| status: s.status, | ||
| })); | ||
| return { content: [{ type: "text", text: JSON.stringify(sites, null, 2) }] }; | ||
| } | ||
| catch (err) { | ||
| return errorResult(`Failed to list sites: ${err instanceof Error ? err.message : String(err)}`); | ||
| } | ||
| }); | ||
| server.tool("lattis_search", "Search across all indexed websites for facts, capabilities, and actions. Returns ranked results with source URLs. Use this to find real data about products, services, pricing, and how to accomplish tasks.", { | ||
| query: z.string().describe("Search query, e.g. 'analytics for startups' or 'book a meeting'"), | ||
| site: z.string().optional().describe("Optional: limit search to a specific domain"), | ||
| type: z.string().optional().describe("Optional: filter by 'fact', 'capability', or 'action'"), | ||
| }, async ({ query, site, type }) => { | ||
| try { | ||
| const body = { q: query }; | ||
| if (site) | ||
| body.site = site; | ||
| if (type) | ||
| body.type = type; | ||
| const data = await api("/search", { method: "POST", body: JSON.stringify(body) }); | ||
| if (data.results.length === 0) { | ||
| if (!data.results || data.results.length === 0) { | ||
| return { content: [{ type: "text", text: `No results for "${query}"` }] }; | ||
| } | ||
| const output = data.results.map((r) => ({ | ||
| type: r.type, | ||
| return asJson(data.results.map((r) => ({ | ||
| text: r.text, | ||
@@ -89,4 +79,4 @@ site: r.site_name, | ||
| url: r.url, | ||
| })); | ||
| return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] }; | ||
| score: r.score, | ||
| }))); | ||
| } | ||
@@ -97,27 +87,56 @@ catch (err) { | ||
| }); | ||
| server.tool("lattis_get_items", "Get all extracted items (facts, capabilities, actions) for a specific website. Use to understand what a site offers, its pricing, features, and how to use it.", { | ||
| domain: z.string().describe("Domain, e.g. 'posthog.com' or 'cal.com'"), | ||
| type: z.string().optional().describe("Optional: filter by 'fact', 'capability', or 'action'"), | ||
| }, async ({ domain, type }) => { | ||
| server.tool("lattis_list_sites", "List every website indexed by Lattis. Returns slug, domain, category, AI Visibility Score, and page count for each site. Use this to discover what is available before searching.", {}, async () => { | ||
| try { | ||
| const typeParam = type ? `?type=${type}` : ""; | ||
| const data = await api(`/sites/${domain}/items${typeParam}`); | ||
| if (data.items.length === 0) { | ||
| return { content: [{ type: "text", text: `No items for "${domain}". Use lattis_list_sites to see available sites.` }] }; | ||
| } | ||
| return { content: [{ type: "text", text: JSON.stringify(data.items, null, 2) }] }; | ||
| const data = await api("/sites"); | ||
| const sites = (data.sites ?? []).map((s) => ({ | ||
| domain: s.domain, | ||
| name: s.analysis?.name ?? s.domain, | ||
| category: s.analysis?.category ?? "unknown", | ||
| pages: s.page_count, | ||
| visibility_score: s.ai_score ?? s.eval_score ?? null, | ||
| status: s.status, | ||
| })); | ||
| return asJson(sites); | ||
| } | ||
| catch (err) { | ||
| return errorResult(`Failed to get items: ${err instanceof Error ? err.message : String(err)}`); | ||
| return errorResult(`Failed to list sites: ${err instanceof Error ? err.message : String(err)}`); | ||
| } | ||
| }); | ||
| server.tool("lattis_get_page", "Get the full markdown content of a specific page from an indexed website.", { | ||
| server.tool("lattis_get_site", "Get metadata and the AI Visibility Score for a single indexed site — name, description, category, page counts, and which evaluation questions the site answers vs misses.", { | ||
| domain: z.string().describe("Domain, e.g. 'posthog.com' (slug form also accepted)"), | ||
| }, async ({ domain }) => { | ||
| try { | ||
| const data = await api(`/sites/${encodeURIComponent(domain)}`); | ||
| const s = data.site; | ||
| if (!s) | ||
| return errorResult(`Site "${domain}" not found. Use lattis_list_sites to see available sites.`); | ||
| return asJson({ | ||
| domain: s.domain, | ||
| name: s.analysis?.name ?? s.domain, | ||
| description: s.analysis?.description ?? null, | ||
| category: s.analysis?.category ?? "unknown", | ||
| subcategories: s.analysis?.subcategories ?? [], | ||
| pages: s.page_count, | ||
| pages_discovered: s.pages_discovered ?? s.page_count, | ||
| visibility_score: s.ai_score ?? s.eval_score ?? null, | ||
| visibility_details: s.ai_score_details ?? null, | ||
| evaluation_questions: s.evaluation_questions ?? null, | ||
| verified: s.verified ?? false, | ||
| status: s.status, | ||
| }); | ||
| } | ||
| catch (err) { | ||
| return errorResult(`Failed to get site: ${err instanceof Error ? err.message : String(err)}`); | ||
| } | ||
| }); | ||
| server.tool("lattis_get_page", "Get the full markdown of a specific page from an indexed website. Use this to zoom in after `lattis_search` points you at a URL.", { | ||
| domain: z.string().describe("Domain, e.g. 'posthog.com'"), | ||
| path: z.string().describe("Page path, e.g. '/pricing' or '/about'"), | ||
| path: z.string().describe("Page path starting with /, e.g. '/pricing' or '/docs/api'"), | ||
| }, async ({ domain, path }) => { | ||
| try { | ||
| const data = await api(`/sites/${domain}/pages${path}`); | ||
| const normalized = path.startsWith("/") ? path : `/${path}`; | ||
| const data = await api(`/sites/${encodeURIComponent(domain)}/pages${normalized}`); | ||
| if (data.error) { | ||
| const available = data.available?.map((p) => p.path).join(", ") ?? "none"; | ||
| return { content: [{ type: "text", text: `Page not found: ${path}. Available: ${available}` }] }; | ||
| const available = data.available?.map((p) => p.path).slice(0, 10).join(", ") ?? "none"; | ||
| return { content: [{ type: "text", text: `Page not found: ${normalized}. Available: ${available}` }] }; | ||
| } | ||
@@ -124,0 +143,0 @@ const page = data.page; |
+3
-2
| { | ||
| "name": "@lattis-dev/cli", | ||
| "version": "0.2.0", | ||
| "version": "0.3.0", | ||
| "mcpName": "dev.lattis/mcp", | ||
| "repository": { | ||
@@ -9,3 +10,3 @@ "type": "git", | ||
| "homepage": "https://lattis.dev", | ||
| "description": "Search any website's facts, capabilities, and actions from the command line", | ||
| "description": "Search indexed websites and read raw page markdown from the command line — real web content for AI agents", | ||
| "type": "module", | ||
@@ -12,0 +13,0 @@ "bin": { |
+38
-38
| # @lattis-dev/cli | ||
| Search any website's facts, capabilities, and actions from the command line. | ||
| Search indexed websites and read raw page markdown from the command line. | ||
| Lattis pre-indexes websites into structured data that AI agents can use — real prices, working URLs, API endpoints, not hallucinated guesses. This CLI lets you search across all indexed sites instantly. | ||
| Lattis pre-indexes websites and serves them over a simple API. This CLI gives you semantic search across every indexed site, per-site AI Visibility Scores, and raw page markdown — real content, not training data. | ||
@@ -16,3 +16,3 @@ ## Install | ||
| ```bash | ||
| npx @lattis-dev/cli search "compare credit cards" | ||
| npx @lattis-dev/cli search "posthog free tier limits" | ||
| ``` | ||
@@ -28,16 +28,9 @@ | ||
| Add to your Claude Code settings (`~/.claude/settings.json`): | ||
| The remote MCP server is the easiest path — no local install, always up to date: | ||
| ```json | ||
| { | ||
| "mcpServers": { | ||
| "lattis": { | ||
| "command": "npx", | ||
| "args": ["-y", "@lattis-dev/cli", "mcp"] | ||
| } | ||
| } | ||
| } | ||
| ```bash | ||
| claude mcp add lattis --transport http https://mcp.lattis.dev/mcp | ||
| ``` | ||
| Or for Cursor, add to `.cursor/mcp.json`: | ||
| If you'd rather run a local stdio MCP server (same 4 tools, no network hop), use the bundled `mcp` command in Claude Code config (`~/.claude/settings.json`): | ||
@@ -55,3 +48,3 @@ ```json | ||
| This gives your AI 4 tools: `lattis_search`, `lattis_list_sites`, `lattis_get_items`, `lattis_get_page`. | ||
| Both expose the same 4 tools: `lattis_search`, `lattis_list_sites`, `lattis_get_site`, `lattis_get_page`. | ||
@@ -61,6 +54,6 @@ ## Use with any AI (pipe mode) | ||
| ```bash | ||
| # Pipe structured JSON into your agent | ||
| # Compact JSON for LLM consumption | ||
| lattis search "PostHog pricing" --agent | your-agent | ||
| # Or use as context in a prompt | ||
| # Use as context in a prompt | ||
| echo "Based on this data: $(lattis site posthog.com --agent)" | llm | ||
@@ -71,19 +64,14 @@ ``` | ||
| ### Search across all indexed sites | ||
| ### Search across every indexed site | ||
| ```bash | ||
| lattis search "analytics for my startup" | ||
| lattis search "analytics for startups" | ||
| ``` | ||
| ``` | ||
| 20 results across 10 sites | ||
| Returns ranked markdown passages with source URLs. | ||
| [FACT ] PostHog free tier includes 1M events/month, 5K session replays | ||
| PostHog (posthog.com) — https://posthog.com | ||
| ### Scope a search to one site | ||
| [CAPABILITY ] Product search with dietary filters at helloyumi.com/shop?diet={value} | ||
| YUMI (helloyumi.com) — https://helloyumi.com/shop | ||
| [ACTION ] Sign up for PostHog free: go to app.posthog.com/signup | ||
| PostHog (posthog.com) — https://app.posthog.com/signup | ||
| ```bash | ||
| lattis search "rate limits" --site=openai.com | ||
| ``` | ||
@@ -97,4 +85,6 @@ | ||
| ### View a specific site | ||
| Shows every indexed site with its AI Visibility Score. | ||
| ### View one site | ||
| ```bash | ||
@@ -104,8 +94,20 @@ lattis site posthog.com | ||
| ### Get items with type filter | ||
| Metadata and AI Visibility Score. | ||
| ### Read a specific page | ||
| ```bash | ||
| lattis items cal.com --type=action | ||
| lattis page posthog.com /pricing | ||
| ``` | ||
| Full raw markdown of the page. | ||
| ### AI Visibility Score breakdown | ||
| ```bash | ||
| lattis report incident.io | ||
| ``` | ||
| Per-question breakdown: which questions the site answers, which it misses, and suggestions per miss. | ||
| ### Submit a site for indexing | ||
@@ -133,10 +135,8 @@ | ||
| Lattis crawls any website and extracts three types of structured data: | ||
| Lattis crawls any website and serves its content back as raw markdown. You get: | ||
| - **Facts** — specific data points (prices, specs, limits) | ||
| - **Capabilities** — what users can do (search URLs, filter patterns, forms) | ||
| - **Actions** — how to accomplish goals (step-by-step with URLs, code samples) | ||
| - **Raw page passages** — semantic search returns the actual text from the page, with source URLs, so your agent can read and cite directly. | ||
| - **Full page markdown** — fetch any indexed page end-to-end when you need more than an excerpt. | ||
| - **AI Visibility Score** — a per-site diagnostic: which questions agents can answer about the site, which they can't, and why. | ||
| AI agents get real, current, actionable data instead of hallucinating. | ||
| Learn more at [lattis.dev](https://lattis.dev) | ||
| Learn more at [lattis.dev](https://lattis.dev). |
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
20661
20.9%408
18.95%4
33.33%