diff --git a/apps/landing/src/content.config.ts b/apps/landing/src/content.config.ts index 773cc4e7..606ac806 100644 --- a/apps/landing/src/content.config.ts +++ b/apps/landing/src/content.config.ts @@ -4,6 +4,7 @@ import { glob } from "astro/loaders"; import { defineCollection } from "astro:content"; import { createBlogPostContentSchema } from "@/features/blog/schema"; +import { createComparisonContentSchema } from "@/features/compare/schema"; const blog = defineCollection({ loader: glob({ @@ -14,9 +15,18 @@ const blog = defineCollection({ schema: createBlogPostContentSchema, }); +const compare = defineCollection({ + loader: glob({ + base: "./src/content/compare", + pattern: "**/*.mdx", + retainBody: true, + }), + schema: createComparisonContentSchema, +}); + const docs = defineCollection({ loader: docsLoader(), schema: docsSchema(), }); -export const collections = { blog, docs }; +export const collections = { blog, compare, docs }; diff --git a/apps/landing/src/content/compare/onequery-vs-bi-chatbot.mdx b/apps/landing/src/content/compare/onequery-vs-bi-chatbot.mdx new file mode 100644 index 00000000..4050233c --- /dev/null +++ b/apps/landing/src/content/compare/onequery-vs-bi-chatbot.mdx @@ -0,0 +1,73 @@ +--- +order: 4 +title: "OneQuery vs BI Chatbot | Operational Agent Access" +metaDescription: "Compare OneQuery with BI chatbots for AI agent data access. Use dashboards for business answers and governed sources for operational agents." +alternativeName: "BI chatbot" +category: "Analytics versus operations" +eyebrow: "OneQuery vs BI chatbot" +summary: "BI chatbots make analytics more accessible. OneQuery makes production source access safer for agents that do operational work." +answer: "Use OneQuery instead of a BI chatbot when the agent needs operational production context, not just dashboard-backed analytics answers. BI chatbots are best for business metrics; OneQuery is built for governed source access by coding, incident, and internal automation agents." +heroSignals: + - "BI for metrics" + - "OneQuery for source context" + - "Separate analytics from agent access" +keywords: + - "OneQuery vs BI chatbot" + - "AI agent data access vs BI chatbot" + - "operational data access for agents" + - "business intelligence chatbot" + - "production context for coding agents" +criteria: + - factor: "Primary context" + oneQuery: "Agents can query approved databases and provider APIs for task-specific production context." + alternative: "Answers usually come from dashboards, saved questions, models, or a BI semantic layer." + - factor: "Primary user" + oneQuery: "Designed for agent workflows running through CLI, gateway, and source commands." + alternative: "Designed for humans asking business questions in a BI workspace or embedded analytics surface." + - factor: "Governance model" + oneQuery: "Governance is tied to source credentials, query validation, limits, and audit history." + alternative: "Governance is tied to BI permissions, curated datasets, and metric definitions." + - factor: "Best answer shape" + oneQuery: "Best for retrieving narrow operational evidence that an agent can use to debug, triage, or automate." + alternative: "Great for explaining trends, charts, cohorts, and reports." + - factor: "Operational fit" + oneQuery: "Can connect production databases, warehouses, observability tools, product analytics, and developer systems." + alternative: "May not expose the low-level source, incident, or developer-tool context an engineering agent needs." +oneQueryBestFor: + - "Coding agents investigating errors, logs, database state, or customer-impacting incidents." + - "Internal automations that need source-specific evidence, not only chart summaries." + - "Teams that need source execution audit history outside a BI workspace." +alternativeBestFor: + - "Business users asking metric, dashboard, and report questions." + - "A governed semantic layer where terms like revenue, churn, and active user already have company-approved definitions." + - "Recurring executive or go-to-market workflows that belong in dashboards and scheduled reports." +migrationSteps: + - "Keep dashboard and metric questions routed to the BI chatbot." + - "Identify operational agent workflows that need raw source context or developer-tool data." + - "Connect those sources to OneQuery with read-only or least-privilege credentials." + - "Document when agents should use BI answers versus OneQuery source execution." +faqs: + - question: "Does OneQuery replace a BI chatbot?" + answer: "Usually no. A BI chatbot and OneQuery solve different layers. Use the BI chatbot for business analytics questions and OneQuery for governed source access in engineering and automation workflows." + - question: "How do I choose between the two?" + answer: "Choose a BI chatbot when the answer should come from approved metrics, dashboards, reports, or a semantic model. Choose OneQuery when an agent needs bounded access to production sources during an operational task." + - question: "Can BI and OneQuery coexist?" + answer: "Yes. Teams can keep BI as the business analytics interface and use OneQuery as the access layer for coding agents, incident agents, and internal tools that need broader source context." +references: + - label: "OneQuery connectors" + href: "/connectors/" + description: "OneQuery docs on supported databases, warehouses, analytics systems, and developer tools." + - label: "Query boundaries" + href: "/docs/concepts/query-boundaries/" + description: "OneQuery docs on what source requests are allowed to do." +--- + +## Analytics answers and operational context are different jobs + +BI chatbots are strongest when the company has already curated the data shape: metrics, dashboards, models, reports, and common business definitions. That is the right interface for many business questions. + +Operational agents often need a different kind of evidence. They may need a narrow production query, a recent error from an observability tool, a linked issue, or a support-system record. OneQuery gives those workflows a source boundary without turning the BI layer into the agent runtime. + +## Recommended rollout + +Write down which questions should stay in BI. Route the remaining operational workflows through OneQuery sources, starting with incident or debugging tasks where audit history and small result windows matter. diff --git a/apps/landing/src/content/compare/onequery-vs-datagrip-for-ai-agents.mdx b/apps/landing/src/content/compare/onequery-vs-datagrip-for-ai-agents.mdx new file mode 100644 index 00000000..d8a70a5f --- /dev/null +++ b/apps/landing/src/content/compare/onequery-vs-datagrip-for-ai-agents.mdx @@ -0,0 +1,76 @@ +--- +order: 6 +title: "OneQuery vs DataGrip for AI Agents | Safe Database Access" +metaDescription: "Compare OneQuery with DataGrip for AI agents. Keep DataGrip for human SQL work and OneQuery for bounded agent source access." +alternativeName: "DataGrip for AI agents" +category: "IDE versus agent access layer" +eyebrow: "OneQuery vs DataGrip for agents" +summary: "DataGrip is excellent for trusted humans writing SQL. OneQuery is the safer path when the caller is an agent that should not hold database connection details." +answer: "Use OneQuery instead of DataGrip for AI agents when the user is an autonomous agent rather than a human SQL operator. DataGrip is a database IDE for people; OneQuery is a governed source access layer that lets agents query approved data without receiving database connection details." +heroSignals: + - "DataGrip for humans" + - "OneQuery for agents" + - "No IDE credentials in runtime" +keywords: + - "OneQuery vs DataGrip for AI agents" + - "DataGrip AI agent alternative" + - "AI agent SQL access" + - "database IDE versus agent access layer" + - "safe agent database queries" +criteria: + - factor: "Primary user" + oneQuery: "OneQuery is built around agent and CLI workflows that need controlled source access." + alternative: "DataGrip is built around human database work: data sources, query consoles, SQL files, schema browsing, and editor assistance." + - factor: "Connection details" + oneQuery: "The agent receives a source identifier while OneQuery stores and uses the source credential server-side." + alternative: "The IDE needs connection details such as host, port, password, and related settings for each data source." + - factor: "Execution judgment" + oneQuery: "OneQuery applies deterministic boundaries before agent-generated SQL reaches the source." + alternative: "A human chooses which SQL to run and can inspect the database state through the IDE." + - factor: "Audit model" + oneQuery: "Source requests are recorded as agent access events with actor, source, operation, outcome, and time." + alternative: "IDE history and database logs may help, but they are not an agent access control plane." + - factor: "Best fit" + oneQuery: "Best for giving coding agents narrow, repeatable, auditable access to production context." + alternative: "Excellent for interactive development, schema exploration, and SQL authoring by trusted users." +oneQueryBestFor: + - "AI coding agents that need database evidence during production debugging." + - "Teams that want humans to keep powerful IDEs while agents get narrower source interfaces." + - "Workflows where query validation and audit history matter more than IDE ergonomics." +alternativeBestFor: + - "Human developers writing, debugging, and organizing SQL in a full database IDE." + - "Schema browsing, query consoles, data editor workflows, and database object inspection." + - "A trusted operator session where the person using the tool should hold the database connection details." +migrationSteps: + - "Keep human SQL authoring and schema exploration in DataGrip." + - "Create a scoped OneQuery source for the production data the agent can inspect." + - "Remove DataGrip connection details, passwords, and DSNs from the agent environment." + - "Use OneQuery audit history to review the agent's production source activity." +faqs: + - question: "Does OneQuery replace DataGrip?" + answer: "No. Keep DataGrip for human database work. Use OneQuery for agent workflows where the database credential should stay outside the agent runtime." + - question: "Why not give an agent a DataGrip connection?" + answer: "Giving an agent an IDE connection profile recreates the direct-credential problem. OneQuery lets the agent request data through a bounded interface without owning the connection details." + - question: "How can developers use DataGrip and OneQuery together?" + answer: "A developer can use DataGrip to design or verify a query, then give the agent a OneQuery source and a narrow command pattern for repeatable production-safe runs." +references: + - label: "DataGrip data sources" + href: "https://www.jetbrains.com/help/datagrip/managing-data-sources.html" + description: "JetBrains documentation on DataGrip data sources and supported databases." + - label: "DataGrip database connections" + href: "https://www.jetbrains.com/help/datagrip/connecting-to-a-database.html" + description: "JetBrains documentation on DataGrip database connection details and sessions." + - label: "DataGrip code editor" + href: "https://www.jetbrains.com/help/datagrip/code-editor.html" + description: "JetBrains documentation on DataGrip SQL query editing and consoles." +--- + +## Keep the IDE for humans + +DataGrip is a strong human database IDE. It helps trusted operators manage data sources, write SQL, inspect schemas, and work interactively with query consoles. + +That is a different job from giving an autonomous agent production context. An agent does not need the full IDE connection profile. It needs a narrow way to ask a source-specific question and receive a bounded answer. + +## Recommended rollout + +Keep DataGrip in the human workflow for query design and review. Give the agent a OneQuery source identifier and remove IDE connection details from the agent runtime. diff --git a/apps/landing/src/content/compare/onequery-vs-direct-database-credentials.mdx b/apps/landing/src/content/compare/onequery-vs-direct-database-credentials.mdx new file mode 100644 index 00000000..c6e40fe3 --- /dev/null +++ b/apps/landing/src/content/compare/onequery-vs-direct-database-credentials.mdx @@ -0,0 +1,73 @@ +--- +order: 1 +title: "OneQuery vs Direct Database Credentials | Safe AI Agent Access" +metaDescription: "Compare OneQuery with direct database credentials for AI agents. See when governed source access beats handing agents raw passwords or DSNs." +alternativeName: "direct database credentials" +category: "Credential strategy" +eyebrow: "OneQuery vs raw database access" +summary: "Direct database credentials can be fast for a human, but they make the agent runtime the security boundary. OneQuery moves that boundary into a governed execution layer." +answer: "Use OneQuery instead of direct database credentials when an AI agent needs production context. Direct credentials put the password, DSN, or cloud token inside the agent runtime; OneQuery keeps the credential server-side and gives the agent a bounded source interface." +heroSignals: + - "Credential stays centralized" + - "Read-only query boundary" + - "Auditable agent access" +keywords: + - "OneQuery vs direct database credentials" + - "AI agent database credentials" + - "safe production database access for agents" + - "centralized credentials" + - "read-only query validation" +criteria: + - factor: "Credential exposure" + oneQuery: "Credentials stay in OneQuery source configuration and the agent receives only a source identifier and command surface." + alternative: "The agent receives a password, DSN, IAM credential, or environment variable that can reach the database." + - factor: "Query boundary" + oneQuery: "SQL requests pass through read-only validation, single-statement enforcement, limits, and source-specific execution controls." + alternative: "Read-only behavior depends on the database role and on the agent choosing safe SQL." + - factor: "Audit trail" + oneQuery: "Each request records actor, source, operation, outcome, and time in a purpose-built access history." + alternative: "Teams often reconstruct agent activity from shell history, database logs, or model transcripts." + - factor: "Rotation" + oneQuery: "Rotate the source credential centrally while keeping the agent workflow pointed at the same approved source." + alternative: "Credential rotation requires updating every place where the agent or tool runtime received the secret." + - factor: "Failure mode" + oneQuery: "Denied writes, oversized responses, missing permissions, and broad queries become explicit blocked states." + alternative: "A prompt rule can tell the agent to be careful, but it cannot reduce the authority attached to the credential." +oneQueryBestFor: + - "Production debugging where agents need real database context but should not hold secrets." + - "Teams that need an audit trail for agent data access." + - "Workflows where prompt rules are not enough to enforce read-only behavior." +alternativeBestFor: + - "A local development database that contains no production data." + - "A one-off human DBA session where the operator already owns the credential." + - "A throwaway read-only role that can be revoked immediately after testing." +migrationSteps: + - "Create a read-only upstream database role for the data the agent is allowed to inspect." + - "Connect that role as a named OneQuery source." + - "Replace the agent's DSN or password with the OneQuery source identifier and command pattern." + - "Run a small production debugging workflow and review the resulting audit history." +faqs: + - question: "When are direct database credentials acceptable for an AI agent?" + answer: "Use a direct credential only for low-risk human workflows or isolated test data. For production data, the safer pattern is to give the agent a controlled source interface while the credential stays outside the model runtime." + - question: "Does OneQuery replace database-level permissions?" + answer: "OneQuery is not a replacement for scoped database roles. It works best with read-only upstream roles, then adds source resolution, validation, limits, and audit records around the agent workflow." + - question: "How do I move from direct credentials to OneQuery?" + answer: "Point the agent at a OneQuery source identifier, remove the database secret from the agent environment, and review the first runs in audit history before adding more sources." +references: + - label: "Governed access" + href: "/docs/concepts/governed-access/" + description: "OneQuery docs on keeping raw provider credentials outside the agent runtime." + - label: "Query boundaries" + href: "/docs/concepts/query-boundaries/" + description: "OneQuery docs on read-only validation, single-statement execution, limits, and blocked requests." +--- + +## The practical difference + +A direct credential turns the agent environment into the control plane. If the credential can reach production, the agent can try to use that authority whenever its plan points there. + +OneQuery keeps the useful part of the workflow: the agent can still ask focused database questions. The difference is that the source, credential, validation, limits, and audit record stay outside the prompt and outside the shell environment. + +## Recommended rollout + +Start with the smallest read-only source that answers a real debugging question. Remove the raw DSN from the agent setup, give the agent the OneQuery source identifier, and treat denied or truncated requests as normal feedback instead of reasons to broaden the credential. diff --git a/apps/landing/src/content/compare/onequery-vs-internal-data-agent.mdx b/apps/landing/src/content/compare/onequery-vs-internal-data-agent.mdx new file mode 100644 index 00000000..b235ccc7 --- /dev/null +++ b/apps/landing/src/content/compare/onequery-vs-internal-data-agent.mdx @@ -0,0 +1,73 @@ +--- +order: 3 +title: "OneQuery vs Internal Data Agent | Build vs Govern" +metaDescription: "Compare OneQuery with building an internal data agent. Keep custom agent logic while outsourcing source credentials, limits, and audit trails." +alternativeName: "internal data agent" +category: "Build versus govern" +eyebrow: "OneQuery vs building the whole agent stack" +summary: "An internal agent can be the right product choice. OneQuery makes sure that agent does not also become the credential store and production query gate." +answer: "Use OneQuery with an internal data agent when you want to build custom agent behavior without rebuilding the production access layer. The internal agent can own planning and UX while OneQuery owns source credentials, query boundaries, limits, and audit logs." +heroSignals: + - "Keep custom agent logic" + - "Outsource access controls" + - "Ship sources faster" +keywords: + - "OneQuery vs internal data agent" + - "build internal data agent" + - "AI data agent infrastructure" + - "agent access control plane" + - "governed production data access" +criteria: + - factor: "Infrastructure scope" + oneQuery: "OneQuery provides the source access layer so the internal agent can focus on task logic and user experience." + alternative: "The internal team must design and maintain credential storage, source mapping, query safety, limits, and audit behavior." + - factor: "Secret handling" + oneQuery: "Credentials stay centralized behind named sources and do not need to appear in agent prompts or runtime env vars." + alternative: "Credentials often spread across service configs, worker environments, and prototype scripts." + - factor: "New source rollout" + oneQuery: "Teams add approved sources through a consistent connection, command, and audit model." + alternative: "Each source needs custom policy and logging code before it is safe for production use." + - factor: "Operational review" + oneQuery: "Access history is part of the governed data layer from the start." + alternative: "Audit review is usually a bespoke feature that competes with agent product work." + - factor: "Responsibility split" + oneQuery: "The agent asks and interprets; OneQuery resolves sources, validates requests, executes, and records outcomes." + alternative: "The internal agent becomes responsible for both reasoning and security-sensitive execution." +oneQueryBestFor: + - "Teams that want custom agent UX without owning every production access control." + - "Engineering agents that need real operational context from several sources." + - "Organizations that need source-by-source rollout, review, and audit behavior." +alternativeBestFor: + - "A differentiated product experience where the agent workflow itself is the core product." + - "Custom reasoning, planning, or UI behavior that cannot be bought as infrastructure." + - "A narrow internal prototype that uses one data source and a small trusted user group." +migrationSteps: + - "Identify where the internal agent currently stores or receives source credentials." + - "Move one production source into OneQuery with a read-only upstream credential." + - "Change the agent tool call to use a OneQuery source identifier instead of direct source metadata." + - "Keep custom planning and response logic in the agent while reviewing source execution in OneQuery." +faqs: + - question: "Does OneQuery replace our internal data agent?" + answer: "No. OneQuery is the data access boundary, not the reasoning layer. Internal teams can still build custom agents, prompts, UIs, and workflows while delegating governed source access to OneQuery." + - question: "What should we build ourselves?" + answer: "Build the agent behavior that is specific to your company. Use OneQuery for the repeated infrastructure work around credentials, source identifiers, safe execution, result limits, and audit history." + - question: "How should an internal agent adopt OneQuery?" + answer: "Connect one low-risk read-only source first, give the internal agent only the source identifier and command surface, and review audit history after the first automated runs." +references: + - label: "Governed access" + href: "/docs/concepts/governed-access/" + description: "OneQuery docs on the split between what stays outside the agent and what the agent receives." + - label: "Security architecture" + href: "/docs/security/security-architecture/" + description: "OneQuery docs on credential boundaries and reviewable request flow." +--- + +## Build the agent, not the whole access layer + +Internal data agents are often worth building when the workflow is specific to the company. The risky part is letting that project quietly expand into credential management, query validation, source authorization, rate limits, and audit review. + +OneQuery gives the internal agent a smaller contract. The agent decides what to ask and how to explain the result. OneQuery decides which source can be used, how the request executes, and what record operators can review later. + +## Recommended rollout + +Keep the custom agent UI and planning loop. Replace direct source calls one at a time with OneQuery-backed source commands, starting with a read-only source that supports a real operational workflow. diff --git a/apps/landing/src/content/compare/onequery-vs-mcp-database-server.mdx b/apps/landing/src/content/compare/onequery-vs-mcp-database-server.mdx new file mode 100644 index 00000000..3988e1fc --- /dev/null +++ b/apps/landing/src/content/compare/onequery-vs-mcp-database-server.mdx @@ -0,0 +1,73 @@ +--- +order: 2 +title: "OneQuery vs MCP Database Server | Governed Agent Data Access" +metaDescription: "Compare OneQuery with a database MCP server for AI agent data access, credential isolation, query boundaries, and audit history." +alternativeName: "MCP database server" +category: "Protocol adapter" +eyebrow: "OneQuery vs MCP database server" +summary: "MCP helps an AI application discover and call tools. OneQuery handles the production data boundary those tools still need." +answer: "Use OneQuery instead of a generic MCP database server when the main problem is governed production access, not only tool exposure. MCP standardizes how AI applications call tools and resources; OneQuery focuses on credential isolation, query boundaries, source limits, and audit history for those data operations." +heroSignals: + - "Protocol-compatible workflows" + - "Governed execution layer" + - "Central source audit history" +keywords: + - "OneQuery vs MCP database server" + - "MCP database server alternative" + - "AI agent database MCP" + - "governed MCP data access" + - "agent query audit logs" +criteria: + - factor: "Primary job" + oneQuery: "OneQuery is the execution boundary for source credentials, read-only SQL, result limits, and audit records." + alternative: "The MCP server defines the tool surface, and governance depends on the server implementation." + - factor: "Credential placement" + oneQuery: "Source credentials are centralized in OneQuery configuration and can be hidden from the AI client." + alternative: "Credentials usually live wherever the database MCP server is deployed or configured." + - factor: "Policy depth" + oneQuery: "OneQuery applies deterministic query validation and source controls before the request reaches the database." + alternative: "The protocol can expose tools and resources, but database safety is not automatic." + - factor: "Review path" + oneQuery: "Operators review source activity through a product-level audit history tied to approved sources." + alternative: "Tool logs may exist in the AI client, MCP host, or custom server logs." + - factor: "Scaling sources" + oneQuery: "Multiple databases and APIs use a consistent source identifier model and governed access path." + alternative: "Each database server may need its own credential, logging, and policy behavior." +oneQueryBestFor: + - "Production database access where MCP tool exposure needs a stronger execution boundary." + - "Teams standardizing agent access across databases, warehouses, and provider APIs." + - "Operators who need query-level audit history outside the AI client transcript." +alternativeBestFor: + - "A prototype where the database is local, read-only, and not production-sensitive." + - "A team that already has a hardened MCP database server with strong policy, logging, and credential handling." + - "An AI application that only needs a protocol adapter and not a broader access control plane." +migrationSteps: + - "List the database tools your MCP server currently exposes." + - "Move the underlying database credential into a OneQuery source." + - "Route agent SQL execution through OneQuery and keep MCP as the optional client adapter." + - "Compare MCP tool logs with OneQuery audit history during the first production runs." +faqs: + - question: "Does OneQuery replace MCP?" + answer: "No. MCP is a useful protocol for connecting AI applications to tools. OneQuery can complement MCP by acting as the governed data execution layer behind the tool surface." + - question: "When is an MCP database server enough?" + answer: "A database MCP server is enough when the data is low-risk and the server already enforces the policy you need. Use OneQuery when production credentials, query validation, source limits, and audit review are first-class requirements." + - question: "How do teams use OneQuery with MCP clients?" + answer: "Expose a OneQuery command or service through the MCP host, or keep OneQuery as the CLI/gateway path for agents that do not need MCP. The important part is that the database credential does not move into the AI client." +references: + - label: "Model Context Protocol server concepts" + href: "https://modelcontextprotocol.io/docs/learn/server-concepts" + description: "The MCP docs describe servers as programs that expose capabilities, including database servers for data queries." + - label: "OneQuery query boundaries" + href: "/docs/concepts/query-boundaries/" + description: "OneQuery docs on the deterministic boundary around SQL and source API requests." +--- + +## MCP is an interface, not the whole boundary + +MCP gives AI applications a standard way to discover and invoke tools. That is useful, but it does not decide where production credentials live, which SQL shapes are allowed, how broad results may be, or how operators review source activity. + +OneQuery fits below or beside an MCP host. The MCP layer can remain the client adapter while OneQuery handles the part that has to be deterministic: source resolution, credential use, validation, limits, and audit records. + +## Recommended rollout + +Keep the MCP tool surface narrow. Move the database credential behind OneQuery first, then expose only the OneQuery-backed operation to the MCP host. That gives the agent a familiar tool while moving the production risk out of the client. diff --git a/apps/landing/src/content/compare/onequery-vs-metabase-for-ai-agents.mdx b/apps/landing/src/content/compare/onequery-vs-metabase-for-ai-agents.mdx new file mode 100644 index 00000000..b02ffef5 --- /dev/null +++ b/apps/landing/src/content/compare/onequery-vs-metabase-for-ai-agents.mdx @@ -0,0 +1,76 @@ +--- +order: 5 +title: "OneQuery vs Metabase for AI Agents | Governed Data Access" +metaDescription: "Compare OneQuery with Metabase for AI agents. See when to use BI-curated analytics versus governed production source access." +alternativeName: "Metabase for AI agents" +category: "BI platform comparison" +eyebrow: "OneQuery vs Metabase for agents" +summary: "Metabase is the right home for BI-curated analytics. OneQuery is the access boundary for agents that need operational source context outside the BI workspace." +answer: "Use OneQuery instead of Metabase for AI agents when the agent needs governed production source access across databases, APIs, and developer tools. Use Metabase when the agent should stay inside BI-curated dashboards, questions, models, and Metabot or Metabase MCP workflows." +heroSignals: + - "Metabase for BI context" + - "OneQuery for source execution" + - "Clear agent access boundary" +keywords: + - "OneQuery vs Metabase for AI agents" + - "Metabase AI agent alternative" + - "Metabase MCP server alternative" + - "BI chatbot vs agent data access" + - "governed production data access" +criteria: + - factor: "Product center" + oneQuery: "OneQuery centers governed source access for agents, CLI workflows, and self-hosted gateways." + alternative: "Metabase centers analytics content such as dashboards, questions, models, collections, and the Metabot AI experience." + - factor: "Data path" + oneQuery: "Best when an agent needs bounded execution against approved production sources and provider APIs." + alternative: "Best when the answer should come from existing BI assets and user-scoped Metabase permissions." + - factor: "Agent job" + oneQuery: "Strong fit for production debugging, incident triage, release review, and operational evidence gathering." + alternative: "Strong fit for analytics exploration, report follow-up, and embedded BI chat." + - factor: "AI integration" + oneQuery: "OneQuery gives the agent a governed command and source interface independent of a BI workspace." + alternative: "Metabase MCP connects third-party AI tools to Metabase data, with responses scoped to Metabase permissions." + - factor: "Control surface" + oneQuery: "Governance follows source credentials, query boundaries, result limits, and audit history for agent execution." + alternative: "Governance follows Metabase content, permissions, and available AI controls." +oneQueryBestFor: + - "Coding agents that need production context from databases, logs, issue trackers, and APIs." + - "Teams that do not want BI permissions to become the only control plane for operational agents." + - "Agent workflows where each source request needs a bounded execution record." +alternativeBestFor: + - "Teams already using Metabase as the source of truth for dashboards, questions, models, and analytics content." + - "Business users who need a BI-native assistant inside Metabase or an embedded analytics experience." + - "Agents that should answer from Metabase-curated analytics rather than querying operational systems directly." +migrationSteps: + - "Keep business metric and dashboard questions in Metabase." + - "Identify agent workflows that need production databases, logs, support tools, or developer APIs outside BI." + - "Connect those operational sources to OneQuery with scoped credentials." + - "Teach agents which questions belong in Metabase and which source operations go through OneQuery." +faqs: + - question: "Does OneQuery replace Metabase?" + answer: "No. Metabase is a BI platform, and OneQuery is a governed access layer for agents. Many teams can use Metabase for analytics and OneQuery for operational source access." + - question: "When should an AI agent use OneQuery instead of Metabase?" + answer: "Use Metabase when the agent should answer from curated BI content. Use OneQuery when the agent needs to inspect approved production sources or developer systems through a bounded execution path." + - question: "Can OneQuery and Metabase be used together?" + answer: "Yes. Keep Metabase permissions and dashboards for analytics, then use OneQuery for the production sources and operational APIs that should not be exposed as raw credentials to coding agents." +references: + - label: "Metabase AI overview" + href: "https://www.metabase.com/docs/latest/ai/overview" + description: "Metabase documentation for Metabot, embedded AI chat, and the Metabase MCP server." + - label: "Metabase basic exploration" + href: "https://www.metabase.com/docs/latest/exploration-and-organization/exploration" + description: "Metabase documentation for dashboards, questions, models, collections, databases, and tables." + - label: "OneQuery governed access" + href: "/docs/concepts/governed-access/" + description: "OneQuery docs on keeping credentials outside the agent while exposing a controlled source interface." +--- + +## Keep BI context in BI + +Metabase is strongest when the agent should work through analytics assets the business already understands: dashboards, saved questions, models, collections, and user-scoped BI permissions. That is a useful boundary for analytics answers. + +OneQuery fits the workflows that should not be reduced to a dashboard question. A coding or incident agent may need operational source context from databases, observability systems, issue trackers, and provider APIs. Those requests need credential isolation and execution records even when the team already has a BI platform. + +## Recommended rollout + +Keep Metabase as the analytics home. Add OneQuery for operational sources that are useful to agents but should not appear as raw credentials in a coding-agent runtime. diff --git a/apps/landing/src/features/compare/data.ts b/apps/landing/src/features/compare/data.ts new file mode 100644 index 00000000..c2303e66 --- /dev/null +++ b/apps/landing/src/features/compare/data.ts @@ -0,0 +1,56 @@ +import type { CollectionEntry } from "astro:content"; +import { getCollection } from "astro:content"; + +import { ONEQUERY } from "@/shared/seo/constants"; + +import type { ComparisonPage } from "./types"; +export type { + ComparisonCriterion, + ComparisonFaq, + ComparisonPage, + ComparisonReference, +} from "./types"; + +type ComparisonEntry = CollectionEntry<"compare">; + +export function toComparisonPage(entry: ComparisonEntry): ComparisonPage { + return { + ...entry.data, + slug: entry.id, + }; +} + +export async function getComparisonEntries(): Promise { + return (await getCollection("compare")).toSorted( + (left, right) => left.data.order - right.data.order + ); +} + +export async function getComparisonPages(): Promise { + return (await getComparisonEntries()).map(toComparisonPage); +} + +export function getComparisonPath( + comparison: Pick +): string { + return `/compare/${comparison.slug}/`; +} + +export function getComparisonKeywords(comparison: ComparisonPage): string { + return [ + ...comparison.keywords, + ONEQUERY.NAME, + "AI agent access control", + "production context without production keys", + "agent data access audit logs", + ].join(", "); +} + +export function getRelatedComparisons( + comparison: Pick, + comparisons: readonly ComparisonPage[] +): ComparisonPage[] { + return comparisons + .filter((candidate) => candidate.slug !== comparison.slug) + .slice(0, 3); +} diff --git a/apps/landing/src/features/compare/schema.ts b/apps/landing/src/features/compare/schema.ts new file mode 100644 index 00000000..2524e6ca --- /dev/null +++ b/apps/landing/src/features/compare/schema.ts @@ -0,0 +1,39 @@ +import { z } from "astro/zod"; + +const comparisonCriterionSchema = z.object({ + alternative: z.string().min(1), + factor: z.string().min(1), + oneQuery: z.string().min(1), +}); + +const comparisonFaqSchema = z.object({ + answer: z.string().min(1), + question: z.string().min(1), +}); + +const comparisonReferenceSchema = z.object({ + description: z.string().min(1), + href: z.string().min(1), + label: z.string().min(1), +}); + +export function createComparisonContentSchema() { + return z.object({ + alternativeBestFor: z.array(z.string().min(1)).min(1), + alternativeName: z.string().min(1), + answer: z.string().min(1), + category: z.string().min(1), + criteria: z.array(comparisonCriterionSchema).min(1), + eyebrow: z.string().min(1), + faqs: z.array(comparisonFaqSchema).min(1), + heroSignals: z.array(z.string().min(1)).min(1), + keywords: z.array(z.string().min(1)).min(1), + migrationSteps: z.array(z.string().min(1)).min(1), + metaDescription: z.string().min(1).max(160), + oneQueryBestFor: z.array(z.string().min(1)).min(1), + order: z.number().int().positive(), + references: z.array(comparisonReferenceSchema).min(1), + summary: z.string().min(1), + title: z.string().min(1), + }); +} diff --git a/apps/landing/src/features/compare/styles.css b/apps/landing/src/features/compare/styles.css new file mode 100644 index 00000000..1edf244a --- /dev/null +++ b/apps/landing/src/features/compare/styles.css @@ -0,0 +1,625 @@ +.compare-shell { + --compare-line: var(--line); + --compare-line-strong: var(--line-strong); + --compare-ink: var(--ink); + --compare-muted: var(--text-muted); + --compare-soft: var(--text-soft); + --compare-accent: #0f766e; + --compare-accent-soft: rgba(15, 118, 110, 0.1); + --compare-blue: #1d4ed8; + --compare-blue-soft: rgba(29, 78, 216, 0.08); + display: flex; + flex-direction: column; + min-height: 100vh; + background: var(--page-bg); + color: var(--compare-ink); +} + +.compare-shell .site-header { + background: rgba(255, 255, 255, 0.92); +} + +.compare-shell .site-nav a[aria-current="page"] { + background: var(--wash); + box-shadow: inset 0 0 0 1px var(--compare-line); + color: var(--compare-ink); +} + +.compare-index-main, +.compare-detail-main { + width: min(100%, 1180px); + margin: 0 auto; + flex: 1; +} + +.compare-index-main { + padding-bottom: 68px; +} + +.compare-index-hero { + display: grid; + grid-template-columns: minmax(0, 1fr) 330px; + gap: 46px; + align-items: end; + padding: 54px 0 42px; + border-bottom: 1px solid var(--compare-line); +} + +.compare-index-copy h1, +.compare-detail-hero h1 { + max-width: 900px; + margin: 12px 0 0; + color: var(--compare-ink); + font-size: 56px; + font-weight: 650; + line-height: 1; + letter-spacing: 0; +} + +.compare-index-copy p:not(.eyebrow), +.compare-detail-lede { + max-width: 760px; + margin: 22px 0 0; + color: var(--compare-muted); + font-size: 19px; + line-height: 1.56; +} + +.compare-index-signals, +.compare-hero-signals { + display: grid; + gap: 8px; + margin: 0; + padding: 0; + list-style: none; +} + +.compare-index-signals li, +.compare-hero-signals li { + min-height: 34px; + padding: 7px 10px; + border: 1px solid var(--compare-line); + border-radius: 8px; + background: var(--surface); + color: var(--compare-muted); + font-size: 14px; + line-height: 20px; + box-shadow: var(--shadow-ring); +} + +.compare-index-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 16px; + padding: 36px 0; +} + +.compare-index-card, +.compare-reference-card, +.compare-related-card { + border: 1px solid var(--compare-line); + border-radius: 8px; + background: rgba(255, 255, 255, 0.82); + color: inherit; + text-decoration: none; + box-shadow: var(--shadow-ring); + transition: + border-color 160ms ease, + box-shadow 160ms ease, + transform 160ms ease; +} + +.compare-index-card { + display: grid; + align-content: start; + min-height: 238px; + padding: 18px; +} + +.compare-index-card:hover, +.compare-reference-card:hover, +.compare-related-card:hover { + border-color: var(--compare-line-strong); + box-shadow: + 0 16px 32px rgba(0, 0, 0, 0.06), + var(--shadow-ring); + transform: translateY(-1px); +} + +.compare-card-category, +.compare-related-card span { + color: var(--compare-soft); + font-size: 12px; + font-weight: 650; + line-height: 16px; + text-transform: uppercase; +} + +.compare-index-card h2 { + margin: 12px 0 0; + color: var(--compare-ink); + font-size: 26px; + font-weight: 650; + line-height: 1.12; + letter-spacing: 0; +} + +.compare-index-card p { + margin: 14px 0 0; + color: var(--compare-muted); + font-size: 15px; + line-height: 1.5; +} + +.compare-card-link { + align-self: end; + margin-top: 22px; + color: var(--compare-accent); + font-size: 14px; + font-weight: 650; + line-height: 20px; +} + +.compare-index-answer { + display: grid; + gap: 18px; + max-width: 880px; + padding-top: 24px; + border-top: 1px solid var(--compare-line); +} + +.compare-index-answer h2, +.compare-answer-section h2, +.compare-section-heading h2 { + margin: 0; + color: var(--compare-ink); + font-size: 32px; + font-weight: 650; + line-height: 1.15; + letter-spacing: 0; +} + +.compare-index-answer h2 { + margin-top: -6px; +} + +.compare-index-answer p, +.compare-answer-section p:not(.eyebrow), +.compare-mdx-content, +.compare-fit-panel li, +.compare-migration-list, +.compare-faq-list p, +.compare-reference-card span, +.compare-related-card em { + color: var(--compare-muted); + font-size: 17px; + line-height: 1.58; +} + +.compare-index-answer p { + margin: 0; +} + +.compare-breadcrumb { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; + padding: 30px 0 12px; + color: var(--compare-soft); + font-size: 14px; + line-height: 20px; +} + +.compare-breadcrumb a { + color: var(--compare-muted); + text-decoration: none; +} + +.compare-breadcrumb a:hover { + color: var(--compare-ink); +} + +.compare-detail-main { + padding-bottom: 68px; +} + +.compare-detail-hero { + display: grid; + grid-template-columns: minmax(0, 1fr) 360px; + gap: 46px; + align-items: start; + padding: 34px 0 52px; + border-bottom: 1px solid var(--compare-line); +} + +.compare-detail-copy { + min-width: 0; +} + +.compare-detail-actions { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 30px; +} + +.compare-hero-visual { + display: grid; + gap: 18px; + padding: 18px; + border: 1px solid var(--compare-line); + border-radius: 8px; + background: rgba(255, 255, 255, 0.86); + box-shadow: var(--shadow-ring); +} + +.compare-flow-head { + display: grid; + grid-template-columns: 48px minmax(0, 1fr); + gap: 12px; + align-items: center; +} + +.compare-flow-logo { + display: inline-flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; + border-radius: 8px; + background: var(--compare-blue-soft); + color: var(--compare-blue); +} + +.compare-flow-head strong, +.compare-flow-head span span { + display: block; +} + +.compare-flow-head strong { + color: var(--compare-ink); + font-size: 18px; + line-height: 1.25; +} + +.compare-flow-head span span { + margin-top: 3px; + color: var(--compare-soft); + font-size: 13px; + line-height: 18px; +} + +.compare-flow-steps { + display: grid; + gap: 8px; + margin: 0; + padding: 0; + counter-reset: compare-step; + list-style: none; +} + +.compare-flow-steps li { + display: grid; + grid-template-columns: 28px minmax(0, 1fr); + gap: 10px; + align-items: center; + min-height: 40px; + color: var(--compare-muted); + font-size: 14px; + line-height: 20px; +} + +.compare-flow-steps li::before { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 8px; + background: var(--compare-accent-soft); + color: var(--compare-accent); + font-size: 12px; + font-weight: 700; + content: counter(compare-step); + counter-increment: compare-step; +} + +.compare-answer-section { + padding: 44px 0; + border-bottom: 1px solid var(--compare-line); +} + +.compare-answer-section h2 { + margin-top: 10px; +} + +.compare-answer-section p:not(.eyebrow) { + max-width: 900px; + margin: 18px 0 0; +} + +.compare-mdx-content { + display: grid; + gap: 20px; + max-width: 900px; + padding: 40px 0; + border-bottom: 1px solid var(--compare-line); +} + +.compare-mdx-content :where(h2, h3, p, ul, ol) { + margin: 0; +} + +.compare-mdx-content h2 { + color: var(--compare-ink); + font-size: 28px; + font-weight: 650; + line-height: 1.18; + letter-spacing: 0; +} + +.compare-mdx-content h3 { + color: var(--compare-ink); + font-size: 22px; + font-weight: 650; + line-height: 1.22; + letter-spacing: 0; +} + +.compare-mdx-content a { + color: var(--compare-blue); +} + +.compare-mdx-content ul, +.compare-mdx-content ol { + display: grid; + gap: 10px; + padding-left: 22px; +} + +.compare-section { + display: grid; + gap: 22px; + padding: 44px 0; + border-bottom: 1px solid var(--compare-line); +} + +.compare-section-heading { + display: grid; + gap: 10px; + max-width: 760px; +} + +.compare-table-wrap { + width: 100%; + overflow-x: auto; + border: 1px solid var(--compare-line); + border-radius: 8px; + background: var(--surface); + box-shadow: var(--shadow-ring); +} + +.compare-table { + width: 100%; + min-width: 760px; + border-collapse: collapse; +} + +.compare-table th, +.compare-table td { + padding: 18px; + border-top: 1px solid var(--compare-line); + text-align: left; + vertical-align: top; +} + +.compare-table thead th { + border-top: 0; + background: var(--surface-muted); + color: var(--compare-ink); + font-size: 13px; + font-weight: 700; + line-height: 18px; + text-transform: uppercase; +} + +.compare-table tbody th { + width: 190px; + color: var(--compare-ink); + font-size: 16px; + font-weight: 650; + line-height: 1.35; +} + +.compare-table td { + color: var(--compare-muted); + font-size: 15px; + line-height: 1.55; +} + +.compare-fit-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 18px; + padding: 44px 0; + border-bottom: 1px solid var(--compare-line); +} + +.compare-fit-panel { + padding: 18px; + border: 1px solid var(--compare-line); + border-radius: 8px; + background: var(--surface); + box-shadow: var(--shadow-ring); +} + +.compare-fit-panel-primary { + border-color: rgba(15, 118, 110, 0.28); + background: var(--compare-accent-soft); +} + +.compare-fit-panel ul { + display: grid; + gap: 12px; + margin: 16px 0 0; + padding-left: 20px; +} + +.compare-migration-list { + display: grid; + gap: 14px; + margin: 0; + padding-left: 24px; +} + +.compare-faq-list { + display: grid; + gap: 0; +} + +.compare-faq-list section { + padding: 20px 0; + border-top: 1px solid var(--compare-line); +} + +.compare-faq-list section:first-child { + border-top: 0; +} + +.compare-faq-list h3 { + margin: 0; + color: var(--compare-ink); + font-size: 20px; + font-weight: 650; + line-height: 1.25; + letter-spacing: 0; +} + +.compare-faq-list p { + max-width: 900px; + margin: 10px 0 0; +} + +.compare-reference-grid, +.compare-related-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 14px; +} + +.compare-reference-card { + display: grid; + gap: 8px; + min-height: 136px; + padding: 16px; +} + +.compare-reference-card strong, +.compare-related-card strong { + color: var(--compare-ink); + font-size: 17px; + line-height: 1.28; +} + +.compare-reference-card span, +.compare-related-card em { + display: block; + font-style: normal; +} + +.compare-related-section { + display: grid; + gap: 22px; + padding-top: 44px; +} + +.compare-related-card { + display: grid; + gap: 8px; + min-height: 164px; + padding: 16px; +} + +@media (max-width: 1100px) { + .compare-index-hero, + .compare-detail-hero { + grid-template-columns: 1fr; + } + + .compare-index-signals { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + +@media (max-width: 860px) { + .compare-index-grid, + .compare-reference-grid, + .compare-related-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .compare-index-copy h1, + .compare-detail-hero h1 { + font-size: 42px; + } + + .compare-fit-grid { + grid-template-columns: 1fr; + } +} + +@media (max-width: 640px) { + .compare-index-hero { + padding-top: 32px; + padding-bottom: 34px; + } + + .compare-index-copy h1, + .compare-detail-hero h1 { + font-size: 34px; + } + + .compare-index-copy p:not(.eyebrow), + .compare-detail-lede, + .compare-index-answer p, + .compare-answer-section p:not(.eyebrow), + .compare-mdx-content, + .compare-fit-panel li, + .compare-migration-list, + .compare-faq-list p, + .compare-reference-card span, + .compare-related-card em { + font-size: 16px; + } + + .compare-index-signals, + .compare-index-grid, + .compare-reference-grid, + .compare-related-grid { + grid-template-columns: 1fr; + } + + .compare-index-card { + min-height: 0; + } + + .compare-detail-hero { + padding-top: 24px; + padding-bottom: 40px; + } + + .compare-index-answer h2, + .compare-answer-section h2, + .compare-section-heading h2 { + font-size: 26px; + } +} + +@media (prefers-reduced-motion: reduce) { + .compare-index-card, + .compare-reference-card, + .compare-related-card { + transition: none; + } +} diff --git a/apps/landing/src/features/compare/types.ts b/apps/landing/src/features/compare/types.ts new file mode 100644 index 00000000..5e79ac48 --- /dev/null +++ b/apps/landing/src/features/compare/types.ts @@ -0,0 +1,39 @@ +export type ComparisonCriterion = { + alternative: string; + factor: string; + oneQuery: string; +}; + +export type ComparisonFaq = { + answer: string; + question: string; +}; + +export type ComparisonReference = { + description: string; + href: string; + label: string; +}; + +export type ComparisonContent = { + alternativeBestFor: readonly string[]; + alternativeName: string; + answer: string; + category: string; + criteria: readonly ComparisonCriterion[]; + eyebrow: string; + faqs: readonly ComparisonFaq[]; + heroSignals: readonly string[]; + keywords: readonly string[]; + migrationSteps: readonly string[]; + metaDescription: string; + oneQueryBestFor: readonly string[]; + order: number; + references: readonly ComparisonReference[]; + summary: string; + title: string; +}; + +export type ComparisonPage = ComparisonContent & { + slug: string; +}; diff --git a/apps/landing/src/pages/compare/[comparisonSlug].astro b/apps/landing/src/pages/compare/[comparisonSlug].astro new file mode 100644 index 00000000..4a843122 --- /dev/null +++ b/apps/landing/src/pages/compare/[comparisonSlug].astro @@ -0,0 +1,248 @@ +--- +import type { CollectionEntry } from "astro:content"; +import { render } from "astro:content"; +import OneQueryIcon from "@/assets/onequery-icon.svg"; +import { + getComparisonEntries, + getComparisonKeywords, + getComparisonPages, + getComparisonPath, + getRelatedComparisons, + toComparisonPage, +} from "@/features/compare/data"; +import "@/features/compare/styles.css"; +import MarketingLayout from "@/layouts/MarketingLayout.astro"; +import TrackedLink from "@/shared/components/TrackedLink.astro"; +import { + createCanonicalUrl, + createComparisonPageStructuredData, +} from "@/shared/seo/schema"; + +export const prerender = true; + +export async function getStaticPaths() { + const entries = await getComparisonEntries(); + + return entries.map((entry) => ({ + params: { + comparisonSlug: entry.id, + }, + props: { + entry, + }, + })); +} + +interface Props { + entry: CollectionEntry<"compare">; +} + +const { entry } = Astro.props; +const comparison = toComparisonPage(entry); +const { Content } = await render(entry); +const comparisons = await getComparisonPages(); +const relatedComparisons = getRelatedComparisons(comparison, comparisons); +const canonicalUrl = createCanonicalUrl(getComparisonPath(comparison), Astro.site); +const structuredData = createComparisonPageStructuredData({ + comparison, + relatedComparisons, + site: Astro.site, +}); + +function isExternalLink(href: string) { + return /^https?:\/\//u.test(href); +} +--- + + +
+ + +
+
+

{comparison.eyebrow}

+

{comparison.title.replace(" | ", ": ")}

+

{comparison.summary}

+
+ + Connect a source + + + View connectors + +
+
+ + +
+ +
+

Direct answer

+

+ OneQuery vs {comparison.alternativeName}: which should agents use? +

+

{comparison.answer}

+
+ +
+ +
+ +
+
+

Comparison criteria

+

Where the boundary lives

+
+
+ + + + + + + + + + { + comparison.criteria.map((criterion) => ( + + + + + + )) + } + +
FactorOneQuery{comparison.alternativeName}
{criterion.factor}{criterion.oneQuery}{criterion.alternative}
+
+
+ +
+
+

Choose OneQuery when

+
    + {comparison.oneQueryBestFor.map((item) =>
  • {item}
  • )} +
+
+
+

Choose {comparison.alternativeName} when

+
    + {comparison.alternativeBestFor.map((item) =>
  • {item}
  • )} +
+
+
+ +
+
+

Rollout pattern

+

Move access without changing the agent job.

+
+
    + {comparison.migrationSteps.map((step) =>
  1. {step}
  2. )} +
+
+ +
+
+

FAQ

+

Common questions

+
+
+ { + comparison.faqs.map((faq) => ( +
+

{faq.question}

+

{faq.answer}

+
+ )) + } +
+
+ +
+
+

Reference points

+

Docs behind the comparison

+
+
+ { + comparison.references.map((reference) => ( + + {reference.label} + {reference.description} + + )) + } +
+
+ + +
+
diff --git a/apps/landing/src/pages/compare/index.astro b/apps/landing/src/pages/compare/index.astro new file mode 100644 index 00000000..5265373f --- /dev/null +++ b/apps/landing/src/pages/compare/index.astro @@ -0,0 +1,90 @@ +--- +import { + getComparisonPages, + getComparisonPath, +} from "@/features/compare/data"; +import "@/features/compare/styles.css"; +import MarketingLayout from "@/layouts/MarketingLayout.astro"; +import TrackedLink from "@/shared/components/TrackedLink.astro"; +import { + createCanonicalUrl, + createComparisonIndexStructuredData, +} from "@/shared/seo/schema"; + +export const prerender = true; + +const title = "OneQuery Comparisons | Safe AI Agent Data Access"; +const description = + "Compare OneQuery with direct credentials, MCP database servers, internal data agents, BI chatbots, Metabase, and DataGrip for AI agent access."; +const canonicalUrl = createCanonicalUrl("/compare", Astro.site); +const comparisons = await getComparisonPages(); +const structuredData = createComparisonIndexStructuredData({ + comparisons, + description, + site: Astro.site, + title, +}); +--- + + +
+
+
+

Compare OneQuery

+

Choose the right data access boundary for AI agents.

+

+ These comparisons separate protocol adapters, BI tools, IDEs, and + custom agents from the governed source access layer production + workflows still need. +

+
+
    +
  • Credential isolation
  • +
  • Read-only query boundaries
  • +
  • Agent audit history
  • +
+
+ +
+ { + comparisons.map((comparison) => ( + + {comparison.category} +

{comparison.alternativeName}

+

{comparison.summary}

+ Read comparison +
+ )) + } +
+ +
+

Direct answer

+

+ OneQuery is for governed production source access, not dashboards or + human SQL editing. +

+

+ Teams can keep BI platforms, MCP clients, database IDEs, and internal + agents. OneQuery fits where those systems need a shared access layer + that keeps credentials centralized, applies deterministic limits, and + records what agents did. +

+ + Read governed access docs + +
+
+
diff --git a/apps/landing/src/shared/components/Header.astro b/apps/landing/src/shared/components/Header.astro index 0c93722c..df7d47bb 100644 --- a/apps/landing/src/shared/components/Header.astro +++ b/apps/landing/src/shared/components/Header.astro @@ -13,6 +13,7 @@ const NAVIGATION_ITEMS = [ { href: "/", label: "Product" }, { href: "/#demo", label: "Demo" }, { href: "/#install", label: "Install" }, + { href: "/compare/", label: "Compare" }, { href: "/docs/", label: "Docs" }, { href: "/connectors/", label: "Connectors" }, { href: "/blog/", label: "Blog" }, diff --git a/apps/landing/src/shared/seo/constants.ts b/apps/landing/src/shared/seo/constants.ts index 3c28a4a5..ab66cea0 100644 --- a/apps/landing/src/shared/seo/constants.ts +++ b/apps/landing/src/shared/seo/constants.ts @@ -55,6 +55,7 @@ export const ONEQUERY = { export const SEO_PATHS = { BLOG: "/blog", + COMPARE: "/compare", CONNECTORS: "/connectors", DOCS: "/docs", } as const; @@ -63,6 +64,8 @@ export const SCHEMA_FRAGMENTS = { ARTICLE: "article", BLOG: "blog", BREADCRUMB: "breadcrumb", + COMPARISON_CRITERIA: "comparison-criteria", + COMPARISONS: "comparisons", CONNECTOR: "connector", CONNECTORS: "connectors", DEMO_VIDEO: "demo-video", diff --git a/apps/landing/src/shared/seo/schema.ts b/apps/landing/src/shared/seo/schema.ts index 353fb164..66e705bb 100644 --- a/apps/landing/src/shared/seo/schema.ts +++ b/apps/landing/src/shared/seo/schema.ts @@ -1,6 +1,7 @@ import type { JsonLdObject, StructuredDataGraph } from "@onequery/astro-seo"; import type { BlogPost, BlogPostSummary } from "@/features/blog/types"; +import type { ComparisonPage } from "@/features/compare/types"; import type { ConnectorFaq, DataSourceConnector, @@ -96,6 +97,19 @@ type ConnectorPageStructuredDataInput = { title: string; }; +type ComparisonIndexStructuredDataInput = { + comparisons: readonly ComparisonPage[]; + description: string; + site?: SiteInput; + title: string; +}; + +type ComparisonPageStructuredDataInput = { + comparison: ComparisonPage; + relatedComparisons: readonly ComparisonPage[]; + site?: SiteInput; +}; + type DocsIndexStructuredDataInput = { description: string; site?: SiteInput; @@ -339,6 +353,30 @@ function createConnectorFeatureList(connector: DataSourceConnector) { ]; } +function getComparisonPagePath(comparison: Pick) { + return `${SEO_PATHS.COMPARE}/${comparison.slug}`; +} + +function createComparisonSummaryStructuredData( + comparison: ComparisonPage, + site: SiteInput +): StructuredDataNode { + const pageUrl = createCanonicalUrl(getComparisonPagePath(comparison), site); + + return { + "@type": "WebPage", + "@id": getNodeId(pageUrl, SCHEMA_FRAGMENTS.WEBPAGE), + url: pageUrl, + name: comparison.title, + headline: comparison.title, + description: comparison.metaDescription, + inLanguage: "en", + about: { + "@id": getSoftwareApplicationId(site), + }, + }; +} + function createConnectorSoftwareApplication( connector: DataSourceConnector, site: SiteInput @@ -494,6 +532,169 @@ export function createDocsIndexStructuredData( ]); } +export function createComparisonIndexStructuredData( + input: ComparisonIndexStructuredDataInput +): StructuredDataGraph { + const siteUrl = normalizeSiteUrl(input.site); + const compareUrl = createCanonicalUrl(SEO_PATHS.COMPARE, siteUrl); + + return createGraph([ + ...createSiteGraph(siteUrl), + createOneQuerySoftwareApplication({ + description: ONEQUERY.SITE_DESCRIPTION, + site: siteUrl, + }), + { + "@type": "CollectionPage", + "@id": getNodeId(compareUrl, SCHEMA_FRAGMENTS.WEBPAGE), + url: compareUrl, + name: input.title, + description: input.description, + inLanguage: "en", + isPartOf: { + "@id": getWebsiteId(siteUrl), + }, + about: { + "@id": getSoftwareApplicationId(siteUrl), + }, + mainEntity: { + "@id": getNodeId(compareUrl, SCHEMA_FRAGMENTS.COMPARISONS), + }, + breadcrumb: { + "@id": getNodeId(compareUrl, SCHEMA_FRAGMENTS.BREADCRUMB), + }, + }, + { + "@type": "ItemList", + "@id": getNodeId(compareUrl, SCHEMA_FRAGMENTS.COMPARISONS), + name: `${ONEQUERY.NAME} AI agent data access comparisons`, + numberOfItems: input.comparisons.length, + itemListElement: input.comparisons.map((comparison, index) => ({ + "@type": "ListItem", + position: index + 1, + item: createComparisonSummaryStructuredData(comparison, siteUrl), + })), + }, + { + "@type": "BreadcrumbList", + "@id": getNodeId(compareUrl, SCHEMA_FRAGMENTS.BREADCRUMB), + itemListElement: [ + { + "@type": "ListItem", + position: 1, + name: ONEQUERY.NAME, + item: `${siteUrl}/`, + }, + { + "@type": "ListItem", + position: 2, + name: "Compare", + item: compareUrl, + }, + ], + }, + ]); +} + +export function createComparisonPageStructuredData( + input: ComparisonPageStructuredDataInput +): StructuredDataGraph { + const siteUrl = normalizeSiteUrl(input.site); + const compareUrl = createCanonicalUrl(SEO_PATHS.COMPARE, siteUrl); + const pageUrl = createCanonicalUrl( + getComparisonPagePath(input.comparison), + siteUrl + ); + + return createGraph([ + ...createSiteGraph(siteUrl), + createOneQuerySoftwareApplication({ + description: ONEQUERY.SITE_DESCRIPTION, + site: siteUrl, + }), + { + "@type": "WebPage", + "@id": getNodeId(pageUrl, SCHEMA_FRAGMENTS.WEBPAGE), + url: pageUrl, + name: input.comparison.title, + headline: input.comparison.title, + description: input.comparison.metaDescription, + inLanguage: "en", + isPartOf: { + "@id": getWebsiteId(siteUrl), + }, + about: { + "@id": getSoftwareApplicationId(siteUrl), + }, + mainEntity: { + "@id": getNodeId(pageUrl, SCHEMA_FRAGMENTS.COMPARISON_CRITERIA), + }, + hasPart: [ + { + "@id": getNodeId(pageUrl, SCHEMA_FRAGMENTS.COMPARISON_CRITERIA), + }, + { + "@id": getNodeId(pageUrl, SCHEMA_FRAGMENTS.FAQ), + }, + ], + relatedLink: input.relatedComparisons.map((comparison) => + createCanonicalUrl(getComparisonPagePath(comparison), siteUrl) + ), + breadcrumb: { + "@id": getNodeId(pageUrl, SCHEMA_FRAGMENTS.BREADCRUMB), + }, + }, + { + "@type": "ItemList", + "@id": getNodeId(pageUrl, SCHEMA_FRAGMENTS.COMPARISON_CRITERIA), + name: `${ONEQUERY.NAME} vs ${input.comparison.alternativeName} comparison criteria`, + numberOfItems: input.comparison.criteria.length, + itemListElement: input.comparison.criteria.map((criterion, index) => ({ + "@type": "ListItem", + position: index + 1, + name: criterion.factor, + description: `${criterion.oneQuery} Alternative: ${criterion.alternative}`, + })), + }, + { + "@type": "FAQPage", + "@id": getNodeId(pageUrl, SCHEMA_FRAGMENTS.FAQ), + mainEntity: input.comparison.faqs.map((faq) => ({ + "@type": "Question", + name: faq.question, + acceptedAnswer: { + "@type": "Answer", + text: faq.answer, + }, + })), + }, + { + "@type": "BreadcrumbList", + "@id": getNodeId(pageUrl, SCHEMA_FRAGMENTS.BREADCRUMB), + itemListElement: [ + { + "@type": "ListItem", + position: 1, + name: ONEQUERY.NAME, + item: `${siteUrl}/`, + }, + { + "@type": "ListItem", + position: 2, + name: "Compare", + item: compareUrl, + }, + { + "@type": "ListItem", + position: 3, + name: input.comparison.alternativeName, + item: pageUrl, + }, + ], + }, + ]); +} + export function createBlogIndexStructuredData( input: BlogIndexStructuredDataInput ): StructuredDataGraph {