Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/landing/astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { agentMarkdown } from "@onequery/astro-agent-markdown/astro";
import { defineConfig, envField, fontProviders } from "astro/config";
import { visualizer } from "rollup-plugin-visualizer";

import { remarkReadingTime } from "./src/features/blog/remark-reading-time";
import {
DEFAULT_DEV_PORT,
DEV_SERVER_HOST,
Expand Down Expand Up @@ -112,6 +113,7 @@ export default defineConfig({
alt: "OneQuery",
src: "/src/assets/onequery-icon.svg",
},
routeMiddleware: "./src/starlightRouteData.ts",
sidebar: [
{
items: ["docs", "docs/getting-started"],
Expand Down Expand Up @@ -170,6 +172,9 @@ export default defineConfig({
],
}),
],
markdown: {
remarkPlugins: [remarkReadingTime],
},
server: {
host: DEV_SERVER_HOST,
port: DEFAULT_DEV_PORT,
Expand Down
3 changes: 3 additions & 0 deletions apps/landing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,16 @@
"@astrojs/starlight": "catalog:",
"@nanostores/react": "catalog:",
"@onequery/astro-agent-markdown": "workspace:*",
"@onequery/astro-seo": "workspace:*",
"@onequery/db": "workspace:*",
"@onequery/ui": "workspace:*",
"astro": "catalog:",
"better-result": "catalog:",
"mdast-util-to-string": "4.0.0",
"nanostores": "catalog:",
"react": "19.3.0-canary-d5736f09-20260507",
"react-dom": "19.3.0-canary-d5736f09-20260507",
"reading-time": "1.5.0",
"simple-icons": "catalog:",
"zod": "catalog:"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ coverImage:
src: "../../assets/blog/context-enrichment-with-onequery-icon.png"
alt: "Context Enrichment with OneQuery - OneQuery blog cover image."
publishedAt: "2026-05-01"
readTime: "10 min read"
---

## From Context Enrichment to Implementation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ coverImage:
src: "../../assets/blog/debug-production-agent-runs-with-onequery-icon.png"
alt: "Debugging production on Cloudflare with Codex - OneQuery blog cover image."
publishedAt: "2026-05-06"
readTime: "7 min read"
---

## The Concept: Connect Cloudflare Logs to Codex
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ coverImage:
src: "../../assets/blog/do-not-give-agents-production-keys-icon.png"
alt: "Do not give agents the keys to production - OneQuery blog cover image."
publishedAt: "2026-04-29"
readTime: "9 min read"
---

## Agents are not operators
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ coverImage:
src: "../../assets/blog/how-startups-can-build-an-in-house-data-agent-icon.png"
alt: "How startups can build an in-house data agent - OneQuery blog cover image."
publishedAt: "2026-04-28"
readTime: "10 min read"
---

## What OpenAI actually built
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ coverImage:
src: "../../assets/blog/llm-safe-data-access-layer-icon.png"
alt: "A Safe Data Access Layer for LLMs - OneQuery blog cover image."
publishedAt: "2026-04-30"
readTime: "8 min read"
---

## LLM Risk Is Authority Risk
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ coverImage:
src: "../../assets/blog/making-data-source-setup-boring-icon.png"
alt: "Making data source setup boring - OneQuery blog cover image."
publishedAt: "2026-04-21"
readTime: "7 min read"
---

## Why source setup stays messy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ coverImage:
src: "../../assets/blog/using-llm-telemetry-to-improve-prompts-with-gepa-icon.png"
alt: "Using LLM telemetry to improve prompts with GEPA - OneQuery blog cover image."
publishedAt: "2026-04-30"
readTime: "9 min read"
---

## What is GEPA?
Expand Down
159 changes: 0 additions & 159 deletions apps/landing/src/content/docs/docs/index.mdx
Original file line number Diff line number Diff line change
@@ -1,165 +1,6 @@
---
title: OneQuery Docs
description: OneQuery documentation for installing the CLI, connecting governed sources, running read-only queries, and giving agents safe production context.
head:
- tag: title
content: OneQuery Documentation | Governed Agent Data Access
- tag: meta
attrs:
name: keywords
content: OneQuery documentation, AI agent data access, governed data access, read-only queries, Source API, source identifiers, production context, audit history, CLI install
- tag: meta
attrs:
name: robots
content: index, follow, max-image-preview:large
- tag: meta
attrs:
property: og:title
content: OneQuery Documentation | Governed Agent Data Access
- tag: meta
attrs:
property: og:type
content: website
- tag: meta
attrs:
property: og:site_name
content: OneQuery
- tag: meta
attrs:
property: og:locale
content: en_US
- tag: meta
attrs:
property: og:image
content: https://onequery.dev/og.png
- tag: meta
attrs:
property: og:image:secure_url
content: https://onequery.dev/og.png
- tag: meta
attrs:
property: og:image:type
content: image/png
- tag: meta
attrs:
property: og:image:width
content: "1200"
- tag: meta
attrs:
property: og:image:height
content: "630"
- tag: meta
attrs:
property: og:image:alt
content: OneQuery - Governed Data Access for AI Agents
- tag: meta
attrs:
name: twitter:title
content: OneQuery Documentation | Governed Agent Data Access
- tag: meta
attrs:
name: twitter:description
content: OneQuery documentation for installing the CLI, connecting governed sources, running read-only queries, and giving agents safe production context.
- tag: meta
attrs:
name: twitter:image
content: https://onequery.dev/og.png
- tag: meta
attrs:
name: twitter:image:alt
content: OneQuery - Governed Data Access for AI Agents
- tag: script
attrs:
type: application/ld+json
content: |
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://onequery.dev/#organization",
"name": "OneQuery",
"url": "https://onequery.dev/",
"logo": {
"@type": "ImageObject",
"url": "https://onequery.dev/onequery-icon.png",
"width": 512,
"height": 512,
"caption": "OneQuery icon"
},
"sameAs": ["https://github.com/wordbricks/onequery"]
},
{
"@type": "WebSite",
"@id": "https://onequery.dev/#website",
"name": "OneQuery",
"url": "https://onequery.dev/",
"description": "OneQuery gives AI agents production context without production keys, using approved sources, centralized credentials, enforced limits, and full audit logs.",
"inLanguage": "en",
"publisher": {
"@id": "https://onequery.dev/#organization"
}
},
{
"@type": "SoftwareApplication",
"@id": "https://onequery.dev/#software",
"name": "OneQuery",
"url": "https://onequery.dev/",
"applicationCategory": "DeveloperApplication",
"operatingSystem": "Web, CLI, Self-hosted gateway",
"description": "OneQuery gives AI agents production context without production keys, using approved sources, centralized credentials, enforced limits, and full audit logs.",
"image": {
"@type": "ImageObject",
"url": "https://onequery.dev/onequery-icon.png",
"width": 512,
"height": 512,
"caption": "OneQuery icon"
},
"publisher": {
"@id": "https://onequery.dev/#organization"
},
"sameAs": ["https://github.com/wordbricks/onequery"],
"codeRepository": "https://github.com/wordbricks/onequery",
"installUrl": "https://www.npmjs.com/package/@onequery/cli",
"softwareHelp": "https://onequery.dev/docs/operations/self-host/"
},
{
"@type": "WebPage",
"@id": "https://onequery.dev/docs/#webpage",
"url": "https://onequery.dev/docs/",
"name": "OneQuery Documentation",
"headline": "OneQuery Documentation",
"description": "OneQuery documentation for installing the CLI, connecting governed sources, running read-only queries, and giving agents safe production context.",
"isPartOf": {
"@id": "https://onequery.dev/#website"
},
"about": {
"@id": "https://onequery.dev/#software"
},
"breadcrumb": {
"@id": "https://onequery.dev/docs/#breadcrumb"
}
},
{
"@type": "BreadcrumbList",
"@id": "https://onequery.dev/docs/#breadcrumb",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "OneQuery",
"item": "https://onequery.dev/"
},
{
"@type": "ListItem",
"position": 2,
"name": "Documentation",
"item": "https://onequery.dev/docs/"
}
]
}
]
}
sidebar:
label: Overview
order: 1
Expand Down
46 changes: 43 additions & 3 deletions apps/landing/src/features/blog/collection.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,50 @@
import { getCollection } from "astro:content";
import type { CollectionEntry } from "astro:content";
import { getCollection, render } from "astro:content";

