@founder-os/module-sdk
v0.1.1
Published
FounderOS module SDK — manifest types, validation schemas, and CLI checker
Readme
@founderos/module-sdk
v0.1.0 — the contract every uniqlabs module ships against. Use this to author a module that drops into uniqlabs-hub (the FounderOS marketplace runtime).
What's in the package
| Surface | Purpose |
|---|---|
| UniqlabsModuleManifest | TypeScript type for manifest.json — the rich shape used by MOD-001 (the canonical example). Required fields: id, name, version, category, description. |
| ModuleManifest (legacy) | The original lighter-weight type. Kept for backwards compatibility — new modules should use UniqlabsModuleManifest. |
| ModuleManifestSchema, validateManifest() | Zod schema + helper for runtime validation. Powers the validate-manifest CLI. |
| ModuleDefinition, ModuleRegistry | Types the host uses to aggregate modules across all installed apps. |
| resolveHostPackage(), resolveHostPackages(), hasFounderOSHost() | Generic dynamic-import helpers that gracefully degrade when @founderos/* packages aren't installed in the host. |
| UniqlabsModuleLifecycle, defineLifecycle() | Optional lifecycle hook contract: onInstall, onUpgrade, onConfigChange, onUninstall. v0 ships the contract; the host's dispatcher lands Q2. |
| validate-manifest (CLI) | npx validate-manifest .uniqlabs.modules/ — validates every manifest in a directory tree. |
How to author a uniqlabs module
A module is just a directory under .uniqlabs.modules/<id>/ containing at minimum:
.uniqlabs.modules/<id>/
├── manifest.json # The contract — see UniqlabsModuleManifest
├── module.tsx # Thin entrypoint, re-exports your root component
├── types.ts # Module-local types (optional)
└── README.md # What the module does (optional)1. Write the manifest
{
"$schema": "../../docs/uniqlabs-module-manifest.schema.json",
"id": "fos-hiring-assistant",
"name": "Hiring Assistant",
"displayName": "founderOS Hiring Assistant",
"version": "1.0.0",
"sourceProduct": "hiring-assistant",
"persona": "Recruiter",
"category": "FOUNDER_OS_ZORD",
"icon": "Briefcase",
"description": "Founder-grade hiring pipeline...",
"requiredFOSPackages": ["@founderos/database", "@founderos/auth"],
"optionalFOSPackages": ["@founderos/event-bus"],
"prismaModels": ["Candidate", "JobRequisition", "Interview"],
"apiRoutes": [
"GET /api/v1/copilot/positions",
"POST /api/v1/copilot/positions"
],
"components": ["HiringAssistantModule"],
"configSchema": {
"title": { "type": "string", "default": "Hiring Assistant", "description": "Card title" }
},
"backendRequirements": { "hasBackend": true, "backendType": "api-only" },
"crossZordContext": {
"publishes": ["candidate.applied", "hire.completed"],
"consumes": ["calendar.events"]
}
}Validate it locally:
npx validate-manifest .uniqlabs.modules/fos-hiring-assistant/manifest.json2. Write the host resolver
Modules that read host data (Prisma, NextAuth session, event bus) must never import @founderos/* packages at the top level — the host may not have them installed in every context (builder preview, standalone test, third-party host, ...). Use the SDK's resolver helpers:
// src/host-resolver.ts
import { resolveHostPackages } from '@founderos/module-sdk';
export async function loadHost() {
const { auth, database } = await resolveHostPackages(['auth', 'database']);
if (!auth || !database) return null; // standalone — caller falls back
return { auth, database };
}The resolver:
- Builds the specifier at runtime so bundlers don't static-analyze the dynamic import
- Returns
nullfor any missing or failing package - Never throws
3. (Optional) Export lifecycle hooks
// src/lifecycle.ts
import { defineLifecycle } from '@founderos/module-sdk';
export const lifecycle = defineLifecycle({
async onInstall(ctx) {
ctx.log.info('Hiring Assistant installed for tenant', ctx.tenantId);
// seed defaults, request OAuth scopes, etc.
},
async onUpgrade(ctx, prevVersion) {
if (prevVersion === '0.9.x') { /* migrate */ }
},
async onConfigChange(ctx, next, prev) {
if (next.enableAIScoring !== prev.enableAIScoring) {
ctx.log.info('AI scoring toggled');
}
},
async onUninstall(ctx) {
ctx.log.info('Cleaning up tenant data');
},
});Every hook is optional. The host treats absent hooks as no-ops.
4. Submit to the host
- Drop your directory under
.uniqlabs.modules/<id>/. - Run the host's ingest script:
npx tsx scripts/ingest-module.ts. - The host validates the manifest, registers it in the builder palette, and updates
src/modules/ingested-modules.generated.json.
Worked example — MOD-001 Hiring Assistant
The canonical reference implementation lives in uniqlabs-hub:
.uniqlabs.modules/hiring-assistant/manifest.json— the manifest.uniqlabs.modules/hiring-assistant/module.tsx— entrypoint.uniqlabs.modules/hiring-assistant/README.md— full walkthroughsrc/modules/fos-hiring-assistant/host-resolver.ts— the pattern this SDK abstracts
The host's tests/modules/sdk-contract.test.ts runs the SDK's validateManifest() against the MOD-001 manifest to ensure the SDK + host stay in lock-step.
Versioning
| SDK version | Manifest schema | Notes | |---|---|---| | 0.1.0 | implicit v1 | Initial public surface. Manifest, host resolver, lifecycle hooks. |
Roadmap
| Wave | Item |
|---|---|
| Q1 (this release) | SDK v0 — types + resolver + lifecycle contract |
| Q2 | Host dispatcher for lifecycle hooks · explicit schemaVersion field in manifest · third-party publisher onboarding · marketplace runtime (Wedge #3) |
| Q3 | Versioned manifest migrations · sandboxing contract for third-party modules |
Internal
This package lives at packages/module-sdk/ inside the FounderOS monorepo. It's part of the pnpm workspace; consumers inside the monorepo import via @founderos/module-sdk.
For consumers outside the workspace (e.g. uniqlabs-hub, which is an independent npm package), see the host-side wire-up doc: docs/MODULE_SDK.md.
