Skip to main content

LLM Instructions (External Modules)

This page mirrors the exact llm.txt that AI agents must follow. Download the file here.

MoLOS External Module LLM Instructions (No Ambiguity)

Scope: Create a NEW external module or EXTEND an existing external module that works inside MoLOS.
Target workspace: Your local MoLOS workspace root
MoLOS main repo: <workspace>/MoLOS

Available Technologies (use only these):
- Language: TypeScript
- UI: Svelte + SvelteKit
- DB: SQLite
- ORM: Drizzle ORM
- Validation: Zod
- Icons: lucide-svelte
- Runtime: Node.js 20+
- Build tooling: Vite (via SvelteKit)

Required Workflow (do not reorder):
1) Create a new git repository for the module.
2) Clone the MoLOS main repo.
3) Clone the new module into: MoLOS/external_modules/<ModuleId>
4) Open editor at MoLOS repo root.
5) Use MoLOS-Sample-Module or another working module as boilerplate.
6) Copy the boilerplate code into the new module git path.
7) Run: npm run dev (from MoLOS root).
8) Start developing the module.

Alternate Workflow (extending an existing module):
1) Clone the MoLOS main repo.
2) Ensure the existing module repo is under: MoLOS/external_modules/<ModuleId>
3) Open editor at MoLOS repo root.
4) Run: npm run dev (from MoLOS root).
5) Modify the existing module in place.

Absolute Rules (non-negotiable):
- Module ID must be identical in ALL locations:
- Repo folder name
- manifest.yaml id
- config.ts id
- DB table prefixes
- All tables are prefixed with <ModuleId>_ (example: MoLOS-Tasks_tasks).
- API routes must use locals.user?.id for auth.
- All database queries must be scoped by userId.
- All module-local imports inside MoLOS use:
$lib/.../external_modules/<ModuleId>
- UI base path: /ui/<ModuleId>
- API base path: /api/<ModuleId>
- Timestamps are unix seconds (number).
- No cross-module writes or schema edits.
- Do not use absolute filesystem paths in code or docs.
- Do not bypass repositories from routes or UI.
- All UI/server pages (+page.svelte, +page.server.ts, +layout.server.ts) must call the module API and never call repositories directly.
- When extending a module, never rename module ID or existing table prefixes.
- When extending a module, add new migrations instead of editing old ones.