import type { BlogPost, BlogPostContent, BlogPostSummary } from "./types";

const READ_TIME_PATTERN = /^\d+ min read$/u;
const blogDateFormatter = new Intl.DateTimeFormat("en-US", {
day: "numeric",
month: "short",
timeZone: "UTC",
year: "numeric",
});

type BlogPostRenderData = {
headings?: BlogPost["headings"];
remarkPluginFrontmatter: unknown;
};

function formatBlogPostDate(publishedAt: string) {
return blogDateFormatter.format(new Date(`${publishedAt}T00:00:00.000Z`));
}

function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}

function getBlogPostReadTime(
entry: CollectionEntry<"blog">,
remarkPluginFrontmatter: unknown
) {
if (!isRecord(remarkPluginFrontmatter)) {
throw new Error(
`Blog post "${entry.id}" is missing remark plugin frontmatter.`
);
}

const { readTime } = remarkPluginFrontmatter;

if (typeof readTime === "string" && READ_TIME_PATTERN.test(readTime)) {
return readTime;
}

throw new Error(
`Blog post "${entry.id}" is missing a valid remark-generated readTime.`
);
}

export function comparePostDates(
left: Pick<BlogPostSummary, "publishedAt">,
right: Pick<BlogPostSummary, "publishedAt">
Expand All @@ -36,7 +67,7 @@ function toBlogPostSummary(post: BlogPost): BlogPostSummary {

export function toBlogPost(
entry: CollectionEntry<"blog">,
headings: BlogPost["headings"] = []
{ headings = [], remarkPluginFrontmatter }: BlogPostRenderData
): BlogPost {
const data = entry.data as BlogPostContent;

Expand All @@ -45,6 +76,7 @@ export function toBlogPost(
body: entry.body ?? "",
date: formatBlogPostDate(data.publishedAt),
headings,
readTime: getBlogPostReadTime(entry, remarkPluginFrontmatter),
slug: entry.id,
};
}
Expand All @@ -56,7 +88,15 @@ export async function getBlogPostEntries(): Promise<CollectionEntry<"blog">[]> {
}

export async function getBlogPosts(): Promise<BlogPost[]> {
return (await getBlogPostEntries()).map((entry) => toBlogPost(entry));
const entries = await getBlogPostEntries();

return Promise.all(
entries.map(async (entry) => {
const { headings, remarkPluginFrontmatter } = await render(entry);

return toBlogPost(entry, { headings, remarkPluginFrontmatter });
})
);
}

export async function getBlogPostSummaries(): Promise<BlogPostSummary[]> {
Expand Down
21 changes: 21 additions & 0 deletions apps/landing/src/features/blog/remark-reading-time.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { toString } from "mdast-util-to-string";
import getReadingTime from "reading-time";

type RemarkVFile = {
data: {
astro?: {
frontmatter?: Record<string, unknown>;
};
};
};

export function remarkReadingTime() {
return function transform(tree: unknown, file: RemarkVFile) {
const textOnPage = toString(tree);
const readingTime = getReadingTime(textOnPage);

file.data.astro ??= {};
file.data.astro.frontmatter ??= {};
file.data.astro.frontmatter.readTime = readingTime.text;
};
}
1 change: 0 additions & 1 deletion apps/landing/src/features/blog/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export function createBlogPostContentSchema(context: SchemaContext) {
coverImage: blogImageSchema,
description: z.string().min(1),
publishedAt: z.iso.date(),
readTime: z.string().regex(/^\d+ min read$/u),
title: z.string().min(1),
});
}
1 change: 1 addition & 0 deletions apps/landing/src/features/blog/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type BlogPost = BlogPostContent & {
body: string;
date: string;
headings: readonly MarkdownHeading[];
readTime: string;
slug: string;
};

Expand Down
Loading
Loading