@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 +1 -1
- package/rules/api.mdc +48 -14
- package/rules/cookbook.mdc +1 -1
- package/skills/test-api-queries/SKILL.md +0 -63
- package/skills/test-api-queries/query-api.js +0 -172
package/package.json
CHANGED
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(
|
package/rules/cookbook.mdc
CHANGED
|
@@ -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();
|