diff --git a/.github/workflows/docs-skills-artifact.yml b/.github/workflows/docs-skills-artifact.yml new file mode 100644 index 000000000000..a98a1daff9f2 --- /dev/null +++ b/.github/workflows/docs-skills-artifact.yml @@ -0,0 +1,68 @@ +name: Build docs skills artifact + +on: + schedule: + - cron: '0 6 * * *' # Daily at 6am UTC + workflow_dispatch: + +concurrency: + group: docs-skills-artifact + cancel-in-progress: true + +env: + NODE_OPTIONS: '--max_old_space_size=8192' + +jobs: + build-docs-artifact: + name: Build & upload docs zip + runs-on: ubuntu-latest + timeout-minutes: 45 + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Set up pnpm + uses: pnpm/action-setup@c5ba7f7862a0f64c1b1a05fbac13e0b8e86ba08c # v4 + + - name: Set up Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: '22' + cache: 'pnpm' + + - name: Cache Gatsby + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: | + .cache + public + key: gatsby-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'gatsby-config.js') }} + restore-keys: | + gatsby-${{ runner.os }}- + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build site + run: pnpm build + env: + GATSBY_MINIMAL: 'true' + GENERATE_DOCS_MD: 'true' + GATSBY_SQUEAK_API_HOST: ${{ secrets.GATSBY_SQUEAK_API_HOST }} + CLOUDINARY_API_KEY: ${{ secrets.CLOUDINARY_API_KEY }} + CLOUDINARY_API_SECRET: ${{ secrets.CLOUDINARY_API_SECRET }} + + - name: Create docs zip + run: | + cd public + find docs -name '*.md' | zip -@ -r ../posthog-docs-md.zip llms.txt + + - name: Upload artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: posthog-docs-md + path: posthog-docs-md.zip + retention-days: 30 diff --git a/gatsby/onPostBuild.ts b/gatsby/onPostBuild.ts index f350f3b39cac..eebc7c75f983 100644 --- a/gatsby/onPostBuild.ts +++ b/gatsby/onPostBuild.ts @@ -486,7 +486,31 @@ const createOrUpdateStrapiPosts = async (posts, roadmaps) => { } export const onPostBuild: GatsbyNode['onPostBuild'] = async ({ graphql, reporter }) => { - if (process.env.GATSBY_MINIMAL === 'true') return + if (process.env.GATSBY_MINIMAL === 'true') { + if (process.env.GENERATE_DOCS_MD === 'true') { + // Minimal build with GENERATE_DOCS_MD: only convert HTML to markdown and generate llms.txt + const markdownPathsRegex = `/^/(${MARKDOWN_CONTENT_PATHS.map((p) => p.replace('/', '')).join('|')})/` + const docsQuery = (await graphql(` + query { + allMdx(filter: { fields: { slug: { regex: "${markdownPathsRegex}" } } }) { + nodes { + fields { + slug + } + frontmatter { + title + } + } + } + } + `)) as { data: { allMdx: { nodes: Array<{ fields: { slug: string }; frontmatter: { title: string } }> } } } + + const filteredPages = await generateRawMarkdownPages(docsQuery.data.allMdx.nodes) + const docsPages = filteredPages.filter((page) => page.fields.slug.startsWith('/docs')) + generateLlmsTxt(docsPages) + } + return + } // Generate API spec markdown files first try { const openApiSpecUrl = process.env.POSTHOG_OPEN_API_SPEC_URL || 'https://app.posthog.com/api/schema/' diff --git a/src/components/ReaderView/index.tsx b/src/components/ReaderView/index.tsx index b0933f748a5c..499395b4b9cf 100644 --- a/src/components/ReaderView/index.tsx +++ b/src/components/ReaderView/index.tsx @@ -9,6 +9,8 @@ import { IconRefresh, IconClockRewind, IconTextWidthFixed, + IconDownload, + IconCheck, } from '@posthog/icons' import ScrollArea from 'components/RadixUI/ScrollArea' import { Select } from '../RadixUI/Select' @@ -35,6 +37,7 @@ import SearchProvider from 'components/Editor/SearchProvider' import { useLocation } from '@reach/router' import { getProseClasses, isMarkdownContentPath } from '../../constants' import { useWindow } from '../../context/Window' +import { useToast } from '../../context/Toast' import { MenuItem, useApp } from '../../context/App' import { Questions } from 'components/Squeak' import { navigate } from 'gatsby' @@ -236,6 +239,63 @@ const EditOnGitHubButton = ({ filePath, sourceInstanceName }: { filePath?: strin ) } +const EXCLUDED_DOCS_PREFIXES = ['/docs/libraries/', '/docs/api/', '/docs/endpoints/', '/docs/open-api-spec/'] + +const InstallSkillButton = ({ pathname }: { pathname: string }) => { + const { addToast } = useToast() + const [popoverOpen, setPopoverOpen] = React.useState(false) + + if (!pathname.startsWith('/docs/')) return null + if (EXCLUDED_DOCS_PREFIXES.some((prefix) => pathname.startsWith(prefix))) return null + + const segments = pathname + .replace(/^\/docs\//, '') + .replace(/\/$/, '') + .split('/') + const firstSegment = segments[0] + if (!firstSegment) return null + + const cleanSlug = firstSegment.startsWith('posthog-') ? firstSegment.slice(8) : firstSegment + const skillId = `posthog-docs-${cleanSlug}` + const downloadUrl = `https://github.com/PostHog/context-mill/releases/latest/download/${skillId}.zip` + + const handleInstall = () => { + setPopoverOpen(false) + addToast({ + description: ( + + + Unzip into .claude/skills/ in your project to use as AI context + + ), + duration: 5000, + }) + } + + return ( + + } /> + + } + dataScheme="secondary" + open={popoverOpen} + onOpenChange={setPopoverOpen} + > + + + Install as a skill + + + ) +} + const EditHistoryPopover = ({ commits }: { commits: any[] }) => { if (!commits?.length || commits.length === 0) { return null @@ -1084,6 +1144,7 @@ function ReaderViewContent({ } ${websiteMode && showSidebar && isTocVisible ? '@6xl:bg-primary pl-0 box-content' : ''}`} animate={showSidebar && isTocVisible ? 'open' : 'closed'} > +