@percepta/create 3.1.2 → 3.1.3

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 (43) hide show
  1. package/README.md +3 -4
  2. package/dist/{chunk-CG7IJSB4.js → chunk-CO3YWUD6.js} +2 -2
  3. package/dist/{chunk-WMJT7CB5.js → chunk-V5EJIUBJ.js} +5 -2
  4. package/dist/index.js +21 -53
  5. package/dist/{init-XDWSYHYK.js → init-EQZ2TCSJ.js} +2 -2
  6. package/dist/{status-BTHGN6QH.js → status-QW5TQDYY.js} +1 -1
  7. package/dist/{sync-3Q27L7XZ.js → sync-RLBZDOFB.js} +1 -1
  8. package/dist/{upstream-C5KFAHVR.js → upstream-TQFVPMEG.js} +1 -1
  9. package/package.json +1 -1
  10. package/templates/monorepo/.dockerignore +18 -0
  11. package/templates/webapp/.github/workflows/__APP_NAME__-ryvn-release.yaml +6 -2
  12. package/templates/webapp/.github/workflows/__APP_NAME__-terraform-ryvn-release.yaml +98 -0
  13. package/templates/webapp/AGENTS.md +17 -5
  14. package/templates/webapp/Dockerfile +16 -7
  15. package/templates/webapp/README.md +64 -2
  16. package/templates/webapp/agent-skills/deploy.md +48 -65
  17. package/templates/webapp/agent-skills/inngest.md +4 -4
  18. package/templates/webapp/agent-skills/langfuse.md +15 -14
  19. package/templates/webapp/agent-skills/llm.md +59 -0
  20. package/templates/webapp/agent-skills/oneshot.md +14 -1
  21. package/templates/webapp/agent-skills/ryvn.md +1 -1
  22. package/templates/webapp/deploy/README.md +34 -33
  23. package/templates/webapp/deploy/ryvn/__APP_NAME__-terraform.service.yaml +10 -0
  24. package/templates/webapp/deploy/ryvn/__APP_NAME__.service.yaml +2 -2
  25. package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__-terraform.env.percepta-test.serviceinstallation.yaml +11 -0
  26. package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml +45 -9
  27. package/templates/webapp/env.example.template +20 -2
  28. package/templates/webapp/eslint.config.mjs +6 -0
  29. package/templates/webapp/next.config.ts +9 -0
  30. package/templates/webapp/package.json.template +6 -2
  31. package/templates/webapp/scripts/deploy-percepta-test.ts +837 -0
  32. package/templates/webapp/scripts/migrate.ts +3 -0
  33. package/templates/webapp/scripts/open-ryvn-deploy-pr.ts +5 -3
  34. package/templates/webapp/scripts/with-local-env.ts +75 -0
  35. package/templates/webapp/src/config/getEnvConfig.ts +14 -0
  36. package/templates/webapp/src/instrumentation.ts +102 -10
  37. package/templates/webapp/src/services/llm/LLMService.ts +88 -0
  38. package/templates/webapp/src/services/llm/LlmProviderService.ts +85 -0
  39. package/templates/webapp/terraform/schema/main.tf +4 -0
  40. package/templates/webapp/terraform/schema/outputs.tf +9 -0
  41. package/templates/webapp/terraform/schema/variables.tf +19 -0
  42. package/templates/webapp/terraform/schema/versions.tf +38 -0
  43. package/templates/webapp/.github/workflows/__APP_NAME__-terraform.yml +0 -28
@@ -9,7 +9,8 @@ A production-ready Next.js application with authentication, database, logging, b
9
9
  - **Database** with PostgreSQL, Drizzle ORM, and migrations
10
10
  - **Logging** with Pino and structured safe/unsafe data separation
11
11
  - **Background Jobs** with Inngest
12
- - **Observability** with OpenTelemetry and Langfuse integration
12
+ - **LLM Calls** with a provider-backed `LLMService`
13
+ - **Observability** with OpenTelemetry, LGTM-compatible traces/metrics/logs, and Langfuse integration
13
14
  - **Infrastructure** with Terraform modules for AWS (RDS, S3, IAM)
14
15
  - **Type Safety** with TypeScript and Zod schemas
15
16
 
@@ -31,7 +32,15 @@ pnpm db:setup-and-migrate
31
32
 
32
33
  Copy `.env.example` to `.env.local` and configure your environment variables.
33
34
 
34
- ### 4. Start Development Server
35
+ ### 4. Start Inngest When Using Background Jobs
36
+
37
+ ```bash
38
+ pnpm inngest:dev
39
+ ```
40
+
41
+ Run this in a separate terminal when the app has Inngest functions or you want the local Inngest dashboard.
42
+
43
+ ### 5. Start Development Server
35
44
 
