Skip to content
Open
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
12 changes: 7 additions & 5 deletions .agents/skills/convex-agent/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@ description: "Add an AI agent / RAG backend (@convex-dev/agent) to the Convex ap

# Add an AI agent / RAG backend

Install @convex-dev/agent for durable threads, message history, tool-calls, and vector search/RAG — the backend for an in-app AI agent.
Install @convex-dev/agent for durable threads, message history, tool-calls, and vector search/RAG — the backend for an in-app AI agent. Call models through the Convex AI Gateway by default: Convex holds the provider credentials, so there is no LLM key to obtain, store, or rotate.

## Workflow

1. Install @convex-dev/agent + add to convex.config.ts.
2. Define the agent (model, tools, instructions); store the LLM key via the `env` micro power.
1. Install @convex-dev/agent + @convex-dev/ai-sdk-provider; add the agent component to convex.config.ts.
2. Define the agent (tools, instructions) with `languageModel: convexGateway("provider/model")` — no API key needed (needs convex 1.45+ on a Convex Cloud deployment, paid plan).
3. Create threads + stream messages; persist history in Convex.
4. For RAG: embed docs into a vector index and retrieve in the tool.
4. For RAG: embed docs into a vector index and retrieve in the tool. The gateway does not serve embeddings yet, so store the embedding provider's key via the `env` micro power.
5. Only if the gateway is unavailable (free plan, self-hosted, local backend): call the provider SDK with a key stored via the `env` micro power.

## Rules

- Keep the LLM API key in Convex env (use the `env` micro power), never client-side.
- Default to the Convex AI Gateway (`convexGateway` from @convex-dev/ai-sdk-provider) for model calls; fall back to a provider key in Convex env only where the gateway is unavailable (free plan, self-hosted, local backend).
- Never expose a provider API key client-side; when one is needed (embeddings, gateway fallback), keep it in Convex env via the `env` micro power.
- Run model calls in actions ('use node' if the SDK needs it).
- Persist threads/messages in Convex for durability + reactivity.
2 changes: 1 addition & 1 deletion .agents/skills/convex-authz/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: convex-authz
description: "Audit and harden Convex authorization: identity-from-arg impersonation, missing per-document ownership checks, PII-leaking public queries, and writes into containers the caller doesn't own. Deterministic scan + canonical requireIdentity/requireOwner fix + tsc verify. Use for 'secure my app' / 'audit auth' / 'who can access this data', not generic code review."
description: "Audit and harden a Convex app's authorization: identity-from-arg impersonation, missing per-document ownership checks, public queries leaking data by a client-supplied id, and writes into a parent/container the caller doesn't own. Scans for the 4 shapes, applies requireIdentity/requireOwner, verifies with tsc. TRIGGER on 'secure my app', 'audit auth/authz', 'who can access this data'. SKIP when there is no convex/ directory."
---