Required Files and Paths (in the module repo):
1) manifest.yaml
2) config.ts
3) package.json
4) lib/models/index.ts
5) lib/repositories/*.ts
6) lib/server/db/schema/tables.ts
7) routes/api/+server.ts (and subroutes as needed)
8) routes/ui/+page.svelte (and subroutes as needed)
9) drizzle/<migration>.sql (must match schema)
10) README.md (short, direct)

Module Skeleton (expected layout):
<ModuleId>/
manifest.yaml
config.ts
package.json
README.md
lib/
models/
index.ts
repositories/
base-repository.ts
<entity>-repository.ts
stores/
index.ts
components/
<component>.svelte
server/
ai/
ai-tools.ts (optional)
db/
schema/
tables.ts
routes/
api/
+server.ts
<entity>/
+server.ts
ui/
+layout.svelte
+layout.server.ts
+page.svelte
dashboard/
+page.svelte
drizzle/
0001_init.sql

Exact manifest.yaml template (replace values):
id: "<ModuleId>"
name: "<Module Name>"
version: "0.1.0"
description: "<Short description>"
author: "<Author>"
icon: "<LucideIconName>"

Exact config.ts template (replace values):
import { LayoutDashboard } from "lucide-svelte";
import type { ModuleConfig } from "$lib/config/types";

export const moduleConfig: ModuleConfig = {
id: "<ModuleId>",
name: "<Module Name>",
href: "/ui/<ModuleId>",
icon: LayoutDashboard,
description: "<Short description>",
navigation: [
{
name: "Dashboard",
icon: LayoutDashboard,
href: "/ui/<ModuleId>/dashboard",
},
],
};

export default moduleConfig;

DB Schema Rules (lib/server/db/schema/tables.ts):
- Use sqliteTable from drizzle-orm/sqlite-core.
- Table names MUST be "<ModuleId>_<entity>".
- Include createdAt and updatedAt.
- Include userId where data is user-specific.
- Use $lib/server/db/utils textEnum for enums.
- Keep schema in sync with drizzle SQL migrations.
- Do not use camelCase in DB column names (use snake_case).

Minimal DB schema example:
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
import { sql } from "drizzle-orm";

export const myEntity = sqliteTable("<ModuleId>_items", {
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
userId: text("user_id").notNull(),
title: text("title").notNull(),
createdAt: integer("created_at").notNull().default(sql`(strftime('%s','now'))`),
updatedAt: integer("updated_at").notNull().default(sql`(strftime('%s','now'))`),
});

Models (lib/models/index.ts):
- Define interfaces mirroring DB schema.
- Include Create/Update types with Omit/Partial.
- Export enums or string unions used by repositories/routes.

Repository Rules (lib/repositories):
- All reads and writes require userId filtering.
- Use BaseRepository pattern like MoLOS-Tasks.
- No direct database use in routes or UI.
- Repository methods must accept userId for any user-scoped entity.

Repository skeleton:
import { and, eq } from "drizzle-orm";
import { myEntity } from "$lib/server/db/schema/external_modules/<ModuleId>/tables";
import type { MyEntity, CreateMyEntityInput } from "$lib/models/external_modules/<ModuleId>";
import { BaseRepository } from "./base-repository";

export class MyEntityRepository extends BaseRepository {
async getByUserId(userId: string): Promise<MyEntity[]> {
return await this.db.select().from(myEntity).where(eq(myEntity.userId, userId));
}

async getById(id: string, userId: string): Promise<MyEntity | null> {
const result = await this.db
.select()
.from(myEntity)
.where(and(eq(myEntity.id, id), eq(myEntity.userId, userId)))
.limit(1);
return result[0] ?? null;
}

async create(data: CreateMyEntityInput): Promise<MyEntity> {
const result = await this.db.insert(myEntity).values(data).returning();
return result[0] as MyEntity;
}
}

API Routes (routes/api):
- Use SvelteKit RequestHandler.
- Use locals.user?.id for auth.
- Validate JSON with zod.
- Return proper status codes.
- Never accept userId from request body or query.

API skeleton:
import { json, error } from "@sveltejs/kit";
import type { RequestHandler } from "./$types";
import { z } from "zod";
import { MyEntityRepository } from "$lib/repositories/external_modules/<ModuleId>/my-entity-repository";
import { db } from "$lib/server/db";

const CreateSchema = z.object({
title: z.string().min(1),
});

export const GET: RequestHandler = async ({ locals }) => {
const userId = locals.user?.id;
if (!userId) throw error(401, "Unauthorized");
const repo = new MyEntityRepository(db);
return json(await repo.getByUserId(userId));
};

export const POST: RequestHandler = async ({ locals, request }) => {
const userId = locals.user?.id;
if (!userId) throw error(401, "Unauthorized");
const data = CreateSchema.parse(await request.json());
const repo = new MyEntityRepository(db);
return json(await repo.create({ ...data, userId }), { status: 201 });
};

UI Routes (routes/ui):
- Use +page.svelte for the module root.
- Use dashboard/+page.svelte as main view.
- Use /ui/<ModuleId>/... paths.
- If you redirect from +page.svelte, use goto("/ui/<ModuleId>/dashboard").
- Use fetch("/api/<ModuleId>") for API calls.
- Never import repositories in UI or +page.server.ts/+layout.server.ts files.

AI Tools (optional):
- File: lib/server/ai/ai-tools.ts
- Export getAiTools(userId) that returns ToolDefinition[].
- Tools are auto-prefixed by MoLOS with <ModuleId>_.
- Use snake_case tool names.

Final Checklist (required for new or existing modules):
- Module loads with npm run dev.
- /ui/<ModuleId> renders.
- /api/<ModuleId> responds.
- Migrations run without errors.
- No missing imports or path errors.
- All queries are user-scoped.
- No lint/type errors in module files.
- config.ts exports moduleConfig and default.
- manifest.yaml id matches config.ts id and folder name.
- Table names use <ModuleId>_ prefix and snake_case columns.
- If extending, existing routes and features still work.
- All UI/server routes use API calls as the only data source.

Good vs Wrong Examples:

GOOD: IDs and paths
- Repo folder: MoLOS-Notes
- manifest.yaml id: "MoLOS-Notes"
- config.ts id: "MoLOS-Notes"
- Table name: "MoLOS-Notes_notes"
- UI route: /ui/MoLOS-Notes
- API route: /api/MoLOS-Notes

WRONG: ID mismatch
- Repo folder: MoLOS-Notes
- manifest.yaml id: "notes"
- Table name: "notes_items"

GOOD: Auth and user scope
const userId = locals.user?.id;
if (!userId) throw error(401, "Unauthorized");
await repo.getByUserId(userId);

WRONG: Accepting userId from the client
const userId = url.searchParams.get("userId");

GOOD: Imports inside MoLOS
import { notes } from "$lib/server/db/schema/external_modules/MoLOS-Notes/tables";

WRONG: Relative imports from symlinked paths
import { notes } from "../../../lib/server/db/schema/tables";

GOOD: Table prefix
sqliteTable("MoLOS-Notes_notes", { ... })

WRONG: No module prefix
sqliteTable("notes", { ... })

Stop Conditions:
- If a required file is missing, create it before proceeding.
- If module ID mismatch is found, fix ALL mismatches immediately.
- If extending a module and an edit risks breaking current behavior, stop and ask for confirmation.