@percepta/create 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/README.md +93 -0
  2. package/dist/chunk-GEVZERMP.js +108 -0
  3. package/dist/chunk-R4FWPE4A.js +49 -0
  4. package/dist/chunk-WMJT7CB5.js +57 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +974 -0
  7. package/dist/init-Z4VGBHAK.js +96 -0
  8. package/dist/status-MITGDLTT.js +76 -0
  9. package/dist/sync-J4SFZHDX.js +136 -0
  10. package/dist/upstream-AQI7P4EU.js +144 -0
  11. package/package.json +58 -0
  12. package/template-versions.json +4 -0
  13. package/templates/library/README.md +30 -0
  14. package/templates/library/eslint.config.js +10 -0
  15. package/templates/library/gitignore.template +18 -0
  16. package/templates/library/package.json.template +29 -0
  17. package/templates/library/src/index.ts +9 -0
  18. package/templates/library/tsconfig.json +19 -0
  19. package/templates/monorepo/README.md +41 -0
  20. package/templates/monorepo/eslint.config.js +10 -0
  21. package/templates/monorepo/gitignore.template +31 -0
  22. package/templates/monorepo/npmrc.template +4 -0
  23. package/templates/monorepo/package.json.template +25 -0
  24. package/templates/monorepo/packages/.gitkeep +0 -0
  25. package/templates/monorepo/pnpm-workspace.yaml +2 -0
  26. package/templates/monorepo/tsconfig.json +16 -0
  27. package/templates/webapp/.claude/commands/sync.md +19 -0
  28. package/templates/webapp/.claude/commands/upstream.md +17 -0
  29. package/templates/webapp/.dockerignore +59 -0
  30. package/templates/webapp/.gitattributes +1 -0
  31. package/templates/webapp/.github/workflows/__APP_NAME__-ryvn-release.yaml +114 -0
  32. package/templates/webapp/.github/workflows/__APP_NAME__-terraform.yml +28 -0
  33. package/templates/webapp/.github/workflows/ci.yml +149 -0
  34. package/templates/webapp/.node-version +2 -0
  35. package/templates/webapp/.prettierrc.mjs +5 -0
  36. package/templates/webapp/AGENTS.md +240 -0
  37. package/templates/webapp/Dockerfile +64 -0
  38. package/templates/webapp/README.md +200 -0
  39. package/templates/webapp/agent-skills/database.md +140 -0
  40. package/templates/webapp/agent-skills/deploy.md +94 -0
  41. package/templates/webapp/agent-skills/inngest.md +147 -0
  42. package/templates/webapp/agent-skills/langfuse.md +117 -0
  43. package/templates/webapp/agent-skills/oneshot.md +216 -0
  44. package/templates/webapp/agent-skills/ryvn.md +25 -0
  45. package/templates/webapp/deploy/README.md +39 -0
  46. package/templates/webapp/deploy/ryvn/__APP_NAME__.service.yaml +11 -0
  47. package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml +121 -0
  48. package/templates/webapp/docker-compose.yml +19 -0
  49. package/templates/webapp/drizzle.config.ts +30 -0
  50. package/templates/webapp/env.example.template +44 -0
  51. package/templates/webapp/eslint.config.mjs +52 -0
  52. package/templates/webapp/gitignore.template +53 -0
  53. package/templates/webapp/next.config.ts +8 -0
  54. package/templates/webapp/npmrc.template +4 -0
  55. package/templates/webapp/package.json.template +122 -0
  56. package/templates/webapp/postcss.config.mjs +5 -0
  57. package/templates/webapp/scripts/create-user.ts +47 -0
  58. package/templates/webapp/scripts/migrate.ts +18 -0
  59. package/templates/webapp/scripts/seed.ts +62 -0
  60. package/templates/webapp/scripts/setup-database.ts +57 -0
  61. package/templates/webapp/scripts/setup-readonly-user.ts +193 -0
  62. package/templates/webapp/scripts/start.sh +52 -0
  63. package/templates/webapp/src/app/(app)/layout.tsx +21 -0
  64. package/templates/webapp/src/app/(app)/page.tsx +30 -0
  65. package/templates/webapp/src/app/(auth)/auth/signin/CredentialsSignInForm.tsx +103 -0
  66. package/templates/webapp/src/app/(auth)/auth/signin/page.tsx +30 -0
  67. package/templates/webapp/src/app/(auth)/layout.tsx +15 -0
  68. package/templates/webapp/src/app/api/auth/[...all]/route.ts +4 -0
  69. package/templates/webapp/src/app/api/healthz/route.ts +10 -0
  70. package/templates/webapp/src/app/api/inngest/route.ts +31 -0
  71. package/templates/webapp/src/app/api/readyz/route.ts +31 -0
  72. package/templates/webapp/src/app/api/trpc/[trpc]/route.ts +21 -0
  73. package/templates/webapp/src/app/favicon.ico +0 -0
  74. package/templates/webapp/src/app/global-error.tsx +27 -0
  75. package/templates/webapp/src/app/layout.tsx +18 -0
  76. package/templates/webapp/src/components/FaroProvider.tsx +37 -0
  77. package/templates/webapp/src/components/Header.tsx +70 -0
  78. package/templates/webapp/src/components/Providers.tsx +45 -0
  79. package/templates/webapp/src/components/form/FormItem.tsx +82 -0
  80. package/templates/webapp/src/config/clientEnvConfig.ts +11 -0
  81. package/templates/webapp/src/config/getEnvConfig.ts +62 -0
  82. package/templates/webapp/src/config/isDev.ts +7 -0
  83. package/templates/webapp/src/drizzle/db.ts +28 -0
  84. package/templates/webapp/src/drizzle/migrations/0000_eager_grandmaster.sql +57 -0
  85. package/templates/webapp/src/drizzle/migrations/meta/0000_snapshot.json +376 -0
  86. package/templates/webapp/src/drizzle/migrations/meta/_journal.json +13 -0
  87. package/templates/webapp/src/drizzle/schema/auth/accounts.ts +33 -0
  88. package/templates/webapp/src/drizzle/schema/auth/sessions.ts +25 -0
  89. package/templates/webapp/src/drizzle/schema/auth/users.ts +38 -0
  90. package/templates/webapp/src/drizzle/schema/auth/verifications.ts +19 -0
  91. package/templates/webapp/src/drizzle/schema/index.ts +4 -0
  92. package/templates/webapp/src/drizzle/schema/utils/jsonbFromZod.ts +25 -0
  93. package/templates/webapp/src/instrumentation.ts +35 -0
  94. package/templates/webapp/src/lib/auth/index.ts +85 -0
  95. package/templates/webapp/src/lib/auth-client.ts +6 -0
  96. package/templates/webapp/src/lib/trpc.ts +15 -0
  97. package/templates/webapp/src/server/api/root.ts +5 -0
  98. package/templates/webapp/src/server/trpc.ts +61 -0
  99. package/templates/webapp/src/services/AuthContextService.ts +63 -0
  100. package/templates/webapp/src/services/DatabaseService.ts +54 -0
  101. package/templates/webapp/src/services/inngest/InngestFunctionCollection.ts +5 -0
  102. package/templates/webapp/src/services/inngest/InngestService.ts +71 -0
  103. package/templates/webapp/src/services/inngest/events/AppEvents.ts +34 -0
  104. package/templates/webapp/src/services/inngest/events/payloads/ExampleEventPayload.ts +14 -0
  105. package/templates/webapp/src/services/langfuse/LangfuseService.ts +80 -0
  106. package/templates/webapp/src/services/logger/AppLogger.ts +61 -0
  107. package/templates/webapp/src/services/logger/withRequestContext.ts +27 -0
  108. package/templates/webapp/src/services/observability/initFaro.ts +22 -0
  109. package/templates/webapp/src/startup-checks.ts +32 -0
  110. package/templates/webapp/src/styles/globals.css +27 -0
  111. package/templates/webapp/src/utils/__tests__/cn.test.ts +20 -0
  112. package/templates/webapp/src/utils/cn.ts +6 -0
  113. package/templates/webapp/src/utils/syncInngestApp.ts +62 -0
  114. package/templates/webapp/terraform/README.md +147 -0
  115. package/templates/webapp/terraform/deploy.sh +97 -0
  116. package/templates/webapp/terraform/main.tf +101 -0
  117. package/templates/webapp/terraform/modules/cloudtrail/main.tf +27 -0
  118. package/templates/webapp/terraform/modules/cloudtrail/outputs.tf +10 -0
  119. package/templates/webapp/terraform/modules/cloudtrail/variables.tf +15 -0
  120. package/templates/webapp/terraform/modules/networking/main.tf +118 -0
  121. package/templates/webapp/terraform/modules/networking/outputs.tf +38 -0
  122. package/templates/webapp/terraform/modules/networking/variables.tf +24 -0
  123. package/templates/webapp/terraform/modules/rds/main.tf +227 -0
  124. package/templates/webapp/terraform/modules/rds/outputs.tf +73 -0
  125. package/templates/webapp/terraform/modules/rds/variables.tf +61 -0
  126. package/templates/webapp/terraform/modules/s3-logging/main.tf +148 -0
  127. package/templates/webapp/terraform/modules/s3-logging/outputs.tf +10 -0
  128. package/templates/webapp/terraform/modules/s3-logging/variables.tf +16 -0
  129. package/templates/webapp/terraform/modules/secrets/main.tf +39 -0
  130. package/templates/webapp/terraform/modules/secrets/outputs.tf +9 -0
  131. package/templates/webapp/terraform/modules/secrets/variables.tf +51 -0
  132. package/templates/webapp/terraform/outputs.tf +102 -0
  133. package/templates/webapp/terraform/providers.tf +32 -0
  134. package/templates/webapp/terraform/terraform.tfvars.example +65 -0
  135. package/templates/webapp/terraform/variables.tf +129 -0
  136. package/templates/webapp/tsconfig.json +14 -0
  137. package/templates/webapp/vitest.config.ts +9 -0
  138. package/templates/webapp/vitest.setup.ts +5 -0