<!-- GENERATED from convex-agents content/capabilities/convex-authz.json — do not edit by hand. -->
Expand Down
44 changes: 15 additions & 29 deletions .agents/skills/convex-billing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,48 +14,37 @@ Wire Stripe to Convex using @convex-dev/stripe: a checkout action, an httpAction
1. Install the component: `npm install @convex-dev/stripe`.
2. Create `convex/convex.config.ts`:
```ts
import { defineApp } from "convex/server";
import stripe from "@convex-dev/stripe/convex.config.js";
import { defineApp } from 'convex/server';
import stripe from '@convex-dev/stripe/convex.config.js';
const app = defineApp();
app.use(stripe);
export default app;
```
3. Store Stripe keys in Convex env (use the `env` micro power): `STRIPE_SECRET_KEY` (sk_test_… / sk_live_…) and `STRIPE_WEBHOOK_SECRET` (whsec_…).
4. Create `convex/http.ts` to register the webhook route (the component handles signature verification automatically):
```ts
import { httpRouter } from "convex/server";
import { components } from "./_generated/api";
import { registerRoutes } from "@convex-dev/stripe";
import { httpRouter } from 'convex/server';
import { components } from './_generated/api';
import { registerRoutes } from '@convex-dev/stripe';
const http = httpRouter();
registerRoutes(http, components.stripe, { webhookPath: "/stripe/webhook" });
registerRoutes(http, components.stripe, { webhookPath: '/stripe/webhook' });
export default http;
```
5. Create `convex/billing.ts` with a checkout action and a subscription-gate query:
```ts
import { action, query } from "./_generated/server";
import { components } from "./_generated/api";
import { StripeSubscriptions } from "@convex-dev/stripe";
import { v } from "convex/values";
import { action, query } from './_generated/server';
import { components } from './_generated/api';
import { StripeSubscriptions } from '@convex-dev/stripe';
import { v } from 'convex/values';
const stripeClient = new StripeSubscriptions(components.stripe, {});
export const createSubscriptionCheckout = action({
args: { priceId: v.string() },
returns: v.object({ sessionId: v.string(), url: v.union(v.string(), v.null()) }),
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Not authenticated");
const customer = await stripeClient.getOrCreateCustomer(ctx, {
userId: identity.subject,
email: identity.email,
name: identity.name,
});
return await stripeClient.createCheckoutSession(ctx, {
priceId: args.priceId,
customerId: customer.customerId,
mode: "subscription",
successUrl: `${process.env.SITE_URL ?? "http://localhost:3000"}/?success=true`,
cancelUrl: `${process.env.SITE_URL ?? "http://localhost:3000"}/?canceled=true`,
subscriptionMetadata: { userId: identity.subject },
});
if (!identity) throw new Error('Not authenticated');
const customer = await stripeClient.getOrCreateCustomer(ctx, { userId: identity.subject, email: identity.email, name: identity.name });
return await stripeClient.createCheckoutSession(ctx, { priceId: args.priceId, customerId: customer.customerId, mode: 'subscription', successUrl: `${process.env.SITE_URL ?? 'http://localhost:3000'}/?success=true`, cancelUrl: `${process.env.SITE_URL ?? 'http://localhost:3000'}/?canceled=true`, subscriptionMetadata: { userId: identity.subject } });
},
});
export const isSubscribed = query({
Expand All @@ -64,11 +53,8 @@ Wire Stripe to Convex using @convex-dev/stripe: a checkout action, an httpAction
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) return false;
const subscriptions = await ctx.runQuery(
components.stripe.public.listSubscriptionsByUserId,
{ userId: identity.subject },
);
return subscriptions.some((sub) => sub.status === "active" || sub.status === "trialing");
const subscriptions = await ctx.runQuery(components.stripe.public.listSubscriptionsByUserId, { userId: identity.subject });
return subscriptions.some((sub) => sub.status === 'active' || sub.status === 'trialing');
},
});
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,22 @@ schema validator:
import { v } from "convex/values";
import schema from "./schema.js";

const notificationDoc = schema.tables.notifications.validator.extend({
_id: v.id("notifications"),
_creationTime: v.number(),
const vNotification = schema.doc("notifications").omit("userId").extend({
user: v.string(),
});

export const getLatest = query({
args: {},
returns: v.nullable(notificationDoc),
export const getNotification = internalQuery({
args: { id: schema.id("notifications") },
returns: v.nullable(vNotification),
handler: async (ctx) => {
return await ctx.db.query("notifications").order("desc").first();
const notification = await ctx.db.get("notifications", args.id);
if (!notification) return null;
const { userId, ...rest } = notification;
const user = await ctx.db.get("users", userId);
return {
...rest,
user: user?.name ?? "Unknown",
};
},
});
```
Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/convex-design/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ Design and build production-grade Convex backends from plain-English product ask
- Gate on tsc --noEmit, not just HMR green.
- DEGRADATION RULE — if the scaffold cannot run (non-interactive session, no network, a sandboxed temp dir, or the user just wants code, not an app): skip scaffolding and write a standard Convex project directly. ALL backend code goes under `convex/` (schema.ts, functions) — NEVER at the project root; Convex functions only run from the `convex/` directory. Write ZERO scaffold/documentation files (no START_HERE.md, ARCHITECTURE.md, MANIFEST.txt, README walls) unless explicitly asked. "Build me a backend" means code, not ceremony.
- Data access + imports — before writing any convex/*.ts: never an unbounded `.collect()` on a table that can grow — use `.withIndex(...)` and `.paginate(...)`/`.take(n)`. Use an index, not `.filter()`, for anything that would be a SQL WHERE. Imports: `query`/`mutation`/`action`/`internalQuery`/`internalMutation`/`internalAction` come from `./_generated/server`; `api`/`internal` come from `./_generated/api`; NEVER import from `convex/server` in application code. `v.literal("exact value")` for fixed string/enum members, not a bare string. `"use node"` only at the top of action-only modules — never in a file that also exports a `query` or `mutation`.
- SELF-VERIFY RULE — before declaring backend work done, verify it compiles and pushes: run `npx tsc --noEmit` and, when a deployment is available (or via a local anonymous one: `CONVEX_AGENT_MODE=anonymous npx convex dev --once`), push it. Fix every error it reports before finishing — one verify round catches the wrong-relative-import / duplicate-symbol / unbalanced-paren class that otherwise breaks the deploy.
- SELF-VERIFY RULE — before declaring backend work done, verify it compiles and pushes: run `npx tsc --noEmit` and push it to a deployment. Prefer the project's existing one; otherwise `npx convex dev --once` when `npx convex whoami` succeeds, and `CONVEX_AGENT_MODE=anonymous npx convex dev --once` ONLY when it does not. Forcing anonymous on a signed-in user rebinds `.env.local` and costs them the persistent, publishable cloud deployment they expect. Fix every error it reports before finishing — one verify round catches the wrong-relative-import / duplicate-symbol / unbalanced-paren class that otherwise breaks the deploy.
2 changes: 1 addition & 1 deletion .agents/skills/convex-expert/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ Always-on Convex backend specialist invoked before touching any code inside a co
- Mutations cannot fetch — all external IO goes in actions; persist via ctx.runMutation(internal.x.y).
- Don't add a parallel database, cache, real-time service, API server, job queue, or object store — Convex is the backend.
- Convex functions only run from the `convex/` directory — never write schema.ts/queries/mutations/actions at the project root.
- SELF-VERIFY RULE — before declaring backend work done, verify it compiles and pushes: run `npx tsc --noEmit` and, when a deployment is available (or via a local anonymous one: `CONVEX_AGENT_MODE=anonymous npx convex dev --once`), push it. Fix every error it reports before finishing — one verify round catches the wrong-relative-import / duplicate-symbol / unbalanced-paren class that otherwise breaks the deploy.
- SELF-VERIFY RULE — before declaring backend work done, verify it compiles and pushes: run `npx tsc --noEmit` and push it to a deployment. Prefer the project's existing one; otherwise `npx convex dev --once` when `npx convex whoami` succeeds, and `CONVEX_AGENT_MODE=anonymous npx convex dev --once` ONLY when it does not. Forcing anonymous on a signed-in user rebinds `.env.local` and costs them the persistent, publishable cloud deployment they expect. Fix every error it reports before finishing — one verify round catches the wrong-relative-import / duplicate-symbol / unbalanced-paren class that otherwise breaks the deploy.
2 changes: 1 addition & 1 deletion .agents/skills/convex-launch-readiness/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Readiness is not one check — it's the union of the checks, deduped, ranked, an
- convex-reviewer — validators, indexes-not-filter, idiom, error handling. Always runnable on code.
- convex-advisor — live read-limit / OCC evidence (only if a deployment with traffic exists; else record 'skipped: no traffic').
- convex-insights — recent failures from logs (only if a deployment exists).
Run independent passes concurrently; each returns findings, not fixes.
Run independent passes concurrently; each returns findings, not fixes.
3. NORMALIZE + DEDUPE: collect all findings into one report. Set each finding's `identity` field to a normalized function/table key (e.g. `messages:list`) that is the SAME whether the pass reported a code-locus or a deployment-locus for that function — so the SAME defect seen from two loci (reviewer flags a missing index at code-locus, advisor flags its read-limit symptom at deployment-locus) collapses to ONE via the bus's (class, identity) dedup and isn't double-counted in the score. Keep the higher-confidence source. Drop nothing silently; a pass that errored/was skipped is a stated coverage gap, not a clean result.
4. SCORE, auditable: start at 100; subtract per CONFIRMED finding by severity (high −15, med −5, low −1), floor at 0; print the exact formula and the per-class breakdown so the number is reproducible, not a vibe. plausible-only findings are listed as candidates but do NOT move the score (evidence-not-vibes). A deployment/traffic-less run reports a code-only score and says so.
5. REPORT: the score, then findings ranked by severity, each with its evidence, its locus, and the fixCapability + a one-line fix note. Group by 'blockers' (high) / 'should-fix' (med) / 'nice-to-have' (low). End with the ordered fix plan: which capability to run next, in what order (authz/data-loss first, then perf/scale, then idiom/observability).
Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/convex-quickstart/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ Stand up a barebones Next.js + Convex template from the idea, locally, with an a
- Reserved names — never `export const <jsReservedWord> = ...` (e.g. `delete`, `new`, `class`, `function`, `return`) as a query/mutation/action export name; esbuild fails to parse it. Never a table or index name starting with `_` (e.g. `_migrations: defineTable(...)`) — `_` is reserved and errors at push as `TableNameReserved`/`IndexNameReserved`.
- HTTP routes — `httpRouter` has no Express-style `:param` segments (`path: "/users/:id"` only matches that literal string and is dead code); use `pathPrefix` and parse the trailing segment yourself. Every `http.route({...})` `handler:` must be wrapped in `httpAction(...)` from `./_generated/server` — a bare `async (ctx, request) => {...}` type-checks but isn't a valid HTTP action.
- `ctx.runQuery`/`ctx.runMutation`/`ctx.runAction` need a codegen'd function reference (`api.foo.bar`/`internal.foo.bar`), never a raw imported module member (`import * as queries from "./queries"; ctx.runQuery(queries.getX, ...)` compiles but fails at runtime).
- SELF-VERIFY RULE — before declaring backend work done, verify it compiles and pushes: run `npx tsc --noEmit` and, when a deployment is available (or via a local anonymous one: `CONVEX_AGENT_MODE=anonymous npx convex dev --once`), push it. Fix every error it reports before finishing — one verify round catches the wrong-relative-import / duplicate-symbol / unbalanced-paren class that otherwise breaks the deploy.
- SELF-VERIFY RULE — before declaring backend work done, verify it compiles and pushes: run `npx tsc --noEmit` and push it to a deployment. Prefer the project's existing one; otherwise `npx convex dev --once` when `npx convex whoami` succeeds, and `CONVEX_AGENT_MODE=anonymous npx convex dev --once` ONLY when it does not. Forcing anonymous on a signed-in user rebinds `.env.local` and costs them the persistent, publishable cloud deployment they expect. Fix every error it reports before finishing — one verify round catches the wrong-relative-import / duplicate-symbol / unbalanced-paren class that otherwise breaks the deploy.
4 changes: 2 additions & 2 deletions convex/_generated/ai/ai-files.state.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"guidelinesHash": "f730e6620e882fef21a3e00c5539cc0b472ef26688efd92bf3a42f0711de6888",
"guidelinesHash": "533ba2428f2dc572e825555e6e681d2e56e7e757c15a3fdd036a5d705413f020",
"agentsMdSectionHash": "5934f676ea9a332e7cd4a4f64aa23b59d926e9faca026c758d4b1f87d2101cc3",
"claudeMdHash": "5934f676ea9a332e7cd4a4f64aa23b59d926e9faca026c758d4b1f87d2101cc3",
"agentSkillsSha": "6843b65f3cbcee34bb2bc984d444f42ac7ca2a61"
"agentSkillsSha": "0aa10576821c6928f6a0f498c087af4ee231536e"
}
5 changes: 3 additions & 2 deletions convex/_generated/ai/guidelines.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Convex guidelines

These guidelines target Convex `^1.41.0`.
These guidelines target Convex `^1.44.0`.

## Function guidelines

Expand Down Expand Up @@ -44,6 +44,7 @@ export default mutation({
```

- `v.object(...)` validators compose: `.pick("a", "b")`, `.omit("c")`, `.partial()`, and `.extend({ d: v.string() })` derive new object validators from an existing one - define a shape once and derive variants instead of duplicating fields. Use an object validator's `.fields` to supply function `args`.
- `schema.doc("tableName")` (import `schema` from `./schema`) returns the validator for a whole stored document: the table's validator with `_id` and `_creationTime` added, to every member for union tables. Use it when an `args` or `returns` validator needs a complete document instead of re-declaring the fields or the system fields; `docValidator("tableName", tableDefinition)` from `convex/server` builds the same from a bare table definition.
- Below is an example of a schema with validators that codify a discriminated union type:

```typescript
Expand Down Expand Up @@ -257,7 +258,7 @@ export const exampleQuery = query({
```

- Be strict with types, particularly around id's of documents. For example, if a function takes in an id for a document in the 'users' table, take in `Id<'users'>` rather than `string`.
- For typed app environment variables, declare them in `convex/convex.config.ts` with `defineApp({ env: { MY_KEY: v.optional(v.string()) } })` and read them with `env` from `./_generated/server` instead of `process.env`.
- For typed app environment variables, declare them in `convex/convex.config.ts` with `defineApp({ env: { MY_KEY: v.optional(v.string()) } })` and read them with `env` from `./_generated/server` instead of `process.env`. The platform-provided `CONVEX_SITE_URL` and `CONVEX_CLOUD_URL` are already on `env` as strings; never declare them in `convex.config.ts` (redeclaring them fails the deploy or breaks the generated `env` type).

## Full text search guidelines

Expand Down