36
45
  ```bash
37
46
  pnpm dev
@@ -39,6 +48,16 @@ pnpm dev
39
48
 
40
49
  Open [http://localhost:3000](http://localhost:3000) to see your app.
41
50
 
51
+ OpenTelemetry, Faro, and Langfuse are optional in local development. Leave their env vars empty unless you are actively debugging telemetry; production deploys wire server traces, metrics, logs, and shared Langfuse demo credentials into the target Ryvn environment. General HTTP and database spans go to the OTEL/LGTM pipeline; Langfuse receives only AI SDK spans by default.
52
+
53
+ If you need local LLM calls, set provider keys once in your shell profile or in `~/.config/percepta/create.env`:
54
+
55
+ ```bash
56
+ ANTHROPIC_API_KEY=sk-ant-...
57
+ ```
58
+
59
+ `pnpm dev` loads that shared file before starting Next.js, so you do not need to copy the same provider key into every generated app.
60
+
42
61
  ## Project Structure
43
62
 
44
63
  ```
@@ -53,6 +72,7 @@ src/
53
72
  ├── services/ # Business logic services
54
73
  │ ├── inngest/ # Background job definitions
55
74
  │ ├── langfuse/ # LLM observability
75
+ │ ├── llm/ # LLM provider selection and call helpers
56
76
  │ └── logger/ # Structured logging
57
77
  └── utils/ # Utility functions
58
78
  ```
@@ -67,6 +87,7 @@ src/
67
87
  | `pnpm lint` | Run ESLint |
68
88
  | `pnpm docker:up` | Start PostgreSQL container |
69
89
  | `pnpm docker:down` | Stop PostgreSQL container |
90
+ | `pnpm inngest:dev` | Start the local Inngest dev server for this app |
70
91
  | `pnpm db:generate` | Generate Drizzle migrations |
71
92
  | `pnpm db:migrate` | Run database migrations |
72
93
  | `pnpm db:setup` | Create database and user |
@@ -164,6 +185,47 @@ node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"
164
185
  | `LANGFUSE_PUBLIC_KEY` | Langfuse public key |
165
186
  | `LANGFUSE_SECRET_KEY` | Langfuse secret key |
166
187
 
188
+ `deploy:percepta-test` sets the shared Langfuse URL and inherits the demo project keys from the `demos-commons` Ryvn variable group.
189
+
190
+ ### LLM Providers
191
+
192
+ | Variable | Description |
193
+ |----------|-------------|
194
+ | `ANTHROPIC_API_KEY` | Anthropic API key. Inherited from `demos-commons` for `percepta-test` deploys |
195
+ | `OPENAI_API_KEY` | OpenAI API key for local or non-demo deployments |
196
+ | `LLM_PROVIDER` | Optional provider override: `anthropic` or `openai` |
197
+ | `LLM_MODEL` | Optional model override |
198
+
199
+ Use `LLMService` for backend model calls:
200
+
201
+ ```typescript
202
+ import { LLMService } from "@/services/llm/LLMService";
203
+
204
+ const result = await LLMService.create().generateText({
205
+ telemetryFunctionId: "summarize-note",
206
+ system: "You summarize notes for internal users.",
207
+ prompt: "Summarize this note...",
208
+ });
209
+ ```
210
+
211
+ For local development, `pnpm dev` also loads `~/.config/percepta/create.env` when that file exists. Production deploys do not use that local file; they inherit provider keys from the target Ryvn environment.
212
+
213
+ ### OpenTelemetry / LGTM
214
+
215
+ | Variable | Description |
216
+ |----------|-------------|
217
+ | `OTEL_SERVICE_NAME` | Service name attached to traces and metrics |
218
+ | `OTEL_RESOURCE_ATTRIBUTES` | Extra resource labels such as deployment environment |
219
+ | `OTEL_EXPORTER_OTLP_PROTOCOL` | OTLP protocol, usually `http/protobuf` |
220
+ | `OTEL_EXPORTER_OTLP_ENDPOINT` | Base OTLP HTTP collector endpoint |
221
+ | `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | Optional trace-specific OTLP endpoint |
222
+ | `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` | Optional metric-specific OTLP endpoint |
223
+ | `OTEL_TRACES_EXPORTER` | Set to `otlp` to export server traces |
224
+ | `OTEL_METRICS_EXPORTER` | Set to `otlp` to export server metrics |
225
+ | `OTEL_METRIC_EXPORT_INTERVAL` | Metrics export interval in milliseconds |
226
+
227
+ `deploy:percepta-test` configures these for the existing percepta-test OTEL collector and LGTM stack. Application logs are written to stdout and collected by the platform collector.
228
+
167
229
  ## Local AWS Development
168
230
 
169
231
  This application uses the default AWS SDK credential provider chain:
@@ -1,107 +1,90 @@
1
1
  # Deploying to Percepta Test
2
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.
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 should run the direct deploy helper below.
4
4
 
5
- ## What's already scaffolded
5
+ This is the existing-environment deploy motion: `percepta-test` already owns the shared platform services, and this app is wired into them. Fresh-environment platform bootstrap is separate and should use a Ryvn blueprint or environment-specific platform rollout before app deploys run.
6
6
 