@@ -0,0 +1,94 @@
1
+ # Deploying to Percepta Test
2
+
3
+ This guide deploys __APP_TITLE__ to `https://__APP_NAME__.percepta-test.aitco.dev` using Ryvn. Tell Claude "deploy this app to percepta-test" and it will follow the steps below.
4
+
5
+ ## What's already scaffolded
6
+
7
+ When this app was created with `@percepta/create`, the IaC files were generated at `deploy/ryvn/` with all values pre-filled:
8
+
9
+ - `deploy/ryvn/__APP_NAME__.service.yaml` — Ryvn Service definition
10
+ - `deploy/ryvn/environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml` — percepta-test installation
11
+ - `.github/workflows/__APP_NAME__-ryvn-release.yaml` (at the repo root) — release workflow that builds the Docker image and creates a Ryvn release on push to `main`. Path filter scoped to `packages/__APP_NAME__/` so unrelated changes don't trigger builds.
12
+
13
+ Per-app values (URLs, k8s service names, database name) are substituted at create-time. Shared platform values (Inngest, Langfuse, OTel endpoints) are baked in as literals — they're stable across percepta-test apps. Three secrets stay in the Ryvn UI.
14
+
15
+ See [`deploy/README.md`](../deploy/README.md) for the file-by-file breakdown.
16
+
17
+ ## Prerequisites
18
+
19
+ - The app repo is under the `Percepta-Core` GitHub org. If not yet, run `gh repo create Percepta-Core/__APP_NAME__ --private --source=. --push` from the monorepo root.
20
+ - The Percepta-Core org has `RYVN_CLIENT_ID`, `RYVN_CLIENT_SECRET`, and `NPM_TOKEN` as org-level GitHub secrets (already in place).
21
+ - The `percepta-internal-terraform` installation provides the shared PostgreSQL — already deployed on percepta-test.
22
+ - The infra repo (`Percepta-Core/infra`) is checked out locally, typically at `../../infra` relative to this package.
23
+
24
+ ## Deploy
25
+
26
+ ### Step 1: Open the infra PR
27
+
28
+ Copy the two scaffolded files into the infra repo and open a PR:
29
+
30
+ ```bash
31
+ INFRA=../../infra # adjust to your local path
32
+ NAME=__APP_NAME__
33
+
34
+ cp deploy/ryvn/$NAME.service.yaml \
35
+ $INFRA/ryvn/$NAME.service.yaml
36
+
37
+ cp deploy/ryvn/environments/percepta-test/installations/$NAME.env.percepta-test.serviceinstallation.yaml \
38
+ $INFRA/ryvn/environments/percepta-test/installations/$NAME.env.percepta-test.serviceinstallation.yaml
39
+
40
+ cd $INFRA
41
+ git checkout -b deploy/$NAME-percepta-test
42
+ git add ryvn/$NAME.service.yaml ryvn/environments/percepta-test/installations/$NAME.env.percepta-test.serviceinstallation.yaml
43
+ git commit -m "Add $NAME service + percepta-test installation"
44
+ git push -u origin deploy/$NAME-percepta-test
45
+ gh pr create --fill
46
+ ```
47
+
48
+ Get the PR reviewed and merged.
49
+
50
+ ### Step 2: Set the three secrets in the Ryvn UI
51
+
52
+ After the infra PR merges, Ryvn creates the installation. Open the Ryvn UI for the `__APP_NAME__` installation in `percepta-test` and set:
53
+
54
+ | Secret | Value |
55
+ |--------|-------|
56
+ | `BETTER_AUTH_SECRET` | `openssl rand -base64 32` |
57
+ | `ENCRYPTION_SECRET_KEY` | `openssl rand -hex 16` |
58
+ | `LANGFUSE_SECRET_KEY` | from 1Password: "Percepta Test Secrets" |
59
+
60
+ ### Step 3: Trigger the first deploy
61
+
62
+ Push to `main` in the app repo (or `gh workflow run "Build & Release __APP_NAME__"`). The release workflow builds the Docker image and creates a Ryvn release; Ryvn deploys it to percepta-test.
63
+
64
+ The workflow lives at `.github/workflows/__APP_NAME__-ryvn-release.yaml` (at the repo root, where GitHub Actions picks it up). It only fires on changes under `packages/__APP_NAME__/`, so unrelated edits to other packages in the monorepo won't trigger it.
65
+
66
+ ### Step 4: Verify
67
+
68
+ ```bash
69
+ ryvn get installation __APP_NAME__ -e percepta-test
70
+ ryvn logs __APP_NAME__ -e percepta-test
71
+ curl -s https://__APP_NAME__.percepta-test.aitco.dev/api/healthz
72
+ curl -s https://__APP_NAME__.percepta-test.aitco.dev/api/readyz
73
+ ```
74
+
75
+ The app will be available at **https://__APP_NAME__.percepta-test.aitco.dev**.
76
+
77
+ ## Troubleshooting
78
+
79
+ - **Pod crash-looping** → secrets probably aren't set yet (Step 2). Check `ryvn logs`.
80
+ - **Database connection refused** → verify `DATABASE_USE_SSL=true` and that `percepta-internal-terraform` is deployed.
81
+ - **Inngest can't reach the app** → `INNGEST_APP_URL` must use the k8s service name `__APP_NAME__-web-server`. The scaffolded YAML gets this right; if you renamed anything, double-check.
82
+ - **Image build fails** → check `NPM_TOKEN` in the service definition is current. Build context is `packages/__APP_NAME__`.
83
+
84
+ For Ryvn CLI operations, use the `/use-ryvn` skill.
85
+
86
+ ## Updating shared platform values
87
+
88
+ The Inngest/Langfuse/OTel URLs are baked as literals in every webapp's installation YAML. If percepta-test infra ever changes those endpoints, update them with a single grep across the infra repo:
89
+
90
+ ```bash
91
+ grep -rl "inngest.percepta-test.svc.cluster.local:8288" ryvn/environments/percepta-test/installations/
92
+ ```
93
+
94
+ A future improvement would be a shared ConfigMap or Ryvn output reference; for now the scaffolded literals are the convention.
@@ -0,0 +1,147 @@
1
+ # Inngest — Background Jobs & Agent Workflows
2
+
3
+ Inngest is a durable execution engine for background tasks. Instead of managing queues, workers, and retries yourself, you define functions that respond to events — Inngest handles scheduling, retries, concurrency, and observability. Think of it as "cron jobs + event-driven workflows + built-in retry logic" in one tool.
4
+
5
+ In this template, Inngest powers all async work: background processing, scheduled jobs, multi-step agent pipelines, and anything that shouldn't block the HTTP request/response cycle.
6
+
7
+ ## Adding a New Event
8
+
9
+ ### 1. Define the payload schema
10
+
11
+ Create a Zod schema for the event payload:
12
+
13
+ ```typescript
14
+ import z from "zod";
15
+
16
+ export type DocumentProcessedPayload = z.infer<typeof DocumentProcessedPayload.SCHEMA>;
17
+ export namespace DocumentProcessedPayload {
18
+ export const SCHEMA = z.object({
19
+ documentId: z.string(),
20
+ userId: z.string(),
21
+ pageCount: z.number(),
22
+ });
23
+ }
24
+ ```
25
+
26
+ ### 2. Register in AppEvents
27
+
28
+ Add the event to the central `AppEvents` registry:
29
+
30
+ ```typescript
31
+ import { DocumentProcessedPayload } from "./payloads/DocumentProcessedPayload";
32
+
33
+ export const AppEvents = {
34
+ "app/document.processed": z.object({
35
+ data: DocumentProcessedPayload.SCHEMA,
36
+ }),
37
+ };
38
+ ```
39
+
40
+ Event names follow the convention `"app/<entity>.<action>"`.
41
+
42
+ ## Adding a New Function
43
+
44
+ ### 1. Create a function collection
45
+
46
+ Group related functions into a collection class that implements `InngestFunctionCollection`:
47
+
48
+ ```typescript
49
+ import { type InngestFunction } from "inngest";
50
+ import { type InngestFunctionCollection } from "../InngestFunctionCollection";
51
+ import { InngestService } from "../InngestService";
52
+
53
+ export class DocumentFunctions implements InngestFunctionCollection {
54
+ private inngestService = InngestService.create();
55
+
56
+ public get functions(): InngestFunction.Like[] {
57
+ return [this.processDocument()];
58
+ }
59
+
60
+ private processDocument() {
61
+ return this.inngestService.createFunction(
62
+ { id: "process-document", retries: 3 },
63
+ { event: "app/document.processed" },
64
+ async ({ event, step }) => {
65
+ const { documentId, userId } = event.data;
66
+
67
+ // step.run wraps each unit of work for durability — if a step fails,
68
+ // Inngest retries from that step, not from the beginning
69
+ const extracted = await step.run("extract-text", async () => {
70
+ // ... extract text from document
71
+ return { text: "..." };
72
+ });
73
+
74
+ await step.run("store-results", async () => {
75
+ // ... save to database
76
+ });
77
+ },
78
+ );
79
+ }
80
+ }
81
+ ```
82
+
83
+ ### 2. Register with the serve endpoint
84
+
85
+ Add your collection to the `functionCollections` array in the Inngest serve endpoint:
86
+
87
+ ```typescript
88
+ const functionCollections: InngestFunctionCollection[] = compact([
89
+ new DocumentFunctions(),
90
+ ]);
91
+ ```
92
+
93
+ ## Sending Events
94
+
95
+ ```typescript
96
+ const inngest = InngestService.create();
97
+ await inngest.client.send({
98
+ name: "app/document.processed",
99
+ data: { documentId: "abc", userId: "user-1", pageCount: 5 },
100
+ });
101
+ ```
102
+
103
+ ## Running Inngest Locally
104
+
105
+ ### 1. Install the Inngest Dev Server
106
+
107
+ ```bash
108
+ pnpm dlx inngest-cli@latest dev
109
+ ```
110
+
111
+ This starts the Inngest Dev Server at `http://localhost:8288` with a dashboard UI.
112
+
113
+ ### 2. Set environment variables
114
+
115
+ In `.env.local`:
116
+
117
+ ```bash
118
+ INNGEST_BASE_URL=http://localhost:8288
119
+ INNGEST_EVENT_KEY=local # any value works locally
120
+ # INNGEST_SIGNING_KEY is not needed for local dev
121
+ ```
122
+
123
+ ### 3. Start the app
124
+
125
+ ```bash
126
+ pnpm dev
127
+ ```
128
+
129
+ The Inngest Dev Server auto-discovers functions by calling the serve endpoint at `/api/inngest`. Open `http://localhost:8288` to see registered functions, trigger events manually, and inspect runs.
130
+
131
+ ## Environment Variables
132
+
133
+ | Variable | Required | Description |
134
+ |----------|----------|-------------|
135
+ | `INNGEST_BASE_URL` | Yes | Inngest server URL (`http://localhost:8288` locally) |
136
+ | `INNGEST_EVENT_KEY` | Yes (prod) | Event authentication key |
137
+ | `INNGEST_SIGNING_KEY` | Yes (prod) | Function registration signing key |
138
+ | `INNGEST_APP_URL` | No | Override app URL for Inngest to call back |
139
+ | `SKIP_INNGEST_SYNC` | No | Set `true` to skip Inngest app sync on startup |
140
+
141
+ ## Key Concepts
142
+
143
+ - **Events** are the trigger. Define them with Zod schemas for runtime validation.
144
+ - **Functions** respond to events. They contain one or more **steps**.
145
+ - **Steps** (`step.run`, `step.sleep`, `step.waitForEvent`) are durable — if the function crashes mid-execution, Inngest resumes from the last completed step.
146
+ - **Retries** are automatic. Default is 3 retries with exponential backoff.
147
+ - The `InngestService` singleton wraps the Inngest client and enforces typed events via `AppEvents`.
@@ -0,0 +1,117 @@
1
+ # Langfuse — LLM Observability & Tracing
2
+
3
+ Langfuse is an open-source LLM observability platform. It captures traces, spans, and generations from LLM calls so you can debug prompt behavior, monitor latency/cost, and run evaluations. Think of it as "Datadog for LLM apps."
4
+
5
+ **Our opinionated setup:** We integrate Langfuse via OpenTelemetry (OTEL), not the Langfuse SDK directly. OTEL auto-instruments the app (HTTP, DB, etc.), and Langfuse acts as a span processor that receives LLM-specific traces. This gives us unified tracing — both traditional backend spans and LLM generations in the same trace.
6
+
7
+ ## When to Use Langfuse
8
+
9
+ **Use Langfuse when the app calls LLMs** (OpenAI, Bedrock/Claude, etc.). It gives you:
10
+ - Trace visualization of multi-step LLM chains
11
+ - Token usage and cost tracking per request
12
+ - Prompt versioning and A/B testing
13
+ - Evaluation scores attached to traces
14
+
15
+ **Skip Langfuse when the app has no LLM calls.** The OTEL instrumentation still works without Langfuse — you just won't have the LLM-specific dashboard. If Langfuse env vars are not set, the integration gracefully no-ops.
16
+
17
+ ## How It Works
18
+
19
+ ### OpenTelemetry + Langfuse Span Processor
20
+
21
+ The template uses Next.js's instrumentation hook (called on server startup) to bootstrap OTEL with Langfuse:
22
+
23
+ ```typescript
24
+ import { LangfuseSpanProcessor } from "@langfuse/otel";
25
+ import { NodeSDK } from "@opentelemetry/sdk-node";
26
+ import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
27
+
28
+ const sdk = new NodeSDK({
29
+ spanProcessors: [new LangfuseSpanProcessor({ baseUrl, publicKey, secretKey })],
30
+ instrumentations: [getNodeAutoInstrumentations()],
31
+ });
32
+ sdk.start();
33
+ ```
34
+
35
+ - `getNodeAutoInstrumentations()` automatically instruments HTTP calls, database queries, and other standard Node.js operations.
36
+ - `LangfuseSpanProcessor` forwards spans to Langfuse. LLM spans (from the Vercel AI SDK or OpenAI SDK) are recognized and displayed as generations in the Langfuse UI.
37
+
38
+ **You do not need to manually instrument LLM calls.** If you use the Vercel AI SDK (`ai` package) or the OpenAI SDK, OTEL picks them up automatically.
39
+
40
+ ### LangfuseService (Direct API)
41
+
42
+ For operations beyond automatic tracing — like attaching metadata or evaluation scores to a trace — the template includes a `LangfuseService` singleton:
43
+
44
+ ```typescript
45
+ const langfuse = LangfuseService.create();
46
+
47
+ if (langfuse.isConfigured()) {
48
+ await langfuse.updateTraceMetadata(traceId, {
49
+ userId: "user-123",
50
+ feedbackScore: 0.95,
51
+ });
52
+ }
53
+ ```
54
+
55
+ This is a singleton — `create()` returns the same instance every time. It gracefully no-ops when Langfuse is not configured.
56
+
57
+ ## Getting Langfuse Keys
58
+
59
+ ### Percepta's Internal Instance
60
+
61
+ Percepta runs a shared Langfuse instance. To get keys:
62
+
63
+ 1. Ask your team lead for access to the Percepta Langfuse instance
64
+ 2. Log into the Langfuse dashboard
65
+ 3. Go to **Settings → API Keys** and create a new key pair
66
+ 4. You'll get a **public key** and **secret key**
67
+
68
+ ### Self-Hosted / Langfuse Cloud
69
+
70
+ For external projects, you can:
71
+ - Use [Langfuse Cloud](https://cloud.langfuse.com) (free tier available)
72
+ - Self-host with `docker run langfuse/langfuse`
73
+
74
+ ## Running Locally
75
+
76
+ ### Option 1: Use Percepta's Langfuse (recommended)
77
+
78
+ Set the keys in `.env.local` pointing to the shared instance:
79
+
80
+ ```bash
81
+ LANGFUSE_BASE_URL=https://langfuse.internal.percepta.ai # ask team for actual URL
82
+ LANGFUSE_PUBLIC_KEY=pk-lf-...
83
+ LANGFUSE_SECRET_KEY=sk-lf-...
84
+ ```
85
+
86
+ Traces from local dev appear in the shared dashboard under your project.
87
+
88
+ ### Option 2: Run Langfuse locally
89
+
90
+ ```bash
91
+ docker run -d --name langfuse \
92
+ -p 3001:3000 \
93
+ -e DATABASE_URL=postgresql://postgres:postgres@host.docker.internal:5434/langfuse \
94
+ langfuse/langfuse
95
+ ```
96
+
97
+ Then set:
98
+
99
+ ```bash
100
+ LANGFUSE_BASE_URL=http://localhost:3001
101
+ LANGFUSE_PUBLIC_KEY=pk-lf-... # create in local UI after first login
102
+ LANGFUSE_SECRET_KEY=sk-lf-...
103
+ ```
104
+
105
+ ### Option 3: Skip Langfuse entirely
106
+
107
+ Simply don't set the `LANGFUSE_*` env vars. The instrumentation gracefully skips the Langfuse span processor, and `LangfuseService.isConfigured()` returns `false`. OTEL still instruments the app for general tracing.
108
+
109
+ ## Environment Variables
110
+
111
+ | Variable | Required | Description |
112
+ |----------|----------|-------------|
113
+ | `LANGFUSE_BASE_URL` | No | Langfuse server URL. If unset, Langfuse integration is disabled. |
114
+ | `LANGFUSE_PUBLIC_KEY` | No | Public key from Langfuse dashboard |
115
+ | `LANGFUSE_SECRET_KEY` | No | Secret key from Langfuse dashboard |
116
+
117
+ All three must be set for Langfuse to activate. If any is missing, the integration silently no-ops.
@@ -0,0 +1,216 @@
1
+ # Oneshot — Build a Complete App from Requirements
2
+
3
+ Build a full application end-to-end in one shot: gather requirements, design, implement, verify locally, create the GitHub repo, and optionally deploy — fully autonomous after requirements are locked.
4
+
5
+ **When to use this:** The user has scaffolded a new project with `@percepta/create` and wants to build an app on top of it. They'll describe what they want at a high level. Your job is to turn that into a running application.
6
+
7
+ <HARD-GATE>
8
+ After requirements are confirmed, do NOT stop and give the turn back to the user until the app is fully built and running locally. Do NOT ask for approval between phases. Make reasonable assumptions and keep moving. The user can interrupt with course corrections at any time — treat those as input, not as a reason to pause.
9
+ </HARD-GATE>
10
+
11
+ ## Process Flow
12
+
13
+ ```
14
+ Gather requirements
15
+ → Confirm with user (ONLY pause point)
16
+ → Design architecture
17
+ → Implement in a loop (build → verify → fix → repeat)
18
+ → Get local app running
19
+ → Create GitHub repo
20
+ → [If requested] Deploy to Ryvn
21
+ ```
22
+
23
+ ---
24
+
25
+ ## Phase 1: Gather Requirements
26
+
27
+ Ask the user structured questions AND open-ended questions. Do this in ONE message — do not drip-feed questions across multiple turns.
28
+
29
+ ### Structured Questions
30
+
31
+ Ask all that are relevant:
32
+
33
+ 1. **What does this app do?** One-sentence description of the core purpose.
34
+ 2. **Who are the users?** Internal team, external customers, both?
35
+ 3. **What are the core entities?** (e.g., "Documents, Users, Teams" — the nouns in the data model)
36
+ 4. **What are the key workflows?** (e.g., "User uploads a document → system extracts text → user reviews results")
37
+ 5. **Does this app call LLMs?** If yes, which provider (OpenAI, Bedrock/Claude, both)? What for?
38
+ 6. **Does this app need background jobs?** Long-running tasks, scheduled jobs, multi-step pipelines?
39
+ 7. **Auth requirements?** Which provider — Google, Okta, Credentials, or just use the template default?
40
+ 8. **Any external integrations?** APIs, webhooks, third-party services?
41
+
42
+ ### Open-Ended
43
+
44
+ After the structured questions, ask:
45
+
46
+ > "Is there anything else I should know about this app — constraints, design preferences, prior art, or things you definitely don't want?"
47
+
48
+ ### Confirm Requirements
49
+
50
+ Summarize what you understood back to the user in a structured format:
51
+
52
+ ```
53
+ ## Requirements Summary
54
+
55
+ **App:** [one sentence]
56
+ **Users:** [who]
57
+ **Entities:** [list]
58
+ **Workflows:** [numbered list]
59
+ **LLM usage:** [yes/no, details]
60
+ **Background jobs:** [yes/no, details]
61
+ **Auth:** [provider]
62
+ **Integrations:** [list or none]
63
+ **Notes:** [anything else]
64
+ ```
65
+
66
+ Ask: **"Does this look right? Any corrections before I start building?"**
67
+
68
+ This is the ONLY point where you pause for user confirmation. After they confirm, go fully autonomous.
69
+
70
+ ---
71
+
72
+ ## Phase 2: Design Architecture
73
+
74
+ Based on the confirmed requirements, design the architecture. Do NOT write a plan document — think through it and write it as a brief message to the user (so they can see your thinking), then start building.
75
+
76
+ Cover:
77
+ 1. **Database schema** — which tables, columns, relationships
78
+ 2. **API routes** — which tRPC routers and procedures
79
+ 3. **Pages** — which routes/pages in the app
80
+ 4. **Background jobs** — which Inngest events and functions (if any)
81
+ 5. **LLM integration** — which service to use, how to wire it (if any)
82
+
83
+ ### Decision: Langfuse
84
+
85
+ Read [agent-skills/langfuse.md](langfuse.md) to understand Langfuse. **Use Langfuse if the app calls LLMs.** Skip it otherwise. The template already has the integration wired — you just need to ensure env vars are set.
86
+
87
+ ### Decision: Inngest
88
+
89
+ Read [agent-skills/inngest.md](inngest.md) to understand Inngest. **Use Inngest if the app has background jobs, scheduled tasks, or multi-step agent pipelines.** The template has the scaffold — you add events and functions.
90
+
91
+ ### Decision: Database
92
+
93
+ Read [agent-skills/database.md](database.md) for the Drizzle patterns. You will almost always need new tables.
94
+
95
+ ---
96
+
97
+ ## Phase 3: Implement
98
+
99
+ Build the app in logical chunks. For each chunk: implement, then verify it compiles and runs. Fix issues before moving on.
100
+
101
+ ### Implementation Order
102
+
103
+ Follow this order — each layer builds on the previous:
104
+
105
+ 1. **Database schema** — Define tables, export from schema index, generate migration, apply it
106
+ 2. **Services** — Business logic services (follow the existing singleton + `AsyncLocalStorage` pattern)
107
+ 3. **tRPC routers** — API endpoints, compose in the root appRouter
108
+ 4. **Inngest functions** — Events and function collections (if needed)
109
+ 5. **Pages and components** — React pages and UI components
110
+ 6. **Polish** — Error states, loading states, edge cases
111
+
112
+ ### After Each Chunk
113
+
114
+ Run:
115
+
116
+ ```bash
117
+ pnpm build
118
+ ```
119
+
120
+ If it fails, fix the errors before moving to the next chunk. Do not accumulate broken code.
121
+
122
+ ### Implementation Rules
123
+
124
+ - **Use `@percepta/design` components.** Do not write custom UI for things that exist in the design system (Button, Dialog, Table, Card, Input, etc.). Read `node_modules/@percepta/design/dist/src/index.d.ts` to see what's available.
125
+ - **Use `AsyncContent` for loading states.** Every data-fetching UI should use `AsyncContent` from `@percepta/components`.
126
+ - **Use `getEnvConfig()` for env vars.** Never use `process.env` directly.
127
+ - **Use `getLogger()` for logging.** Never use `console.log`. Use safe/unsafe field separation.
128
+ - **Follow the singleton pattern** for new services. Follow the existing `DatabaseService` singleton pattern in the codebase.
129
+ - **Use `protectedProcedure`** for authenticated tRPC routes.
130
+
131
+ ---
132
+
133
+ ## Phase 4: Local Verification
134
+
135
+ Once all chunks are implemented, verify the app runs end-to-end locally.
136
+
137
+ ### Step 1: Start dependencies
138
+
139
+ ```bash
140
+ pnpm docker:up
141
+ pnpm db:setup-and-migrate
142
+ ```
143
+
144
+ ### Step 2: Start the dev server
145
+
146
+ ```bash
147
+ pnpm dev
148
+ ```
149
+
150
+ ### Step 3: Verify the app works
151
+
152
+ Open the app in a browser and walk through the core workflows from the requirements. Check:
153
+ - Pages load without errors
154
+ - Data can be created, read, updated, deleted
155
+ - Auth works (if configured)
156
+ - Background jobs trigger and complete (if applicable)
157
+ - No console errors in the browser
158
+
159
+ ### Step 4: Build check
160
+
161
+ ```bash
162
+ pnpm build
163
+ pnpm lint
164
+ ```
165
+
166
+ Both must pass clean.
167
+
168
+ **Do not report the app as complete until it builds, lints, and the core workflows work in the browser.**
169
+
170
+ ---
171
+
172
+ ## Phase 5: Create GitHub Repository
173
+
174
+ After the app is verified locally, create the GitHub repo:
175
+
176
+ ```bash
177
+ # Initialize git if not already done
178
+ git init
179
+ git add -A
180
+ git commit -m "Initial implementation of <app-name>"
181
+
182
+ # Create the repo under percepta-ai org
183
+ gh repo create percepta-ai/<app-name> --private --source=. --push
184
+ ```
185
+
186
+ If `gh` is not authenticated, tell the user to run `gh auth login` and then continue.
187
+
188
+ ---
189
+
190
+ ## Phase 6: Deploy to Ryvn (When Requested)
191
+
192
+ Only do this when the user explicitly asks to deploy.
193
+
194
+ Follow the step-by-step guide in [agent-skills/deploy.md](deploy.md). It contains the service definition, installation YAML, environment variables, and secrets needed for percepta-test.
195
+
196
+ For Ryvn CLI operations (checking status, logs, troubleshooting), use the `/use-ryvn` skill.
197
+
198
+ Report the deployment URL to the user when done.
199
+
200
+ ---
201
+
202
+ ## Common Mistakes
203
+
204
+ | Mistake | Fix |
205
+ |---------|-----|
206
+ | Stopping after design to ask permission | After requirements are confirmed, build autonomously. |
207
+ | Not running `pnpm build` between chunks | Build after every chunk. Fix errors immediately. |
208
+ | Writing custom UI instead of using `@percepta/design` | Check the design system first. |
209
+ | Using `console.log` | Use `getLogger()` with safe/unsafe fields. |
210
+ | Using `process.env` | Use `getEnvConfig()`. |
211
+ | Forgetting to export new schema from the schema index | Every new table must be exported from the index. |
212
+ | Forgetting to register Inngest functions in the serve endpoint | Function collections must be added to the serve endpoint. |
213
+ | Forgetting to add new router to the root appRouter | Every new tRPC router must be composed in the root. |
214
+ | Not verifying the app in the browser | Build + lint passing is necessary but not sufficient. Test the UI. |
215
+ | Creating the GitHub repo before the app works locally | Verify locally first, then create the repo. |
216
+ | Deploying without the user asking | Only deploy when explicitly requested. |
@@ -0,0 +1,25 @@
1
+ # Ryvn — Deployment Platform
2
+
3
+ Ryvn is the deployment platform used by Percepta to manage cloud infrastructure. It provisions environments, deploys services, and handles CI/CD — abstracting away Kubernetes, Terraform, and cloud provider details.
4
+
5
+ ## Percepta Environments
6
+
7
+ | Environment | Purpose | Domain pattern |
8
+ |-------------|---------|---------------|
9
+ | `percepta-test` | Internal dev/test | `<app>.percepta-test.aitco.dev` |
10
+
11
+ New apps are deployed to **percepta-test**. This environment has a shared PostgreSQL instance, Inngest server, Langfuse, and OTEL collector.
12
+
13
+ ## Deploying This App
14
+
15
+ For the full step-by-step deployment guide to percepta-test, see [deploy.md](deploy.md).
16
+
17
+ ## Ryvn CLI
18
+
19
+ For all Ryvn CLI operations (checking status, viewing logs, troubleshooting, managing installations), use the **`/use-ryvn`** skill. It has comprehensive CLI reference docs and handles authentication, deployment, configuration, and operations.
20
+
21
+ ```bash
22
+ # Quick status check
23
+ ryvn get installation __APP_NAME__ -e percepta-test
24
+ ryvn logs __APP_NAME__ -e percepta-test
25
+ ```
@@ -0,0 +1,39 @@
1
+ # Deploy
2
+
3
+ This directory holds infrastructure-as-code for deploying __APP_TITLE__ to Percepta environments.
4
+
5
+ ```
6
+ deploy/
7
+ └── ryvn/
8
+ ├── __APP_NAME__.service.yaml # Ryvn Service definition
9
+ └── environments/
10
+ └── percepta-test/
11
+ └── installations/
12
+ └── __APP_NAME__.env.percepta-test.serviceinstallation.yaml # percepta-test installation
13
+ ```
14
+
15
+ These files are pre-filled with all values needed to deploy to `https://__APP_NAME__.percepta-test.aitco.dev`. The only manual steps are: open a PR in `Percepta-Core/infra` to copy them in, and set three secrets in the Ryvn UI.
16
+
17
+ ## Deploying
18
+
19
+ Tell Claude "deploy this app to percepta-test" — it'll follow [`agent-skills/deploy.md`](../agent-skills/deploy.md), which walks through copying these files into the infra repo, opening the PR, and the manual Ryvn UI step.
20
+
21
+ ## What's in these files
22
+
23
+ **`__APP_NAME__.service.yaml`** — tells Ryvn how to build the Docker image. Points at `Percepta-Core/__APP_NAME__` (change the repo if you've named the GitHub repo differently). The `NPM_TOKEN` build arg is a read-only npm token shared across all Percepta apps for installing `@percepta/*` packages — it's not a secret.
24
+
25
+ **`__APP_NAME__.env.percepta-test.serviceinstallation.yaml`** — configures the deployment on `percepta-test`. Three categories of values are baked in:
26
+
27
+ 1. **Per-app values** that vary by app name (URLs, k8s service names) — substituted at create-time.
28
+ 2. **Shared platform values** (Inngest, Langfuse, OTel endpoints) — copied as literals from the `tc-emo` reference installation. These are stable across percepta-test apps.
29
+ 3. **Secrets** (`BETTER_AUTH_SECRET`, `ENCRYPTION_SECRET_KEY`, `LANGFUSE_SECRET_KEY`) — declared in the YAML, value entered once in the Ryvn UI.
30
+
31
+ ## Repo layout note
32
+
33
+ These files assume this repo is a **monorepo** with the app at `packages/__APP_NAME__/` (the layout produced by `@percepta/create`). The `service.yaml` sets `context: packages/__APP_NAME__` accordingly. If you've flattened this into a single-package repo, change `context:` and `dockerfile:` to `.` and `Dockerfile`.
34
+
35
+ The release workflow lives at `.github/workflows/__APP_NAME__-ryvn-release.yaml` (at the repo root, where GitHub Actions picks it up). It's path-filtered to `packages/__APP_NAME__/`, so changes to other packages won't trigger this app's deploy. Multiple webapps in the same monorepo each get their own `<name>-ryvn-release.yaml` and don't collide.
36
+
37
+ ## Adding more environments
38
+
39
+ Copy `environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml` to a new `environments/<env>/installations/__APP_NAME__.env.<env>.serviceinstallation.yaml` and change the `environment:`, host, and any environment-specific values.
@@ -0,0 +1,11 @@
1
+ kind: Service
2
+ metadata:
3
+ name: __APP_NAME__
4
+ spec:
5
+ type: server
6
+ repo: Percepta-Core/__APP_NAME__
7
+ build:
8
+ args:
9
+ NPM_TOKEN: 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
10
+ context: packages/__APP_NAME__
11
+ dockerfile: packages/__APP_NAME__/Dockerfile