@yottagraph-app/aether-instructions 1.1.18 → 1.1.20
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/commands/build_my_app.md +4 -1
- package/package.json +1 -1
- package/rules/aether.mdc +1 -1
- package/rules/agents.mdc +151 -2
- package/rules/api.mdc +108 -85
- package/rules/architecture.mdc +13 -8
- package/rules/cookbook.mdc +56 -18
- package/rules/design.mdc +3 -1
- package/rules/mcp-servers.mdc +1 -1
- package/rules/pref.mdc +16 -20
- package/rules/server.mdc +104 -0
- package/skills/data-model/newsdata/schema.yaml +10 -0
- package/skills/elemental-api/SKILL.md +4 -10
- package/skills/elemental-api/entities.md +37 -112
- package/skills/elemental-api/find.md +19 -4
- package/skills/elemental-api/graph.md +3 -3
- package/skills/elemental-api/overview.md +46 -22
- package/skills/elemental-api/schema.md +10 -30
- package/skills/elemental-api/articles.md +0 -386
- package/skills/elemental-api/events.md +0 -145
- package/skills/elemental-api/llm.md +0 -18
- package/skills/elemental-api/relationships.md +0 -310
- package/skills/elemental-api/sentiment.md +0 -93
package/commands/build_my_app.md
CHANGED
|
@@ -62,12 +62,15 @@ MCP tools — the app can still be built using the Elemental API client
|
|
|
62
62
|
|
|
63
63
|
## Step 3: Understand the Environment
|
|
64
64
|
|
|
65
|
-
First, ensure dependencies are installed (
|
|
65
|
+
First, ensure dependencies are installed (types aren't available without this):
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
68
|
test -d node_modules || npm install
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
Skills in `.cursor/skills/` are populated during project init (`node init-project.js`).
|
|
72
|
+
If that directory is empty after `npm install`, run `node init-project.js` to install them.
|
|
73
|
+
|
|
71
74
|
Then read these files to understand what's available:
|
|
72
75
|
|
|
73
76
|
1. `DESIGN.md` -- project vision and current status
|
package/package.json
CHANGED
package/rules/aether.mdc
CHANGED
|
@@ -18,4 +18,4 @@ alwaysApply: true
|
|
|
18
18
|
|
|
19
19
|
**First action for a new project:** Run `/build_my_app`.
|
|
20
20
|
|
|
21
|
-
**Task-specific rules:** `architecture` (project structure, navigation, server routes, agents, MCP), `api` (Elemental API client, schema discovery, gotchas, optional MCP servers), `design` (DESIGN.md workflow, feature docs), `ui` (page templates, layout patterns), `cookbook` (copy-paste UI patterns), `pref` (KV preferences), `branding` (colors, fonts), `server` (Nitro routes, Neon Postgres), `something-broke` (error recovery, build failures).
|
|
21
|
+
**Task-specific rules:** `architecture` (project structure, navigation, server routes, agents, MCP), `api` (Elemental API client, schema discovery, gotchas, optional MCP servers), `design` (DESIGN.md workflow, feature docs), `ui` (page templates, layout patterns), `cookbook` (copy-paste UI patterns), `pref` (KV preferences), `branding` (colors, fonts), `server` (Nitro routes, Neon Postgres, server-side Elemental API), `something-broke` (error recovery, build failures).
|
package/rules/agents.mdc
CHANGED
|
@@ -88,9 +88,9 @@ dev (`agents/` on sys.path → absolute import) and Agent Engine runtime
|
|
|
88
88
|
|
|
89
89
|
Key endpoints:
|
|
90
90
|
- `GET /elemental/metadata/schema` — entity types and properties
|
|
91
|
-
- `POST /elemental/find` — search for entities
|
|
91
|
+
- `POST /elemental/find` — search for entities by expression
|
|
92
|
+
- `POST /entities/search` — search for entities by name (batch, scored)
|
|
92
93
|
- `POST /elemental/entities/properties` — get entity property values
|
|
93
|
-
- `GET /entities/lookup?q=<name>` — look up entity by name
|
|
94
94
|
|
|
95
95
|
Requirements for agents using the Elemental API (add to `requirements.txt`):
|
|
96
96
|
```
|
|
@@ -352,6 +352,31 @@ for await (const { event, data } of readSSE(res)) {
|
|
|
352
352
|
The `done` event always includes the final extracted text, so you don't
|
|
353
353
|
need to track text deltas yourself.
|
|
354
354
|
|
|
355
|
+
## useAgentChat Gotcha — Vue Reactivity
|
|
356
|
+
|
|
357
|
+
When building a chat UI with `useAgentChat`, do NOT hold a local reference
|
|
358
|
+
to a message object after pushing it into the `messages` array. Vue's
|
|
359
|
+
reactivity only tracks mutations made through the reactive Proxy — writes
|
|
360
|
+
to the original plain object are invisible to the template.
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
// WRONG — local ref bypasses Vue's reactivity, UI won't update:
|
|
364
|
+
const msg: ChatMessage = { id: '...', role: 'agent', text: '', streaming: true };
|
|
365
|
+
messages.value.push(msg);
|
|
366
|
+
msg.text = 'hello'; // data changes, but Vue doesn't know
|
|
367
|
+
msg.streaming = false; // template still shows typing indicator
|
|
368
|
+
|
|
369
|
+
// CORRECT — access through the reactive array:
|
|
370
|
+
messages.value.push({ id: '...', role: 'agent', text: '', streaming: true });
|
|
371
|
+
const idx = messages.value.length - 1;
|
|
372
|
+
messages.value[idx].text = 'hello'; // Vue detects this
|
|
373
|
+
messages.value[idx].streaming = false; // template re-renders
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
The `useAgentChat` composable uses the correct pattern internally (via an
|
|
377
|
+
`updateAgent()` helper that writes through the array index). If you build a
|
|
378
|
+
custom chat composable or modify `sendMessage`, follow the same approach.
|
|
379
|
+
|
|
355
380
|
## Agent Design Guidelines
|
|
356
381
|
|
|
357
382
|
- Keep agents focused: one agent per domain or task type
|
|
@@ -359,3 +384,127 @@ need to track text deltas yourself.
|
|
|
359
384
|
- Tool docstrings are critical: they're the LLM's API documentation
|
|
360
385
|
- Handle errors gracefully in tools: return error messages, don't raise exceptions
|
|
361
386
|
- Use `get_schema` as a discovery tool so the agent can learn about entity types at runtime
|
|
387
|
+
|
|
388
|
+
## Agent Tool Design
|
|
389
|
+
|
|
390
|
+
LLMs are better at parsing prose than nested JSON. The difference between
|
|
391
|
+
a tool that works reliably and one that confuses the agent often comes down
|
|
392
|
+
to how the tool formats its output. Follow these principles:
|
|
393
|
+
|
|
394
|
+
### Return formatted strings, not raw JSON
|
|
395
|
+
|
|
396
|
+
This applies to ADK agent tools, where the LLM reads tool output directly.
|
|
397
|
+
MCP server tools follow different conventions (structured dicts/lists) — see
|
|
398
|
+
the `mcp-servers` rule.
|
|
399
|
+
|
|
400
|
+
The agent's LLM will try to interpret whatever the tool returns. Raw API
|
|
401
|
+
responses with nested dicts, numeric IDs, and arrays of unlabeled values
|
|
402
|
+
create unnecessary interpretation burden.
|
|
403
|
+
|
|
404
|
+
```python
|
|
405
|
+
# BAD — raw API response overwhelms the LLM:
|
|
406
|
+
def lookup_entity(name: str) -> dict:
|
|
407
|
+
resp = elemental_client.get(f"/entities/lookup?entityName={name}&maxResults=5")
|
|
408
|
+
resp.raise_for_status()
|
|
409
|
+
return resp.json() # {"results": [{"neid": "00416400910670863867", ...}]}
|
|
410
|
+
|
|
411
|
+
# GOOD — formatted string the LLM can immediately use:
|
|
412
|
+
def lookup_entity(name: str) -> str:
|
|
413
|
+
"""Look up an entity by name. Returns entity name, ID, and type."""
|
|
414
|
+
try:
|
|
415
|
+
resp = elemental_client.get(f"/entities/lookup?entityName={name}&maxResults=5")
|
|
416
|
+
resp.raise_for_status()
|
|
417
|
+
data = resp.json()
|
|
418
|
+
results = data.get("results", [])
|
|
419
|
+
if not results:
|
|
420
|
+
return f"No entities found matching '{name}'."
|
|
421
|
+
lines = []
|
|
422
|
+
for r in results[:5]:
|
|
423
|
+
lines.append(f"- {r.get('name', 'Unknown')} (ID: {r.get('neid', '?')}, Type: {r.get('type', '?')})")
|
|
424
|
+
return f"Found {len(results)} result(s) for '{name}':\n" + "\n".join(lines)
|
|
425
|
+
except Exception as e:
|
|
426
|
+
return f"Error looking up '{name}': {e}"
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Catch all exceptions — never let errors propagate
|
|
430
|
+
|
|
431
|
+
`raise_for_status()` without a try/catch will crash the tool and produce an
|
|
432
|
+
opaque error in the agent's event stream. Always catch exceptions and return
|
|
433
|
+
a descriptive error string.
|
|
434
|
+
|
|
435
|
+
```python
|
|
436
|
+
# BAD — unhandled exception kills the tool call:
|
|
437
|
+
def get_data(neid: str) -> dict:
|
|
438
|
+
resp = elemental_client.post("/elemental/entities/properties", data={...})
|
|
439
|
+
resp.raise_for_status()
|
|
440
|
+
return resp.json()
|
|
441
|
+
|
|
442
|
+
# GOOD — agent gets a useful error message it can relay to the user:
|
|
443
|
+
def get_data(neid: str) -> str:
|
|
444
|
+
"""Get properties for an entity by its NEID."""
|
|
445
|
+
try:
|
|
446
|
+
resp = elemental_client.post("/elemental/entities/properties", data={...})
|
|
447
|
+
resp.raise_for_status()
|
|
448
|
+
# ... format results ...
|
|
449
|
+
return formatted_output
|
|
450
|
+
except Exception as e:
|
|
451
|
+
return f"Error fetching data for {neid}: {e}"
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Pre-resolve known entities for focused agents
|
|
455
|
+
|
|
456
|
+
If your agent tracks specific companies, people, or assets, hardcode their
|
|
457
|
+
NEIDs instead of requiring a lookup step. This eliminates a fragile
|
|
458
|
+
search-then-resolve flow.
|
|
459
|
+
|
|
460
|
+
```python
|
|
461
|
+
TRACKED_COMPANIES = {
|
|
462
|
+
"Apple": "00416400910670863867",
|
|
463
|
+
"Tesla": "00508379502570440213",
|
|
464
|
+
"Microsoft": "00112855504880632635",
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
def get_company_info(company_name: str) -> str:
|
|
468
|
+
"""Get current information about a tracked company.
|
|
469
|
+
Available companies: Apple, Tesla, Microsoft."""
|
|
470
|
+
neid = TRACKED_COMPANIES.get(company_name)
|
|
471
|
+
if not neid:
|
|
472
|
+
return f"Unknown company '{company_name}'. Available: {', '.join(TRACKED_COMPANIES)}"
|
|
473
|
+
# ... fetch and format properties using the known NEID ...
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Combine multi-step API flows into single tools
|
|
477
|
+
|
|
478
|
+
The fewer tools the agent needs to chain together, the more reliably it
|
|
479
|
+
operates. If every query requires lookup → get properties → format, combine
|
|
480
|
+
those steps into a single tool.
|
|
481
|
+
|
|
482
|
+
```python
|
|
483
|
+
# BAD — agent must chain 3 tools correctly:
|
|
484
|
+
# 1. lookup_entity("Apple") → get NEID
|
|
485
|
+
# 2. get_schema() → find property PIDs
|
|
486
|
+
# 3. get_property_values(neid, pids) → parse values array
|
|
487
|
+
|
|
488
|
+
# GOOD — one tool does the full flow:
|
|
489
|
+
def get_company_summary(name: str) -> str:
|
|
490
|
+
"""Get a summary of a company including name, country, and industry."""
|
|
491
|
+
try:
|
|
492
|
+
neid = resolve_entity(name)
|
|
493
|
+
if not neid:
|
|
494
|
+
return f"Could not find entity '{name}'."
|
|
495
|
+
props = fetch_properties(neid, [NAME_PID, COUNTRY_PID, INDUSTRY_PID])
|
|
496
|
+
return format_company_summary(neid, props)
|
|
497
|
+
except Exception as e:
|
|
498
|
+
return f"Error getting summary for '{name}': {e}"
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### Two agent archetypes
|
|
502
|
+
|
|
503
|
+
**General explorer** — uses `get_schema` + `find_entities` +
|
|
504
|
+
`get_property_values` with runtime discovery. Good for open-ended
|
|
505
|
+
exploration but requires more tool calls and is more error-prone.
|
|
506
|
+
|
|
507
|
+
**Focused domain agent** — pre-resolves entity IDs, hardcodes relevant PIDs,
|
|
508
|
+
returns formatted strings. Fewer tools, more reliable, better for production
|
|
509
|
+
use cases. **Prefer this pattern** unless the agent genuinely needs to
|
|
510
|
+
explore arbitrary entity types.
|
package/rules/api.mdc
CHANGED
|
@@ -28,9 +28,11 @@ For Lovelace **entity types, properties, relationships, and per-source schemas**
|
|
|
28
28
|
|
|
29
29
|
## Test Before You Build
|
|
30
30
|
|
|
31
|
-
**
|
|
32
|
-
|
|
33
|
-
assumptions about
|
|
31
|
+
**ALWAYS test API calls via curl before writing code that depends on them.**
|
|
32
|
+
This is not optional — the Elemental API has response shapes that differ from
|
|
33
|
+
what the TypeScript types suggest, and assumptions about nesting, property
|
|
34
|
+
formats, and field names will be wrong without testing. This applies doubly
|
|
35
|
+
to server-side code, where you can't inspect responses in the browser console.
|
|
34
36
|
|
|
35
37
|
### How to test
|
|
36
38
|
|
|
@@ -82,15 +84,18 @@ import { useElementalClient } from '@yottagraph-app/elemental-api/client';
|
|
|
82
84
|
|
|
83
85
|
const client = useElementalClient();
|
|
84
86
|
|
|
85
|
-
const results = await client.getNEID({ entityName: 'Apple', maxResults: 5 });
|
|
86
|
-
const report = await client.getNamedEntityReport(results.neids[0]);
|
|
87
87
|
const schema = await client.getSchema();
|
|
88
|
+
const report = await client.getNamedEntityReport('00508379502570440213');
|
|
89
|
+
const entities = await client.findEntities({
|
|
90
|
+
expression: JSON.stringify({ type: 'comparison', comparison: { operator: 'string_like', pid: 8, value: 'Apple' } }),
|
|
91
|
+
limit: 5,
|
|
92
|
+
});
|
|
88
93
|
```
|
|
89
94
|
|
|
90
95
|
Types are also imported from the client:
|
|
91
96
|
|
|
92
97
|
```typescript
|
|
93
|
-
import type { NamedEntityReport
|
|
98
|
+
import type { NamedEntityReport } from '@yottagraph-app/elemental-api/client';
|
|
94
99
|
```
|
|
95
100
|
|
|
96
101
|
### Client Method Quick Reference
|
|
@@ -101,28 +106,28 @@ All methods return data directly and throw on non-2xx responses.
|
|
|
101
106
|
|
|
102
107
|
| Method | Signature | Purpose |
|
|
103
108
|
|---|---|---|
|
|
104
|
-
| `getNEID` | `(params: { entityName, maxResults?, includeNames? })` | Lookup entity by name |
|
|
105
109
|
| `findEntities` | `(body: FindEntitiesBody)` | Expression-based search (see `find.md`) |
|
|
106
110
|
| `getNamedEntityReport` | `(neid: string)` | Entity details (name, aliases, type) |
|
|
107
111
|
| `getEntityDetails` | `(neid: string)` | Alias for entity reports |
|
|
108
112
|
|
|
113
|
+
> **Entity search**: Use `findEntities()` with `string_like` on the name PID
|
|
114
|
+
> for name-based searches, or call `POST /entities/search` directly via
|
|
115
|
+
> `$fetch` for batch name resolution with scored ranking (this endpoint is
|
|
116
|
+
> not wrapped by the generated client).
|
|
117
|
+
|
|
109
118
|
**Properties and schema:**
|
|
110
119
|
|
|
111
120
|
| Method | Signature | Purpose |
|
|
112
121
|
|---|---|---|
|
|
113
122
|
| `getSchema` | `()` | All entity types (flavors) and properties (PIDs) |
|
|
114
|
-
| `getPropertyValues` | `(body: { eids: string, pids: string })` | Property values (eids
|
|
123
|
+
| `getPropertyValues` | `(body: { eids: string, pids: string })` | Property values (eids: JSON array of NEID strings; pids: JSON array of numeric PIDs) |
|
|
115
124
|
| `summarizeProperty` | `(pid: number)` | Summary stats for a property |
|
|
116
125
|
|
|
117
126
|
**Relationships and graph:**
|
|
118
127
|
|
|
119
128
|
| Method | Signature | Purpose |
|
|
120
129
|
|---|---|---|
|
|
121
|
-
| `
|
|
122
|
-
| `getLinks` | `(sourceNeid, targetNeid, params?)` | Links between two specific entities |
|
|
123
|
-
| `getLinkCounts` | `(sourceNeid, targetNeid)` | Link counts between entities |
|
|
124
|
-
| `getNeighborhood` | `(centerNeid, params?)` | Neighboring entities |
|
|
125
|
-
| `getGraphLayout` | `(centerNeid, params?)` | Graph layout for visualization |
|
|
130
|
+
| `findEntities` | `(body: { expression, limit? })` | Find linked entities via `linked` expression (see `find.md`) |
|
|
126
131
|
|
|
127
132
|
**Other:**
|
|
128
133
|
|
|
@@ -205,43 +210,82 @@ const type = res.report?.type ?? (res as any).type;
|
|
|
205
210
|
The `(res as any).name` fallback handles the case where the client is
|
|
206
211
|
eventually fixed to unwrap the response.
|
|
207
212
|
|
|
213
|
+
### Relationship property values need zero-padding to form valid NEIDs
|
|
214
|
+
|
|
215
|
+
Relationship properties (`data_nindex`) return linked entity IDs as raw
|
|
216
|
+
numbers (e.g. `4926132345040704022`). These must be **zero-padded to 20
|
|
217
|
+
characters** to form valid NEIDs. This is easy to miss and causes silent
|
|
218
|
+
failures — `getNamedEntityReport` returns a 404, `getPropertyValues`
|
|
219
|
+
returns empty results.
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
// WRONG — raw value is NOT a valid NEID:
|
|
223
|
+
const filingId = res.values[0].value; // "4926132345040704022" (19 chars)
|
|
224
|
+
|
|
225
|
+
// CORRECT — always pad to 20 characters:
|
|
226
|
+
const filingNeid = String(res.values[0].value).padStart(20, '0'); // "04926132345040704022"
|
|
227
|
+
```
|
|
228
|
+
|
|
208
229
|
> **WARNING -- `getPropertyValues()` takes JSON-stringified arrays**: The `eids`
|
|
209
230
|
> and `pids` parameters must be JSON-encoded strings, NOT native arrays. The
|
|
210
231
|
> TypeScript type is `string`, not `string[]`. Passing a raw array will silently
|
|
211
232
|
> return no data.
|
|
212
233
|
|
|
234
|
+
> **WARNING -- PIDs are numeric IDs, not string names.** Property IDs (PIDs)
|
|
235
|
+
> are integers, not human-readable names. `pids: JSON.stringify(['name'])`
|
|
236
|
+
> will fail — use `pids: JSON.stringify([8])` (where 8 is the PID for "name"
|
|
237
|
+
> from `getSchema()`). Always call `getSchema()` first to discover the
|
|
238
|
+
> numeric PID for each property.
|
|
239
|
+
|
|
213
240
|
```typescript
|
|
241
|
+
// WRONG — PIDs are numbers, not strings:
|
|
214
242
|
const values = await client.getPropertyValues({
|
|
215
243
|
eids: JSON.stringify(['00416400910670863867']),
|
|
216
|
-
pids: JSON.stringify(['name', 'country', 'industry']),
|
|
244
|
+
pids: JSON.stringify(['name', 'country', 'industry']), // FAILS
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// CORRECT — use numeric PIDs from getSchema():
|
|
248
|
+
const values = await client.getPropertyValues({
|
|
249
|
+
eids: JSON.stringify(['00416400910670863867']),
|
|
250
|
+
pids: JSON.stringify([8, 313]), // 8=name, 313=country (from schema)
|
|
217
251
|
});
|
|
218
252
|
```
|
|
219
253
|
|
|
220
|
-
###
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
type
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
254
|
+
### Traversing relationships: graph-layer vs property-layer entities
|
|
255
|
+
|
|
256
|
+
The knowledge graph has two layers:
|
|
257
|
+
|
|
258
|
+
- **Graph layer** — people, organizations, and locations are first-class
|
|
259
|
+
nodes with edges between them. Use `findEntities()` with a `linked`
|
|
260
|
+
expression to traverse these (see `find.md`).
|
|
261
|
+
- **Property layer** — documents, filings, articles, financial instruments,
|
|
262
|
+
events, and all other types are attached as property values on graph
|
|
263
|
+
nodes. Use `getPropertyValues()` with the relationship PID to traverse
|
|
264
|
+
these.
|
|
265
|
+
|
|
266
|
+
If you need to find people linked to an organization, use `findEntities`
|
|
267
|
+
with a `linked` expression:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
const res = await client.findEntities({
|
|
271
|
+
expression: JSON.stringify({
|
|
272
|
+
type: 'linked',
|
|
273
|
+
linked: {
|
|
274
|
+
to_entity: orgNeid,
|
|
275
|
+
distance: 1,
|
|
276
|
+
pids: [isOfficerPid, isDirectorPid, worksAtPid],
|
|
277
|
+
direction: 'incoming',
|
|
278
|
+
},
|
|
279
|
+
}),
|
|
280
|
+
limit: 50,
|
|
281
|
+
});
|
|
282
|
+
const personNeids = (res as any).eids ?? [];
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
For non-graph-node types (filings, documents, etc.), use `getPropertyValues`
|
|
286
|
+
with the relationship PID. Relationship properties (`data_nindex`) return
|
|
287
|
+
linked entity IDs as values. Zero-pad the returned IDs to 20 characters
|
|
288
|
+
to form valid NEIDs.
|
|
245
289
|
|
|
246
290
|
```typescript
|
|
247
291
|
const pidMap = await getPropertyPidMap(client);
|
|
@@ -255,64 +299,43 @@ const docNeids = (res.values ?? []).map((v) => String(v.value).padStart(20, '0')
|
|
|
255
299
|
|
|
256
300
|
See the **cookbook** rule for a full "Get filings for a company" recipe.
|
|
257
301
|
|
|
258
|
-
###
|
|
259
|
-
|
|
260
|
-
- **`client.getNEID()`** -- simple single-entity lookup by name
|
|
261
|
-
(`GET /entities/lookup`). Best for resolving one company/person name.
|
|
262
|
-
- **`client.findEntities()`** -- expression-based search
|
|
263
|
-
(`POST /elemental/find`). Best for filtered searches (by type, property,
|
|
264
|
-
relationship). See `find.md` for the expression language.
|
|
265
|
-
|
|
266
|
-
## Common Entity Relationships
|
|
267
|
-
|
|
268
|
-
The knowledge graph connects entities through relationship properties
|
|
269
|
-
(`data_nindex` PIDs). These are the most common patterns:
|
|
302
|
+
### Entity Search
|
|
270
303
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
| Person | `employed_by` | Organization | `getLinkedEntities` (organization is a graph node type) |
|
|
276
|
-
| Organization | `headquartered_in` | Location | `getLinkedEntities` (location is a graph node type) |
|
|
277
|
-
| Any entity | `related_to` | Any entity | `getLinkedEntities` for person/org/location; `getPropertyValues` for others |
|
|
304
|
+
Use `client.findEntities()` (`POST /elemental/find`) for entity search.
|
|
305
|
+
It supports filtering by type, property value, and relationship via the
|
|
306
|
+
expression language (see `find.md`). For name-based lookups, use
|
|
307
|
+
`string_like` on the name property (PID 8).
|
|
278
308
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
the relationship PID. See the cookbook rule (recipe #7) for a full filings
|
|
283
|
-
example.
|
|
309
|
+
For batch name resolution with scored ranking, call `POST /entities/search`
|
|
310
|
+
directly via `$fetch` (not on the generated client). See the
|
|
311
|
+
**elemental-api skill** (`entities.md`) for request/response shapes.
|
|
284
312
|
|
|
285
|
-
|
|
313
|
+
## Traversing Relationships
|
|
286
314
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
315
|
+
Relationships between entities are discoverable via the schema — use
|
|
316
|
+
`getSchema()` to find relationship properties (`data_nindex` type) and
|
|
317
|
+
their PIDs. Do NOT hardcode relationship names or PIDs; they can change
|
|
318
|
+
as the knowledge graph evolves. See the **data-model skill** for
|
|
319
|
+
source-specific schemas.
|
|
292
320
|
|
|
293
|
-
|
|
294
|
-
const res = await client.getPropertyValues({
|
|
295
|
-
eids: JSON.stringify([orgNeid]),
|
|
296
|
-
pids: JSON.stringify([filedPid]),
|
|
297
|
-
});
|
|
321
|
+
**Two traversal methods:**
|
|
298
322
|
|
|
299
|
-
|
|
300
|
-
|
|
323
|
+
- **Graph-layer entities** (person, organization, location): Use
|
|
324
|
+
`findEntities()` with a `linked` expression. See `find.md`.
|
|
325
|
+
- **Property-layer entities** (documents, filings, articles, etc.): Use
|
|
326
|
+
`getPropertyValues()` with the relationship PID. Values are entity IDs
|
|
327
|
+
that must be zero-padded to 20 characters.
|
|
301
328
|
|
|
302
|
-
|
|
303
|
-
const reports = await Promise.all(
|
|
304
|
-
docNeids.map(async (neid: string) => {
|
|
305
|
-
const r = await client.getNamedEntityReport(neid);
|
|
306
|
-
return r.report ?? r;
|
|
307
|
-
}),
|
|
308
|
-
);
|
|
309
|
-
```
|
|
329
|
+
See the **cookbook** rule (recipe #7) for a full example.
|
|
310
330
|
|
|
311
331
|
## Error Handling
|
|
312
332
|
|
|
313
333
|
```typescript
|
|
314
334
|
try {
|
|
315
|
-
const data = await client.
|
|
335
|
+
const data = await client.findEntities({
|
|
336
|
+
expression: JSON.stringify({ type: 'comparison', comparison: { operator: 'string_like', pid: 8, value: 'Apple' } }),
|
|
337
|
+
limit: 5,
|
|
338
|
+
});
|
|
316
339
|
} catch (error) {
|
|
317
340
|
console.error('API Error:', error);
|
|
318
341
|
showError('Failed to load data. Please try again.');
|
package/rules/architecture.mdc
CHANGED
|
@@ -99,21 +99,26 @@ Only add navigation if the app genuinely needs multiple views. Choose the patter
|
|
|
99
99
|
|
|
100
100
|
## New Page Checklist
|
|
101
101
|
|
|
102
|
-
- [ ] Created a design doc in `design/` (copy `design/feature_template.md`)
|
|
103
102
|
- [ ] Page in `pages/` with `<script setup lang="ts">`
|
|
104
103
|
- [ ] Uses Vuetify components and the project's dark theme
|
|
105
104
|
- [ ] Updated `DESIGN.md` with current status
|
|
105
|
+
- [ ] (Optional) Created a design doc in `design/` for complex features
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
Design docs in `design/` are most useful for incremental feature work where
|
|
108
|
+
you need to plan, track decisions, and coordinate across multiple changes.
|
|
109
|
+
For initial app builds via `/build_my_app`, skip the per-page design docs —
|
|
110
|
+
they add friction without value when building the whole app at once. Start
|
|
111
|
+
using them later when adding features to an established app.
|
|
108
112
|
|
|
109
|
-
|
|
113
|
+
## Server Routes
|
|
110
114
|
|
|
111
|
-
|
|
112
|
-
server
|
|
113
|
-
server
|
|
114
|
-
|
|
115
|
+
Nitro server routes live in `server/api/` and deploy with the app to Vercel.
|
|
116
|
+
Use server routes when you need to proxy external APIs (avoid CORS), call
|
|
117
|
+
the Elemental API server-side, or keep secrets off the client. Call them
|
|
118
|
+
from client code with `$fetch('/api/my-data/fetch')`.
|
|
115
119
|
|
|
116
|
-
|
|
120
|
+
See the `server` rule for file-routing conventions, Neon Postgres patterns,
|
|
121
|
+
and server-side Elemental API access via `useRuntimeConfig()`.
|
|
117
122
|
|
|
118
123
|
## Beyond the UI: Agents, MCP Servers, and Server Routes
|
|
119
124
|
|
package/rules/cookbook.mdc
CHANGED
|
@@ -9,7 +9,9 @@ Copy-paste patterns using the project's actual composables and Vuetify component
|
|
|
9
9
|
|
|
10
10
|
## 1. Entity Search Page
|
|
11
11
|
|
|
12
|
-
Search for entities by name and display results.
|
|
12
|
+
Search for entities by name and display results. Uses `$fetch` directly
|
|
13
|
+
because `POST /entities/search` (batch name resolution with scored ranking)
|
|
14
|
+
is not wrapped by the generated `useElementalClient()` — see the `api` rule.
|
|
13
15
|
|
|
14
16
|
```vue
|
|
15
17
|
<template>
|
|
@@ -53,19 +55,35 @@ Search for entities by name and display results.
|
|
|
53
55
|
const error = ref<string | null>(null);
|
|
54
56
|
const searched = ref(false);
|
|
55
57
|
|
|
58
|
+
function getSearchUrl() {
|
|
59
|
+
const config = useRuntimeConfig();
|
|
60
|
+
const gw = (config.public as any).gatewayUrl as string;
|
|
61
|
+
const org = (config.public as any).tenantOrgId as string;
|
|
62
|
+
return `${gw}/api/qs/${org}/entities/search`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getApiKey() {
|
|
66
|
+
return (useRuntimeConfig().public as any).qsApiKey as string;
|
|
67
|
+
}
|
|
68
|
+
|
|
56
69
|
async function search() {
|
|
57
70
|
if (!query.value.trim()) return;
|
|
58
71
|
loading.value = true;
|
|
59
72
|
error.value = null;
|
|
60
73
|
searched.value = true;
|
|
61
74
|
try {
|
|
62
|
-
const res = await
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
const res = await $fetch<any>(getSearchUrl(), {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
headers: { 'Content-Type': 'application/json', 'X-Api-Key': getApiKey() },
|
|
78
|
+
body: {
|
|
79
|
+
queries: [{ queryId: 1, query: query.value.trim() }],
|
|
80
|
+
maxResults: 10,
|
|
81
|
+
includeNames: true,
|
|
82
|
+
},
|
|
66
83
|
});
|
|
67
|
-
|
|
68
|
-
|
|
84
|
+
const matches = res?.results?.[0]?.matches ?? [];
|
|
85
|
+
results.value = matches.map((m: any) => m.neid);
|
|
86
|
+
names.value = matches.map((m: any) => m.name || m.neid);
|
|
69
87
|
} catch (e: any) {
|
|
70
88
|
error.value = e.message || 'Search failed';
|
|
71
89
|
results.value = [];
|
|
@@ -373,10 +391,14 @@ Two-column layout with selectable list and detail panel.
|
|
|
373
391
|
## 7. Get Filings for a Company
|
|
374
392
|
|
|
375
393
|
Fetch Edgar filings (or any relationship-linked documents) for an organization.
|
|
394
|
+
Uses `$fetch` for the initial entity search because `POST /entities/search`
|
|
395
|
+
is not wrapped by the generated client (same as recipe #1). Filing
|
|
396
|
+
properties are then fetched via `useElementalClient()`.
|
|
376
397
|
|
|
377
|
-
**Important:**
|
|
378
|
-
|
|
379
|
-
|
|
398
|
+
**Important:** For graph-layer entities (person, organization, location),
|
|
399
|
+
use `findEntities` with a `linked` expression. For property-layer entities
|
|
400
|
+
(documents, filings, articles), use `getPropertyValues` with the
|
|
401
|
+
relationship PID. See the `api` rule for the two-layer architecture.
|
|
380
402
|
|
|
381
403
|
```vue
|
|
382
404
|
<template>
|
|
@@ -430,22 +452,38 @@ NOT supported — use `getPropertyValues` with the relationship PID instead.
|
|
|
430
452
|
return new Map(properties.map((p: any) => [p.name, p.pid]));
|
|
431
453
|
}
|
|
432
454
|
|
|
455
|
+
function getSearchUrl() {
|
|
456
|
+
const config = useRuntimeConfig();
|
|
457
|
+
const gw = (config.public as any).gatewayUrl as string;
|
|
458
|
+
const org = (config.public as any).tenantOrgId as string;
|
|
459
|
+
return `${gw}/api/qs/${org}/entities/search`;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function getApiKey() {
|
|
463
|
+
return (useRuntimeConfig().public as any).qsApiKey as string;
|
|
464
|
+
}
|
|
465
|
+
|
|
433
466
|
async function search() {
|
|
434
467
|
if (!query.value.trim()) return;
|
|
435
468
|
loading.value = true;
|
|
436
469
|
error.value = null;
|
|
437
470
|
searched.value = true;
|
|
438
471
|
try {
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
472
|
+
const res = await $fetch<any>(getSearchUrl(), {
|
|
473
|
+
method: 'POST',
|
|
474
|
+
headers: { 'Content-Type': 'application/json', 'X-Api-Key': getApiKey() },
|
|
475
|
+
body: {
|
|
476
|
+
queries: [{ queryId: 1, query: query.value.trim(), flavors: ['organization'] }],
|
|
477
|
+
maxResults: 1,
|
|
478
|
+
includeNames: true,
|
|
479
|
+
},
|
|
443
480
|
});
|
|
444
|
-
|
|
481
|
+
const matches = res?.results?.[0]?.matches ?? [];
|
|
482
|
+
if (!matches.length) {
|
|
445
483
|
filings.value = [];
|
|
446
484
|
return;
|
|
447
485
|
}
|
|
448
|
-
const orgNeid =
|
|
486
|
+
const orgNeid = matches[0].neid;
|
|
449
487
|
|
|
450
488
|
const pidMap = await getPropertyPidMap(client);
|
|
451
489
|
const filedPid = pidMap.get('filed');
|
|
@@ -454,12 +492,12 @@ NOT supported — use `getPropertyValues` with the relationship PID instead.
|
|
|
454
492
|
return;
|
|
455
493
|
}
|
|
456
494
|
|
|
457
|
-
const
|
|
495
|
+
const propRes = await client.getPropertyValues({
|
|
458
496
|
eids: JSON.stringify([orgNeid]),
|
|
459
497
|
pids: JSON.stringify([filedPid]),
|
|
460
498
|
});
|
|
461
499
|
|
|
462
|
-
const docNeids = (
|
|
500
|
+
const docNeids = (propRes.values ?? []).map((v: any) =>
|
|
463
501
|
String(v.value).padStart(20, '0'),
|
|
464
502
|
);
|
|
465
503
|
|
package/rules/design.mdc
CHANGED
|
@@ -40,6 +40,8 @@ Feature docs are used as working documents to help a user and agent collaborate
|
|
|
40
40
|
|
|
41
41
|
When a user starts working on implementing a change, if they are using a feature doc, read the relevant feature doc before starting work. Then update the feature doc with every change you make. Be sure to create checklists as you plan work, and check off the checklists as the work is completed. Document design choices and open questions.
|
|
42
42
|
|
|
43
|
-
If the user is implementing a change that has no feature doc, encourage them to work with you to create one before starting work. A new feature doc should be created by copying `design/feature_template.md` to a new file in `design`, giving it an appropriate name, and editing it from there.
|
|
43
|
+
If the user is implementing a change that has no feature doc, encourage them to work with you to create one before starting work. A new feature doc should be created by copying `design/feature_template.md` to a new file in `design`, giving it an appropriate name, and editing it from there.
|
|
44
|
+
|
|
45
|
+
**Exception for initial app builds:** When building a new app from scratch via `/build_my_app`, skip per-page feature docs. They add overhead during greenfield builds where the entire app is being created at once. Start using feature docs once the initial app is built and you're iterating on individual features.
|
|
44
46
|
|
|
45
47
|
It is a good practice to close out one feature doc and start a new one as the focus of the work shifts. It is acceptable to be working with multiple feature docs at once, if the user is working on multiple unconnected or loosely connected features. The sections of the feature doc are flexible and you should add or remove sections to fit the needs of the project.
|