@yottagraph-app/aether-instructions 1.1.22 → 1.1.23

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.
@@ -92,7 +92,47 @@ Key capabilities:
92
92
 
93
93
  ---
94
94
 
95
- ## Step 4: Design the UX
95
+ ## Step 4: Verify Data Availability
96
+
97
+ Before designing UX, verify that the data your app needs actually exists
98
+ in the knowledge graph. This prevents building features around empty data.
99
+
100
+ **If MCP tools are available:**
101
+
102
+ ```
103
+ elemental_get_schema() → list entity types, confirm your target types exist
104
+ elemental_get_entity(entity="Microsoft") → verify entity lookup works with a known entity
105
+ elemental_get_entity(entity="Apple Inc") → try another known entity
106
+ ```
107
+
108
+ If schema calls succeed but entity lookups return "not found," that means
109
+ the entity type exists in the schema but has no data. That's a **data
110
+ issue**, not a broken server. Try different, well-known entity names.
111
+
112
+ **If MCP tools are NOT available, use curl:**
113
+
114
+ ```bash
115
+ # Read credentials from broadchurch.yaml
116
+ GW=$(grep 'url:' broadchurch.yaml | head -1 | sed 's/.*"\(.*\)".*/\1/')
117
+ ORG=$(grep 'org_id:' broadchurch.yaml | sed 's/.*"\(.*\)".*/\1/')
118
+ KEY=$(grep 'qs_api_key:' broadchurch.yaml | sed 's/.*"\(.*\)".*/\1/')
119
+
120
+ # List entity types
121
+ curl -s "$GW/api/qs/$ORG/elemental/metadata/schema" -H "X-Api-Key: $KEY"
122
+
123
+ # Search for a known entity
124
+ curl -s "$GW/api/qs/$ORG/entities/search" \
125
+ -X POST -H "Content-Type: application/json" -H "X-Api-Key: $KEY" \
126
+ -d '{"queries":[{"queryId":1,"query":"Microsoft"}],"maxResults":3,"includeNames":true}'
127
+ ```
128
+
129
+ **Document what you find.** If certain entity types have sparse data, note
130
+ it in your UX plan. Design features around data that actually exists, and
131
+ mark aspirational features (that need more data) as future work.
132
+
133
+ ---
134
+
135
+ ## Step 5: Design the UX
96
136
 
97
137
  Based on the brief, think about the right UX for this specific problem. Do NOT default to a sidebar-with-tabs layout. Consider:
98
138
 
@@ -116,7 +156,7 @@ Present the plan to the user and ask for approval before proceeding.
116
156
 
117
157
  ---
118
158
 
119
- ## Step 5: Build
159
+ ## Step 6: Build
120
160
 
121
161
  Implement the plan:
122
162
 
@@ -128,6 +168,13 @@ Implement the plan:
128
168
  6. Use Vuetify components and the project's dark theme
129
169
  7. Update `DESIGN.md` with what you built
130
170
 
171
+ **Use the pre-built platform utilities:**
172
+
173
+ - `useElementalSchema()` — schema discovery with caching, flavor/PID lookup helpers
174
+ - `buildGatewayUrl()`, `getApiKey()`, `padNeid()` from `utils/elementalHelpers`
175
+ - `searchEntities()`, `getEntityName()` from `utils/elementalHelpers`
176
+ - `useElementalClient()` from `@yottagraph-app/elemental-api/client`
177
+
131
178
  **Follow the project's coding conventions:**
132
179
 
133
180
  - `<script setup lang="ts">` for all Vue components
@@ -136,7 +183,7 @@ Implement the plan:
136
183
 
137
184
  ---
138
185
 
139
- ## Step 6: Verify
186
+ ## Step 7: Verify
140
187
 
141
188
  After building, check dependencies are installed and run a build:
142
189
 
@@ -151,7 +198,7 @@ Then suggest the user run `npm run dev` to preview their app locally.
151
198
 
152
199
  ---
153
200
 
154
- ## Step 7: Next Steps
201
+ ## Step 8: Next Steps
155
202
 
156
203
  > Your app is taking shape! Here's what you can do next:
157
204
  >
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yottagraph-app/aether-instructions",
3
- "version": "1.1.22",
3
+ "version": "1.1.23",
4
4
  "description": "Cursor rules, commands, and skills for Aether development",
