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
26 changes: 0 additions & 26 deletions .caplets.lock.json

This file was deleted.

8 changes: 8 additions & 0 deletions .changeset/tidy-observability-traces.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@caplets/core": patch
"caplets": patch
"@caplets/opencode": patch
"@caplets/pi": patch
---

Add privacy-gated anonymous telemetry, Sentry source-map uploads for runtime packages, and observability wiring for the public sites.
30 changes: 30 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,23 @@ jobs:
- name: Run quality gates
run: pnpm verify

- name: Check observability deploy env
run: pnpm telemetry:check-web-env && pnpm telemetry:check-source-maps
env:
PUBLIC_CAPLETS_POSTHOG_TOKEN: ${{ secrets.CAPLETS_POSTHOG_TOKEN }}
PUBLIC_CAPLETS_POSTHOG_HOST: ${{ secrets.CAPLETS_POSTHOG_HOST }}
PUBLIC_CAPLETS_LANDING_SENTRY_DSN: ${{ secrets.CAPLETS_LANDING_SENTRY_DSN }}
PUBLIC_CAPLETS_DOCS_SENTRY_DSN: ${{ secrets.CAPLETS_DOCS_SENTRY_DSN }}
PUBLIC_CAPLETS_CATALOG_SENTRY_DSN: ${{ secrets.CAPLETS_CATALOG_SENTRY_DSN }}
CAPLETS_CATALOG_SENTRY_DSN: ${{ secrets.CAPLETS_CATALOG_SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.CAPLETS_SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.CAPLETS_SENTRY_ORG }}
CAPLETS_LANDING_SENTRY_PROJECT: ${{ secrets.CAPLETS_LANDING_SENTRY_PROJECT }}
CAPLETS_DOCS_SENTRY_PROJECT: ${{ secrets.CAPLETS_DOCS_SENTRY_PROJECT }}
CAPLETS_CATALOG_SENTRY_PROJECT: ${{ secrets.CAPLETS_CATALOG_SENTRY_PROJECT }}
PUBLIC_CAPLETS_RELEASE: sites@${{ github.sha }}
PUBLIC_CAPLETS_ENVIRONMENT: production

- name: Deploy sites
run: pnpm run alchemy:deploy
env:
Expand All @@ -56,3 +73,16 @@ jobs:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }}
PUBLIC_CAPLETS_POSTHOG_TOKEN: ${{ secrets.CAPLETS_POSTHOG_TOKEN }}
PUBLIC_CAPLETS_POSTHOG_HOST: ${{ secrets.CAPLETS_POSTHOG_HOST }}
PUBLIC_CAPLETS_LANDING_SENTRY_DSN: ${{ secrets.CAPLETS_LANDING_SENTRY_DSN }}
PUBLIC_CAPLETS_DOCS_SENTRY_DSN: ${{ secrets.CAPLETS_DOCS_SENTRY_DSN }}
PUBLIC_CAPLETS_CATALOG_SENTRY_DSN: ${{ secrets.CAPLETS_CATALOG_SENTRY_DSN }}
CAPLETS_CATALOG_SENTRY_DSN: ${{ secrets.CAPLETS_CATALOG_SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.CAPLETS_SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.CAPLETS_SENTRY_ORG }}
CAPLETS_LANDING_SENTRY_PROJECT: ${{ secrets.CAPLETS_LANDING_SENTRY_PROJECT }}
CAPLETS_DOCS_SENTRY_PROJECT: ${{ secrets.CAPLETS_DOCS_SENTRY_PROJECT }}
CAPLETS_CATALOG_SENTRY_PROJECT: ${{ secrets.CAPLETS_CATALOG_SENTRY_PROJECT }}
PUBLIC_CAPLETS_RELEASE: sites@${{ github.sha }}
PUBLIC_CAPLETS_ENVIRONMENT: production
31 changes: 31 additions & 0 deletions .github/workflows/pr-preview-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,24 @@ jobs:
if: ${{ github.event.action != 'closed' }}
run: pnpm verify

