Claude Code Skills
The Vertical Template includes 8 Claude Code skills in the .claude/skills/ directory. Each skill is a structured instruction set that automates a specific development task. Skills are invoked as slash commands in Claude Code and guide the assistant through a multi-step process with verification at each stage.
Skills Summary
| Skill | Slash Command | Purpose | Key Output |
|---|---|---|---|
| vertical-setup | /vertical-setup | Initialize a new vertical project | Tenant config, object types, hooks, deployment config |
| add-page | /add-page | Add a new page to the application | Page component in src/app/(presentation)/ |
| add-tenant | /add-tenant | Add a new tenant configuration | Tenant config file + registration in index.ts |
| define-object-types | /define-object-types | Define the data model | Object type definitions in object-types.ts |
| seed-configurator | /seed-configurator | Push object types to the platform | Object types seeded in Configurator |
| setup-data-access | /setup-data-access | Generate CRUD hooks | Typed React hooks for each entity |
| configure-ai | /configure-ai | Set up AI chat and document processing | Workflow, chat hook, document hook wiring |
| deploy-vertical | /deploy-vertical | Configure and deploy to Azure | GitHub Actions workflow, secrets, infrastructure |
vertical-setup
Slash Command: /vertical-setup
What it does: Orchestrates the full end-to-end setup of a new vertical application. This is the master skill that coordinates all other skills in sequence.
When to use it: Starting a brand-new vertical project from scratch.
What the assistant will ask you:
- What is the tenant ID? (lowercase, hyphens allowed -- e.g.,
acme-corp) - What is the display name? (e.g., "Acme Corporation")
- What is the primary use case? (e.g., "immigration case management")
Steps performed:
- Creates the tenant configuration file at
src/eai.config/tenants/{tenant-id}.config.ts - Registers the tenant in
src/eai.config/index.ts - Invokes
define-object-typesto create the data model - Sets up environment variables from
.env.example - Invokes
seed-configuratorto push types to the platform - Invokes
setup-data-accessto generate CRUD hooks - Optionally invokes
configure-aifor AI features - Invokes
deploy-verticalfor deployment configuration - Runs the full verification checklist
Expected outcome: A fully scaffolded vertical application with tenant config, object types, data access hooks, and deployment pipeline -- ready for UI development.
Verification checklist:
- Tenant config file exists and is registered
- Object types defined and compile without errors
- Environment configured (
.env.local) - Object types seeded to Configurator
- Data access hooks generated
- App accessible at target URL
add-page
Slash Command: /add-page
What it does: Creates a new page in the Next.js application with proper structure, authentication checks, metadata, loading states, and error boundaries.
When to use it: Adding a new route or view to your vertical application.
What the assistant will ask you:
- What is the page name/path? (e.g.,
dashboard,settings/profile) - Does it require authentication? (default: yes)
- What is the page title?
Steps performed:
- Creates the page component at
src/app/(presentation)/{path}/page.tsx - Adds a loading state (
loading.tsx) for better UX during navigation - Adds an error boundary (
error.tsx) for graceful error handling - Updates middleware route protection if the page requires authentication
Expected outcome: A new page accessible at the specified URL path, with authentication checks, SEO metadata, loading skeleton, and error handling.
Page types created:
Protected page (with auth):
import { auth } from '@/auth';
import { redirect } from 'next/navigation';
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Dashboard | My Vertical',
};
export default async function DashboardPage() {
const session = await auth();
if (!session) {
redirect('/api/auth/signin');
}
return (
<div className="container mx-auto py-8">
<h1 className="text-2xl font-bold">Dashboard</h1>
<p className="text-gray-600 mt-2">
Welcome, {session.user?.name}
</p>
</div>
);
}
Basic page (no auth):
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'About | My Vertical',
};
export default function AboutPage() {
return (
<div className="container mx-auto py-8">
<h1 className="text-2xl font-bold">About</h1>
</div>
);
}
File structure created:
src/app/(presentation)/{path}/
page.tsx # Main page component
loading.tsx # Loading skeleton
error.tsx # Error boundary
add-tenant
Slash Command: /add-tenant
What it does: Creates a new tenant configuration for multi-tenant or white-label deployments. Each tenant gets isolated UI layout, theming, state management, and feature flags.
When to use it: Adding a new client, brand, or environment configuration to the application.
What the assistant will ask you:
- What is the tenant ID? (lowercase, hyphens only -- e.g.,
acme-corp) - What is the display name? (e.g., "Acme Corporation")
- What features are needed? (dashboard, authentication, chat/AI, custom workflows)
Steps performed:
- Creates the tenant configuration file at
src/eai.config/tenants/{tenant-id}.config.ts - Defines store slices (user, workflow, UI state) with initial state and persistence settings
- Configures the layout (header, sidebar, main content) with component declarations
- Sets up conditional rendering rules (
showWhen) for authenticated/unauthenticated states - Registers the new tenant in
src/eai.config/index.ts - Optionally creates tenant-specific assets (logo, favicon) and CSS variables
Expected outcome: A new tenant configuration that can be activated by setting the tenant query parameter or environment variable. The tenant will have its own layout, theming, and feature flags.
Example tenant config:
import type { EAIConfig } from '../types';
export const acmeCorpConfig: EAIConfig = {
tenantId: 'acme-corp',
displayName: 'Acme Corporation',
store: {
user: {
initialState: {
name: null,
email: null,
isAuthenticated: false,
role: null,
},
persist: true,
},
ui: {
initialState: {
sidebarOpen: true,
theme: 'light',
},
persist: false,
},
},
layout: {
header: [
{
component: 'Header',
priority: 1,
props: { title: 'Acme Corporation', showUserMenu: true },
storeBindings: [
{ prop: 'userName', storePath: 'user.name' },
],
},
],
leftPane: [],
middlePane: [
{
component: 'WelcomeCard',
priority: 1,
props: {
title: 'Welcome to Acme Corporation',
message: 'Sign in to get started.',
},
showWhen: (state) => !state.user?.isAuthenticated,
},
{
component: 'Dashboard',
priority: 2,
showWhen: (state) => state.user?.isAuthenticated,
},
],
rightPane: [],
},
};
define-object-types
Slash Command: /define-object-types
What it does: Defines the data model for your vertical by creating Object Type definitions that map your domain entities to the platform schema format. Object Types are used by Configurator for metadata and ResourceAPI for runtime data validation.
When to use it: Designing the data model for a new vertical, or adding entities to an existing one.
What the assistant will ask you:
- What are the main entities/objects? (e.g., Application, Document, Notification)
- For each entity, what properties does it have?
- Are there relationships between entities?
- What actions can users perform? (e.g., submit, approve, reject)
Steps performed:
- Maps your domain entities to the Object Type schema format
- Selects appropriate field types for each property (see table below)
- Defines relationships between entities via
linkTypes - Configures actions with role requirements, validation rules, and side effects
- Writes definitions to
src/eai.config/object-types.ts - Runs type checking (
npx tsc --noEmit) to verify definitions compile
Field type mapping:
| Domain Concept | Field Type | TypeScript Type | Example |
|---|---|---|---|
| Names, emails, IDs | text | string | applicantName, email |
| Counts, amounts | number | number | score, amount |
| Flags | boolean | boolean | isVerified, isActive |
| Timestamps, deadlines | date | string (ISO 8601) | submittedAt, dueDate |
| Status, category | select | 'draft' | 'active' | status, priority |
| Metadata, notes | json | Record<string, unknown> | metadata, notes |
| Attachments | file | string (URL) | attachment, photo |
| References | relationship | string (ID) | parentId, assigneeId |
Expected outcome: Complete object type definitions in object-types.ts with properties, relationships, and actions that compile without errors.
Example output:
export const objectTypes = {
'immigration-portal': [
{
name: 'Application',
displayName: 'Visa Application',
description: 'Tracks visa application lifecycle',
properties: [
{ name: 'applicantName', type: 'text', required: true, indexed: true },
{ name: 'applicantEmail', type: 'text', required: true, indexed: true },
{ name: 'visaType', type: 'select', required: true, indexed: true,
options: [
{ label: 'H-1B', value: 'h1b' },
{ label: 'L-1', value: 'l1' },
{ label: 'O-1', value: 'o1' },
],
},
{ name: 'status', type: 'select', required: true, indexed: true,
defaultValue: 'draft',
options: [
{ label: 'Draft', value: 'draft' },
{ label: 'Submitted', value: 'submitted' },
{ label: 'Under Review', value: 'under-review' },
{ label: 'Approved', value: 'approved' },
{ label: 'Denied', value: 'denied' },
],
},
{ name: 'submittedAt', type: 'date', required: false },
{ name: 'notes', type: 'json', required: false },
],
linkTypes: [
{
name: 'documents',
targetObjectType: 'Document',
cardinality: 'one-to-many',
cascadeDelete: true,
},
],
actions: [
{
name: 'submit',
displayName: 'Submit Application',
requiredRole: 'tenant-user',
validationRules: {
requiredFields: ['applicantName', 'applicantEmail', 'visaType'],
requiredStatus: 'draft',
},
sideEffects: [
{ type: 'set_field', field: 'status', value: 'submitted' },
{ type: 'set_timestamp', field: 'submittedAt' },
],
},
],
storageBackend: 'postgresql',
status: 'published',
},
],
};
seed-configurator
Slash Command: /seed-configurator
What it does: Pushes the object type definitions from src/eai.config/object-types.ts to the Configurator service via PublicAPI. This makes your data model available to ResourceAPI for runtime CRUD operations.
When to use it: After defining or updating object types, to sync them with the platform.
Prerequisites:
- Object types defined in
object-types.ts(usedefine-object-typesfirst) - Platform SDK installed
- Environment configured with
BASE_URL_PUBLIC_APIand auth credentials
Steps performed:
- Reads object type definitions from
object-types.ts - For each type, checks if it already exists in Configurator (idempotency)
- Creates new types or updates existing ones via the orchestrate endpoint
- Reports per-type results (created / updated / skipped)
- Runs
verifyPlatform()to confirm all types are accessible
How it works:
The skill uses the orchestrate endpoint to communicate with Configurator:
// Check if type exists
await client.orchestrate.send({
target_backend: 'payload',
endpoint: '/object-types', // NOT /api/object-types
method: 'GET',
params: { where: { name: { equals: 'Application' } } },
});
// Create new type
await client.orchestrate.send({
target_backend: 'payload',
endpoint: '/object-types',
method: 'POST',
body: { name: 'Application', properties: [...], tenant: tenantId },
});
The endpoint must be /object-types, not /api/object-types. The PublicAPI orchestrator automatically prepends /api for the payload backend.
Alternative: Use the built-in seeding utility:
import { seedObjectTypes } from '@/lib/platform/seed-object-types';
const results = await seedObjectTypes('my-tenant');
Or via the API route:
curl -X POST http://localhost:3000/api/eai/seed \
-H "Content-Type: application/json" \
-d '{"tenantKey": "my-tenant"}'
Expected outcome: All object types from object-types.ts are present in Configurator, and verifyPlatform() returns all checks passing.
setup-data-access
Slash Command: /setup-data-access
What it does: Generates typed React hooks for CRUD operations on each object type. Each hook wraps the generic useResources<T> hook with entity-specific TypeScript interfaces.
When to use it: After defining object types and seeding them to Configurator, to create the data access layer for your UI.
Prerequisites:
- Object types defined in
object-types.ts - Object types seeded to Configurator
useResources.tsbase hook exists
Steps performed:
- Reads object type definitions to identify entities
- Maps each property's field type to a TypeScript type
- Generates a typed interface for each entity
- Creates a hook file (
src/hooks/use{Entity}s.ts) for each object type - Verifies all hooks compile without errors
Type mapping applied:
| Object Type Field | TypeScript Type |
|---|---|
text | string |
number | number |
boolean | boolean |
date | string (ISO 8601) |
select | Union of option values (e.g., 'draft' | 'submitted') |
json | Record<string, unknown> |
file | string |
relationship | string |
Example generated hook:
'use client';
import { useResources } from './useResources';
export interface ApplicationData {
applicantName: string;
applicantEmail: string;
visaType: 'h1b' | 'l1' | 'o1';
status: 'draft' | 'submitted' | 'under-review' | 'approved' | 'denied';
submittedAt: string;
notes: Record<string, unknown>;
}
/**
* Hook for Application CRUD operations.
* Uses Platform SDK -> BFF Proxy -> PublicAPI -> ResourceAPI.
*/
export function useApplications() {
return useResources<ApplicationData>('Application');
}
Expected outcome: One hook file per object type in src/hooks/, each with a typed interface that matches the object type schema. All hooks compile and delegate to useResources<T>.
Usage in components:
'use client';
import { useApplications } from '@/hooks/useApplications';
import { useEffect, useState } from 'react';
export function ApplicationList() {
const { list, create } = useApplications();
const [apps, setApps] = useState([]);
useEffect(() => {
list({ page: 1, limit: 20 })
.then(res => res.json())
.then(data => setApps(data.docs));
}, []);
return (
<ul>
{apps.map(app => (
<li key={app.id}>{app.data.applicantName}</li>
))}
</ul>
);
}
configure-ai
Slash Command: /configure-ai
What it does: Sets up AI-powered features including RAG chat workflows and document processing (upload, classification, indexing). Creates the workflow in Configurator, wires the chat and document hooks, and verifies SSE streaming works end-to-end.
When to use it: When your vertical needs an AI assistant, document classification, or RAG-powered chat.
What the assistant will ask you:
- What type of AI assistant is needed? (RAG chat, document classifier, both)
- What domain knowledge should the chat have?
- What document types will be uploaded?
- What workflow stages are needed? (e.g.,
chat,review,summary)
Steps performed:
- Creates a workflow record in Configurator with stage definitions and system prompts
- Sets the
WORKFLOW_*_IDenvironment variable - Wires the
useChathook for SSE streaming - Wires the
useDocumentshook for file uploads and classification - Verifies the SSE stream proxy exists at
src/app/api/eai/stream/[[...rest]]/route.ts - Tests the full streaming chain: Browser -> BFF -> PublicAPI -> AICore
Chat streaming architecture:
Browser
-> POST /api/eai/stream/v3/chat/stream/{tenant}/{workflow}/{stage}
-> Stream Proxy (injects Bearer token, sets SSE headers)
-> PublicAPI /v3/chat/stream/{tenant}/{workflow}/{stage}
-> AICore (RAG + LLM)
-> SSE events flow back through proxy to browser
Example chat integration:
'use client';
import { useChat } from '@/hooks/useChat';
export function ChatPanel() {
const { stream } = useChat('my-workflow', 'chat');
async function handleSend(message: string) {
const readableStream = await stream({
message, // NOT chat_input
conversationId: crypto.randomUUID(), // REQUIRED
params: {}, // REQUIRED, even if empty
});
const reader = readableStream.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
// Parse SSE events and update UI
}
}
return (/* chat UI */);
}
Example document processing:
'use client';
import { useDocuments } from '@/hooks/useDocuments';
export function DocumentUploader() {
const { upload, classify } = useDocuments();
async function handleUpload(files: FileList) {
for (const file of files) {
await upload(file, { category: 'legal' });
}
}
async function handleClassify(files: FileList) {
const results = await classify(Array.from(files));
// Display classification results
}
return (/* upload UI */);
}
- Use
message(notchat_input) for the chat message conversation_idis required -- generate withcrypto.randomUUID()paramsis required -- use{}if no parameters are needed
Expected outcome: A working AI chat integration with SSE streaming and document upload/classification capabilities, connected to the platform's AICore service.
deploy-vertical
Slash Command: /deploy-vertical
What it does: Configures the CI/CD pipeline and deploys the vertical application to Azure App Service. Sets up GitHub Actions, secrets, infrastructure registration, and app configuration.
When to use it: When the application is ready for deployment to a shared Azure environment.
Steps performed:
- Sets
APP_NAMEin.github/workflows/deploy-demo.yml - Configures GitHub secrets (Azure credentials, resource group, webapp name)
- Registers the app in the demo-infra
verticalAppsarray - Seeds Azure App Configuration with environment-specific values
- Triggers deployment (push to
mainor manual workflow dispatch) - Verifies the deployment is accessible and platform connectivity works
What APP_NAME determines:
| Aspect | Value |
|---|---|
| URL path | https://app-demo-eai-dev.azurewebsites.net/{app-name} |
| Database name | {app-name} |
| Deployment folder | /home/site/wwwroot/{app-name} |
| App config label | {app-name} |
Required GitHub secrets:
| Secret | Description |
|---|---|
AZUREAPPSERVICE_CLIENTID | Azure AD app registration client ID |
AZUREAPPSERVICE_TENANTID | Corporate Azure AD tenant ID |
AZUREAPPSERVICE_SUBSCRIPTIONID | Azure subscription ID |
AZURE_RESOURCE_GROUP | Resource group name (e.g., rg-demo-infrastructure) |
AZURE_WEBAPP_NAME | Shared App Service name (e.g., app-demo-eai-dev) |
Deployment trigger:
# Automatic: push to main
git push origin main
# Manual: trigger via CLI
gh workflow run deploy-demo.yml
Verification:
# Check HTTP status
curl -s -o /dev/null -w "%{http_code}" \
https://app-demo-eai-dev.azurewebsites.net/{app-name}
# Check platform connectivity
curl https://app-demo-eai-dev.azurewebsites.net/{app-name}/api/eai/config
Expected outcome: The application is deployed to Azure App Service, accessible at the configured URL, with authentication, CRUD operations, and AI features (if configured) all functioning.