This commit is contained in:
2026-05-10 21:29:30 +02:00
parent 6714493af9
commit cd6dc3fc8f
5 changed files with 190 additions and 3 deletions

View File

@@ -3,5 +3,7 @@ npm-debug.log*
.env .env
.git .git
.idea/* .idea/*
!.idea/
!.idea/SKILL.md
coverage coverage
dist dist

4
.gitignore vendored
View File

@@ -6,4 +6,6 @@ npm-debug.log*
coverage/ coverage/
dist/ dist/
.DS_Store .DS_Store
.idea .idea/*
!.idea/
!.idea/SKILL.md

169
.idea/SKILL.md generated Normal file
View File

@@ -0,0 +1,169 @@
# Dune API Skill For OpenClaw Agents
You are working with the Dune API, a Node.js Express service that imports Dune: Awakening data from Questlog into MongoDB and exposes it through REST endpoints for OpenClaw.
## Primary Goal
Help OpenClaw retrieve complete Dune: Awakening records with the least guesswork. Prefer the detailed singular datasets for client-facing lookups and search.
## Public Base URL
Use this production API base URL:
```text
https://dune.api.coppnic.cc
```
Swagger/OpenAPI is available here:
```text
https://dune.api.coppnic.cc/openapi.json
https://ui.dune.api.coppnic.cc/docs
```
## Best Endpoints For OpenClaw
Prefer singular datasets because they contain full Questlog single-record payloads in `raw`:
```text
GET /api/item
GET /api/item/{id}
GET /api/skill
GET /api/skill/{id}
GET /api/recipe
GET /api/recipe/{id}
GET /api/placeable
GET /api/placeable/{id}
GET /api/npc
GET /api/npc/{id}
```
Useful examples:
```text
GET /api/item/LongRifle_Unique_Poison_03?language=en
GET /api/item/Bloodsack_02?language=en
GET /api/skill/skills_ability_poisonmine?language=en
GET /api/recipe/Bloodsack_2_Recipe?language=en
GET /api/placeable/Atre_Banner_Placeable?language=en
GET /api/npc/bs43q?language=en
```
For text search:
```text
GET /api/search?q=rifle&datasets=item&language=en
GET /api/search?q=poison&datasets=item,skill,recipe&language=en
```
Supported languages are `en` and `de`.
## Dataset Rules
Detailed singular datasets:
| Dataset | Mongo collection | Questlog detail method |
| ----------- | ---------------- | ----------------------- |
| `item` | `item` | `database.getItem` |
| `skill` | `skill` | `database.getSkill` |
| `recipe` | `recipe` | `database.getRecipe` |
| `placeable` | `placeable` | `database.getPlaceable` |
| `npc` | `npc` | `database.getNpc` |
Summary plural datasets:
| Dataset | Mongo collection | Questlog page method |
| ------------ | ---------------- | ------------------------ |
| `items` | `items` | `database.getItems` |
| `skills` | `skills` | `database.getSkills` |
| `recipes` | `recipes` | `database.getRecipes` |
| `placeables` | `placeables` | `database.getPlaceables` |
| `npcs` | `npcs` | `database.getNpcs` |
Use plural datasets only when a compact Questlog page-summary record is enough. If a plural lookup includes an id, for example `/api/items/LongRifle_Unique_Poison_03`, the API checks the matching singular collection first and falls back to the plural summary collection.
## Response Shape
Stored documents have normalized metadata and the original Questlog payload:
```json
{
"dataset": "item",
"language": "en",
"source": "questlog.gg",
"sourceMethod": "database.getItem",
"sourceId": "LongRifle_Unique_Poison_03",
"name": "Assassin's Rifle",
"raw": {}
}
```
For OpenClaw, inspect `raw` for domain-specific fields:
- Items: `raw.stats`, `raw.mainCategory`, `raw.subCategory`, recipe relationships, wearable/equip fields.
- Skills: `raw.levels`, `raw.connections`, grid position, bonuses.
- Recipes: `raw.recipeInputItems`, `raw.recipeOutputItems`, crafting requirements, crafting stations.
- Placeables: production types, power/water fields, craftable recipe relationships.
- NPCs: `raw.description`, `raw.npcTags`, category metadata.
## Repository Map
Look here when changing behavior:
```text
src/datasets.js Dataset keys, collections, Questlog methods, API allowlists.
src/importer/questlogClient.js Questlog URL building and TRPC response extraction.
src/importer/importer.js Import orchestration, detail fetches, Mongo upserts.
src/db/indexes.js Mongo indexes for summary and singular collections.
src/routes/api.js REST API routes, search, dataset lookup behavior.
src/swagger/openapi.js OpenAPI/Swagger documentation.
src/app.js Express middleware and public top-level routes.
scripts/import.js CLI import entry point.
```
## Import Commands
Run all imports:
```powershell
npm run import
```
Run a safe smoke import:
```powershell
npm run import:smoke
```
Import selected datasets:
```powershell
node scripts/import.js --datasets=items,skills,recipes,placeables,npcs --languages=en,de
```
The importer writes both plural summaries and singular detailed records when a dataset has a detail endpoint.
## Validation
Use the existing syntax check:
```powershell
npm run check
```
Useful endpoint checks:
```text
GET /health
GET /api/datasets
GET /api/item/LongRifle_Unique_Poison_03?language=en
GET /api/search?q=poison&datasets=item,skill,recipe&language=en
GET /SKILL.md
```
## Important Constraints
- Do not expose secrets in public documentation or API responses.
- Keep `.env` values private; production configuration comes from environment variables.
- Keep the singular datasets first-class for OpenClaw.
- Keep changes small and consistent with the existing CommonJS/Express style.

View File

@@ -14,7 +14,7 @@ WORKDIR /app
ENV NODE_ENV=production ENV NODE_ENV=production
COPY --from=dependencies /app/node_modules ./node_modules COPY --from=dependencies /app/node_modules ./node_modules
COPY .ai/SKILL.md ./.ai/SKILL.md COPY .idea/SKILL.md ./.idea/SKILL.md
COPY src ./src COPY src ./src
COPY scripts ./scripts COPY scripts ./scripts

View File

@@ -1,5 +1,6 @@
const cors = require("cors"); const cors = require("cors");
const express = require("express"); const express = require("express");
const fs = require("fs");
const path = require("path"); const path = require("path");
const swaggerUi = require("swagger-ui-express"); const swaggerUi = require("swagger-ui-express");
const { config } = require("./config"); const { config } = require("./config");
@@ -7,7 +8,14 @@ const { pingMongo } = require("./db/client");
const { router: apiRouter } = require("./routes/api"); const { router: apiRouter } = require("./routes/api");
const { openApiDocument } = require("./swagger/openapi"); const { openApiDocument } = require("./swagger/openapi");
const skillFilePath = path.join(__dirname, "..", ".ai", "SKILL.md"); const skillFilePaths = [
path.join(__dirname, "..", ".idea", "SKILL.md"),
path.join(__dirname, "..", ".ai", "SKILL.md"),
];
function getSkillFilePath() {
return skillFilePaths.find((filePath) => fs.existsSync(filePath));
}
function createApp() { function createApp() {
const app = express(); const app = express();
@@ -54,6 +62,12 @@ function createApp() {
}); });
app.get("/SKILL.md", (request, response) => { app.get("/SKILL.md", (request, response) => {
const skillFilePath = getSkillFilePath();
if (!skillFilePath) {
response.status(404).json({ error: "SKILL.md not found" });
return;
}
response.type("text/markdown"); response.type("text/markdown");
response.sendFile(skillFilePath); response.sendFile(skillFilePath);
}); });