7
- When this app was created with `@percepta/create`, the IaC files were generated at `deploy/ryvn/` with all values pre-filled:
7
+ ## What's Already Scaffolded
8
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 schema) are substituted at create-time. The Better Auth and encryption secrets are written to `deploy/ryvn/percepta-test.secrets.env`, which is ignored by git and intended for Ryvn UI import. Shared platform values (Inngest and OTel endpoints) are baked in as literals because they're stable across percepta-test apps.
9
+ - `deploy/ryvn/__APP_NAME__.service.yaml` — Ryvn server service for the web app.
10
+ - `deploy/ryvn/__APP_NAME__-terraform.service.yaml` — Ryvn Terraform service that creates the app's Postgres schema.
11
+ - `deploy/ryvn/environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml` — web installation.
12
+ - `deploy/ryvn/environments/percepta-test/installations/__APP_NAME__-terraform.env.percepta-test.serviceinstallation.yaml` — schema installation.
13
+ - `.github/workflows/__APP_NAME__-ryvn-release.yaml` builds the Docker image and creates the web Ryvn release.
14
+ - `.github/workflows/__APP_NAME__-terraform-ryvn-release.yaml` — creates the schema Terraform Ryvn release.
15
+ - `deploy/ryvn/percepta-test.secrets.env` — generated locally and ignored by git; patched into Ryvn by the deploy helper.
14
16
 
15
17
  See [`deploy/README.md`](../deploy/README.md) for the file-by-file breakdown.
16
18
 
17
19
  ## Prerequisites
18
20
 
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
- - `git` and `gh` are installed and authenticated.
23
- - The infra repo (`Percepta-Core/infra`) is checked out locally or can be cloned by the deploy script.
21
+ - `git`, `gh`, and `ryvn` are installed and authenticated.
22
+ - The worktree is clean and committed. The helper pushes the current branch to `main` because GitHub Actions builds from GitHub.
23
+ - The Percepta-Core org has `RYVN_CLIENT_ID`, `RYVN_CLIENT_SECRET`, and `NPM_TOKEN` available as org-level GitHub secrets.
24
+ - These shared platform installations are already deployed and healthy in `percepta-test`: `percepta-internal-terraform`, `inngest-test`, `otel-collector`, `lgtm-stack-helm`, and `langfuse`.
25
+ - The `demos-commons` Ryvn variable group exists in `percepta-test` and provides `LANGFUSE_PUBLIC_KEY` plus sensitive `ANTHROPIC_API_KEY` and `LANGFUSE_SECRET_KEY` for shared demo LLM calls and Langfuse tracing.
24
26
 
25
27
  ## Deploy
26
28
 
27
- ### Step 1: Open the service/schema infra PR
28
-
29
- Run the generated deploy helper from this package directory:
29
+ From this package directory:
30
30
 
31
31
  ```bash
32
- pnpm deploy:percepta-test -- --phase service --yes
32
+ pnpm deploy:percepta-test -- --yes
33
33
  ```
34
34
 
35
- This script:
36
-
37
- - uses `INFRA_REPO` or `--infra <path>` when provided
38
- - otherwise uses a sibling `../infra` checkout, cloning `Percepta-Core/infra` there if needed
39
- - copies the Ryvn service YAML into infra
40
- - appends a `postgresql_schema "__APP_NAME_SNAKE__"` resource to `terraform/percepta-internal/databases.tf`
41
- - opens the first PR against `Percepta-Core/infra`
42
-
43
- Get the PR reviewed and merged.
44
-
45
- ### Step 2: Wait for service/schema import
35
+ The helper:
46
36
 
47
- After the service/schema PR merges, wait for Ryvn GitOps to import the service. If Ryvn creates a `percepta-internal-terraform` task for the database schema, approve/apply it in Ryvn.
37
+ 1. Checks the existing platform installations and shared demo variable group in `percepta-test`.
38
+ 2. Creates `Percepta-Core/__REPO_NAME__` if needed.
39
+ 3. Pushes the current branch to `main`.
40
+ 4. Creates or replaces the Ryvn web and schema services.
41
+ 5. Runs the schema Terraform release workflow.
42
+ 6. Creates or replaces the schema installation and approves the Terraform plan.
43
+ 7. Runs the web release workflow.
44
+ 8. Creates or replaces the web installation.
45
+ 9. Patches `BETTER_AUTH_SECRET` and `ENCRYPTION_SECRET_KEY` from `deploy/ryvn/percepta-test.secrets.env`.
46
+ 10. Waits for Ryvn health and checks `/api/healthz`, `/api/readyz`, and the protected app route.
48
47
 
49
- ### Step 3: Create the first release
50
-
51
- 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. Do this before creating the ServiceInstallation, otherwise GitOps can fail with `ReleaseNotFound`.
52
-
53
- 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.
54
-
55
- ### Step 4: Open the installation infra PR
48
+ The app will be available at **https://__APP_NAME__.percepta-test.aitco.dev**.
56
49
 
57
- After the first release exists, open the installation PR:
50
+ ## Useful Variants
58
51
 
