@cloudgrid-io/mcp 0.20.20 → 0.20.22

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 (99) hide show
  1. package/package.json +2 -1
  2. package/src/corpus/capability-map.md +1 -1
  3. package/src/corpus/cloudgrid-yaml.md +3 -3
  4. package/src/corpus/examples/ai-app/index.md +20 -21
  5. package/src/corpus/templates/_cloudgrid.yaml.reference +2 -2
  6. package/src/corpus/templates/admin-dashboard/cloudgrid.yaml +2 -2
  7. package/src/corpus/templates/ai-app/README.md +18 -13
  8. package/src/corpus/templates/ai-app/cloudgrid.yaml +2 -2
  9. package/src/corpus/templates/ai-app/index.md +23 -18
  10. package/src/corpus/templates/ai-app/services/web/app/api/chat/route.js +11 -8
  11. package/src/corpus/templates/ai-app/services/web/package.json +1 -1
  12. package/src/corpus/templates/ai-knowledge-base/AGENTS.md +19 -21
  13. package/src/corpus/templates/ai-knowledge-base/README.md +1 -1
  14. package/src/corpus/templates/ai-knowledge-base/cloudgrid.yaml +2 -2
  15. package/src/corpus/templates/ai-knowledge-base/index.md +2 -2
  16. package/src/corpus/templates/analytics-dashboard/cloudgrid.yaml +2 -2
  17. package/src/corpus/templates/api-dashboard/cloudgrid.yaml +2 -2
  18. package/src/corpus/templates/api-docs/cloudgrid.yaml +2 -2
  19. package/src/corpus/templates/api-service/cloudgrid.yaml +2 -2
  20. package/src/corpus/templates/app-with-data/cloudgrid.yaml +2 -2
  21. package/src/corpus/templates/appointment-booking/AGENTS.md +1 -1
  22. package/src/corpus/templates/appointment-booking/cloudgrid.yaml +2 -2
  23. package/src/corpus/templates/approval-workflow/AGENTS.md +2 -2
  24. package/src/corpus/templates/approval-workflow/cloudgrid.yaml +2 -2
  25. package/src/corpus/templates/billing-dashboard/AGENTS.md +2 -2
  26. package/src/corpus/templates/billing-dashboard/cloudgrid.yaml +2 -2
  27. package/src/corpus/templates/blog-cms/cloudgrid.yaml +2 -2
  28. package/src/corpus/templates/booking-system/AGENTS.md +2 -2
  29. package/src/corpus/templates/booking-system/cloudgrid.yaml +2 -2
  30. package/src/corpus/templates/booking-system/index.md +1 -1
  31. package/src/corpus/templates/calendar-scheduler/AGENTS.md +1 -1
  32. package/src/corpus/templates/calendar-scheduler/cloudgrid.yaml +2 -2
  33. package/src/corpus/templates/calendar-scheduler/index.md +1 -1
  34. package/src/corpus/templates/changelog/cloudgrid.yaml +2 -2
  35. package/src/corpus/templates/company-website/cloudgrid.yaml +2 -2
  36. package/src/corpus/templates/course-platform/AGENTS.md +2 -2
  37. package/src/corpus/templates/course-platform/cloudgrid.yaml +2 -2
  38. package/src/corpus/templates/crm/cloudgrid.yaml +2 -2
  39. package/src/corpus/templates/dashboard/cloudgrid.yaml +2 -2
  40. package/src/corpus/templates/deck/cloudgrid.yaml +2 -2
  41. package/src/corpus/templates/directory/cloudgrid.yaml +2 -2
  42. package/src/corpus/templates/docs-site/cloudgrid.yaml +2 -2
  43. package/src/corpus/templates/erp/AGENTS.md +2 -2
  44. package/src/corpus/templates/erp/cloudgrid.yaml +2 -2
  45. package/src/corpus/templates/erp/index.md +1 -1
  46. package/src/corpus/templates/event-board/cloudgrid.yaml +2 -2
  47. package/src/corpus/templates/expense-tracker/cloudgrid.yaml +2 -2
  48. package/src/corpus/templates/feature-request-board/cloudgrid.yaml +2 -2
  49. package/src/corpus/templates/financial-dashboard/cloudgrid.yaml +2 -2
  50. package/src/corpus/templates/forum/AGENTS.md +3 -3
  51. package/src/corpus/templates/forum/cloudgrid.yaml +2 -2
  52. package/src/corpus/templates/hr-portal/AGENTS.md +1 -1
  53. package/src/corpus/templates/hr-portal/cloudgrid.yaml +2 -2
  54. package/src/corpus/templates/internal-tools-portal/AGENTS.md +2 -2
  55. package/src/corpus/templates/internal-tools-portal/cloudgrid.yaml +2 -2
  56. package/src/corpus/templates/inventory/cloudgrid.yaml +2 -2
  57. package/src/corpus/templates/invoice/cloudgrid.yaml +2 -2
  58. package/src/corpus/templates/job-board/cloudgrid.yaml +2 -2
  59. package/src/corpus/templates/kanban/cloudgrid.yaml +2 -2
  60. package/src/corpus/templates/landing-page/cloudgrid.yaml +2 -2
  61. package/src/corpus/templates/lms/AGENTS.md +3 -3
  62. package/src/corpus/templates/lms/cloudgrid.yaml +2 -2
  63. package/src/corpus/templates/marketplace/AGENTS.md +1 -1
  64. package/src/corpus/templates/marketplace/cloudgrid.yaml +2 -2
  65. package/src/corpus/templates/marketplace/index.md +1 -1
  66. package/src/corpus/templates/membership-site/AGENTS.md +1 -1
  67. package/src/corpus/templates/membership-site/cloudgrid.yaml +2 -2
  68. package/src/corpus/templates/monitoring-dashboard/cloudgrid.yaml +2 -2
  69. package/src/corpus/templates/online-store/AGENTS.md +1 -1
  70. package/src/corpus/templates/online-store/cloudgrid.yaml +2 -2
  71. package/src/corpus/templates/portfolio/cloudgrid.yaml +2 -2
  72. package/src/corpus/templates/product-catalog/cloudgrid.yaml +2 -2
  73. package/src/corpus/templates/product-launch/cloudgrid.yaml +2 -2
  74. package/src/corpus/templates/project-management/cloudgrid.yaml +2 -2
  75. package/src/corpus/templates/property-listings/cloudgrid.yaml +2 -2
  76. package/src/corpus/templates/quiz-platform/cloudgrid.yaml +2 -2
  77. package/src/corpus/templates/report/cloudgrid.yaml +2 -2
  78. package/src/corpus/templates/restaurant-reservations/AGENTS.md +2 -2
  79. package/src/corpus/templates/restaurant-reservations/cloudgrid.yaml +2 -2
  80. package/src/corpus/templates/revenue-dashboard/cloudgrid.yaml +2 -2
  81. package/src/corpus/templates/saas-marketing/cloudgrid.yaml +2 -2
  82. package/src/corpus/templates/semantic-search/cloudgrid.yaml +1 -1
  83. package/src/corpus/templates/simple-semantic-search/cloudgrid.yaml +3 -3
  84. package/src/corpus/templates/status-page/cloudgrid.yaml +2 -2
  85. package/src/corpus/templates/subscription-management/AGENTS.md +1 -1
  86. package/src/corpus/templates/subscription-management/cloudgrid.yaml +2 -2
  87. package/src/corpus/templates/task-manager/cloudgrid.yaml +2 -2
  88. package/src/corpus/templates/ticket-system/cloudgrid.yaml +2 -2
  89. package/src/corpus/templates/time-tracking/cloudgrid.yaml +2 -2
  90. package/src/corpus/templates/travel-booking/AGENTS.md +2 -2
  91. package/src/corpus/templates/travel-booking/cloudgrid.yaml +2 -2
  92. package/src/corpus/templates/travel-booking/index.md +1 -1
  93. package/src/corpus/templates/waitlist/cloudgrid.yaml +2 -2
  94. package/src/corpus/templates/web-app/cloudgrid.yaml +2 -2
  95. package/src/corpus/workflows/ai-app.md +13 -9
  96. package/src/corpus/workflows/ai-knowledge-base.md +15 -12
  97. package/src/tools/constants.js +1 -1
  98. package/src/tools/register.js +22 -14
  99. package/src/tools.js +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudgrid-io/mcp",
