# Agent Instructions — {{titleCase name}} ## This App Read these files to understand the infrastructure and configuration: - `index.ts` — Pulumi app definition (primitives, services, linking) - `Pulumi.*.yaml` — Stack config (tenant, platform, deploy mode) ## Pattern Every mesh app follows 5 steps in `index.ts`: 1. `mesh.apps.getConfig(pulumi)` — read stack config 2. `new mesh.apps.AppEnvironment(...)` — namespace, IAM, providers 3. Add primitives (Database, Bucket, TemporalWorker, etc.) 4. Create services with `link()` — wires env vars, tunnels, secrets 5. `env.register()` — finalize SSM metadata + dev outputs {{#if service}} ## API docs `api/openapi.json` is the API's OpenAPI document and `docs/pages/` holds its guides; `index.ts` declares a `mesh.apps.ApiDocs` site from both, reachable under `mesh dev`. A docs site has no hostname of its own — it is read inside the Hub. To publish it, declare the API as a `mesh.apps.AppApiSurface` whose `docs` block builds the site; the Hub serves it through its docs door to the readers the app's `docs` role admits. A site declared on a `mesh.apps.VendorApiSurface` (a third-party core's raw reference) is read through the same door by holders of the Hub's `vendor-docs` add-on only — never by a partner — and declaring `audience: { roles: [] }` on one fails the deploy. `mesh app check` flags a docs site put on an ingress (`DOCS_SITE_PUBLIC`). Keep the spec in step with the routes in `api/src/index.ts` — the site renders what the file says. Keep pages `.md`, not `.mdx`: a site an API surface declares is served by the Hub under its own origin, which carries data and never code, so an `.mdx` page fails that deploy (the bare `ApiDocs` here accepts it, which is exactly the trap). {{/if}} {{#if temporal}} ## Temporal Workers & Clients **Always use `@mesh-tech/app-kit` factories** — never raw `@temporalio/*` setup. ### Worker (`worker/src/worker.ts`) ```typescript import { createTemporalWorker, resolveWorkflows } from "@mesh-tech/app-kit/temporal"; import * as activities from "./activities.js"; const { worker, cleanup } = await createTemporalWorker({ activities, ...await resolveWorkflows(import.meta.url), }); ``` `createTemporalWorker()` handles authenticated connections (M2M tokens), encryption codec, and OpenTelemetry — no manual setup needed. ### Client (`api/src/index.ts`) ```typescript import { createTemporalClient } from "@mesh-tech/app-kit/temporal-client"; const { withClient } = await createTemporalClient(); // Use withClient() for each operation — it handles auth token injection const result = await withClient(async (client) => { const handle = await client.workflow.start("myWorkflow", { ... }); return handle.result(); }); ``` **Important:** Always use `withClient()` callback — don't hold a raw `Client` reference. The callback injects fresh auth metadata per call. {{/if}} {{#if database}} ## Database Uses Prisma with the `pg` adapter. Connection via `DATABASE_URL` from `link()`. ```typescript import { getDb } from "./db.js"; const db = getDb(); ``` Run migrations: `pnpm mesh db exec -- npx prisma migrate dev` {{/if}} {{#if bucket}} ## S3 Bucket The `uploads` bucket is linked to the API service. See `api/src/storage.ts` for helper functions using the injected `UPLOADS_BUCKET` and `UPLOADS_REGION` env vars. {{/if}} ## Documentation - `pnpm exec intent load @mesh-tech/app-kit#core` — the authoritative app-authoring API (getConfig → AppEnvironment → primitives → link → register; arg tables + `link()` semantics) - The **apps** skill (`.claude/skills/mesh-app-kit-apps/`, or `pnpm exec intent load @mesh-tech/app-kit#apps`) — the scaffold → wire → run → debug recipe - `pnpm exec intent load @mesh-tech/infra-components#core` — per-component reference ## CLI ```bash pnpm mesh dev # Local development (tunnels + services in tmux) pnpm mesh deploy up # Deploy to cluster {{#if database}} pnpm mesh db psql # Interactive database session {{/if}} pnpm mesh tunnel dev # Open tunnels without starting services ```