@yottagraph-app/aether-instructions 1.1.2 → 1.1.4

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.
@@ -158,6 +158,5 @@ gcloud projects describe $PROJECT_ID --format="value(projectId)" 2>&1
158
158
  >
159
159
  > - Create an agent in `agents/` and deploy with `/deploy_agent`
160
160
  > - Create an MCP server in `mcp-servers/` and deploy with `/deploy_mcp`
161
- > - Deploy your UI to Vercel with `/vercel_deploy`
162
161
 
163
162
  **If it fails:** Help the user troubleshoot the GCP authentication.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yottagraph-app/aether-instructions",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "Cursor rules, commands, and skills for Aether development",
5
5
  "files": [
6
6
  "rules",
package/rules/agents.mdc CHANGED
@@ -168,8 +168,9 @@ Once deployed, the agent is reachable through the Portal Gateway:
168
168
  ```
169
169
  Chat UI (pages/chat.vue)
170
170
  → POST NUXT_PUBLIC_GATEWAY_URL/api/agents/{tenantId}/{agentId}/query
171
- → Portal Gateway proxies to Vertex AI Agent Engine
172
- → Agent runs, returns response
171
+ → Portal Gateway proxies to Vertex AI Agent Engine (streamQuery)
172
+ → Agent runs (may invoke tools, make multiple LLM calls)
173
+ → Gateway collects the ADK event stream, extracts final text
173
174
  → Chat UI displays it
174
175
  ```
175
176
 
@@ -177,6 +178,126 @@ The gateway URL and tenant ID come from `broadchurch.yaml` (injected as
177
178
  `NUXT_PUBLIC_GATEWAY_URL` and `NUXT_PUBLIC_TENANT_ORG_ID`). The chat page
178
179
  discovers available agents from the Portal config endpoint.
179
180
 
181
+ ### Gateway Request
182
+
183
+ ```
184
+ POST {NUXT_PUBLIC_GATEWAY_URL}/api/agents/{tenantId}/{agentId}/query
185
+ Content-Type: application/json
186
+
187
+ { "message": "Summarize the latest 8-K filing", "session_id": "optional-session-id" }
188
+ ```
189
+
190
+ Omit `session_id` on the first message — the gateway auto-creates one.
191
+
192
+ ### Gateway Response Format
193
+
194
+ ```json
195
+ {
196
+ "output": "The agent's final text response",
197
+ "session_id": "session-abc-123",
198
+ "events": [ /* raw ADK event stream */ ]
199
+ }
200
+ ```
201
+
202
+ | Field | Type | Description |
203
+ |---|---|---|
204
+ | `output` | `string \| any[]` | Usually the agent's final text. Falls back to the raw `events` array if the gateway couldn't extract text. |
205
+ | `session_id` | `string` | Pass back on subsequent messages to continue the conversation. |
206
+ | `events` | `any[]` | Full ADK event stream. Useful for debugging or building UIs that show intermediate agent steps. |
207
+
208
+ **Important:** `output` is NOT always a string. When the agent's response
209
+ involves complex tool chains, the gateway's server-side extraction may miss
210
+ the text and return the raw events array instead. Always use
211
+ `extractAgentText()` to parse `output` safely rather than treating it as a
212
+ string directly.
213
+
214
+ ### ADK Event Stream Format
215
+
216
+ The `events` array contains one object per step the agent took. Each event
217
+ has `content.parts[]` where each part is one of:
218
+
219
+ ```json
220
+ { "text": "The agent's text response..." }
221
+ { "functionCall": { "name": "search", "args": { "q": "AAPL" } } }
222
+ { "functionResponse": { "name": "search", "response": { "results": [...] } } }
223
+ ```
224
+
225
+ A typical stream for an agent that uses a tool:
226
+
227
+ ```json
228
+ [
229
+ { "content": { "parts": [{ "functionCall": { "name": "search", "args": {"q": "AAPL 8-K"} } }], "role": "model" } },
230
+ { "content": { "parts": [{ "functionResponse": { "name": "search", "response": {"results": ["..."]} } }], "role": "tool" } },
231
+ { "content": { "parts": [{ "text": "Here is the summary of the 8-K filing..." }], "role": "model" } }
232
+ ]
233
+ ```
234
+
235
+ The final text is the last `text` part that isn't in a `functionCall` or
236
+ `functionResponse` event. Events may also arrive as JSON strings rather
237
+ than objects — always handle both.
238
+
239
+ ### Parsing Agent Responses (Non-Streaming)
240
+
241
+ For one-shot calls (server routes, background tasks) where streaming isn't
242
+ needed, use the buffered `/query` endpoint with `extractAgentText`:
243
+
244
+ ```typescript
245
+ import { extractAgentText } from '~/composables/useAgentChat';
246
+
247
+ const response = await $fetch(url, { method: 'POST', body: { message } });
248
+ const text = extractAgentText(response.output);
249
+ ```
250
+
251
+ `extractAgentText` handles: plain strings, ADK event stream arrays (with
252
+ JSON-string or object elements), single event objects, and several legacy
253
+ Agent Engine response shapes. It skips `functionCall` / `functionResponse`
254
+ events and returns the agent's final text.
255
+
256
+ ### Streaming Responses
257
+
258
+ The gateway also exposes a streaming endpoint that returns Server-Sent
259
+ Events as the agent executes. **The `useAgentChat` composable uses this by
260
+ default** — it tries `/stream` first and falls back to `/query`
261
+ automatically.
262
+
263
+ ```
264
+ POST {NUXT_PUBLIC_GATEWAY_URL}/api/agents/{tenantId}/{agentId}/stream
265
+ Content-Type: application/json
266
+
267
+ { "message": "Summarize the latest 8-K filing", "session_id": "optional" }
268
+ ```
269
+
270
+ The response is an SSE stream with these event types:
271
+
272
+ | Event | Data Shape | Description |
273
+ |---|---|---|
274
+ | `text` | `{ "text": "..." }` | Agent text output (replaces previous text) |
275
+ | `function_call` | `{ "name": "...", "args": {...} }` | Agent is calling a tool |
276
+ | `function_response` | `{ "name": "...", "response": {...} }` | Tool returned a result |
277
+ | `error` | `{ "message": "..." }` | Error during processing |
278
+ | `done` | `{ "session_id": "...", "text": "..." }` | Stream complete with final text |
279
+
280
+ For custom agent UIs that need streaming, import `readSSE`:
281
+
282
+ ```typescript
283
+ import { readSSE } from '~/composables/useAgentChat';
284
+
285
+ const res = await fetch(streamUrl, {
286
+ method: 'POST',
287
+ headers: { 'Content-Type': 'application/json' },
288
+ body: JSON.stringify({ message: 'Hello' }),
289
+ });
290
+
291
+ for await (const { event, data } of readSSE(res)) {
292
+ if (event === 'text') console.log('Agent says:', data.text);
293
+ if (event === 'function_call') console.log('Calling:', data.name);
294
+ if (event === 'done') console.log('Session:', data.session_id);
295
+ }
296
+ ```
297
+
298
+ The `done` event always includes the final extracted text, so you don't
299
+ need to track text deltas yourself.
300
+
180
301
  ## Agent Design Guidelines
181
302
 
182
303
  - Keep agents focused: one agent per domain or task type
package/rules/api.mdc CHANGED
@@ -51,6 +51,57 @@ Types are also imported from the client:
51
51
  import type { NamedEntityReport, GetNEIDResponse } from '@yottagraph-app/elemental-api/client';
52
52
  ```
53
53
 
54
+ ### Client Method Quick Reference
55
+
56
+ All methods return data directly and throw on non-2xx responses.
57
+
58
+ **Entity search and lookup:**
59
+
60
+ | Method | Signature | Purpose |
61
+ |---|---|---|
62
+ | `getNEID` | `(params: { entityName, maxResults?, includeNames? })` | Lookup entity by name |
63
+ | `findEntities` | `(body: FindEntitiesBody)` | Expression-based search (see `find.md`) |
64
+ | `getNamedEntityReport` | `(neid: string)` | Entity details (name, aliases, type) |
65
+ | `getEntityDetails` | `(neid: string)` | Alias for entity reports |
66
+
67
+ **Properties and schema:**
68
+
69
+ | Method | Signature | Purpose |
70
+ |---|---|---|
71
+ | `getSchema` | `()` | All entity types (flavors) and properties (PIDs) |
72
+ | `getPropertyValues` | `(body: { eids: string, pids: string })` | Property values (eids/pids are JSON-stringified arrays!) |
73
+ | `summarizeProperty` | `(pid: number)` | Summary stats for a property |
74
+
75
+ **Relationships and graph:**
76
+
77
+ | Method | Signature | Purpose |
78
+ |---|---|---|
79
+ | `getLinkedEntities` | `(neid, params?: { entity_type?, link_type? })` | Linked entities (person/org/location only) |
80
+ | `getLinks` | `(sourceNeid, targetNeid, params?)` | Links between two specific entities |
81
+ | `getLinkCounts` | `(sourceNeid, targetNeid)` | Link counts between entities |
82
+ | `getNeighborhood` | `(centerNeid, params?)` | Neighboring entities |
83
+ | `getGraphLayout` | `(centerNeid, params?)` | Graph layout for visualization |
84
+
85
+ **News, events, and sentiment:**
86
+
87
+ | Method | Signature | Purpose |
88
+ |---|---|---|
89
+ | `getArticle` | `(artid: string)` | Article by ID |
90
+ | `getArticleText` | `(artid: string)` | Article full text |
91
+ | `getEvent` | `(eveid: string)` | Event by ID |
92
+ | `getEventsForEntity` | `(params: { neid, startTime?, endTime? })` | Events involving an entity |
93
+ | `getMentions` | `(params: { neid, startTime?, endTime? })` | Mention codes for entities |
94
+ | `getMentionCounts` | `(params: { neid, ... })` | Bucketed mention counts |
95
+ | `getNamedEntitySentiment` | `(neid: string)` | Sentiment for an entity |
96
+
97
+ **Other:**
98
+
99
+ | Method | Signature | Purpose |
100
+ |---|---|---|
101
+ | `getHealth` | `()` | Health check |
102
+ | `getStatus` | `()` | Server status and capabilities |
103
+ | `adaMessage` | `(body: AdaMessageBody)` | Ada AI chat |
104
+
54
105
  ## Discovery-First Pattern
55
106
 
56
107
  The knowledge graph contains many entity types and properties, and new datasets
@@ -81,17 +132,27 @@ knowledge of what's in the graph.
81
132
 
82
133
  ## API Gotchas
83
134
 
84
- > **WARNING -- `getSchema()` response nesting**: The generated TypeScript types
85
- > put `flavors` and `properties` at the top level, but the actual API response
86
- > nests them under a `schema` key. Using `response.properties` directly will
87
- > crash with `Cannot read properties of undefined`. Always use:
135
+ ### `getSchema()` response is nested WILL crash if you don't handle it
136
+
137
+ The generated TypeScript types suggest `response.properties` and
138
+ `response.flavors` exist at the top level. **They don't.** The API nests
139
+ them under `response.schema`. This mismatch between types and reality
140
+ causes `Cannot read properties of undefined` every time.
88
141
 
89
142
  ```typescript
143
+ // WRONG — will crash at runtime despite TypeScript compiling fine:
144
+ const res = await client.getSchema();
145
+ const props = res.properties; // undefined!
146
+
147
+ // CORRECT — always access through .schema:
90
148
  const res = await client.getSchema();
91
149
  const properties = res.schema?.properties ?? (res as any).properties ?? [];
92
150
  const flavors = res.schema?.flavors ?? (res as any).flavors ?? [];
93
151
  ```
94
152
 
153
+ The `(res as any).properties` fallback is there in case the API is ever
154
+ fixed to match the types. Use this pattern every time.
155
+
95
156
  > **WARNING -- `getPropertyValues()` takes JSON-stringified arrays**: The `eids`
96
157
  > and `pids` parameters must be JSON-encoded strings, NOT native arrays. The
97
158
  > TypeScript type is `string`, not `string[]`. Passing a raw array will silently
@@ -139,6 +200,48 @@ See the **cookbook** rule for a full "Get filings for a company" recipe.
139
200
  (`POST /elemental/find`). Best for filtered searches (by type, property,
140
201
  relationship). See `find.md` for the expression language.
141
202
 
203
+ ## Common Entity Relationships
204
+
205
+ The knowledge graph connects entities through relationship properties
206
+ (`data_nindex` PIDs). These are the most common patterns:
207
+
208
+ | From | PID | To | How to traverse |
209
+ |---|---|---|---|
210
+ | Organization | `filed` | Document (filings) | `getPropertyValues` with the `filed` PID on the org's NEID |
211
+ | Organization | `employs` | Person | `getLinkedEntities` (person is a graph node type) |
212
+ | Person | `employed_by` | Organization | `getLinkedEntities` (organization is a graph node type) |
213
+ | Organization | `headquartered_in` | Location | `getLinkedEntities` (location is a graph node type) |
214
+ | Any entity | `related_to` | Any entity | `getLinkedEntities` for person/org/location; `getPropertyValues` for others |
215
+
216
+ **Key constraint:** `getLinkedEntities` only works for three target types:
217
+ **person**, **organization**, **location**. For documents, filings,
218
+ articles, financial instruments, and events, use `getPropertyValues` with
219
+ the relationship PID. See the cookbook rule (recipe #7) for a full filings
220
+ example.
221
+
222
+ **Traversal pattern for non-graph-node types:**
223
+
224
+ ```typescript
225
+ // 1. Get the PID for the relationship
226
+ const schema = await client.getSchema();
227
+ const pids = schema.schema?.properties ?? [];
228
+ const filedPid = pids.find((p: any) => p.name === 'filed')?.pid;
229
+
230
+ // 2. Get linked entity IDs via getPropertyValues
231
+ const res = await client.getPropertyValues({
232
+ eids: JSON.stringify([orgNeid]),
233
+ pids: JSON.stringify([filedPid]),
234
+ });
235
+
236
+ // 3. Pad IDs to 20 chars to form valid NEIDs
237
+ const docNeids = res.values.map((v: any) => String(v.value).padStart(20, '0'));
238
+
239
+ // 4. Get details for each linked entity
240
+ const reports = await Promise.all(
241
+ docNeids.map((neid: string) => client.getNamedEntityReport(neid)),
242
+ );
243
+ ```
244
+
142
245
  ## Error Handling
143
246
 
144
247
  ```typescript
@@ -133,9 +133,17 @@ Tenant-specific configuration generated during provisioning. Contains GCP projec
133
133
 
134
134
  ## Built-in Pages
135
135
 
136
- These pages ship with the template and can be kept, modified, or removed based on the app's needs:
137
-
138
- - `pages/chat.vue` -- Agent chat UI. Talks to deployed ADK agents through the Portal Gateway. Keep if the app uses AI agents.
139
- - `pages/mcp.vue` -- MCP Explorer. Browse and test MCP server tools. Keep if the app uses MCP servers.
140
- - `pages/entity-lookup.vue` -- Entity search tool. Useful for looking up NEIDs. Keep or remove based on the app.
136
+ These pages ship with the template and can be kept, modified, or removed
137
+ based on the app's needs:
138
+
139
+ - `pages/chat.vue` -- Agent chat UI. Talks to deployed ADK agents through
140
+ the Portal Gateway with streaming support. Keep if the app uses AI agents.
141
+ - `pages/mcp.vue` -- MCP Explorer. Browse and test MCP server tools. Keep
142
+ if the app uses MCP servers.
143
+ - `pages/entity-lookup.vue` -- Entity search tool. Useful for looking up
144
+ NEIDs. Keep or remove based on the app.
145
+
146
+ If any of these pages are missing from your project, your project may have
147
+ been created from an older template version. Copy them from the latest
148
+ aether-dev source or run `/update_instructions` to check for updates.
141
149
 
@@ -1,278 +0,0 @@
1
- # Vercel Deploy
2
-
3
- Deploy the current state of the project to Vercel via Git integration.
4
-
5
- ## Overview
6
-
7
- This command helps the user deploy their code to Vercel. It handles committing changes, choosing production vs. preview (branch) deployment, pushing to the right branch, and confirming the deployment succeeded.
8
-
9
- **Prerequisite:** The project must already be set up with Vercel (linked, env vars synced, etc.). If it hasn't been set up yet, redirect the user to `/vercel_setup`.
10
-
11
- ---
12
-
13
- ## Step 1: Verify Vercel is Set Up
14
-
15
- Check that the project is linked to Vercel by checking whether `.vercel/project.json` exists (read the file or check the directory).
16
-
17
- **If the file does not exist:** The project hasn't been set up yet. Tell the user:
18
-
19
- > This project hasn't been connected to Vercel yet. Let's run the setup first.
20
-
21
- Then stop and redirect them to run `/vercel_setup`.
22
-
23
- Also verify there's at least one prior deployment:
24
-
25
- ```bash
26
- npx vercel ls 2>&1
27
- ```
28
-
29
- **If no deployments are listed:** Same as above — redirect to `/vercel_setup`.
30
-
31
- **If deployments exist:** Continue.
32
-
33
- ---
34
-
35
- ## Step 2: Check for Uncommitted Changes
36
-
37
- ```bash
38
- git status
39
- ```
40
-
41
- **If the working tree is clean** (nothing to commit): Continue to Step 3.
42
-
43
- **If there are uncommitted changes:** Show the user what's changed:
44
-
45
- ```bash
46
- git status --short
47
- ```
48
-
49
- Then ask:
50
-
51
- ```
52
- AskQuestion({
53
- title: "Uncommitted Changes",
54
- questions: [
55
- {
56
- id: "commit-changes",
57
- prompt: "You have uncommitted changes. These need to be committed before deploying.\n\n[paste the git status output here]\n\nWould you like me to commit them now?",
58
- options: [
59
- { id: "commit", label: "Yes — commit all changes and continue" },
60
- { id: "review", label: "Let me review first — I'll commit manually" }
61
- ]
62
- }
63
- ]
64
- })
65
- ```
66
-
67
- If the user selects "commit": Follow the commit workflow in `git-support.mdc` (format, stage, commit). Do not proceed to Step 3 with a failed commit.
68
-
69
- If the user selects "review": Stop and wait for them to come back after committing manually.
70
-
71
- ---
72
-
73
- ## Step 3: Choose Deployment Target
74
-
75
- Determine the current branch and the main branch:
76
-
77
- ```bash
78
- git branch --show-current
79
- git remote show origin | grep 'HEAD branch'
80
- ```
81
-
82
- Then ask the user where they want to deploy:
83
-
84
- ```
85
- AskQuestion({
86
- title: "Deployment Target",
87
- questions: [
88
- {
89
- id: "deploy-target",
90
- prompt: "Where would you like to deploy?\n\nYou're currently on branch: [current-branch]",
91
- options: [
92
- { id: "production", label: "Production — deploy to main branch (live site)" },
93
- { id: "preview", label: "Preview — deploy to a branch (staging/test URL)" }
94
- ]
95
- }
96
- ]
97
- })
98
- ```
99
-
100
- Then follow the appropriate path below.
101
-
102
- ---
103
-
104
- ### Path A: Deploy to Production
105
-
106
- The user wants to deploy to the main branch.
107
-
108
- **If already on main:**
109
-
110
- ```bash
111
- git push origin main 2>&1
112
- ```
113
-
114
- **If the push is rejected:** Pull the latest changes first with `git pull --rebase origin main`, then try pushing again. If branch protection rules prevent direct pushes to main, the user will need to use the PR path instead.
115
-
116
- **If on a feature branch:**
117
-
118
- The user's changes need to get to main. Ask how:
119
-
120
- ```
121
- AskQuestion({
122
- title: "Merge to Main",
123
- questions: [
124
- {
125
- id: "merge-strategy",
126
- prompt: "You're on branch [current-branch]. To deploy to production, your changes need to be on main.\n\nHow would you like to proceed?",
127
- options: [
128
- { id: "merge", label: "Merge [current-branch] into main and push" },
129
- { id: "pr", label: "Create a pull request instead (I'll merge it myself)" }
130
- ]
131
- }
132
- ]
133
- })
134
- ```
135
-
136
- If "merge":
137
-
138
- ```bash
139
- git checkout main
140
- git pull origin main
141
- git merge [feature-branch] --no-edit
142
- git push origin main
143
- ```
144
-
145
- If the merge has conflicts, stop and tell the user:
146
-
147
- > There are merge conflicts that need to be resolved manually. After resolving them, commit the merge and run this command again.
148
-
149
- **If the push is rejected:** Pull the latest changes with `git pull --rebase origin main` and try pushing again. If branch protection rules prevent direct pushes to main, the user will need to use the PR path instead.
150
-
151
- If "pr":
152
-
153
- First push the branch if needed:
154
-
155
- ```bash
156
- git push -u origin [feature-branch]
157
- ```
158
-
159
- Then create a PR:
160
-
161
- ```bash
162
- gh pr create --title "Deploy [feature-branch] to production" --body "$(cat <<'EOF'
163
- [Summarize the actual changes — see below]
164
- EOF
165
- )"
166
- ```
167
-
168
- Before creating the PR, analyze the commits on the branch (using `git log main..[branch] --oneline`) and draft a meaningful PR description summarizing the changes, rather than using a generic message.
169
-
170
- Return the PR URL and tell the user:
171
-
172
- > Pull request created: [PR URL]
173
- >
174
- > Merge it on GitHub to trigger a production deployment. Vercel will auto-deploy when the merge lands on main.
175
-
176
- Then stop — the deployment will happen automatically when they merge.
177
-
178
- ---
179
-
180
- ### Path B: Deploy to Preview (Branch)
181
-
182
- The user wants a preview deployment on a branch.
183
-
184
- **If already on a feature branch:**
185
-
186
- Push the current branch:
187
-
188
- ```bash
189
- git push -u origin [current-branch] 2>&1
190
- ```
191
-
192
- **If the push is rejected:** Pull the latest changes with `git pull --rebase origin [current-branch]` and try pushing again. If there are rebase conflicts, stop and let the user resolve them.
193
-
194
- **If on main:**
195
-
196
- The user needs a branch. Ask for a name:
197
-
198
- ```
199
- AskQuestion({
200
- title: "Branch Name",
201
- questions: [
202
- {
203
- id: "branch-name",
204
- prompt: "You're on main. To create a preview deployment, we need a branch.\n\nWhat would you like to call it? (e.g., 'dev', 'staging', 'leah/feature-x')",
205
- options: [
206
- { id: "dev", label: "dev" },
207
- { id: "staging", label: "staging" },
208
- { id: "custom", label: "I'll type a custom branch name" }
209
- ]
210
- }
211
- ]
212
- })
213
- ```
214
-
215
- If the user picks "custom", ask them to provide the branch name.
216
-
217
- Create and push the branch:
218
-
219
- ```bash
220
- git checkout -b [branch-name]
221
- git push -u origin [branch-name] 2>&1
222
- ```
223
-
224
- ---
225
-
226
- ## Step 4: Confirm Deployment
227
-
228
- After pushing, Vercel's Git integration will automatically start a build. Monitor it:
229
-
230
- ```bash
231
- sleep 5
232
- npx vercel ls 2>&1
233
- ```
234
-
235
- Look for the latest deployment matching the push. It may show status as `● Building` initially.
236
-
237
- If the deployment is building, wait and poll:
238
-
239
- ```bash
240
- sleep 15
241
- npx vercel ls 2>&1
242
- ```
243
-
244
- Keep polling (with increasing intervals: 15s, 30s, 30s) until the status changes to `● Ready` or shows an error. If the build hasn't completed after ~5 minutes of polling, provide the Vercel dashboard link and suggest the user check the build logs there.
245
-
246
- **If status is `● Ready`:**
247
-
248
- Get the deployment URL from the output. Then verify static assets are served correctly:
249
-
250
- ```bash
251
- curl -sI "https://<deployment-url>/_nuxt/<any-chunk>.js" | head -5
252
- ```
253
-
254
- Confirm `content-type: application/javascript`.
255
-
256
- Present the result to the user:
257
-
258
- > Deployment successful!
259
- >
260
- > **URL:** [deployment URL]
261
- > **Environment:** [Production / Preview]
262
- > **Branch:** [branch name]
263
- >
264
- > [If production and custom domain exists]: Also available at: [custom domain URL]
265
-
266
- **If the build failed:**
267
-
268
- Check the logs:
269
-
270
- ```bash
271
- npx vercel logs <deployment-url> 2>&1 | tail -30
272
- ```
273
-
274
- Common failures:
275
-
276
- - **E401 npm install:** `NPM_TOKEN` not set for this environment. See `/vercel_setup` Step 1 (NPM Authentication).
277
- - **Build error:** Show the user the relevant log lines and help them debug.
278
- - **Missing env vars:** Env vars may not be set for the preview environment. See `/vercel_setup` Step 5 (Sync Environment Variables).
@@ -1,528 +0,0 @@
1
- # Vercel Setup
2
-
3
- Set up and sync this Aether project with Vercel for web deployment.
4
-
5
- ## Overview
6
-
7
- This command walks through connecting an Aether project to Vercel and syncing environment variables. It assumes the user has already created a GitHub repository from the Aether template and wants to deploy it to Vercel.
8
-
9
- The Vercel connection itself (linking the GitHub repo to a Vercel project) must be done by the user through the Vercel dashboard. Once connected, this command handles everything else via the Vercel CLI.
10
-
11
- ---
12
-
13
- ## Step 1: Check NPM Authentication for Private Packages
14
-
15
- This project depends on private `@lovelace-ai` packages from GitHub Packages. Both local installs and Vercel builds need a valid token. Setting this up first avoids build failures later.
16
-
17
- ### Check `.npmrc`
18
-
19
- Read `.npmrc` in the project root. It should contain:
20
-
21
- ```
22
- @lovelace-ai:registry=https://npm.pkg.github.com
23
- //npm.pkg.github.com/:_authToken=${NPM_TOKEN}
24
- ```
25
-
26
- **If `.npmrc` is missing:** Create it with the content above.
27
-
28
- **If `.npmrc` has a hardcoded token** (a literal value like `_authToken=ghp_xxxx` instead of `${NPM_TOKEN}`): This shouldn't normally happen since `.npmrc` is committed to git with `${NPM_TOKEN}`. Stop and tell the user:
29
-
30
- > Your `.npmrc` has a hardcoded token instead of `${NPM_TOKEN}`. Please move the token value to your `NPM_TOKEN` environment variable, then replace the hardcoded value in `.npmrc` with `${NPM_TOKEN}`.
31
-
32
- Wait for the user to confirm before continuing.
33
-
34
- **If `.npmrc` already uses `${NPM_TOKEN}`:** Good, continue.
35
-
36
- ### Ensure `NPM_TOKEN` is a valid PAT
37
-
38
- ```bash
39
- echo $NPM_TOKEN
40
- ```
41
-
42
- Check the token prefix:
43
-
44
- - `ghp_` — Classic PAT. Good, skip to **"Ensure `.npmrc` is committed"**.
45
- - `github_pat_` — Fine-grained PAT. Good, skip to **"Ensure `.npmrc` is committed"**.
46
- - `gho_` — **This is an OAuth token, NOT a PAT.** OAuth tokens are tied to a specific device/session and will fail on Vercel's build servers. Tell the user their current token value so they can save it if needed:
47
-
48
- > Your current `NPM_TOKEN` is an OAuth token (`gho_...`), which won't work on Vercel. Here's the value in case you want to save it: `<the full gho_ token>`
49
- >
50
- > We need to replace it with a classic PAT (`ghp_`).
51
-
52
- Check `~/.npmrc` — it may contain a `ghp_` classic PAT that can be reused. If found, use it as the new `NPM_TOKEN` and persist it (see below). If not, the user needs to create one — use the AskQuestion below.
53
-
54
- - **Empty** — The user needs a GitHub Personal Access Token. Check `~/.npmrc` for a usable `ghp_` token first. If found, use it and persist it (see below). If not, use the AskQuestion below.
55
-
56
- ```
57
- AskQuestion({
58
- title: "GitHub Token Required for Private Packages",
59
- questions: [
60
- {
61
- id: "npm-token",
62
- prompt: "This project needs a GitHub Personal Access Token (PAT) to install private @lovelace-ai packages.\n\nNote: OAuth tokens (gho_) won't work on Vercel — you need a classic PAT (ghp_).\n\nDo you already have one, or do you need to create one?",
63
- options: [
64
- { id: "have-token", label: "I have a classic PAT — I'll set it as NPM_TOKEN" },
65
- { id: "need-token", label: "I need to create one" }
66
- ]
67
- }
68
- ]
69
- })
70
- ```
71
-
72
- If the user selects "need-token", guide them:
73
-
74
- > To create a GitHub token for npm packages:
75
- >
76
- > 1. Go to https://github.com/settings/tokens
77
- > 2. Click **Generate new token** > **Generate new token (classic)**
78
- > 3. Give it a name like "Aether npm packages"
79
- > 4. Select the **read:packages** scope (that's all you need)
80
- > 5. Click **Generate token**
81
- > 6. Copy the token — you won't see it again
82
-
83
- ### Persist `NPM_TOKEN` to the shell profile
84
-
85
- Once you have a valid PAT (from `~/.npmrc` or from the user), persist it. Detect the user's shell:
86
-
87
- ```bash
88
- echo $SHELL
89
- ```
90
-
91
- | `$SHELL` | Profile file |
92
- | ----------- | ------------ |
93
- | `/bin/zsh` | `~/.zshrc` |
94
- | `/bin/bash` | `~/.bashrc` |
95
-
96
- If the user's shell is something else (fish, etc.), tell them to set `NPM_TOKEN` as a persistent environment variable themselves and move on.
97
-
98
- For **zsh** or **bash**, add or update the export (replacing `<shell_rc>` with the profile filename from the table):
99
-
100
- ```bash
101
- sed -i.bak '/^export NPM_TOKEN=/d' ~/.<shell_rc> && rm -f ~/.<shell_rc>.bak
102
- echo 'export NPM_TOKEN=<the_token>' >> ~/.<shell_rc>
103
- source ~/.<shell_rc>
104
- ```
105
-
106
- Verify it took effect:
107
-
108
- ```bash
109
- echo $NPM_TOKEN
110
- ```
111
-
112
- ### Ensure `.npmrc` is committed
113
-
114
- Check that `.npmrc` is NOT in `.gitignore`. Since it uses `${NPM_TOKEN}` (not a hardcoded secret), it's safe to commit and must be present in the repo for Vercel builds to work.
115
-
116
- If `.npmrc` appears in `.gitignore`, remove that line and replace it with a comment:
117
-
118
- ```
119
- # .npmrc is committed — it uses ${NPM_TOKEN} env var, not a hardcoded token
120
- ```
121
-
122
- ---
123
-
124
- ## Step 2: Check Vercel CLI & Authentication
125
-
126
- The `vercel` package is included as a devDependency, so it should already be available after `npm install` / `npm run init`. Use `npx vercel` to invoke it.
127
-
128
- Verify it's available and the user is authenticated:
129
-
130
- ```bash
131
- npx vercel whoami
132
- ```
133
-
134
- **If the command succeeds** (prints a username): Continue to Step 3.
135
-
136
- **If the command fails:** Run the login command in the background so you can read its output:
137
-
138
- ```bash
139
- npx vercel login
140
- ```
141
-
142
- Run this as a background command (set `block_until_ms: 0`) so you can read its output while it waits. Then read the terminal output to find the one-time device code. The CLI will print a line like:
143
-
144
- ```
145
- > Please visit the following URL to log in: https://vercel.com/device
146
- > Enter code: XXXX-XXXX
147
- ```
148
-
149
- **IMPORTANT:** Extract the code from the terminal output and present it to the user in chat, along with the URL. For example:
150
-
151
- > Vercel needs you to authorize this device. Please:
152
- >
153
- > 1. Open **https://vercel.com/device** in your browser (it may have opened automatically)
154
- > 2. Enter code: **XXXX-XXXX**
155
- > 3. Verify the location, IP, and request time, then approve
156
- >
157
- > Let me know once you've approved it.
158
-
159
- If the code is not immediately visible in the terminal output, wait a couple of seconds and re-read the terminal file — the CLI may take a moment to contact Vercel's servers.
160
-
161
- After the user confirms they've approved, poll the terminal output until the login command completes. Then verify:
162
-
163
- ```bash
164
- npx vercel whoami
165
- ```
166
-
167
- **Stop here if authentication fails.** The user may need to:
168
-
169
- - Check that their browser opened and they completed the authorization
170
- - Ensure they have a Vercel account (sign up at https://vercel.com/signup)
171
- - Try running `npx vercel login` again
172
-
173
- ---
174
-
175
- ## Step 3: Select the Correct Vercel Team
176
-
177
- The project should be deployed under the shared **Lovelace** team, not a personal account. Check and switch to the correct team:
178
-
179
- ```bash
180
- npx vercel teams ls
181
- ```
182
-
183
- Look for `lovelace-web` (team name: Lovelace) in the list. If the checkmark (✔) is not on `lovelace-web`, switch to it:
184
-
185
- ```bash
186
- npx vercel teams switch lovelace-web
187
- ```
188
-
189
- **If `lovelace-web` is not listed:** The user needs to be invited to the Lovelace team on Vercel. They should ask a team admin to add them at https://vercel.com/lovelace-web/settings/members.
190
-
191
- **Stop here if they can't access the team.** All subsequent steps assume the Lovelace team scope.
192
-
193
- ---
194
-
195
- ## Step 4: Check Vercel Project Link
196
-
197
- Check if the project is already linked to Vercel by checking whether `.vercel/project.json` exists (read the file or check the directory).
198
-
199
- **If the file exists:** Read it and confirm the project link. Continue to Step 5.
200
-
201
- **If the file does not exist:** The project is not linked to Vercel yet. Tell the user:
202
-
203
- > Your project needs to be connected to Vercel before we can continue.
204
- >
205
- > Please complete these steps:
206
- >
207
- > 1. Go to [vercel.com/new](https://vercel.com/new)
208
- > 2. Click 'Import Git Repository'
209
- > 3. Select your GitHub repository (the one created from the Aether template)
210
- > 4. In the 'Configure Project' screen:
211
- > - Framework Preset should auto-detect as 'Nuxt.js'
212
- > - Leave Build Command as default
213
- > - Leave Output Directory as default
214
- > 5. Click 'Deploy'
215
- > - The first deploy may fail (missing env vars) — that's OK, we'll fix it next
216
- >
217
- > Once the project is imported, come back here.
218
-
219
- Use the AskQuestion tool:
220
-
221
- ```
222
- AskQuestion({
223
- title: "Vercel Project Connection Required",
224
- questions: [
225
- {
226
- id: "vercel-connected",
227
- prompt: "Can you confirm that you imported the git repository to Vercel?",
228
- options: [
229
- { id: "done", label: "Done — I've imported the project to Vercel" },
230
- { id: "help", label: "I need help with this step" }
231
- ]
232
- }
233
- ]
234
- })
235
- ```
236
-
237
- If the user selects "help", provide additional guidance:
238
-
239
- - They need a Vercel account at https://vercel.com/signup (free tier works fine)
240
- - They need to install the Vercel GitHub App when prompted during import
241
- - The repository must be the one they created from the Aether template, not the original template repo
242
- - If the repository is in their personal github account, they should be able to import it. However, if it is the team github account, or someone else's github account, they will probably need additional permissions.
243
-
244
- After the user confirms they've imported the project, link the local project:
245
-
246
- ```bash
247
- npx vercel link --yes
248
- ```
249
-
250
- This creates the `.vercel/project.json` file that connects the local directory to the Vercel project. If it prompts to select a project, choose the one that matches the repository name.
251
-
252
- **Note:** `vercel link` automatically appends `.vercel` to `.gitignore`, even if `.vercel/` is already listed. Check `.gitignore` after linking and remove the duplicate entry if one was added.
253
-
254
- Verify the link succeeded by confirming `.vercel/project.json` now exists.
255
-
256
- **Stop here if linking fails.** The user may need to re-run `npx vercel link` and select the correct project.
257
-
258
- ---
259
-
260
- ## Step 5: Sync Environment Variables to Vercel
261
-
262
- Now that Vercel is authenticated and the project is linked, sync all required environment variables — starting with `NPM_TOKEN`, then the rest from `.env`.
263
-
264
- ### Add `NPM_TOKEN` to Vercel
265
-
266
- Step 1 ensured `NPM_TOKEN` is valid locally. Now push it to Vercel so builds can use it too.
267
-
268
- **IMPORTANT:** Add `NPM_TOKEN` to BOTH the `production` AND `preview` environments. Production deploys come from the main branch; preview deploys come from all other branches. If you only set it for production, branch deploys will fail with E401 during `npm install`.
269
-
270
- Check if it already exists on Vercel:
271
-
272
- ```bash
273
- npx vercel env ls production
274
- npx vercel env ls preview
275
- ```
276
-
277
- Add it to both environments:
278
-
279
- ```bash
280
- printf '%s' "$NPM_TOKEN" | npx vercel env add NPM_TOKEN production --force
281
- printf '%s' "$NPM_TOKEN" | npx vercel env add NPM_TOKEN preview --force
282
- ```
283
-
284
- This uses the local `NPM_TOKEN` value directly. Verify it was added to both:
285
-
286
- ```bash
287
- npx vercel env ls production
288
- npx vercel env ls preview
289
- ```
290
-
291
- ### Sync `.env` variables
292
-
293
- With `NPM_TOKEN` on Vercel, sync the remaining app-level configuration from `.env`.
294
-
295
- Read the local `.env` file and parse all non-commented, non-empty lines into KEY=VALUE pairs.
296
-
297
- **Variables to sync:** All variables defined in `.env` that are NOT commented out. This typically includes:
298
-
299
- - `NUXT_PUBLIC_APP_ID`
300
- - `NUXT_PUBLIC_APP_NAME`
301
- - `NUXT_PUBLIC_QUERY_SERVER_ADDRESS`
302
- - `NUXT_PUBLIC_AUTH0_CLIENT_ID`
303
- - `NUXT_PUBLIC_AUTH0_CLIENT_SECRET`
304
- - `NUXT_PUBLIC_USER_NAME`
305
-
306
- **Variables to skip:** Do NOT sync these to Vercel:
307
-
308
- - Commented-out lines (starting with `#`)
309
- - Variables with empty values (e.g., `NUXT_PUBLIC_USER_NAME=`, `NUXT_PUBLIC_QUERY_SERVER_ADDRESS=`)
310
- > **Why skip empty values?** Nuxt's runtime config defaults them to empty strings already.
311
- > The Vercel CLI cannot store truly empty values — it either prompts interactively or
312
- > requires a non-empty string. Using a space or placeholder causes bugs. For example,
313
- > a space in `NUXT_PUBLIC_USER_NAME` bypasses Auth0 login entirely.
314
-
315
- ### Sync Process
316
-
317
- **IMPORTANT:** Sync env vars to BOTH `production` and `preview` environments. Production is used for main branch deploys; preview is used for all other branch deploys. Missing env vars in preview will cause branch builds to fail or the app to misbehave.
318
-
319
- For each variable to sync, push it to Vercel for **both** environments. Use the following pattern to avoid interactive prompts:
320
-
321
- ```bash
322
- printf '%s' "VALUE" | npx vercel env add VAR_NAME production --force
323
- printf '%s' "VALUE" | npx vercel env add VAR_NAME preview --force
324
- ```
325
-
326
- **IMPORTANT:** Use `printf '%s'` — NOT `echo`. `echo` appends a trailing newline to the value, which gets stored as part of the env var and causes runtime bugs (e.g., Auth0 rejecting a client ID with `\n` appended).
327
-
328
- **Note on `--force`:** This flag overwrites existing values without prompting. This is safe for syncing because we want local values to be the source of truth.
329
-
330
- If a variable has a value with spaces or special characters (like `NUXT_PUBLIC_APP_NAME`), the printf/pipe approach handles this correctly.
331
-
332
- **IMPORTANT:** Strip surrounding quotes from `.env` values before piping. If `.env` has `NUXT_PUBLIC_APP_NAME="Aether Vercel Demo"`, pipe `Aether Vercel Demo` (without the `"` characters). Vercel stores the value literally — quotes included.
333
-
334
- ### Sensitive Variables Warning
335
-
336
- Before syncing, warn the user about sensitive values:
337
-
338
- > **Heads up:** The following sensitive values will be synced to Vercel:
339
- >
340
- > - `NUXT_PUBLIC_AUTH0_CLIENT_SECRET` — Note: this is in `NUXT_PUBLIC_` which means it's exposed to the browser. Consider moving it to a server-only config in the future.
341
- >
342
- > Vercel encrypts environment variables at rest and in transit.
343
-
344
- ### After Syncing
345
-
346
- Confirm the sync by listing the variables:
347
-
348
- ```bash
349
- npx vercel env ls production
350
- ```
351
-
352
- Display the list to the user so they can verify.
353
-
354
- ---
355
-
356
- ## Step 6: Trigger a Production Deployment
357
-
358
- After environment variables are synced, the existing deployment (if any) won't have them yet. Trigger a new production build:
359
-
360
- ```bash
361
- npx vercel --prod --yes
362
- ```
363
-
364
- This builds and deploys the app using the CLI. Alternatively, if the user prefers to use Git integration for deploys:
365
-
366
- > Your environment variables are synced. To trigger a deploy with the new variables, you can either:
367
- >
368
- > 1. Push a commit to your `main` branch (Vercel will auto-deploy via Git integration)
369
- > 2. Go to the Vercel dashboard and click "Redeploy" on the latest deployment
370
-
371
- **If the deploy fails:** Check `npx vercel logs <deployment-url>` and refer to the Troubleshooting section. The most common cause is missing environment variables — re-run Step 5 if needed.
372
-
373
- ---
374
-
375
- ## Step 7: Configure Custom Domain
376
-
377
- After the first successful deploy, add a custom domain under `.yottagraph.app`.
378
-
379
- First, determine the subdomain. Read the repository name from the git remote to use as a suggested default, then ask the user:
380
-
381
- ```bash
382
- git remote get-url origin
383
- ```
384
-
385
- Extract the repo name (e.g., `aether_vercel_demo` from the URL), convert underscores to hyphens for a cleaner subdomain (e.g., `aether-vercel-demo`), and present it as a suggestion.
386
-
387
- Use the AskQuestion tool with a text-like prompt:
388
-
389
- ```
390
- AskQuestion({
391
- title: "Custom Domain",
392
- questions: [
393
- {
394
- id: "subdomain",
395
- prompt: "Choose a subdomain for your app.\n\nYour app will be available at: <subdomain>.yottagraph.app\n\nSuggested based on your repo name: [suggested-name]",
396
- options: [
397
- { id: "suggested", label: "[suggested-name]" },
398
- { id: "other", label: "Other" }
399
- ]
400
- }
401
- ]
402
- })
403
- ```
404
-
405
- If the user selects "other", ask them to type just the subdomain they'd like (not the full domain). Confirm the full URL (`<their-choice>.yottagraph.app`) before proceeding. Always append `.yottagraph.app` to whatever the user provides.
406
-
407
- Check what domains are already configured:
408
-
409
- ```bash
410
- npx vercel domains ls 2>&1
411
- ```
412
-
413
- Add the custom domain:
414
-
415
- ```bash
416
- npx vercel domains add <subdomain>.yottagraph.app
417
- ```
418
-
419
- **If the domain add succeeds:** Vercel will automatically provision a TLS certificate. The domain should be active within a few minutes.
420
-
421
- **If it prompts for DNS configuration:** The `.yottagraph.app` domain must have a wildcard CNAME or individual CNAME record pointing to `cname.vercel-dns.com`. If DNS is already configured for `*.yottagraph.app`, new subdomains should work automatically.
422
-
423
- **If it fails with a permission error:** The user may need to verify domain ownership. Domain verification is typically done once per team — if another project in the Lovelace team already uses `.yottagraph.app`, new subdomains should be automatically trusted.
424
-
425
- After adding, verify the domain is assigned:
426
-
427
- ```bash
428
- npx vercel domains ls 2>&1
429
- ```
430
-
431
- ---
432
-
433
- ## Step 8: Verify Deployment
434
-
435
- After the deploy completes, Vercel will output a production URL (e.g., `https://project-name.vercel.app`). **Do not show this URL to the user.** Always use the custom domain chosen in Step 7 (`<subdomain>.yottagraph.app`) when referring to the deployment.
436
-
437
- ### Verify static assets are served correctly
438
-
439
- Before opening the app, confirm that `_nuxt/` static files are being served with the correct MIME type. Use the custom domain for verification:
440
-
441
- ```bash
442
- curl -sI "https://<subdomain>.yottagraph.app/_nuxt/<any-chunk>.js"
443
- ```
444
-
445
- Look for `content-type: application/javascript`. If it returns `text/html` instead, the static file routing is broken — the Nuxt serverless function is catching `_nuxt/` requests and serving the SPA template. This usually means `nitro.output.publicDir` is overriding the Vercel preset's output paths. See the troubleshooting section below.
446
-
447
- ### Verify the app loads
448
-
449
- Open `https://<subdomain>.yottagraph.app` and check:
450
-
451
- 1. The app loads (not a blank page or build error)
452
- 2. The navigation sidebar appears
453
- 3. If Auth0 is configured, the login flow works
454
-
455
- **If the app shows a blank page:**
456
-
457
- - First check the static asset verification above — a MIME type mismatch is the most common cause
458
- - Check the Vercel build logs: `npx vercel logs <deployment-url>`
459
- - Common issue: missing environment variables or build errors
460
-
461
- **If Auth0 login fails:**
462
- Instruct the user:
463
-
464
- > Your custom domain needs to be registered as an allowed callback in Auth0.
465
- >
466
- > 1. Go to https://manage.auth0.com/
467
- > 2. Navigate to Applications > Your Application > Settings
468
- > 3. Add your custom domain to these fields:
469
- > - **Allowed Callback URLs:** `https://<subdomain>.yottagraph.app/api/auth/callback`
470
- > - **Allowed Logout URLs:** `https://<subdomain>.yottagraph.app`
471
- > - **Allowed Web Origins:** `https://<subdomain>.yottagraph.app`
472
- > 4. Save changes
473
-
474
- ---
475
-
476
- ## Step 9: Summary
477
-
478
- After completing all steps, provide the user with a summary:
479
-
480
- ```
481
- Vercel Setup Complete!
482
-
483
- Project: [project name from .vercel/project.json]
484
- URL: https://[subdomain].yottagraph.app
485
- Dashboard: https://vercel.com/[org]/[project]
486
-
487
- Environment variables synced:
488
- - NPM_TOKEN
489
- - NUXT_PUBLIC_APP_ID
490
- - NUXT_PUBLIC_APP_NAME
491
- - [... list all synced vars]
492
-
493
- Next steps:
494
- - Push code changes to main branch — Vercel will auto-deploy
495
- - If using Auth0: add your custom domain as an allowed callback (see the Verify Deployment step)
496
- ```
497
-
498
- ---
499
-
500
- ## Troubleshooting
501
-
502
- ### `vercel link` fails or selects the wrong project
503
-
504
- Run `npx vercel link` again interactively (without `--yes`) and manually select the correct project from the list.
505
-
506
- ### Environment variable sync fails
507
-
508
- If `npx vercel env add` fails with a permission error, the user may need to verify they have the correct Vercel team/scope selected:
509
-
510
- ```bash
511
- npx vercel whoami
512
- npx vercel teams ls
513
- npx vercel link --yes
514
- ```
515
-
516
- ### Build fails on Vercel
517
-
518
- Check build logs:
519
-
520
- ```bash
521
- npx vercel logs <deployment-url>
522
- ```
523
-
524
- Common causes:
525
-
526
- - **Missing npm packages (E401 Unauthorized):** Private `@lovelace-ai` packages need `NPM_TOKEN` configured. See Step 1 — ensure `.npmrc` uses `${NPM_TOKEN}`, the token is set as a Vercel env var, and `.npmrc` is committed (not gitignored).
527
- - **Orval/API spec errors:** If the build fails during `prebuild` (orval code generation), ensure the API spec file exists or remove the orval dependency if unused.
528
- - **Memory issues:** The build command includes `--max-old-space-size=8192`. Vercel's free tier may have lower limits. Try removing this from the build command in `package.json` if builds OOM.