@yottagraph-app/aether-instructions 1.1.16 → 1.1.18

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yottagraph-app/aether-instructions",
3
- "version": "1.1.16",
3
+ "version": "1.1.18",
4
4
  "description": "Cursor rules, commands, and skills for Aether development",
5
5
  "files": [
6
6
  "rules",
package/rules/api.mdc CHANGED
@@ -26,6 +26,52 @@ the directory is missing, run `/update_instructions` to install it.
26
26
 
27
27
  For Lovelace **entity types, properties, relationships, and per-source schemas** (EDGAR, FRED, FDIC, etc.), read the **data-model skill** in `.cursor/skills/data-model/`. Start with `SKILL.md`, then `overview.md` and the source-specific folders. Both skills are distributed via `@yottagraph-app/aether-instructions` and installed during project init.
28
28
 
29
+ ## Test Before You Build
30
+
31
+ **Before writing app code that calls the Query Server, test the actual API
32
+ call first and verify the response shape.** This avoids wasted iteration from incorrect
33
+ assumptions about response shapes.
34
+
35
+ ### How to test
36
+
37
+ The gateway proxy authenticates on your behalf — no Auth0 tokens needed.
38
+ Read `broadchurch.yaml` for the three values you need:
39
+
40
+ | YAML path | Purpose |
41
+ |---|---|
42
+ | `gateway.url` | Portal Gateway base URL |
43
+ | `tenant.org_id` | Your tenant ID (path segment) |
44
+ | `gateway.qs_api_key` | API key (sent as `X-Api-Key` header) |
45
+
46
+ Build the request URL as `{gateway.url}/api/qs/{tenant.org_id}/{endpoint}`
47
+ and include the header `X-Api-Key: {gateway.qs_api_key}`.
48
+
49
+ ### Example calls
50
+
51
+ ```bash
52
+ # Variables — read these from broadchurch.yaml
53
+ GW="https://broadchurch-portal-194773164895.us-central1.run.app"
54
+ ORG="org_abc123"
55
+ KEY="qs_..."
56
+
57
+ # Search for an entity by name
58
+ curl -s "$GW/api/qs/$ORG/entities/search" \
59
+ -X POST -H "Content-Type: application/json" \
60
+ -H "X-Api-Key: $KEY" \
61
+ -d '{"queries":[{"queryId":1,"query":"Microsoft"}],"maxResults":3}'
62
+
63
+ # Get entity properties (form-encoded)
64
+ curl -s -X POST "$GW/api/qs/$ORG/elemental/entities/properties" \
65
+ -H "X-Api-Key: $KEY" \
66
+ -H "Content-Type: application/x-www-form-urlencoded" \
67
+ --data-urlencode 'eids=["00416400910670863867"]' \
68
+ --data-urlencode 'pids=[8,313]' | jq .
69
+ ```
70
+
71
+ `/elemental/find` and `/elemental/entities/properties` require
72
+ `application/x-www-form-urlencoded` with JSON-stringified parameter values.
73
+ All other endpoints accept `application/json`.
74
+
29
75
  ## Client Usage
30
76
 
31
77
  All API calls go through `useElementalClient()` from `@yottagraph-app/elemental-api/client`.
@@ -78,18 +124,6 @@ All methods return data directly and throw on non-2xx responses.
78
124
  | `getNeighborhood` | `(centerNeid, params?)` | Neighboring entities |
79
125
  | `getGraphLayout` | `(centerNeid, params?)` | Graph layout for visualization |
80
126
 
81
- **News, events, and sentiment:**
82
-
83
- | Method | Signature | Purpose |
84
- |---|---|---|
85
- | `getArticle` | `(artid: string)` | Article by ID |
86
- | `getArticleText` | `(artid: string)` | Article full text |
87
- | `getEvent` | `(eveid: string)` | Event by ID |
88
- | `getEventsForEntity` | `(params: { neid, startTime?, endTime? })` | Events involving an entity |
89
- | `getMentions` | `(params: { neid, startTime?, endTime? })` | Mention codes for entities |
90
- | `getMentionCounts` | `(params: { neid, ... })` | Bucketed mention counts |
91
- | `getNamedEntitySentiment` | `(neid: string)` | Sentiment for an entity |
92
-
93
127
  **Other:**
94
128
 
95
129
  | Method | Signature | Purpose |
@@ -216,7 +250,7 @@ const res = await client.getPropertyValues({
216
250
  eids: JSON.stringify([orgNeid]),
217
251
  pids: JSON.stringify([filedPid]),
218
252
  });
219
- const docNeids = res.values.map((v) => String(v.value).padStart(20, '0'));
253
+ const docNeids = (res.values ?? []).map((v) => String(v.value).padStart(20, '0'));
220
254
  ```
221
255
 
222
256
  See the **cookbook** rule for a full "Get filings for a company" recipe.
@@ -263,7 +297,7 @@ const res = await client.getPropertyValues({
263
297
  });
264
298
 
265
299
  // 3. Pad IDs to 20 chars to form valid NEIDs
266
- const docNeids = res.values.map((v: any) => String(v.value).padStart(20, '0'));
300
+ const docNeids = (res.values ?? []).map((v: any) => String(v.value).padStart(20, '0'));
267
301
 
268
302
  // 4. Get details for each linked entity (response is nested under .report)
269
303
  const reports = await Promise.all(
@@ -459,7 +459,7 @@ NOT supported — use `getPropertyValues` with the relationship PID instead.
459
459
  pids: JSON.stringify([filedPid]),
460
460
  });
461
461
 
462
- const docNeids = res.values.map((v: any) =>
462
+ const docNeids = (res.values ?? []).map((v: any) =>
463
463
  String(v.value).padStart(20, '0'),
464
464
  );
465
465
 
package/rules/pref.mdc CHANGED
@@ -46,19 +46,23 @@ The backing store auto-initializes on first use — no need to call
46
46
 
47
47
  ## Local Development
48
48
 
49
- When KV credentials (`KV_REST_API_URL`, `KV_REST_API_TOKEN`) are not set
50
- (e.g. local dev without Vercel env vars), the KV server routes return
51
- `undefined` for reads and silently skip writes. Prefs will work with
52
- their default values but won't persist across page refreshes.
49
+ KV credentials are only available in deployed builds (Vercel auto-injects
50
+ them at runtime). In local dev, `getRedis()` returns `null` and KV routes
51
+ return `undefined` for reads and silently skip writes. `Pref<T>` still
52
+ works with its default value but won't persist across page refreshes.
53
53
 
54
- For full local persistence, copy the KV credentials from your Vercel
55
- project settings into your local `.env` file (or use the Portal's
56
- "Get .env file" feature).
54
+ This is expected push to `main` and test persistence on the deployed build.
55
+
56
+ **Auth dependency:** All `/api/kv/*` routes call `unsealCookie(event)` to
57
+ identify the user. In dev mode (no `NUXT_PUBLIC_AUTH0_CLIENT_SECRET` set),
58
+ this is bypassed automatically using `NUXT_PUBLIC_USER_NAME`. If you set an
59
+ Auth0 client secret without proper cookie setup, KV writes will silently
60
+ no-op.
57
61
 
58
62
  ## Direct API Alternative
59
63
 
60
64
  If you prefer not to use the `Pref<T>` class, you can call the KV
61
- routes directly:
65
+ routes directly from client-side code:
62
66
 
63
67
  ```typescript
64
68
  // Read
@@ -73,6 +77,11 @@ await $fetch('/api/kv/write', {
73
77
  });
74
78
  ```
75
79
 
80
+ These routes require the browser's auth cookie — they work from client-side
81
+ `$fetch` calls but not from server routes or external scripts. For
82
+ server-to-server KV access, use `getRedis()` from `server/utils/redis.ts`
83
+ directly.
84
+
76
85
  ## Feature-Scoped Preferences
77
86
 
78
87
  Features should namespace preferences under the app's prefix:
package/rules/server.mdc CHANGED
@@ -68,6 +68,9 @@ if (redis) {
68
68
 
69
69
  Returns `null` if KV is not configured (env vars missing). Always check.
70
70
 
71
+ For client-side preferences, use `usePrefsStore()` and `Pref<T>` instead of
72
+ calling KV routes directly — see the `pref` rule.
73
+
71
74
  ## Neon Postgres
72
75
 
73
76
  If `DATABASE_URL` is in `.env`, Postgres is ready to use. The project init
@@ -81,6 +84,12 @@ automatically when a Neon database is detected.
81
84
  - If either is missing, the project wasn't provisioned with Neon (add it
82
85
  from the Broadchurch Portal, then re-run `node init-project.js`)
83
86
 
87
+ **Local dev:** `DATABASE_URL` is not yet available for local development.
88
+ `getDb()` returns `null` when the credential is missing or invalid. Write
89
+ your server routes to handle this gracefully (return a "database not
90
+ configured" error or empty state). Push to `main` to test with a real
91
+ database on the deployed build, where credentials are auto-injected.
92
+
84
93
  ### Usage
85
94
 
86
95
  `server/utils/neon.ts` exports `getDb()` (same lazy-init pattern as
@@ -103,6 +112,25 @@ The Neon driver uses tagged template literals for automatic SQL injection
103
112
  protection — `await sql\`SELECT * FROM notes WHERE id = ${id}\`` is safe.
104
113
  No ORM, no query builder, no connection pool setup needed.
105
114
 
115
+ ### Creating tables
116
+
117
+ There is no migrations framework. Use `CREATE TABLE IF NOT EXISTS` directly
118
+ in a setup route or at the top of a route that needs the table:
119
+
120
+ ```typescript
121
+ const sql = getDb()!;
122
+ await sql`CREATE TABLE IF NOT EXISTS notes (
123
+ id SERIAL PRIMARY KEY,
124
+ content TEXT NOT NULL,
125
+ created_at TIMESTAMPTZ DEFAULT NOW()
126
+ )`;
127
+ ```
128
+
129
+ For simple apps, putting the `CREATE TABLE IF NOT EXISTS` in each route that
130
+ uses the table is fine — it's a no-op after the first call. For more complex
131
+ schemas, create a `server/api/db/setup.post.ts` route that initializes all
132
+ tables.
133
+
106
134
  ### If `server/utils/neon.ts` doesn't exist
107
135
 
108
136
  Create it manually (or re-run init):
@@ -136,3 +164,6 @@ export function getDb(): NeonQueryFunction | null {
136
164
  - They have access to Redis, Neon Postgres, secrets, and server-only APIs
137
165
  - They do NOT have access to Vue composables, Vuetify, or any client-side code
138
166
  - Use `defineEventHandler`, not Vue component patterns
167
+
168
+ See `architecture` rule for the full data architecture overview, `pref` rule
169
+ for client-side KV preferences.
@@ -1,63 +0,0 @@
1
- ---
2
- name: test-api-queries
3
- description: Test Elemental API queries before integrating into app code. Use when writing code that calls the query server.
4
- ---
5
-
6
- # Test API Queries
7
-
8
- **When writing code that calls the Query Server, test queries with this CLI tool before integrating them into app code.**
9
-
10
- This prevents wasted iteration cycles from incorrect assumptions about API responses.
11
-
12
- ## Workflow
13
-
14
- 1. **Test the query** using the CLI tool below
15
- 2. **Verify the response** matches what you expect
16
- 3. **Then write the app code** with confidence
17
-
18
- ## CLI Tool
19
-
20
- Location: `~/.cursor/skills/test-api-queries/query-api.js`
21
-
22
- ### Requirements
23
-
24
- The CLI reads these from the project's `.env` file or shell environment:
25
-
26
- - `AUTH0_M2M_DEV_TOKEN` - Auth0 M2M dev token for API access
27
- - `NUXT_PUBLIC_QUERY_SERVER_ADDRESS` - Query server URL
28
-
29
- **Before using this tool**, check that both variables are set in the project's `.env` file. If either is missing or empty, add them to `.env` (see `.env.example` for the format). Tell the user to ask the engineering team for the token value if they don't have it.
30
-
31
- ### Usage
32
-
33
- ```bash
34
- node ~/.cursor/skills/test-api-queries/query-api.js <METHOD> <ENDPOINT> [JSON_PARAMS]
35
- ```
36
-
37
- ### Examples
38
-
39
- ```bash
40
- # Search for entities by name
41
- node ~/.cursor/skills/test-api-queries/query-api.js POST /entities/search \
42
- '{"queries":[{"queryId":1,"query":"Apple"}],"maxResults":3}'
43
-
44
- # Get entity details by ID
45
- node ~/.cursor/skills/test-api-queries/query-api.js GET /entities/00416400910670863867
46
-
47
- # Find entities by type
48
- node ~/.cursor/skills/test-api-queries/query-api.js POST /elemental/find \
49
- '{"expression":{"type":"is_type","is_type":{"fid":10}},"limit":5}'
50
-
51
- # Find entities with a specific property
52
- node ~/.cursor/skills/test-api-queries/query-api.js POST /elemental/find \
53
- '{"expression":{"type":"comparison","comparison":{"operator":"has_value","pid":313}},"limit":10}'
54
-
55
- # Get entity properties
56
- node ~/.cursor/skills/test-api-queries/query-api.js POST /elemental/entities/properties \
57
- '{"eids":["00416400910670863867"],"pids":[8,313]}'
58
- ```
59
-
60
- ### Notes
61
-
62
- - Run from the project directory so the `.env` file is found
63
- - `/elemental/find` and `/elemental/entities/properties` require form-encoded bodies—the tool handles this automatically
@@ -1,172 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Simple CLI tool to query the Elemental API.
4
- *
5
- * Auth: set AUTH0_M2M_DEV_TOKEN as an env var or in your .env file.
6
- * Server: set NUXT_PUBLIC_QUERY_SERVER_ADDRESS in your .env file.
7
- */
8
-
9
- const fs = require('fs');
10
- const path = require('path');
11
-
12
- const FORM_ENCODED_ENDPOINTS = ['/elemental/find', '/elemental/entities/properties'];
13
-
14
- function loadEnvFile() {
15
- // Try current working directory first (project .env), then script directory
16
- const envPaths = [path.join(process.cwd(), '.env'), path.join(__dirname, '.env')];
17
-
18
- for (const envPath of envPaths) {
19
- try {
20
- const contents = fs.readFileSync(envPath, 'utf8');
21
- for (const line of contents.split('\n')) {
22
- const trimmed = line.trim();
23
- if (!trimmed || trimmed.startsWith('#')) continue;
24
- const eqIndex = trimmed.indexOf('=');
25
- if (eqIndex === -1) continue;
26
- const key = trimmed.slice(0, eqIndex).trim();
27
- const value = trimmed.slice(eqIndex + 1).trim();
28
- if (!process.env[key]) {
29
- process.env[key] = value;
30
- }
31
- }
32
- return; // Stop after first successful load
33
- } catch {
34
- // Try next path
35
- }
36
- }
37
- }
38
-
39
- function requiresFormEncoding(endpoint) {
40
- return FORM_ENCODED_ENDPOINTS.some((e) => endpoint.startsWith(e));
41
- }
42
-
43
- function buildFormBody(endpoint, params) {
44
- const formParams = new URLSearchParams();
45
- for (const [key, value] of Object.entries(params)) {
46
- if (typeof value === 'object') {
47
- formParams.append(key, JSON.stringify(value));
48
- } else {
49
- formParams.append(key, String(value));
50
- }
51
- }
52
- return formParams.toString();
53
- }
54
-
55
- async function main() {
56
- loadEnvFile();
57
-
58
- const args = process.argv.slice(2);
59
-
60
- if (args.length < 2 || args[0] === '--help' || args[0] === '-h') {
61
- console.log(`
62
- Elemental API Query Tool
63
-
64
- Usage:
65
- node query-api.js <METHOD> <ENDPOINT> [JSON_PARAMS]
66
-
67
- Examples:
68
- # Search for entities (JSON body)
69
- node query-api.js POST /entities/search '{"queries":[{"queryId":1,"query":"Apple"}],"maxResults":3}'
70
-
71
- # Get entity details
72
- node query-api.js GET /entities/00416400910670863867
73
-
74
- # Find entities by expression (auto form-encoded)
75
- node query-api.js POST /elemental/find '{"expression":{"type":"is_type","is_type":{"fid":10}},"limit":5}'
76
-
77
- # Find entities with a property value
78
- node query-api.js POST /elemental/find '{"expression":{"type":"comparison","comparison":{"operator":"has_value","pid":313}},"limit":10}'
79
-
80
- # Get entity properties (auto form-encoded)
81
- node query-api.js POST /elemental/entities/properties '{"eids":["00416400910670863867"],"pids":[8,313]}'
82
-
83
- Environment variables (required):
84
- AUTH0_M2M_DEV_TOKEN Bearer token for API auth
85
- NUXT_PUBLIC_QUERY_SERVER_ADDRESS Query server URL (e.g. https://query.news.prod.g.lovelace.ai)
86
-
87
- Setup:
88
- 1. Get a dev token from your team
89
- 2. Add AUTH0_M2M_DEV_TOKEN to your .env file or export in your shell
90
- (NUXT_PUBLIC_QUERY_SERVER_ADDRESS should already be in your .env from project init)
91
-
92
- Note: /elemental/find and /elemental/entities/properties require form-encoded bodies.
93
- This tool automatically handles the encoding for these endpoints.
94
- `);
95
- process.exit(0);
96
- }
97
-
98
- const [method, endpoint, jsonParams] = args;
99
-
100
- const token = process.env.AUTH0_M2M_DEV_TOKEN;
101
- if (!token) {
102
- console.error('Error: AUTH0_M2M_DEV_TOKEN is not set.');
103
- console.error('Add it to your .env file or export it in your shell.');
104
- console.error('Run: node query-api.js --help');
105
- process.exit(1);
106
- }
107
-
108
- let params = {};
109
- if (jsonParams) {
110
- try {
111
- params = JSON.parse(jsonParams);
112
- } catch (err) {
113
- console.error('Error: Invalid JSON parameters');
114
- console.error(err.message);
115
- process.exit(1);
116
- }
117
- }
118
-
119
- const server = process.env.NUXT_PUBLIC_QUERY_SERVER_ADDRESS;
120
- if (!server) {
121
- console.error('Error: NUXT_PUBLIC_QUERY_SERVER_ADDRESS is not set.');
122
- console.error('Add it to your .env file or export it in your shell.');
123
- console.error('Run: node query-api.js --help');
124
- process.exit(1);
125
- }
126
-
127
- const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : '/' + endpoint;
128
- let url = `${server}${normalizedEndpoint}`;
129
-
130
- const useFormEncoding = requiresFormEncoding(normalizedEndpoint);
131
- const fetchOptions = {
132
- method: method.toUpperCase(),
133
- headers: {
134
- Authorization: `Bearer ${token}`,
135
- 'Content-Type': useFormEncoding
136
- ? 'application/x-www-form-urlencoded'
137
- : 'application/json',
138
- Accept: 'application/json',
139
- },
140
- };
141
-
142
- if (method.toUpperCase() === 'GET' && Object.keys(params).length > 0) {
143
- const queryString = new URLSearchParams(params).toString();
144
- url = `${url}?${queryString}`;
145
- } else if (method.toUpperCase() !== 'GET' && Object.keys(params).length > 0) {
146
- if (useFormEncoding) {
147
- fetchOptions.body = buildFormBody(normalizedEndpoint, params);
148
- } else {
149
- fetchOptions.body = JSON.stringify(params);
150
- }
151
- }
152
-
153
- console.error(`\n📡 ${method.toUpperCase()} ${url}\n`);
154
-
155
- try {
156
- const response = await fetch(url, fetchOptions);
157
- const data = await response.json();
158
-
159
- if (!response.ok) {
160
- console.error(`❌ Error ${response.status}: ${response.statusText}`);
161
- } else {
162
- console.error(`✅ Success (${response.status})\n`);
163
- }
164
-
165
- console.log(JSON.stringify(data, null, 2));
166
- } catch (err) {
167
- console.error('❌ Request failed:', err.message);
168
- process.exit(1);
169
- }
170
- }
171
-
172
- main();