- name: Check observability preview env
if: ${{ github.event.action != 'closed' }}
run: pnpm telemetry:check-web-env && pnpm telemetry:check-source-maps
env:
PUBLIC_CAPLETS_POSTHOG_TOKEN: ${{ secrets.CAPLETS_POSTHOG_TOKEN }}
PUBLIC_CAPLETS_POSTHOG_HOST: ${{ secrets.CAPLETS_POSTHOG_HOST }}
PUBLIC_CAPLETS_LANDING_SENTRY_DSN: ${{ secrets.CAPLETS_LANDING_SENTRY_DSN }}
PUBLIC_CAPLETS_DOCS_SENTRY_DSN: ${{ secrets.CAPLETS_DOCS_SENTRY_DSN }}
PUBLIC_CAPLETS_CATALOG_SENTRY_DSN: ${{ secrets.CAPLETS_CATALOG_SENTRY_DSN }}
CAPLETS_CATALOG_SENTRY_DSN: ${{ secrets.CAPLETS_CATALOG_SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.CAPLETS_SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.CAPLETS_SENTRY_ORG }}
CAPLETS_LANDING_SENTRY_PROJECT: ${{ secrets.CAPLETS_LANDING_SENTRY_PROJECT }}
CAPLETS_DOCS_SENTRY_PROJECT: ${{ secrets.CAPLETS_DOCS_SENTRY_PROJECT }}
CAPLETS_CATALOG_SENTRY_PROJECT: ${{ secrets.CAPLETS_CATALOG_SENTRY_PROJECT }}
PUBLIC_CAPLETS_RELEASE: preview-${{ github.event.pull_request.number }}@${{ github.sha }}
PUBLIC_CAPLETS_ENVIRONMENT: preview

- name: Deploy preview
if: ${{ github.event.action != 'closed' }}
run: pnpm run alchemy:deploy
Expand All @@ -66,6 +84,19 @@ jobs:
GITHUB_REPOSITORY_NAME: ${{ github.event.repository.name }}
GITHUB_TOKEN: ${{ github.token }}
PULL_REQUEST: ${{ github.event.pull_request.number }}
PUBLIC_CAPLETS_POSTHOG_TOKEN: ${{ secrets.CAPLETS_POSTHOG_TOKEN }}
PUBLIC_CAPLETS_POSTHOG_HOST: ${{ secrets.CAPLETS_POSTHOG_HOST }}
PUBLIC_CAPLETS_LANDING_SENTRY_DSN: ${{ secrets.CAPLETS_LANDING_SENTRY_DSN }}
PUBLIC_CAPLETS_DOCS_SENTRY_DSN: ${{ secrets.CAPLETS_DOCS_SENTRY_DSN }}
PUBLIC_CAPLETS_CATALOG_SENTRY_DSN: ${{ secrets.CAPLETS_CATALOG_SENTRY_DSN }}
CAPLETS_CATALOG_SENTRY_DSN: ${{ secrets.CAPLETS_CATALOG_SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.CAPLETS_SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.CAPLETS_SENTRY_ORG }}
CAPLETS_LANDING_SENTRY_PROJECT: ${{ secrets.CAPLETS_LANDING_SENTRY_PROJECT }}
CAPLETS_DOCS_SENTRY_PROJECT: ${{ secrets.CAPLETS_DOCS_SENTRY_PROJECT }}
CAPLETS_CATALOG_SENTRY_PROJECT: ${{ secrets.CAPLETS_CATALOG_SENTRY_PROJECT }}
PUBLIC_CAPLETS_RELEASE: preview-${{ github.event.pull_request.number }}@${{ github.sha }}
PUBLIC_CAPLETS_ENVIRONMENT: preview

- name: Destroy preview
if: ${{ github.event.action == 'closed' }}
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,12 @@ jobs:
title: "chore: version packages"
env:
CAPLETS_POSTHOG_TOKEN: ${{ secrets.CAPLETS_POSTHOG_TOKEN }}
CAPLETS_SENTRY_DSN: ${{ secrets.CAPLETS_SENTRY_DSN }}
CAPLETS_RUNTIME_SENTRY_DSN: ${{ secrets.CAPLETS_RUNTIME_SENTRY_DSN }}
CAPLETS_SENTRY_AUTH_TOKEN: ${{ secrets.CAPLETS_SENTRY_AUTH_TOKEN }}
CAPLETS_SENTRY_ORG: ${{ secrets.CAPLETS_SENTRY_ORG }}
CAPLETS_RUNTIME_SENTRY_PROJECT: ${{ secrets.CAPLETS_RUNTIME_SENTRY_PROJECT }}
CAPLETS_SENTRY_RELEASE: caplets-runtime@${{ github.sha }}
CAPLETS_SENTRY_ENVIRONMENT: production
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Check whether CLI package was published
Expand Down
12 changes: 12 additions & 0 deletions CONCEPTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,18 @@ Opt-out Caplets usage and reliability reporting that uses a stable anonymous ins

Anonymous Telemetry is split by purpose: PostHog receives product usage events, while Sentry receives sanitized reliability events. It must not collect raw config, prompts, Code Mode code, tool arguments, tool outputs, paths, URLs, hostnames, Caplet IDs, credentials, or unsanitized error payloads.