59
52
  ```bash
60
- pnpm deploy:percepta-test -- --phase installation --yes
53
+ pnpm deploy:percepta-test -- --skip-workflows --yes
54
+ pnpm deploy:percepta-test -- --skip-push --yes
55
+ pnpm deploy:percepta-test -- --timeout-minutes 30 --yes
61
56
  ```
62
57
 
63
- The `--` is the pnpm argument delimiter; it passes `--phase` and `--yes` through to the deploy helper script.
64
-
65
- Get the PR reviewed and merged. Ryvn GitOps will import the ServiceInstallation and the Staging channel should deploy the latest release.
58
+ Use `--skip-workflows` when the required Ryvn releases already exist. Use `--skip-push` only when the target ref is already pushed.
66
59
 
67
- ### Step 5: Import Ryvn secrets
68
-
69
- After GitOps imports the installation, import this generated file in the Ryvn UI for the installation secrets:
60
+ The legacy infra-PR path is still available:
70
61
 
71
62
  ```bash
72
- deploy/ryvn/percepta-test.secrets.env
63
+ pnpm deploy:percepta-test:pr -- --phase service --yes
64
+ pnpm deploy:percepta-test:pr -- --phase installation --yes
73
65
  ```
74
66
 
75
- Also set any app-specific secrets the implementation added, such as `OPENAI_API_KEY`.
76
-
77
- ### Step 6: Verify
67
+ ## Verify
78
68
 
79
69
  ```bash
80
70
  ryvn get installation __APP_NAME__ -e percepta-test
81
71
  ryvn logs __APP_NAME__ -e percepta-test
82
72
  curl -s https://__APP_NAME__.percepta-test.aitco.dev/api/healthz
83
73
  curl -s https://__APP_NAME__.percepta-test.aitco.dev/api/readyz
74
+ curl -I https://__APP_NAME__.percepta-test.aitco.dev/
84
75
  ```
85
76
 
86
- The app will be available at **https://__APP_NAME__.percepta-test.aitco.dev**.
87
-
88
77
  ## Troubleshooting
89
78
 
90
- - **Auth/sign-in routes fail after install** → import `deploy/ryvn/percepta-test.secrets.env` in the Ryvn UI, then let Ryvn update/restart the installation.
79
+ - **Image build fails fetching @percepta packages** → check the Percepta-Core org-level `NPM_TOKEN` secret. Do not add a repo-level token unless the org secret is unavailable.
80
+ - **Ryvn release already exists** → commit a new change or re-run with `--skip-workflows` if the current releases are already present.
81
+ - **Terraform plan needs approval** → the helper approves it when run with `--yes`; without `--yes`, approve the prompt.
82
+ - **Auth/sign-in routes fail after install** → verify the deploy helper patched `BETTER_AUTH_SECRET` and `ENCRYPTION_SECRET_KEY`.
91
83
  - **Pod crash-looping** → check `ryvn logs`; migration or database connectivity failures are the most common fresh-deploy causes.
92
- - **Database connection refused** → verify `DATABASE_USE_SSL=true` and that `percepta-internal-terraform` is deployed.
93
- - **Database schema missing** → verify the infra PR added `postgresql_schema "__APP_NAME_SNAKE__"` and Ryvn applied `percepta-internal-terraform`.
94
- - **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.
95
- - **Image build fails fetching @percepta packages** → check the `NPM_TOKEN` GitHub org secret. The workflow passes it as a build arg.
84
+ - **Database schema missing** → check `ryvn get installation __APP_NAME__-terraform -e percepta-test`.
85
+ - **Inngest can't reach the app** → `INNGEST_APP_URL` must use the k8s service name `__APP_NAME__-web-server`.
86
+ - **Platform preflight fails** → deploy or repair the missing shared installation first. This helper only wires apps into an existing environment.
87
+ - **No Langfuse traces** → verify the target environment has Langfuse deployed and that the `demos-commons` variable group has `LANGFUSE_PUBLIC_KEY` and sensitive `LANGFUSE_SECRET_KEY`.
88
+ - **LLM calls fail after deploy** → verify `demos-commons` has sensitive `ANTHROPIC_API_KEY` and the installation has `LLM_PROVIDER=anthropic`.
96
89
 
97
90
  For Ryvn CLI operations, use the `/use-ryvn` skill.
