@yottagraph-app/aether-instructions 1.1.17 → 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.17",
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
 
@@ -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();