### Telemetry Observability Loop

The combined PostHog and Sentry feedback loop that connects public-site intent, runtime usage, provider delivery health, and debuggable release errors into maintainable product readouts.

Telemetry Observability Loop keeps PostHog as the usage and conversion system, keeps Sentry as the reliability system, and preserves Anonymous Telemetry boundaries by separating public catalog indexing and avoiding known-user attribution.

### Anonymous Install Attribution

A short nonsecret categorical marker generated from public-site install intent and optionally reported by the CLI on first activation.

Anonymous Install Attribution connects landing, docs, and catalog intent to activation readouts without carrying browser visitor identities, account identities, raw source URLs, or hidden user identifiers into runtime telemetry.

## Remote Attach

### Remote Attach
Expand Down
9 changes: 9 additions & 0 deletions alchemy.run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ export const catalogPage = await Astro("catalog-page", {
},
bindings: {
CATALOG_DB: catalogDatabase,
...(process.env.CAPLETS_CATALOG_SENTRY_DSN
? { CAPLETS_CATALOG_SENTRY_DSN: process.env.CAPLETS_CATALOG_SENTRY_DSN }
: {}),
...(process.env.PUBLIC_CAPLETS_ENVIRONMENT
? { PUBLIC_CAPLETS_ENVIRONMENT: process.env.PUBLIC_CAPLETS_ENVIRONMENT }
: {}),
...(process.env.PUBLIC_CAPLETS_RELEASE
? { PUBLIC_CAPLETS_RELEASE: process.env.PUBLIC_CAPLETS_RELEASE }
: {}),
},
domains: [catalogPageDomain],
});
Expand Down
24 changes: 23 additions & 1 deletion apps/catalog/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @ts-check
import cloudflare from "@astrojs/cloudflare";
import { sentryVitePlugin } from "@sentry/vite-plugin";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "astro/config";
import { fileURLToPath } from "node:url";
Expand All @@ -11,6 +12,11 @@ const cloudflareDevWorkers = fileURLToPath(
new URL("./src/cloudflare-workers-dev.ts", import.meta.url),
);
const isProductionBuild = process.env.NODE_ENV === "production";
const sentryProject = process.env.CAPLETS_CATALOG_SENTRY_PROJECT;
const sentryRelease = process.env.PUBLIC_CAPLETS_RELEASE;
const sentryConfigured = Boolean(
process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_ORG && sentryProject && sentryRelease,
);
const optimizeExclude = [
"tailwind-variants",
"unified",
Expand All @@ -30,8 +36,24 @@ export default defineConfig({
vite: {
build: {
assetsInlineLimit: 0,
sourcemap: sentryConfigured ? "hidden" : false,
},
plugins: [tailwindcss()],
plugins: [
tailwindcss(),
...(sentryConfigured
? [
sentryVitePlugin({
authToken: process.env.SENTRY_AUTH_TOKEN,
org: process.env.SENTRY_ORG,
project: sentryProject,
release: { name: sentryRelease },
sourcemaps: {
filesToDeleteAfterUpload: ["./dist/**/*.map"],
},
}),
]
: []),
],
resolve: {
alias: {
"@astrojs/cloudflare/entrypoints/middleware.js": cloudflareDevMiddleware,
Expand Down
11 changes: 7 additions & 4 deletions apps/catalog/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@
},
"dependencies": {
"@astrojs/check": "^0.9.9",
"@astrojs/cloudflare": "^13.7.0",
"@astrojs/cloudflare": "^14.0.1",
"@caplets/core": "workspace:*",
"@caplets/web-observability": "workspace:*",
"@hugeicons/core-free-icons": "^4.2.2",
"@sentry/browser": "^10.62.0",
"@tabler/icons": "^3.44.0",
"@tailwindcss/forms": "^0.5.11",
"@tailwindcss/vite": "^4.3.1",
"@tanstack/virtual-core": "^3.17.2",
"astro": "^6.4.6",
"astro": "^7.0.3",
"posthog-js": "^1.395.0",
"rehype-sanitize": "^6.0.0",
"rehype-stringify": "^10.0.1",
"remark-parse": "^11.0.0",
Expand All @@ -33,8 +36,8 @@
},
"devDependencies": {
"happy-dom": "^20.10.6",
"vite": "^7.3.5",
"vitest": "^4.1.8",
"vite": "^8.1.0",
"vitest": "^4.1.9",
"wrangler": "^4.105.0"
},
"packageManager": "pnpm@11.7.0"
Expand Down
2 changes: 1 addition & 1 deletion apps/catalog/src/components/CapletDetail.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
import { ChevronRightIcon } from "@hugeicons/core-free-icons";
import { ChevronRightIcon } from "../lib/hugeicons";
import type { CatalogEntryRecord } from "../lib/catalog-store";
import { renderCatalogMarkdown, splitCatalogMarkdown } from "../lib/markdown";
import HugeIcon from "./HugeIcon.astro";
Expand Down
2 changes: 1 addition & 1 deletion apps/catalog/src/components/CatalogHeader.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
import { Cancel01Icon } from "@hugeicons/core-free-icons";
import { Cancel01Icon } from "../lib/hugeicons";
import HugeIcon from "./HugeIcon.astro";
import ThemeToggle from "./ThemeToggle.astro";

Expand Down
2 changes: 1 addition & 1 deletion apps/catalog/src/components/InstallCommand.astro
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
import type { CatalogInstallCommand } from "@caplets/core/catalog";
import { Copy01Icon } from "@hugeicons/core-free-icons";
import { Copy01Icon } from "../lib/hugeicons";
import HugeIcon from "./HugeIcon.astro";
import { Button } from "./starwind/button";

Expand Down
2 changes: 1 addition & 1 deletion apps/catalog/src/components/SafetyNotice.astro
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
import type { CatalogWarning } from "@caplets/core/catalog";
import { AlertCircleIcon } from "@hugeicons/core-free-icons";
import { AlertCircleIcon } from "../lib/hugeicons";
import HugeIcon from "./HugeIcon.astro";

type Props = {
Expand Down
2 changes: 1 addition & 1 deletion apps/catalog/src/components/SearchShell.astro
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
Key01Icon,
Link01Icon,
Settings02Icon,
} from "@hugeicons/core-free-icons";
} from "../lib/hugeicons";

type Props = {
entries: CatalogEntryRecord[];
Expand Down
2 changes: 1 addition & 1 deletion apps/catalog/src/components/ThemeToggle.astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
Moon02Icon,
Sun01Icon,
Tick02Icon,
} from "@hugeicons/core-free-icons";
} from "../lib/hugeicons";
import HugeIcon from "./HugeIcon.astro";

const options = [
Expand Down
3 changes: 3 additions & 0 deletions apps/catalog/src/lib/catalog-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { env } from "cloudflare:workers";

export type CatalogEnv = {
CATALOG_DB?: D1Database;
CAPLETS_CATALOG_SENTRY_DSN?: string;
PUBLIC_CAPLETS_ENVIRONMENT?: string;
PUBLIC_CAPLETS_RELEASE?: string;
};

export function getCatalogEnv(): CatalogEnv {
Expand Down
35 changes: 35 additions & 0 deletions apps/catalog/src/lib/hugeicons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import AlertCircleIcon from "@hugeicons/core-free-icons/AlertCircleIcon";
import ArrowDown01Icon from "@hugeicons/core-free-icons/ArrowDown01Icon";
import BadgeCheckIcon from "@hugeicons/core-free-icons/BadgeCheckIcon";
import Cancel01Icon from "@hugeicons/core-free-icons/Cancel01Icon";
import ChevronRightIcon from "@hugeicons/core-free-icons/ChevronRightIcon";
import ComputerIcon from "@hugeicons/core-free-icons/ComputerIcon";
import ComputerUserIcon from "@hugeicons/core-free-icons/ComputerUserIcon";
import Copy01Icon from "@hugeicons/core-free-icons/Copy01Icon";
import DatabaseSyncIcon from "@hugeicons/core-free-icons/DatabaseSyncIcon";
import Key01Icon from "@hugeicons/core-free-icons/Key01Icon";
import Link01Icon from "@hugeicons/core-free-icons/Link01Icon";
import Moon02Icon from "@hugeicons/core-free-icons/Moon02Icon";
import Settings02Icon from "@hugeicons/core-free-icons/Settings02Icon";
import Shield01Icon from "@hugeicons/core-free-icons/Shield01Icon";
import Sun01Icon from "@hugeicons/core-free-icons/Sun01Icon";
import Tick02Icon from "@hugeicons/core-free-icons/Tick02Icon";

export {
AlertCircleIcon,
ArrowDown01Icon,
BadgeCheckIcon,
Cancel01Icon,
ChevronRightIcon,
ComputerIcon,
ComputerUserIcon,
Copy01Icon,
DatabaseSyncIcon,
Key01Icon,
Link01Icon,
Moon02Icon,
Settings02Icon,
Shield01Icon,
Sun01Icon,
Tick02Icon,
};
Loading