Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 234 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
# AGENTS.md - DKG Node Codebase Guide

> Quick reference for coding agents working on this Turborepo monorepo.

## Architecture

- **Turborepo monorepo** | Node.js >= 22 | npm workspaces
- **Main app**: `apps/agent` (Expo frontend + Express backend with MCP server)
- **Plugins**: `packages/plugin-*` (modular DKG/MCP/API functionality)
- **Tech stack**: SQLite, Drizzle ORM, Expo Router, Model Context Protocol, DKG.js

## Plugin System (Core Pattern)

```typescript
// packages/plugin-{name}/src/index.ts
import { defineDkgPlugin } from "@dkg/plugins";
import { openAPIRoute, z } from "@dkg/plugin-swagger";

export default defineDkgPlugin((ctx, mcp, api) => {
// ctx: { dkg: DKG, blob: BlobStorage }
// mcp: McpServer for AI tools
// api: Express Router for HTTP endpoints

// Register MCP tool
mcp.registerTool("tool-name", {
title: "Tool Title",
description: "Description for LLM",
inputSchema: { param: z.string() },
}, async ({ param }) => ({
content: [{ type: "text", text: "result" }],
}));

// Register API endpoint
api.get("/endpoint", openAPIRoute({
tag: "Category",
summary: "Description",
query: z.object({ param: z.string() }),
response: { schema: z.object({ result: z.any() }) },
}, (req, res) => {
res.json({ result: req.query.param });
}));
});

// Namespaced plugin with auth
export const protectedPlugin = plugin.withNamespace("protected", {
middlewares: [authorized(["scope-name"])],
});
```

## Critical Conventions

### API Routes
- **ALWAYS** use `openAPIRoute()` wrapper (auto-validates + generates Swagger docs)
- **ALWAYS** validate with Zod schemas
- Returns 400 auto for invalid inputs

### Testing
Always write tests along with features

```typescript
// tests/{name}.spec.ts - MUST import from dist/
import plugin from "../dist/index.js";
import { createMcpServerClientPair, createExpressApp,
createInMemoryBlobStorage, createMockDkgClient } from "@dkg/plugins/testing";

const mockDkgContext = {
dkg: createMockDkgClient(),
blob: createInMemoryBlobStorage(),
};

// Required test categories: "Core Functionality" and "Error Handling"
```

### Database
- Drizzle ORM with SQLite
- Schemas in `src/database/schema.ts` or `src/server/database/sqlite/`
- Migrations in `drizzle/` folder
- Generate: `npm run build:migrations`

### File Structure
```
packages/plugin-{name}/
├── src/index.ts # Export defineDkgPlugin
├── tests/{name}.spec.ts # Import from ../dist/
├── package.json # Dependencies: @dkg/plugins, @dkg/plugin-swagger
├── tsconfig.json
└── eslint.config.mjs
```

## Essential Commands

```bash
# Development
npm run dev # Start all services
turbo gen plugin # Generate new plugin
npm run build # Build all packages

# Before commits
turbo format check-types lint build

# Testing
npm test # All tests
npm run test:api # Plugin tests only
npm run test:integration # Integration tests

# Database
npm run build:migrations # Generate migrations
npm run drizzle:studio # Visual DB browser
```

## Auth & Middleware

```typescript
import { authorized } from "@dkg/plugin-oauth";

// Apply scope-based auth
api.use("/protected", authorized(["scope-name"]));

// Access auth in handler
const userId = res.locals.auth?.extra?.userId;
```

## Common Pitfalls

1. **Tests import from dist**, NOT src: `import from "../dist/index.js"`
2. **Always use openAPIRoute** for API endpoints (breaks Swagger otherwise)
3. **Run `npm install` at root** after adding plugin dependencies
4. **TypeScript config**: Extends `@dkg/typescript-config/base.json`
5. **Plugin namespaces**: Prefix MCP tools automatically (`namespace__toolName`)

## Key Imports

```typescript
import { defineDkgPlugin, DkgContext, DkgPlugin } from "@dkg/plugins";
import { openAPIRoute, z } from "@dkg/plugin-swagger";
import { authorized } from "@dkg/plugin-oauth";
import type { express } from "@dkg/plugins/types";
```

## Environment Variables

```bash
# Required
DATABASE_URL=dkg.db # SQLite DB name
OPENAI_API_KEY=sk-... # LLM API key
DKG_PUBLISH_WALLET=0x... # Blockchain wallet

# Optional
DKG_BLOCKCHAIN=hardhat1:31337 # Network
DKG_OTNODE_URL=http://localhost:8900 # OT-node
PORT=9200 # Server port
EXPO_PUBLIC_MCP_URL=http://localhost:9200
EXPO_PUBLIC_APP_URL=http://localhost:8081
```

## Test Template

```typescript
describe("Plugin Name", () => {
let mockMcpServer, mockMcpClient, app;

beforeEach(async () => {
const { server, client, connect } = await createMcpServerClientPair();
mockMcpServer = server;
mockMcpClient = client;
app = createExpressApp();

plugin(mockDkgContext, mockMcpServer, express.Router());
await connect();
});

describe("Core Functionality", () => {
it("should register tools", async () => {
const tools = await mockMcpClient.listTools().then(t => t.tools);
expect(tools.some(t => t.name === "tool-name")).to.equal(true);
});
});

describe("Error Handling", () => {
it("should return 400 for invalid input", async () => {
await request(app).get("/endpoint").expect(400);
});
});
});
```

## Frontend (React Native + Expo)

- **Path alias**: `@/` → `src/`
- **Routing**: File-based via Expo Router
- **Protected routes**: `app/(protected)/`
- **Layout**: `app/_layout.tsx`

## Plugin Registration

```typescript
// apps/agent/src/server/index.ts
import myPlugin from "@dkg/plugin-my-name";

const app = createPluginServer({
name: "DKG API",
version: "1.0.0",
context: { dkg, blob: blobStorage },
plugins: [
defaultPlugin,
oauthPlugin,
myPlugin, // Add here
swaggerPlugin({ version, securitySchemes }),
],
});
```

## Code Quality

- **Formatter**: Prettier (auto-formats)
- **Linter**: ESLint with TypeScript
- **Type checking**: `npm run check-types`
- **Pre-commit**: Format → Lint → Type check → Build

## Debugging Tips

1. Check `apps/agent/dist/index.js` exists after build
2. Test database: `rm *.db && npm run script:setup`
3. View Swagger docs: `http://localhost:9200/swagger`
4. Integration tests in `apps/agent/tests/integration/`
5. Use `tsx` for running scripts: `npx tsx script.ts`
6. Use docs about DKG Node project at `docs`

## Reference Examples

- **Simple plugin**: `packages/plugin-example/`
- **Complex plugin**: `packages/plugin-dkg-publisher/`
- **OAuth patterns**: `packages/plugin-oauth/`
- **Testing guide**: `packages/PLUGIN_TESTING_GUIDE.md`
Loading