3
- "version": "0.20.20",
3
+ "version": "0.20.22",
4
4
  "description": "Use CloudGrid via MCP to brainstorm, build, deploy, and improve apps, games, websites, dashboards, and tools and get a live URL - templates, recipes, managed databases and AI, instant single-page publish, and full-app deploys, for developers and non-technical users. Three editions: local stdio (full toolset), hosted web (light, CLI-free), and a public docs-search server, over MCP.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,6 +30,7 @@
30
30
  "test:plug-output-schema": "node test/plug-output-schema.test.mjs",
31
31
  "test:zip-deploy": "node test/zip-deploy.test.mjs",
32
32
  "test:create-gates": "node test/create-gates.test.mjs",
33
+ "test:create-project-args": "node test/create-project-args.test.mjs",
33
34
  "test:mcpb-boot": "node test/mcpb-boot.test.mjs",
34
35
  "test:plug-timeout": "node test/plug-timeout.test.mjs",
35
36
  "test:manifest-confirm": "node test/manifest-confirm.test.mjs",
@@ -159,7 +159,7 @@ engine hint. Cron is NOT a need — it is a **service type** (`type: cron` with
159
159
  | `vector: pgvector` | pgvector embeddings DB | `VECTOR_PGVECTOR_URL` (+legacy `PGVECTOR_URL`) | **Injects via `needs:`** (#1545 shipped, verified live 2026-07-16) |
160
160
  | `object_storage: true` | GCS bucket | `OBJECT_STORAGE_GCS_BUCKET`, `OBJECT_STORAGE_GCS_REGION` | **GATED (#1678)** - rejected at plug-time; use `disk` or a BYO bucket via secret |
161
161
  | `disk: true` | Persistent filesystem at `/data` | `DISK_PATH` | Injects via `needs:` |
162
- | `ai: true` | AI Gateway access | `AI_GATEWAY_URL` | Injects via `needs:` |
162
+ | `ai: true` | AI Gateway access | `RUNTIME_GATEWAY_URL` | Injects via `needs:` |
163
163
  | `type: cron` (service) | Scheduled job (`schedule`, `timezone`) | — | Service type, not a need |
164
164
 
165
165
  ### Injection status
@@ -156,7 +156,7 @@ needs:
156
156
  vector: pgvector # pgvector. Injects VECTOR_PGVECTOR_URL. (#1545 shipped, verified live 2026-07-16.)
157
157
  object_storage: true # GATED (platform #1678) - rejected at plug-time; use disk: or a BYO bucket via secret.
158
158
  disk: true # Persistent filesystem at /data. Injects DISK_PATH.
159
- ai: true # AI Gateway access. Injects AI_GATEWAY_URL.
159
+ ai: true # AI Gateway access. Injects RUNTIME_GATEWAY_URL.
160
160
 
161
161
  # ─── Inter-entity communication ────────────────────────────────────
162
162
  calls: # Entities this app calls (outbound grants).
@@ -366,7 +366,7 @@ that path. `{ path, expected }` → HTTP with expected status
366
366
 
367
367
  String key-values injected at runtime (not build). **Reserved names (rejected):**
368
368
  `PORT`, `APP_NAME`, `SERVICE_NAME`, `NODE_ENV`, `MONGODB_URL`, `REDIS_URL`,
369
- `AI_GATEWAY_URL`, `N8N_WEBHOOK_URL`, and any key starting with `CLOUDGRID_`. Use
369
+ `RUNTIME_GATEWAY_URL`, `N8N_WEBHOOK_URL`, and any key starting with `CLOUDGRID_`. Use
370
370
  `grid secrets set` for sensitive values.
371
371
 
372
372
  ### Per-service `needs` override
@@ -407,7 +407,7 @@ service type.**
407
407
  | `vector` | Vector embeddings DB | `VECTOR_PGVECTOR_URL` (+legacy `PGVECTOR_URL`) | pgvector (Postgres) |
408
408
  | `object_storage` | Object/blob bucket - **GATED (#1678)**: rejected at plug-time, use `disk` or a BYO bucket via secret | `OBJECT_STORAGE_GCS_BUCKET`, `OBJECT_STORAGE_GCS_REGION` | GCS |
409
409
  | `disk` | Persistent filesystem mount | `DISK_PATH` | PVC |
410
- | `ai` | AI Gateway access | `AI_GATEWAY_URL` | CloudGrid AI Gateway |
410
+ | `ai` | AI Gateway access | `RUNTIME_GATEWAY_URL` | CloudGrid AI Gateway |
411
411
 
412
412
  `cache`, `kv`, `queue`, `pubsub` all share one physical Redis but each gets a
413
413
  distinct env var. `cache` runs `allkeys-lru` eviction (derived data); the other
@@ -1,7 +1,7 @@
1
1
  # Example: ai-app — "Trip Planner Bot"
2
2
 
3
3
  A filled reference imitating the `ai-app` template: a themed AI assistant — a
4
- travel trip planner. Same stack (Next.js App Router + `@cloudgrid-io/ai` for the
4
+ travel trip planner. Same stack (Next.js App Router + `@cloudgrid-io/runtime` for the
5
5
  grid AI gateway + the `mongodb` driver for chat history), slightly richer than
6
6
  the bare chatbot: it gives the assistant a **persona** via a `system` message and
7
7
  sends the **prior conversation** as context, so replies stay on-theme and
@@ -11,8 +11,9 @@ Data persists in the grid-shared Mongo, so the conversation survives refresh.
11
11
 
12
12
  **Same four proven rules as the template:** (1) app code lives under
13
13
  **`services/web/`** (the service name, NOT the repo root — `path:` is the URL
14
- mount); (2) the AI call is **zero-config — `createClient()` with no key** (it
15
- uses the in-grid identity); (3) the DB connection string is read **lazily inside
14
+ mount); (2) the AI call is **zero-config — `runtime.ai.chat({ model, messages })`
15
+ with no key** (the SDK reads `RUNTIME_GATEWAY_URL` and the in-grid identity
16
+ itself); (3) the DB connection string is read **lazily inside
16
17
  the `getDb` getter**, never at module top level (a top-level read fails `next
17
18
  build`); (4) the datastore is declared with the canonical **`needs: { ai: true,
18
19
  database: true }`**. Don't use the deprecated `requires:` alias.
@@ -48,7 +49,7 @@ needs:
48
49
  "next": "^15.1.0",
49
50
  "react": "^19.0.0",
50
51
  "react-dom": "^19.0.0",
51
- "@cloudgrid-io/ai": "^0.2.0",
52
+ "@cloudgrid-io/runtime": "^1.0.3",
52
53
  "mongodb": "^6.12.0"
53
54
  }
54
55
  }
@@ -87,21 +88,18 @@ export async function getDb() {
87
88
  ```js
88
89
  // Trip-planner chat route: give the assistant a persona, send the prior
89
90
  // conversation as context, ask the grid AI gateway, persist the exchange,
90
- // return the reply. Zero-config AI — createClient() with no key uses the in-grid
91
- // identity.
91
+ // return the reply. Zero-config AI — @cloudgrid-io/runtime reads RUNTIME_GATEWAY_URL
92
+ // (injected by the grid) itself and uses the in-grid identity — no key.
92
93
  import { NextResponse } from "next/server";
93
- import { createClient } from "@cloudgrid-io/ai";
94
+ import { runtime } from "@cloudgrid-io/runtime";
94
95
  import { getDb } from "../../../lib/db.js";
95
96
 
96
97
  export const dynamic = "force-dynamic";
97
98
 
98
- const SYSTEM = {
99
- role: "system",
100
- content:
101
- "You are Trip Planner Bot, a concise, friendly travel assistant. Suggest " +
102
- "itineraries, budgets, and practical tips. Ask a clarifying question when the " +
103
- "destination or dates are unclear.",
104
- };
99
+ const SYSTEM =
100
+ "You are Trip Planner Bot, a concise, friendly travel assistant. Suggest " +
101
+ "itineraries, budgets, and practical tips. Ask a clarifying question when the " +
102
+ "destination or dates are unclear.";
105
103
 
106
104
  async function messages() {
107
105
  return (await getDb()).collection("messages");
@@ -119,12 +117,13 @@ export async function POST(request) {
119
117
  prior.reverse();
120
118
  const context = prior.map((m) => ({ role: m.role, content: m.content }));
121
119
 
122
- // Ask the grid AI gateway: persona + prior turns + the new user message.
123
- const client = createClient();
124
- const r = await client.chat({
125
- messages: [SYSTEM, ...context, { role: "user", content: text }],
120
+ // Ask the grid AI gateway: persona (system) + prior turns + the new user
121
+ // message. Chat expects a model and returns { text }.
122
+ const { text: reply } = await runtime.ai.chat({
123
+ model: "claude-haiku",
124
+ system: SYSTEM,
125
+ messages: [...context, { role: "user", content: text }],
126
126
  });
127
- const reply = r.text ?? r.content ?? "";
128
127
 
129
128
  // Persist the exchange.
130
129
  const now = new Date();
@@ -229,8 +228,8 @@ export default function Chat({ initialHistory }) {
229
228
  - Deploy the same way as the template: `grid dev` locally, `grid plug` to deploy
230
229
  (async — poll status to a live URL). Re-plug the same entity to keep one URL.
231
230
  - The needs are Mongo + the AI gateway (`needs: { ai: true, database: true }`).
232
- The AI call is zero-config — `createClient()` with no key uses the in-grid
233
- identity.
231
+ The AI call is zero-config — `runtime.ai.chat` with no key; the SDK reads
232
+ `RUNTIME_GATEWAY_URL` and the in-grid identity itself.
234
233
  - All app code lives under `services/web/`; the connection is read lazily inside
235
234
  `getDb`, never at module top level.
236
235
  - To answer over your own documents (RAG) you'd add `needs: { vector: pgvector }`
@@ -96,7 +96,7 @@ name: my-app
96
96
  # # disk — persistent FS mount (PVC). Injects DISK_PATH. Pins to 1 replica.
97
97
  # # true | { size: 1Gi, mount: /var/data } (forbidden: /proc /sys /dev)
98
98
  # disk: true
99
- # # ai — AI Gateway via @cloudgrid-io/ai. Injects AI_GATEWAY_URL. true | false.
99
+ # # ai — AI Gateway via @cloudgrid-io/runtime. Injects RUNTIME_GATEWAY_URL. true | false.
100
100
  # ai: true
101
101
 
102
102
 
@@ -192,7 +192,7 @@ services:
192
192
  # path: /api/health
193
193
  # expected: "200"
194
194
  # Static runtime env vars (strings). Reserved: PORT, APP_NAME, SERVICE_NAME,
195
- # NODE_ENV, MONGODB_URL, REDIS_URL, AI_GATEWAY_URL, N8N_WEBHOOK_URL, CLOUDGRID_*.
195
+ # NODE_ENV, MONGODB_URL, REDIS_URL, RUNTIME_GATEWAY_URL, N8N_WEBHOOK_URL, CLOUDGRID_*.
196
196
  # env:
197
197
  # LOG_LEVEL: info
198
198
  # Sibling services/private resources that must be Ready first (no cron, no cycles).
@@ -96,7 +96,7 @@ name: my-admin
96
96
  # # disk — persistent FS mount (PVC). Injects DISK_PATH. Pins to 1 replica.
97
97
  # # true | { size: 1Gi, mount: /var/data } (forbidden: /proc /sys /dev)
98
98
  # disk: true
99
- # # ai — AI Gateway via @cloudgrid-io/ai. Injects AI_GATEWAY_URL. true | false.
99
+ # # ai — AI Gateway via @cloudgrid-io/runtime. Injects RUNTIME_GATEWAY_URL. true | false.
100
100
  # ai: true
101
101
 
102
102
 
@@ -192,7 +192,7 @@ services:
192
192
  # path: /api/health
193
193
  # expected: "200"
194
194
  # Static runtime env vars (strings). Reserved: PORT, APP_NAME, SERVICE_NAME,
195
- # NODE_ENV, MONGODB_URL, REDIS_URL, AI_GATEWAY_URL, N8N_WEBHOOK_URL, CLOUDGRID_*.
195
+ # NODE_ENV, MONGODB_URL, REDIS_URL, RUNTIME_GATEWAY_URL, N8N_WEBHOOK_URL, CLOUDGRID_*.
196
196
  # env:
197
197
  # LOG_LEVEL: info
198
198
  # Sibling services/private resources that must be Ready first (no cron, no cycles).
@@ -1,7 +1,7 @@
1
1
  # ai-app template — AI chatbot on Next.js + the grid AI gateway + Mongo
2
2
 
3
3
  A minimal but real, deployable AI chatbot. A Next.js App Router app that calls
4
- the grid AI gateway (via `@cloudgrid-io/ai`) for replies and persists the
4
+ the grid AI gateway (via `@cloudgrid-io/runtime`) for replies and persists the
5
5
  conversation to the grid-shared MongoDB, so history survives refresh and is
6
6
  shared across sessions.
7
7
 
@@ -10,8 +10,9 @@ shared across sessions.
10
10
  You do **not** set an API key or provision a database. In `cloudgrid.yaml` you
11
11
  declare `needs: { ai: true, database: true }`, and the grid:
12
12
 
13
- - wires the **AI gateway** with the app's in-grid identity — `@cloudgrid-io/ai`
14
- `createClient()` auto-detects it, so there is **no API key to set**; and
13
+ - wires the **AI gateway** with the app's in-grid identity — `@cloudgrid-io/runtime`
14
+ reads the gateway URL from the injected `RUNTIME_GATEWAY_URL` itself, so there is
15
+ **no API key to set**; and
15
16
  - provisions shared Mongo and injects the connection string as the
16
17
  **`DATABASE_MONGODB_URL`** environment variable (plus the legacy `MONGODB_URL`
17
18
  alias) — at dev-time (`grid dev`) and at runtime (after `grid plug`).
@@ -19,15 +20,18 @@ declare `needs: { ai: true, database: true }`, and the grid:
19
20
  ### The AI call
20
21
 
21
22
  ```js
22
- import { createClient } from "@cloudgrid-io/ai";
23
- const client = createClient(); // zero-arg, no key
24
- const r = await client.chat({ messages: [{ role: "user", content: text }] });
25
- const reply = r.text ?? r.content; // reply text
23
+ import { runtime } from "@cloudgrid-io/runtime";
24
+ // Chat expects a model and returns { text }. No key, no baseURL.
25
+ const { text: reply } = await runtime.ai.chat({
26
+ model: "claude-haiku",
27
+ messages: [{ role: "user", content: text }],
28
+ });
26
29
  ```
27
30
 
28
- `createClient()` takes no key — it uses the in-grid identity, so it only works
29
- inside a deployed grid app (or under `grid dev`). Do **not** pass or set an API
30
- key.
31
+ `runtime.ai.chat` takes no key — the SDK reads `RUNTIME_GATEWAY_URL` (injected by
32
+ the grid) and uses the in-grid identity, so it only works inside a deployed grid
33
+ app (or under `grid dev`). Do **not** pass or set an API key, and never reference
34
+ the env var yourself.
31
35
 
32
36
  ### The database
33
37
 
@@ -71,18 +75,19 @@ update the same URL.
71
75
 
72
76
  ```
73
77
  cloudgrid.yaml # name + services.web (nextjs) + needs: { ai: true, database: true }
74
- services/web/package.json # next, react, react-dom, @cloudgrid-io/ai, mongodb
78
+ services/web/package.json # next, react, react-dom, @cloudgrid-io/runtime, mongodb
75
79
  services/web/lib/db.js # lazy Mongo client from DATABASE_MONGODB_URL (legacy MONGODB_URL fallback)
76
80
  services/web/app/layout.js # root layout + inline CSS
77
81
  services/web/app/page.js # server component: reads chat history from Mongo
78
82
  services/web/app/chat.js # client component: sends messages, renders the log
79
- services/web/app/api/chat/route.js # POST: createClient().chat(...) -> persist -> reply; GET: history
83
+ services/web/app/api/chat/route.js # POST: runtime.ai.chat(...) -> persist -> reply; GET: history
80
84
  ```
81
85
 
82
86
  ## Adapt it
83
87
 
84
88
  - Give the assistant a persona — pass a `{ role: "system", content: "…" }`
85
- message ahead of the user message in the `chat({ messages })` call.
89
+ message ahead of the user message in the `runtime.ai.chat({ model, messages })`
90
+ call (or pass `system:` directly alongside `messages`).
86
91
  - Send prior turns as context by including the stored history in `messages`.
87
92
  - Change the `messages` collection / fields; add users, sessions, titles.
88
93
  - Restyle the chat UI in `layout.js` / `chat.js`.
@@ -96,7 +96,7 @@ name: my-chatbot
96
96
  # # disk — persistent FS mount (PVC). Injects DISK_PATH. Pins to 1 replica.
97
97
  # # true | { size: 1Gi, mount: /var/data } (forbidden: /proc /sys /dev)
98
98
  # disk: true
99
- # # ai — AI Gateway via @cloudgrid-io/ai. Injects AI_GATEWAY_URL. true | false.
99
+ # # ai — AI Gateway via @cloudgrid-io/runtime. Injects RUNTIME_GATEWAY_URL. true | false.
100
100
  # ai: true
101
101
 
102
102
 
@@ -192,7 +192,7 @@ services:
192
192
  # path: /api/health
193
193
  # expected: "200"
194
194
  # Static runtime env vars (strings). Reserved: PORT, APP_NAME, SERVICE_NAME,
195
- # NODE_ENV, MONGODB_URL, REDIS_URL, AI_GATEWAY_URL, N8N_WEBHOOK_URL, CLOUDGRID_*.
195
+ # NODE_ENV, MONGODB_URL, REDIS_URL, RUNTIME_GATEWAY_URL, N8N_WEBHOOK_URL, CLOUDGRID_*.
196
196
  # env:
197
197
  # LOG_LEVEL: info
198
198
  # Sibling services/private resources that must be Ready first (no cron, no cycles).
@@ -1,7 +1,7 @@
1
1
  # Template: ai-app (Next.js AI chatbot + grid AI gateway + Mongo)
2
2
 
3
3
  A minimal but real, deployable AI chatbot. A Next.js App Router app that calls
4
- the grid AI gateway (via `@cloudgrid-io/ai`) for replies and persists the
4
+ the grid AI gateway (via `@cloudgrid-io/runtime`) for replies and persists the
5
5
  conversation to the grid-shared MongoDB. History survives refresh and is shared
6
6
  across sessions.
7
7
 
@@ -11,11 +11,12 @@ across sessions.
11
11
  `path:` in `cloudgrid.yaml` is the URL mount, NOT the filesystem path. The
12
12
  service named `web` → the CLI looks for `services/web/`. Files at the root
13
13
  fail with `Error: Service directory not found: …/services/web`.
14
- 2. **The AI call is zero-config — no API key.** `import { createClient } from
15
- "@cloudgrid-io/ai"; const client = createClient();` auto-detects the in-grid
16
- identity, so it only works inside a deployed grid app (or under `grid dev`).
17
- Do NOT pass or set an API key. Read the reply from `r.text` (fall back to
18
- `r.content`).
14
+ 2. **The AI call is zero-config — no API key.** `import { runtime } from
15
+ "@cloudgrid-io/runtime"` then `runtime.ai.chat({ model, messages })`. The SDK
16
+ reads the gateway URL from `RUNTIME_GATEWAY_URL` (injected by the grid) itself,
17
+ so it only works inside a deployed grid app (or under `grid dev`). Do NOT pass
18
+ or set an API key, and never reference the env var yourself. Chat expects a
19
+ `model` and returns `{ text }`.
19
20
  3. **Read the DB connection string LAZILY (inside the getter), never at module
20
21
  top level.** The grid injects it as `DATABASE_MONGODB_URL` (plus the legacy
21
22
  `MONGODB_URL` alias) at dev + runtime; the app reads
@@ -36,12 +37,12 @@ Write these files into the scaffolded app folder — the app code goes under
36
37
 
37
38
  ```
38
39
  cloudgrid.yaml # name + services.web (nextjs) + needs: { ai: true, database: true }
39
- services/web/package.json # next, react, react-dom, @cloudgrid-io/ai, mongodb
40
+ services/web/package.json # next, react, react-dom, @cloudgrid-io/runtime, mongodb
40
41
  services/web/lib/db.js # lazy Mongo client from DATABASE_MONGODB_URL (legacy MONGODB_URL fallback)
41
42
  services/web/app/layout.js # root layout + inline CSS
42
43
  services/web/app/page.js # server component: reads chat history from Mongo
43
44
  services/web/app/chat.js # client component: sends messages, renders the log
44
- services/web/app/api/chat/route.js # POST: createClient().chat(...) -> persist -> reply; GET: history
45
+ services/web/app/api/chat/route.js # POST: runtime.ai.chat(...) -> persist -> reply; GET: history
45
46
  ```
46
47
 
47
48
  ## cloudgrid.yaml
@@ -78,7 +79,7 @@ needs:
78
79
  "next": "^15.1.0",
79
80
  "react": "^19.0.0",
80
81
  "react-dom": "^19.0.0",
81
- "@cloudgrid-io/ai": "^0.2.0",
82
+ "@cloudgrid-io/runtime": "^1.0.3",
82
83
  "mongodb": "^6.12.0"
83
84
  }
84
85
  }
@@ -118,11 +119,12 @@ export async function getDb() {
118
119
  // Chat API route: take the user's message, ask the grid AI gateway for a reply,
119
120
  // persist the exchange to Mongo, return the reply.
120
121
  //
121
- // The AI call uses @cloudgrid-io/ai with ZERO config — no API key. createClient()
122
- // auto-detects the in-grid identity, so it only works inside a deployed grid app
123
- // (or under `grid dev`). Do NOT pass a key.
122
+ // The AI call uses @cloudgrid-io/runtime with ZERO config — no API key. The SDK
123
+ // reads the gateway URL from RUNTIME_GATEWAY_URL (injected by the grid) itself,
124
+ // so it only works inside a deployed grid app (or under `grid dev`). Do NOT set a
125
+ // key or reference the env var yourself.
124
126
  import { NextResponse } from "next/server";
125
- import { createClient } from "@cloudgrid-io/ai";
127
+ import { runtime } from "@cloudgrid-io/runtime";
126
128
  import { getDb } from "../../../lib/db.js";
127
129
 
128
130
  export const dynamic = "force-dynamic";
@@ -139,10 +141,12 @@ export async function POST(request) {
139
141
  return NextResponse.json({ error: "message is required" }, { status: 400 });
140
142
  }
141
143
 
142
- // 1. Ask the grid AI gateway (zero-config in-grid identity — no key).
143
- const client = createClient();
144
- const r = await client.chat({ messages: [{ role: "user", content: text }] });
145
- const reply = r.text ?? r.content ?? "";
144
+ // 1. Ask the grid AI gateway (zero-config in-grid identity — no key). Chat
145
+ // expects a model; it returns { text }.
146
+ const { text: reply } = await runtime.ai.chat({
147
+ model: "claude-haiku",
148
+ messages: [{ role: "user", content: text }],
149
+ });
146
150
 
147
151
  // 2. Persist the exchange so the conversation survives refresh.
148
152
  const col = await messages();
@@ -248,7 +252,8 @@ export default function Chat({ initialHistory }) {
248
252
  ## Adapt it
249
253
 
250
254
  - Give the assistant a persona — pass a `{ role: "system", content: "…" }`
251
- message ahead of the user message in `chat({ messages })`.
255
+ message ahead of the user message in `runtime.ai.chat({ model, messages })`
256
+ (or pass `system:` directly alongside `messages`).
252
257
  - Send prior turns as context by including the stored history in `messages`.
253
258
  - Change the `messages` collection / fields; add users, sessions, titles.
254
259
  - Run `grid dev` to test locally, `grid plug` to deploy (async — poll to live).
@@ -1,11 +1,12 @@
1
1
  // Chat API route: takes the user's message, asks the grid AI gateway for a
2
2
  // reply, persists the exchange to Mongo, and returns the reply.
3
3
  //
4
- // The AI call uses @cloudgrid-io/ai with ZERO config — no API key. createClient()
5
- // auto-detects the in-grid identity, so it only works inside a deployed grid app
6
- // (or under `grid dev`). Do NOT pass a key.
4
+ // The AI call uses @cloudgrid-io/runtime with ZERO config — no API key. The SDK
5
+ // reads the gateway URL from RUNTIME_GATEWAY_URL (injected by the grid) all on
6
+ // its own, so it only works inside a deployed grid app (or under `grid dev`). Do
7
+ // NOT set a key or reference the env var yourself.
7
8
  import { NextResponse } from "next/server";
8
- import { createClient } from "@cloudgrid-io/ai";
9
+ import { runtime } from "@cloudgrid-io/runtime";
9
10
  import { getDb } from "../../../lib/db.js";
10
11
 
11
12
  // Never cache — always generate/persist live.
@@ -24,10 +25,12 @@ export async function POST(request) {
24
25
  return NextResponse.json({ error: "message is required" }, { status: 400 });
25
26
  }
26
27
 
27
- // 1. Ask the grid AI gateway (zero-config in-grid identity — no key).
28
- const client = createClient();
29
- const r = await client.chat({ messages: [{ role: "user", content: text }] });
30
- const reply = r.text ?? r.content ?? "";
28
+ // 1. Ask the grid AI gateway (zero-config in-grid identity — no key). Chat
29
+ // expects a model; it returns { text }.
30
+ const { text: reply } = await runtime.ai.chat({
31
+ model: "claude-haiku",
32
+ messages: [{ role: "user", content: text }],
33
+ });
31
34
 
32
35
  // 2. Persist the exchange so the conversation survives refresh.
33
36
  const col = await messages();
@@ -11,7 +11,7 @@
11
11
  "next": "^15.1.0",
12
12
  "react": "^19.0.0",
13
13
  "react-dom": "^19.0.0",
14
- "@cloudgrid-io/ai": "^0.2.0",
14
+ "@cloudgrid-io/runtime": "^1.0.3",
15
15
  "mongodb": "^6.12.0"
16
16
  }
17
17
  }
@@ -10,7 +10,7 @@ as `app-with-data` / `crm`) with a **RAG** loop layered on: ingest documents →
10
10
  chunk them → embed each chunk → store the vectors → at query time embed the
11
11
  question, retrieve the nearest chunks, and ask the model to answer grounded in
12
12
  them (with citations). Both the embedding and the answer step run through
13
- CloudGrid's **AI Gateway** (`@cloudgrid-io/ai`, injected via `needs: { ai: true }`).
13
+ CloudGrid's **AI Gateway** (`@cloudgrid-io/runtime`, injected via `needs: { ai: true }`).
14
14
 
15
15
  > **Vector-store status (read this first):** the ideal store for embeddings is
16
16
  > `needs: vector` (pgvector on Postgres, injecting `VECTOR_PGVECTOR_URL`), and it
@@ -32,10 +32,10 @@ URL mount, NOT the filesystem path. Files at the repo root fail with
32
32
  ```
33
33
  cloudgrid.yaml # name + services.web(nextjs,/) + needs:{ai:true, database:true}
34
34
  services/web/
35
- package.json # next, react, react-dom, mongodb, @cloudgrid-io/ai
35
+ package.json # next, react, react-dom, mongodb, @cloudgrid-io/runtime
36
36
  lib/
37
37
  db.js # lazy Mongo client from DATABASE_MONGODB_URL (legacy MONGODB_URL fallback)
38
- ai.js # lazy @cloudgrid-io/ai client from AI_GATEWAY_URL — embed() + chat()
38
+ ai.js # @cloudgrid-io/runtime AI (auto-reads RUNTIME_GATEWAY_URL) — embed() + chat()
39
39
  chunk.js # pure fn: splitIntoChunks(text) -> string[] (e.g. ~800-token windows, overlap)
40
40
  retrieve.js # embed query -> cosine-rank chunks in Mongo -> top-k with scores
41
41
  app/
@@ -86,13 +86,13 @@ injected as env vars at `grid dev` (local) and at runtime (after `grid plug`):
86
86
  | What | Declared in cloudgrid.yaml | Injected env var(s) | Read in code |
87
87
  |------|----------------------------|---------------------|--------------|
88
88
  | MongoDB | `needs: { database: true }` | `DATABASE_MONGODB_URL` (+ legacy `MONGODB_URL`) | `lib/db.js` |
89
- | AI Gateway | `needs: { ai: true }` | `AI_GATEWAY_URL` | `lib/ai.js` (via `@cloudgrid-io/ai`) |
89
+ | AI Gateway | `needs: { ai: true }` | `RUNTIME_GATEWAY_URL` | `lib/ai.js` (via `@cloudgrid-io/runtime`) |
90
90
  | Vector store (available — #1545 shipped) | `needs: { vector: pgvector }` | `VECTOR_PGVECTOR_URL` (+ legacy `PGVECTOR_URL`) | `lib/retrieve.js` — not used by this blueprint (Section 7 for the swap) |
91
91
  | Auth key (optional) | `vault: { AUTH_PROVIDER_KEY: auth-provider-key }` | `AUTH_PROVIDER_KEY` | `lib/auth.js` |
92
92
  | Stripe key (optional) | `vault: { STRIPE_KEY: stripe-live-key }` | `STRIPE_KEY` | `lib/stripe.js` |
93
93
 
94
94
  The **AI Gateway needs no API key** — `needs: { ai: true }` injects
95
- `AI_GATEWAY_URL` and `@cloudgrid-io/ai` routes through it. The `vault:` block only
95
+ `RUNTIME_GATEWAY_URL` and `@cloudgrid-io/runtime` routes through it. The `vault:` block only
96
96
  **maps** an org vault item key → an env var name; store the real value once with
97
97
  `grid secrets set <vault-item-key> <value>`. Never commit a secret; never hardcode
98
98
  a connection string.
@@ -114,23 +114,20 @@ export async function getDb() {
114
114
  ```
115
115
 
116
116
  ```js
117
- // lib/ai.js — lazy AI Gateway client (no API key; the gateway URL is injected)
118
- import { createClient } from "@cloudgrid-io/ai";
119
- function ai() {
120
- const url = process.env.AI_GATEWAY_URL;
121
- if (!url) throw new Error("AI_GATEWAY_URL not set — declare needs:{ai:true} and run via `grid dev`/`grid plug`.");
122
- globalThis.__ai ??= createClient({ baseURL: url });
123
- return globalThis.__ai;
124
- }
125
- // embed one or many strings -> vectors (used for both indexing and queries)
117
+ // lib/ai.js — CloudGrid runtime AI. No API key and no baseURL: the SDK reads
118
+ // RUNTIME_GATEWAY_URL (injected by needs:{ai:true}) and the in-grid identity
119
+ // itself. Never reference the env var or pass a key.
120
+ import { runtime } from "@cloudgrid-io/runtime";
121
+ // embed one or many strings -> an aligned array of vectors (indexing + queries)
126
122
  export async function embed(texts) {
127
- const res = await ai().embeddings.create({ input: Array.isArray(texts) ? texts : [texts] });
128
- return res.data.map((d) => d.embedding);
123
+ const input = Array.isArray(texts) ? texts : [texts];
124
+ const vec = await runtime.ai.embeddings({ text: input }); // batch -> vec.values aligned to input order
125
+ return vec.values; // number[][]; vec also carries .model, .dim, .tokens
129
126
  }
130
- // grounded chat answer
131
- export async function chat(messages) {
132
- const res = await ai().chat.completions.create({ messages });
133
- return res.choices[0].message.content;
127
+ // grounded chat answer -> string. Chat expects a model and returns { text }.
128
+ export async function chat({ system, messages }) {
129
+ const { text } = await runtime.ai.chat({ model: "claude-haiku", system, messages });
130
+ return text;
134
131
  }
135
132
  ```
136
133
 
@@ -176,7 +173,8 @@ export async function retrieve(question, k = 5) {
176
173
  2. Build a system prompt: *"Answer ONLY from the provided context; if it isn't
177
174
  there, say you don't know. Cite the chunks you used."* Put the retrieved chunk
178
175
  texts (with ids) in the context.
179
- 3. `chat([...])` via `lib/ai.js`; return the answer plus the source chunks so the
176
+ 3. `chat({ system, messages })` via `lib/ai.js` (the grounding instruction is the
177
+ `system`); return the answer plus the source chunks so the
180
178
  UI can render **citations**. Grounding + citations are the point — never let the
181
179
  model answer from parametric memory alone.
182
180
 
@@ -4,7 +4,7 @@ A blueprint for a retrieval-augmented knowledge-base chatbot on CloudGrid: a
4
4
  persistent Next.js + Mongo app that ingests documents, chunks and embeds them,
5
5
  and answers questions grounded in the retrieved chunks (with citations) — the
6
6
  `app-with-data` runtime shape with a RAG loop layered on. Embeddings and answers
7
- run through CloudGrid's AI Gateway (`@cloudgrid-io/ai`, injected via
7
+ run through CloudGrid's AI Gateway (`@cloudgrid-io/runtime`, injected via
8
8
  `needs: { ai: true }` — works today); the ideal vector store `needs: vector`
9
9
  (pgvector) is **now available** — #1545 shipped (verified live 2026-07-16) — but
10
10
  this blueprint stores embeddings in Mongo and computes similarity in-app. Because RAG involves choices
@@ -101,7 +101,7 @@ needs:
101
101
  # # disk — persistent FS mount (PVC). Injects DISK_PATH. Pins to 1 replica.
102
102
  # # true | { size: 1Gi, mount: /var/data } (forbidden: /proc /sys /dev)
103
103
  # disk: true
104
- # ai — AI Gateway via @cloudgrid-io/ai. Injects AI_GATEWAY_URL. true | false.
104
+ # ai — AI Gateway via @cloudgrid-io/runtime. Injects RUNTIME_GATEWAY_URL. true | false.
105
105
  # Powers embeddings (for indexing docs + queries) and chat completions (the
106
106
  # answer step). Works today.
107
107
  ai: true
@@ -203,7 +203,7 @@ services:
203
203
  # path: /api/health
204
204
  # expected: "200"
205
205
  # Static runtime env vars (strings). Reserved: PORT, APP_NAME, SERVICE_NAME,
206
- # NODE_ENV, MONGODB_URL, REDIS_URL, AI_GATEWAY_URL, N8N_WEBHOOK_URL, CLOUDGRID_*.
206
+ # NODE_ENV, MONGODB_URL, REDIS_URL, RUNTIME_GATEWAY_URL, N8N_WEBHOOK_URL, CLOUDGRID_*.
207
207
  # env:
208
208
  # LOG_LEVEL: info
209
209
  # Sibling services/private resources that must be Ready first (no cron, no cycles).
@@ -21,7 +21,7 @@ the app following it.
21
21
  `cloudgrid.yaml` is the URL mount, NOT the filesystem path.
22
22
  2. **Read injected values LAZILY (inside a getter), never at module top level** —
23
23
  a top-level read fails `next build`. The grid injects `DATABASE_MONGODB_URL`
24
- (+ legacy `MONGODB_URL`) for Mongo and `AI_GATEWAY_URL` for the AI Gateway.
24
+ (+ legacy `MONGODB_URL`) for Mongo and `RUNTIME_GATEWAY_URL` for the AI Gateway.
25
25
  3. **Declare `needs: { ai: true, database: true }`** (canonical shape). Never
26
26
  author `requires:`, and never set `needs:` and `requires:` together (the
27
27
  validator rejects the combination).
@@ -57,7 +57,7 @@ needs:
57
57
 
58
58
  > **Capability:** `needs: { database: true }` → the deployer provisions Mongo and
59
59
  > injects `DATABASE_MONGODB_URL` (plus legacy `MONGODB_URL`). `needs: { ai: true }`
60
- > → injects `AI_GATEWAY_URL`; `@cloudgrid-io/ai` routes embeddings + chat through
60
+ > → injects `RUNTIME_GATEWAY_URL`; `@cloudgrid-io/runtime` routes embeddings + chat through
61
61
  > it with no API key to manage. `needs: { vector: pgvector }` (injects
62
62
  > `VECTOR_PGVECTOR_URL`) is available (#1545 shipped) but not declared here —
63
63
  > this blueprint uses Mongo embeddings. See the capability-map for the full
@@ -96,7 +96,7 @@ name: analytics-dashboard
96
96
  # # disk — persistent FS mount (PVC). Injects DISK_PATH. Pins to 1 replica.
97
97
  # # true | { size: 1Gi, mount: /var/data } (forbidden: /proc /sys /dev)
98
98
  # disk: true
99
- # # ai — AI Gateway via @cloudgrid-io/ai. Injects AI_GATEWAY_URL. true | false.
99
+ # # ai — AI Gateway via @cloudgrid-io/runtime. Injects RUNTIME_GATEWAY_URL. true | false.
100
100
  # ai: true
101
101
 
102
102
 
@@ -192,7 +192,7 @@ services:
192
192
  # path: /api/health
193
193
  # expected: "200"
194
194
  # Static runtime env vars (strings). Reserved: PORT, APP_NAME, SERVICE_NAME,
195
- # NODE_ENV, MONGODB_URL, REDIS_URL, AI_GATEWAY_URL, N8N_WEBHOOK_URL, CLOUDGRID_*.
195
+ # NODE_ENV, MONGODB_URL, REDIS_URL, RUNTIME_GATEWAY_URL, N8N_WEBHOOK_URL, CLOUDGRID_*.
196
196
  # env:
197
197
  # LOG_LEVEL: info
198
198
  # Sibling services/private resources that must be Ready first (no cron, no cycles).
@@ -96,7 +96,7 @@ name: my-api-dashboard
96
96
  # # disk — persistent FS mount (PVC). Injects DISK_PATH. Pins to 1 replica.
97
97
  # # true | { size: 1Gi, mount: /var/data } (forbidden: /proc /sys /dev)
98
98
  # disk: true
99
- # # ai — AI Gateway via @cloudgrid-io/ai. Injects AI_GATEWAY_URL. true | false.
99
+ # # ai — AI Gateway via @cloudgrid-io/runtime. Injects RUNTIME_GATEWAY_URL. true | false.
100
100
  # ai: true
101
101
 
102
102
 
@@ -192,7 +192,7 @@ services:
192
192
  # path: /api/health
193
193
  # expected: "200"
194
194
  # Static runtime env vars (strings). Reserved: PORT, APP_NAME, SERVICE_NAME,
195
- # NODE_ENV, MONGODB_URL, REDIS_URL, AI_GATEWAY_URL, N8N_WEBHOOK_URL, CLOUDGRID_*.
195
+ # NODE_ENV, MONGODB_URL, REDIS_URL, RUNTIME_GATEWAY_URL, N8N_WEBHOOK_URL, CLOUDGRID_*.
196
196
  # env:
197
197
  # LOG_LEVEL: info
198
198
  # Sibling services/private resources that must be Ready first (no cron, no cycles).
@@ -96,7 +96,7 @@ name: api-docs
96
96
  # # disk — persistent FS mount (PVC). Injects DISK_PATH. Pins to 1 replica.
97
97
  # # true | { size: 1Gi, mount: /var/data } (forbidden: /proc /sys /dev)
98
98
  # disk: true
99
- # # ai — AI Gateway via @cloudgrid-io/ai. Injects AI_GATEWAY_URL. true | false.
99
+ # # ai — AI Gateway via @cloudgrid-io/runtime. Injects RUNTIME_GATEWAY_URL. true | false.
100
100
  # ai: true
101
101
 
102
102
 
@@ -192,7 +192,7 @@ services:
192
192
  # path: /api/health
193
193
  # expected: "200"
194
194
  # Static runtime env vars (strings). Reserved: PORT, APP_NAME, SERVICE_NAME,
195
- # NODE_ENV, MONGODB_URL, REDIS_URL, AI_GATEWAY_URL, N8N_WEBHOOK_URL, CLOUDGRID_*.
195
+ # NODE_ENV, MONGODB_URL, REDIS_URL, RUNTIME_GATEWAY_URL, N8N_WEBHOOK_URL, CLOUDGRID_*.
196
196
  # env:
197
197
  # LOG_LEVEL: info
198
198
  # Sibling services/private resources that must be Ready first (no cron, no cycles).
@@ -96,7 +96,7 @@ name: my-api
96
96
  # # disk — persistent FS mount (PVC). Injects DISK_PATH. Pins to 1 replica.
97
97
  # # true | { size: 1Gi, mount: /var/data } (forbidden: /proc /sys /dev)
98
98
  # disk: true
99
- # # ai — AI Gateway via @cloudgrid-io/ai. Injects AI_GATEWAY_URL. true | false.
99
+ # # ai — AI Gateway via @cloudgrid-io/runtime. Injects RUNTIME_GATEWAY_URL. true | false.
100
100
  # ai: true
101
101
 
102
102
 
@@ -192,7 +192,7 @@ services:
192
192
  # path: /api/health
193
193
  # expected: "200"
194
194
  # Static runtime env vars (strings). Reserved: PORT, APP_NAME, SERVICE_NAME,
195
- # NODE_ENV, MONGODB_URL, REDIS_URL, AI_GATEWAY_URL, N8N_WEBHOOK_URL, CLOUDGRID_*.
195
+ # NODE_ENV, MONGODB_URL, REDIS_URL, RUNTIME_GATEWAY_URL, N8N_WEBHOOK_URL, CLOUDGRID_*.
196
196
  # env:
197
197
  # LOG_LEVEL: info
198
198
  # Sibling services/private resources that must be Ready first (no cron, no cycles).