98
-
99
- ## Updating shared platform values
100
-
101
- The Inngest and 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:
102
-
103
- ```bash
104
- grep -rl "inngest.percepta-test.svc.cluster.local:8288" ryvn/environments/percepta-test/installations/
105
- ```
106
-
107
- A future improvement would be a shared ConfigMap or Ryvn output reference; for now the scaffolded literals are the convention.
@@ -102,13 +102,13 @@ await inngest.client.send({
102
102
 
103
103
  ## Running Inngest Locally
104
104
 
105
- ### 1. Install the Inngest Dev Server
105
+ ### 1. Start the Inngest Dev Server
106
106
 
107
107
  ```bash
108
- pnpm dlx inngest-cli@latest dev
108
+ pnpm inngest:dev
109
109
  ```
110
110
 
111
- This starts the Inngest Dev Server at `http://localhost:8288` with a dashboard UI.
111
+ This starts the Inngest Dev Server for `http://localhost:3000/api/inngest` with a dashboard UI.
112
112
 
113
113
  ### 2. Set environment variables
114
114
 
@@ -126,7 +126,7 @@ INNGEST_EVENT_KEY=local # any value works locally
126
126
  pnpm dev
127
127
  ```
128
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.
129
+ The Inngest Dev Server auto-discovers functions by calling the serve endpoint at `/api/inngest`. Open the dashboard URL printed by `pnpm inngest:dev` to see registered functions, trigger events manually, and inspect runs.
130
130
 
131
131
  ## Environment Variables
132
132
 
@@ -2,7 +2,7 @@
2
2
 
3
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
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.
5
+ **Our opinionated setup:** We integrate Langfuse via OpenTelemetry (OTEL), not the Langfuse SDK directly. OTEL auto-instruments the app (HTTP, DB, etc.), exports general traces to the environment's OTEL collector, and adds Langfuse as a span processor when `LANGFUSE_*` values are configured. This gives us unified tracing while still letting the app run when Langfuse is not available in an environment.
6
6
 
7
7
  ## When to Use Langfuse
8
8
 
@@ -18,24 +18,28 @@ Langfuse is an open-source LLM observability platform. It captures traces, spans
18
18
 
19
19
  ### OpenTelemetry + Langfuse Span Processor
20
20
 
21
- The template uses Next.js's instrumentation hook (called on server startup) to bootstrap OTEL with Langfuse:
21
+ The template uses Next.js's instrumentation hook (called on server startup) to bootstrap OTEL with both the environment collector and optional Langfuse:
22
22
 
23
23
  ```typescript
24
24
  import { LangfuseSpanProcessor } from "@langfuse/otel";
25
- import { NodeSDK } from "@opentelemetry/sdk-node";
25
+ import { NodeSDK, tracing } from "@opentelemetry/sdk-node";
26
26
  import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
27
27
 
28
28
  const sdk = new NodeSDK({
29
- spanProcessors: [new LangfuseSpanProcessor({ baseUrl, publicKey, secretKey })],
29
+ spanProcessors: [
30
+ new tracing.BatchSpanProcessor(otlpTraceExporter),
31
+ new LangfuseSpanProcessor({ baseUrl, publicKey, secretKey }),
32
+ ],
30
33
  instrumentations: [getNodeAutoInstrumentations()],
31
34
  });
32
35
  sdk.start();
33
36
  ```
34
37
 
35
38
  - `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.
39
+ - The OTLP span processor forwards general server traces to the environment collector.
40
+ - `LangfuseSpanProcessor` forwards only AI SDK spans to Langfuse when all `LANGFUSE_*` variables are set. General HTTP, DB, and server spans stay in the OTEL/LGTM pipeline and are not sent to Langfuse by default.
37
41
 
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.
42
+ **You do not need to manually instrument LLM calls.** Use `LLMService` from `src/services/llm/LLMService.ts`; it calls the Vercel AI SDK with telemetry enabled and provider/model metadata attached.
39
43
 
40
44
  ### LangfuseService (Direct API)
41
45
 
@@ -58,12 +62,9 @@ This is a singleton — `create()` returns the same instance every time. It grac
58
62
 
59
63
  ### Percepta's Internal Instance
60
64
 
61
- Percepta runs a shared Langfuse instance. To get keys:
65
+ Percepta runs a shared Langfuse instance. For `percepta-test` deploys, the generated Ryvn installation uses the shared demo project by inheriting `LANGFUSE_PUBLIC_KEY` and sensitive `LANGFUSE_SECRET_KEY` from the `demos-commons` variable group.
62
66
 
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
+ For non-demo environments, verify the target Ryvn environment has a Langfuse installation or shared Langfuse project, then store the project keys in an environment-scoped variable group or installation secrets.
67
68
 
68
69
  ### Self-Hosted / Langfuse Cloud
69
70
 
@@ -75,10 +76,10 @@ For external projects, you can:
75
76
 
76
77
  ### Option 1: Use Percepta's Langfuse (recommended)
77
78
 
78
- Set the keys in `.env.local` pointing to the shared instance:
79
+ Set the keys in `.env.local` if you want local traces to go to the shared instance:
79
80
 
80
81
  ```bash
81
- LANGFUSE_BASE_URL=https://langfuse.internal.percepta.ai # ask team for actual URL
82
+ LANGFUSE_BASE_URL=https://langfuse.percepta-test.aitco.dev
82
83
  LANGFUSE_PUBLIC_KEY=pk-lf-...
83
84
  LANGFUSE_SECRET_KEY=sk-lf-...
84
85
  ```
@@ -104,7 +105,7 @@ LANGFUSE_SECRET_KEY=sk-lf-...
104
105
 
105
106
  ### Option 3: Skip Langfuse entirely
106
107
 
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
+ Simply don't set the `LANGFUSE_*` env vars. This is the default local development path unless you are specifically debugging LLM telemetry. The instrumentation gracefully skips the Langfuse span processor, and `LangfuseService.isConfigured()` returns `false`. OTEL still instruments the app for general tracing when an OTLP collector endpoint is configured.
108
109
 
109
110
  ## Environment Variables
110
111
 
@@ -0,0 +1,59 @@
1
+ # LLM Calls
2
+
3
+ Use this guide when the app needs backend model calls, streaming responses, or structured generation.
4
+
5
+ ## Default Pattern
6
+
7
+ Use `LLMService` from `src/services/llm/LLMService.ts`. Do not instantiate provider clients directly in routes, tRPC routers, or Inngest functions.
8
+
9
+ ```typescript
10
+ import { LLMService } from "@/services/llm/LLMService";
11
+
12
+ const result = await LLMService.create().generateText({
13
+ telemetryFunctionId: "summarize-document",
14
+ system: "You summarize documents for internal operators.",
15
+ prompt: `Summarize this document:\n\n${documentText}`,
16
+ });
17
+
18
+ return result.text;
19
+ ```
20
+
21
+ For streaming:
22
+
23
+ ```typescript
24
+ const stream = LLMService.create().streamText({
25
+ telemetryFunctionId: "chat-response",
26
+ system: "You are a concise assistant.",
27
+ messages,
28
+ });
29
+ ```
30
+
31
+ ## Providers
32
+
33
+ `LlmProviderService` chooses a provider at call time:
34
+
35
+ 1. `LLM_PROVIDER`, when explicitly set.
36
+ 2. Anthropic when `ANTHROPIC_API_KEY` is available.
37
+ 3. OpenAI when `OPENAI_API_KEY` is available.
38
+
39
+ `LLM_MODEL` can override the default model. A per-call `modelId` or `provider` passed to `LLMService` overrides env defaults.
40
+
41
+ ## Deployment
42
+
43
+ For `percepta-test`, `deploy:percepta-test` attaches the `demos-commons` Ryvn variable group. That group provides the shared `ANTHROPIC_API_KEY` and Langfuse project keys, so generated demo apps do not need per-app LLM secrets.
44
+
45
+ ## Local Development
46
+
47
+ Local LLM calls are optional. If needed, set a provider key once in your shell profile or `~/.config/percepta/create.env`:
48
+
49
+ ```bash
50
+ ANTHROPIC_API_KEY=sk-ant-...
51
+ ```
52
+
53
+ `pnpm dev` loads `~/.config/percepta/create.env` automatically. Do not put shared demo keys in committed files. Do not require local Langfuse or a local LGTM stack unless the task is specifically about telemetry.
54
+
55
+ ## Observability
56
+
57
+ `LLMService` enables AI SDK telemetry by default and attaches provider/model metadata to each call. When `LANGFUSE_*` values are configured, the template's OpenTelemetry bootstrap sends LLM spans to Langfuse. In `percepta-test`, those values are inherited from the environment.
58
+
59
+ Use a stable `telemetryFunctionId` for every meaningful LLM operation, such as `extract-invoice-fields` or `draft-member-message`.
@@ -78,12 +78,16 @@ Cover:
78
78
  2. **API routes** — which tRPC routers and procedures
79
79
  3. **Pages** — which routes/pages in the app
80
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)
81
+ 5. **LLM integration** — use `LLMService` from `src/services/llm/LLMService.ts` when model calls are needed
82
82
 
83
83
  ### Decision: Langfuse
84
84
 
85
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
86
 
87
+ ### Decision: LLM Calls
88
+
89
+ Read [agent-skills/llm.md](llm.md) when the app calls a model. Use `LLMService` for backend model calls and set a stable `telemetryFunctionId` for each LLM operation. Do not create provider clients directly in app code.
90
+
87
91
  ### Decision: Inngest
88
92
 
89
93
  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.
@@ -125,6 +129,7 @@ If it fails, fix the errors before moving to the next chunk. Do not accumulate b
125
129
  - **Use robust loading states.** `@percepta/components` is optional; add it first if you want `AsyncContent`.
126
130
  - **Use `getEnvConfig()` for env vars.** Never use `process.env` directly.
127
131
  - **Use `getLogger()` for logging.** Never use `console.log`. Use safe/unsafe field separation.
132
+ - **Use `LLMService` for model calls.** It handles provider selection and Langfuse/OTEL telemetry metadata.
128
133
  - **Follow the singleton pattern** for new services. Follow the existing `DatabaseService` singleton pattern in the codebase.
129
134
  - **Use `protectedProcedure`** for authenticated tRPC routes.
130
135
 
@@ -141,6 +146,14 @@ pnpm docker:up
141
146
  pnpm db:setup-and-migrate
142
147
  ```
143
148
 
149
+ If the app uses Inngest functions, start the local Inngest dev server in a separate terminal:
150
+
151
+ ```bash
152
+ pnpm inngest:dev
153
+ ```
154
+
155
+ Do not start local LGTM, OTEL collector, Faro, or Langfuse services unless the task is specifically about telemetry. Those integrations are optional locally and are wired by the Ryvn environment for deploys.
156
+
144
157
  ### Step 2: Start the dev server
145
158
 
146
159
  ```bash
@@ -8,7 +8,7 @@ Ryvn is the deployment platform used by Percepta to manage cloud infrastructure.
8
8
  |-------------|---------|---------------|
9
9
  | `percepta-test` | Internal dev/test | `<app>.percepta-test.aitco.dev` |
10
10
 
11
- New apps are deployed to **percepta-test**. This environment has a shared PostgreSQL instance, Inngest server, Langfuse, and OTEL collector.
11
+ New apps are deployed to **percepta-test** using the existing-environment deploy motion. This environment must already have shared PostgreSQL, Inngest, OTEL collector, LGTM stack, and Langfuse installations before app deploys run. The service installation points at the shared Langfuse URL and attaches the `demos-commons` variable group for shared demo Langfuse project keys.
12
12
 
13
13
  ## Deploying This App
14
14
 
@@ -1,65 +1,66 @@
1
1
  # Deploy
2
2
 
3
- This directory holds infrastructure-as-code for deploying __APP_TITLE__ to Percepta environments.
3
+ This directory holds Ryvn infrastructure-as-code for deploying __APP_TITLE__ to Percepta environments.
4
4
 
5
5
  ```
6
6
  deploy/
7
7
  └── ryvn/
8
- ├── __APP_NAME__.service.yaml # Ryvn Service definition
8
+ ├── __APP_NAME__.service.yaml
9
+ ├── __APP_NAME__-terraform.service.yaml
9
10
  └── environments/
10
11
  └── percepta-test/
11
12
  └── installations/
12
- └── __APP_NAME__.env.percepta-test.serviceinstallation.yaml # percepta-test installation
13
+ ├── __APP_NAME__.env.percepta-test.serviceinstallation.yaml
14
+ └── __APP_NAME__-terraform.env.percepta-test.serviceinstallation.yaml
13
15
  ```
14
16
 
15
- These files are pre-filled with all base values needed to deploy to `https://__APP_NAME__.percepta-test.aitco.dev`. The generated deploy helper opens the required `Percepta-Core/infra` PRs in two phases.
17
+ These files deploy to `https://__APP_NAME__.percepta-test.aitco.dev`.
16
18
 
17
- ## Deploying
18
-
19
- Tell Claude "deploy this app to percepta-test" — it'll follow [`agent-skills/deploy.md`](../agent-skills/deploy.md), which runs the generated PR helper and then verifies Ryvn.
19
+ The default deploy helper performs the existing-environment deploy motion: it assumes the target Ryvn environment already has the shared platform services installed, then wires this app into them. For `percepta-test`, that means shared Postgres, Inngest, the OTEL collector, the LGTM stack, and Langfuse must already exist before app deploy starts. Fresh-environment platform bootstrap is a separate motion and should be handled by a Ryvn blueprint or environment-specific platform rollout.
20
20
 
21
- ## What's in these files
21
+ The helper talks directly to Ryvn: it preflights the existing platform services, creates/updates the services, runs the GitHub Actions release workflows, creates the schema installation, approves the schema Terraform plan, creates the web installation, patches generated secrets, waits for health, and verifies the health and app routes.
22
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 release workflow passes `NPM_TOKEN` as a build arg from GitHub org secrets.
23
+ ## Deploying
24
24
 
25
- **`__APP_NAME__.env.percepta-test.serviceinstallation.yaml`** configures the deployment on `percepta-test`. Three categories of values are baked in:
25
+ Tell Claude "deploy this app to percepta-test" or run:
26
26
 
27
- 1. **Per-app values** that vary by app name (URLs, k8s service names, database schema) — substituted at create-time.
28
- 2. **Shared platform values** (Inngest and OTel endpoints) — copied as literals from existing percepta-test installations. These are stable across percepta-test apps.
29
- 3. **Database wiring** — points at the shared `demos` database and a per-app schema created by the infra PR.
27
+ ```bash
28
+ pnpm deploy:percepta-test -- --yes
29
+ ```
30
30
 
31
- **`percepta-test.secrets.env`** generated locally and ignored by git. Import it in the Ryvn UI for `BETTER_AUTH_SECRET` and `ENCRYPTION_SECRET_KEY`; deployment secret values are intentionally not committed to GitOps IaC.
31
+ The helper expects a clean, committed git worktree because GitHub Actions builds from the pushed repo. It can create `Percepta-Core/__REPO_NAME__` if it does not exist yet, and it pushes the current branch to `main` before triggering releases.
32
32
 
33
- ## Deployment order
33
+ ## What's in these files
34
34
 
35
- Use two infra PRs. The Service must exist before the first release can be created, and the first release must exist before the ServiceInstallation can be imported.
35
+ **`__APP_NAME__.service.yaml`** Ryvn server service for the web app. It points at `Percepta-Core/__REPO_NAME__` and builds from `packages/__APP_NAME__/`.
36
36
 
37
- 1. Open and merge the service/schema PR:
37
+ **`__APP_NAME__-terraform.service.yaml`** Ryvn Terraform service that owns the per-app Postgres schema in the shared `demos` database.
38
38
 
39
- ```bash
40
- pnpm deploy:percepta-test -- --phase service --yes
41
- ```
39
+ **`__APP_NAME__.env.percepta-test.serviceinstallation.yaml`** — web app installation for `percepta-test`. It wires the shared platform services, ingress, health probes, database connection, Inngest, OTEL/LGTM telemetry, the Langfuse base URL, and the shared demo variable group.
42
40
 
43
- 2. Wait for GitOps to import the service. If the schema change creates a `percepta-internal-terraform` task in Ryvn, approve/apply it.
41
+ **`__APP_NAME__-terraform.env.percepta-test.serviceinstallation.yaml`** schema installation for `percepta-test`.
44
42
 
45
- 3. Push the app to `main` or run the release workflow so Ryvn has a first release for `__APP_NAME__`.
43
+ **`percepta-test.secrets.env`** generated locally and ignored by git. The deploy helper patches app-specific auth/encryption secrets into the Ryvn installation; shared Langfuse and LLM demo keys are inherited from a Ryvn variable group.
46
44
 
47
- 4. Open and merge the installation PR:
45
+ ## Platform Wiring
48
46
 
49
- ```bash
50
- pnpm deploy:percepta-test -- --phase installation --yes
51
- ```
47
+ `pnpm deploy:percepta-test` checks these existing `percepta-test` installations before it mutates GitHub or Ryvn app resources:
52
48
 
53
- 5. After GitOps imports the installation, import `deploy/ryvn/percepta-test.secrets.env` into the Ryvn UI for the `__APP_NAME__` installation secrets.
49
+ - `percepta-internal-terraform` for the shared Postgres instance.
50
+ - `inngest-test` for background jobs and function callbacks.
51
+ - `otel-collector` for trace, metric, and log collection.
52
+ - `lgtm-stack-helm` for Loki, Grafana, Tempo, and Mimir.
53
+ - `langfuse` for LLM tracing and eval observability.
54
+ - `demos-commons` variable group for shared demo configuration, including the Anthropic API key and Langfuse demo project keys.
54
55
 
55
- The `--` is the pnpm argument delimiter; it passes the flags after it through to `scripts/open-ryvn-deploy-pr.ts`.
56
+ The service installation sets `LANGFUSE_BASE_URL` to the shared `percepta-test` Langfuse URL, sets `LLM_PROVIDER=anthropic`, and attaches `demos-commons` for `ANTHROPIC_API_KEY`, `LANGFUSE_PUBLIC_KEY`, and sensitive `LANGFUSE_SECRET_KEY`. Individual demo apps do not need LLM or Langfuse keys in `percepta-test.secrets.env`.
56
57
 
57
- ## Repo layout note
58
+ ## Notes
58
59
 
59
- 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`.
60
+ The release workflows live at the repo root under `.github/workflows/`. The web workflow uses the org-level `NPM_TOKEN` secret for private `@percepta/*` packages; do not add a repo-level token unless the org secret is unavailable.
60
61
 
61
- 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.
62
+ For the old GitOps PR path, use `pnpm deploy:percepta-test:pr -- --phase service --yes` and then `pnpm deploy:percepta-test:pr -- --phase installation --yes`.
62
63
 
63
- ## Adding more environments
64
+ ## Adding More Environments
64
65
 
65
- 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.
66
+ Copy the two `percepta-test` installation manifests to `environments/<env>/installations/`, then change the `environment:`, host, and environment-specific config.
@@ -0,0 +1,10 @@
1
+ kind: Service
2
+ metadata:
3
+ name: __APP_NAME__-terraform
4
+ spec:
5
+ type: terraform
6
+ repo: Percepta-Core/__REPO_NAME__
7
+ autoApprove: false
8
+ build:
9
+ path: packages/__APP_NAME__/terraform/schema
10
+ tagPrefix: __APP_NAME__-terraform@
@@ -3,7 +3,7 @@ metadata:
3
3
  name: __APP_NAME__
4
4
  spec:
5
5
  type: server
6
- repo: Percepta-Core/__APP_NAME__
6
+ repo: Percepta-Core/__REPO_NAME__
7
7
  build:
8
- context: packages/__APP_NAME__
8
+ context: .
9
9
  dockerfile: packages/__APP_NAME__/Dockerfile
@@ -0,0 +1,11 @@
1
+ kind: ServiceInstallation
2
+ metadata:
3
+ name: __APP_NAME__-terraform
4
+ spec:
5
+ service: __APP_NAME__-terraform
6
+ environment: percepta-test
7
+ config: |
8
+ aws_region: {{ .ryvn.env.state.cluster_region }}
9
+ database_secret_name: {{ .ryvn.installations.percepta_internal_terraform.outputs.percepta_internal_secrets_manager_secret_name }}
10
+ database_name: demos
11
+ schema_name: __APP_NAME_SNAKE__