5
5
  "files": [
6
6
  "rules",
package/rules/api.mdc CHANGED
@@ -50,6 +50,29 @@ elemental_get_related(entity="Apple",
50
50
  MCP tells you the correct flavor IDs, property IDs, and data shapes. Use
51
51
  these to inform your REST implementation.
52
52
 
53
+ **Verify MCP is working with known-good queries:**
54
+
55
+ ```
56
+ elemental_get_schema() → should return flavors + properties
57
+ elemental_get_entity(entity="Microsoft") → should resolve to a company
58
+ elemental_get_entity(entity="Apple Inc") → another known entity
59
+ elemental_health() → server health check
60
+ ```
61
+
62
+ **Interpreting MCP errors — do NOT assume the server is broken:**
63
+
64
+ - `entity not found` or 404 in entity lookup → the entity doesn't exist
65
+ in the knowledge graph, not a connectivity problem. Try a different entity.
66
+ - `failed to get property values: 404` → the entity was resolved but has
67
+ no data for those properties. The MCP server is working correctly.
68
+ - Schema calls succeed but entity calls fail → data is sparse for that
69
+ entity type. Try well-known entities (Microsoft, Apple Inc, JPMorgan).
70
+ - If `elemental_health()` fails → actual connectivity problem.
71
+
72
+ **Key insight:** A 404 from an MCP entity/property call means "not found,"
73
+ not "server broken." Always test with known entities before concluding
74
+ the server is down.
75
+
53
76
  ### Step 2: curl (verify exact request/response shapes)
54
77
 
55
78
  MCP doesn't cover every REST endpoint (e.g. `/elemental/find` expressions).
@@ -101,11 +124,44 @@ All other endpoints accept `application/json`.
101
124
  **Interpreting errors:** 400 = expression syntax is wrong. 500 = expression
102
125
  is valid but the query failed (wrong PID, unsupported operator for that
103
126
  property type). 200 + empty `eids` = query worked but no results match.
127
+ 404 from entity/property endpoints = entity or data doesn't exist (not a
128
+ server error). Always test with known entities (e.g. search for "Microsoft")
129
+ before assuming the API is broken.
104
130
 
105
131
  ### Step 3: Implement with confidence
106
132
 
107
133
  Now write your composable or server route, knowing the exact API shapes.
108
134
 
135
+ ## Pre-Built Helpers
136
+
137
+ The template includes composables and utilities that handle common
138
+ Elemental API patterns. **Use these instead of writing from scratch:**
139
+
140
+ ### `useElementalSchema()` — Schema Discovery with Caching
141
+
142
+ ```typescript
143
+ const { flavors, properties, flavorByName, pidByName, refresh } = useElementalSchema();
144
+ await refresh(); // fetches once, then cached
145
+ const articleFid = flavorByName('article'); // → number | null
146
+ const namePid = pidByName('name'); // → typically 8
147
+ ```
148
+
149
+ Handles the dual response shapes (`res.schema.flavors` vs `res.flavors`)
150
+ and the `fid`/`findex` naming inconsistency automatically.
151
+
152
+ ### `utils/elementalHelpers` — Gateway URL Helpers
153
+
154
+ ```typescript
155
+ import { buildGatewayUrl, getApiKey, padNeid, searchEntities, getEntityName } from '~/utils/elementalHelpers';
156
+
157
+ const url = buildGatewayUrl('entities/search'); // full gateway URL
158
+ const key = getApiKey(); // from runtimeConfig
159
+ const neid = padNeid('4926132345040704022'); // → "04926132345040704022"
160
+
161
+ const results = await searchEntities('Microsoft'); // batch name search
162
+ const name = await getEntityName(neid); // display name lookup
163
+ ```
164
+
109
165
  ## Client Usage
110
166
 
111
167
  All API calls go through `useElementalClient()` from `@yottagraph-app/elemental-api/client`.
@@ -388,7 +388,169 @@ Two-column layout with selectable list and detail panel.
388
388
  </script>
389
389
  ```
390
390
 
391
- ## 7. Get Filings for a Company
391
+ ## 7. News Feed Recent Articles with Sentiment
392
+
393
+ Fetch recent articles from the knowledge graph. Uses `useElementalSchema()`
394
+ for runtime flavor/PID discovery and `buildGatewayUrl()` for gateway access.
395
+
396
+ ```vue
397
+ <template>
398
+ <div class="d-flex flex-column fill-height pa-4">
399
+ <h1 class="text-h5 mb-4">Recent News</h1>
400
+ <v-alert v-if="error" type="error" variant="tonal" class="mb-4" closable>
401
+ {{ error }}
402
+ </v-alert>
403
+ <v-progress-linear v-if="loading" indeterminate class="mb-4" />
404
+ <v-list v-if="articles.length" lines="three">
405
+ <v-list-item v-for="a in articles" :key="a.neid">
406
+ <template #title>
407
+ <span>{{ a.name || a.neid }}</span>
408
+ <v-chip
409
+ v-if="a.sentiment"
410
+ size="x-small"
411
+ class="ml-2"
412
+ :color="a.sentiment > 0 ? 'success' : a.sentiment < 0 ? 'error' : 'grey'"
413
+ >
414
+ {{ a.sentiment > 0 ? 'Bullish' : a.sentiment < 0 ? 'Bearish' : 'Neutral' }}
415
+ </v-chip>
416
+ </template>
417
+ <template #subtitle>{{ a.neid }}</template>
418
+ </v-list-item>
419
+ </v-list>
420
+ <v-empty-state
421
+ v-else-if="!loading"
422
+ headline="No articles found"
423
+ icon="mdi-newspaper-variant-outline"
424
+ />
425
+ </div>
426
+ </template>
427
+
428
+ <script setup lang="ts">
429
+ import { useElementalClient } from '@yottagraph-app/elemental-api/client';
430
+ import { padNeid } from '~/utils/elementalHelpers';
431
+
432
+ const client = useElementalClient();
433
+ const { flavorByName, pidByName, refresh: loadSchema } = useElementalSchema();
434
+
435
+ const articles = ref<{ neid: string; name: string; sentiment: number | null }[]>([]);
436
+ const loading = ref(false);
437
+ const error = ref<string | null>(null);
438
+
439
+ onMounted(async () => {
440
+ loading.value = true;
441
+ try {
442
+ await loadSchema();
443
+ const articleFid = flavorByName('article');
444
+ if (!articleFid) {
445
+ error.value = 'Article entity type not found in schema';
446
+ return;
447
+ }
448
+
449
+ const res = await client.findEntities({
450
+ expression: JSON.stringify({ type: 'is_type', is_type: { fid: articleFid } }),
451
+ limit: 20,
452
+ });
453
+ const neids: string[] = (res as any).eids ?? [];
454
+
455
+ if (!neids.length) {
456
+ return;
457
+ }
458
+
459
+ const namePid = pidByName('name');
460
+ const sentimentPid = pidByName('sentiment');
461
+ const pids = [namePid, sentimentPid].filter((p): p is number => p !== null);
462
+
463
+ const props = await client.getPropertyValues({
464
+ eids: JSON.stringify(neids),
465
+ pids: JSON.stringify(pids),
466
+ });
467
+
468
+ const valueMap = new Map<string, Record<number, any>>();
469
+ for (const v of (props as any).values ?? []) {
470
+ const eid = padNeid(v.eid ?? v.entity_id ?? '');
471
+ if (!valueMap.has(eid)) valueMap.set(eid, {});
472
+ valueMap.get(eid)![v.pid] = v.value;
473
+ }
474
+
475
+ articles.value = neids.map((neid) => {
476
+ const vals = valueMap.get(neid) ?? {};
477
+ return {
478
+ neid,
479
+ name: namePid ? (vals[namePid] as string) ?? neid : neid,
480
+ sentiment: sentimentPid ? (vals[sentimentPid] as number) ?? null : null,
481
+ };
482
+ });
483
+ } catch (e: any) {
484
+ error.value = e.message || 'Failed to load articles';
485
+ } finally {
486
+ loading.value = false;
487
+ }
488
+ });
489
+ </script>
490
+ ```
491
+
492
+ ## 8. Entity Search with Gateway Helpers
493
+
494
+ Simpler version of recipe #1 using the pre-built `searchEntities()` helper.
495
+
496
+ ```vue
497
+ <template>
498
+ <div class="d-flex flex-column fill-height pa-4">
499
+ <h1 class="text-h5 mb-4">Entity Search</h1>
500
+ <v-text-field
501
+ v-model="query"
502
+ label="Search entities"
503
+ prepend-inner-icon="mdi-magnify"
504
+ variant="outlined"
505
+ @keyup.enter="search"
506
+ :loading="loading"
507
+ />
508
+ <v-alert v-if="error" type="error" variant="tonal" class="mt-2" closable>
509
+ {{ error }}
510
+ </v-alert>
511
+ <v-list v-if="results.length" class="mt-4">
512
+ <v-list-item
513
+ v-for="r in results"
514
+ :key="r.neid"
515
+ :title="r.name"
516
+ :subtitle="r.neid"
517
+ />
518
+ </v-list>
519
+ <v-empty-state
520
+ v-else-if="searched && !loading"
521
+ headline="No results"
522
+ icon="mdi-magnify-remove-outline"
523
+ />
524
+ </div>
525
+ </template>
526
+
527
+ <script setup lang="ts">
528
+ import { searchEntities } from '~/utils/elementalHelpers';
529
+
530
+ const query = ref('');
531
+ const results = ref<{ neid: string; name: string }[]>([]);
532
+ const loading = ref(false);
533
+ const error = ref<string | null>(null);
534
+ const searched = ref(false);
535
+
536
+ async function search() {
537
+ if (!query.value.trim()) return;
538
+ loading.value = true;
539
+ error.value = null;
540
+ searched.value = true;
541
+ try {
542
+ results.value = await searchEntities(query.value.trim());
543
+ } catch (e: any) {
544
+ error.value = e.message || 'Search failed';
545
+ results.value = [];
546
+ } finally {
547
+ loading.value = false;
548
+ }
549
+ }
550
+ </script>
551
+ ```
552
+
553
+ ## 9. Get Filings for a Company
392
554
 
393
555
  Fetch Edgar filings (or any relationship-linked documents) for an organization.
394
556
  Uses `$fetch` for the initial entity search because `POST /entities/search`