Skip to main content

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

SkillSlash CommandPurposeKey Output
vertical-setup/vertical-setupInitialize a new vertical projectTenant config, object types, hooks, deployment config
add-page/add-pageAdd a new page to the applicationPage component in src/app/(presentation)/
add-tenant/add-tenantAdd a new tenant configurationTenant config file + registration in index.ts
define-object-types/define-object-typesDefine the data modelObject type definitions in object-types.ts
seed-configurator/seed-configuratorPush object types to the platformObject types seeded in Configurator
setup-data-access/setup-data-accessGenerate CRUD hooksTyped React hooks for each entity
configure-ai/configure-aiSet up AI chat and document processingWorkflow, chat hook, document hook wiring
deploy-vertical/deploy-verticalConfigure and deploy to AzureGitHub 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:

  1. What is the tenant ID? (lowercase, hyphens allowed -- e.g., acme-corp)
  2. What is the display name? (e.g., "Acme Corporation")
  3. What is the primary use case? (e.g., "immigration case management")

Steps performed:

  1. Creates the tenant configuration file at src/eai.config/tenants/{tenant-id}.config.ts
  2. Registers the tenant in src/eai.config/index.ts
  3. Invokes define-object-types to create the data model
  4. Sets up environment variables from .env.example
  5. Invokes seed-configurator to push types to the platform
  6. Invokes setup-data-access to generate CRUD hooks
  7. Optionally invokes configure-ai for AI features
  8. Invokes deploy-vertical for deployment configuration
  9. 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:

  1. What is the page name/path? (e.g., dashboard, settings/profile)
  2. Does it require authentication? (default: yes)
  3. What is the page title?

Steps performed:

  1. Creates the page component at src/app/(presentation)/{path}/page.tsx
  2. Adds a loading state (loading.tsx) for better UX during navigation
  3. Adds an error boundary (error.tsx) for graceful error handling
  4. 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:

  1. What is the tenant ID? (lowercase, hyphens only -- e.g., acme-corp)
  2. What is the display name? (e.g., "Acme Corporation")
  3. What features are needed? (dashboard, authentication, chat/AI, custom workflows)

Steps performed:

  1. Creates the tenant configuration file at src/eai.config/tenants/{tenant-id}.config.ts
  2. Defines store slices (user, workflow, UI state) with initial state and persistence settings
  3. Configures the layout (header, sidebar, main content) with component declarations
  4. Sets up conditional rendering rules (showWhen) for authenticated/unauthenticated states
  5. Registers the new tenant in src/eai.config/index.ts
  6. 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:

  1. What are the main entities/objects? (e.g., Application, Document, Notification)
  2. For each entity, what properties does it have?
  3. Are there relationships between entities?
  4. What actions can users perform? (e.g., submit, approve, reject)

Steps performed:

  1. Maps your domain entities to the Object Type schema format
  2. Selects appropriate field types for each property (see table below)
  3. Defines relationships between entities via linkTypes
  4. Configures actions with role requirements, validation rules, and side effects
  5. Writes definitions to src/eai.config/object-types.ts
  6. Runs type checking (npx tsc --noEmit) to verify definitions compile

Field type mapping:

Domain ConceptField TypeTypeScript TypeExample
Names, emails, IDstextstringapplicantName, email
Counts, amountsnumbernumberscore, amount
FlagsbooleanbooleanisVerified, isActive
Timestamps, deadlinesdatestring (ISO 8601)submittedAt, dueDate
Status, categoryselect'draft' | 'active'status, priority
Metadata, notesjsonRecord<string, unknown>metadata, notes
Attachmentsfilestring (URL)attachment, photo
Referencesrelationshipstring (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 (use define-object-types first)
  • Platform SDK installed
  • Environment configured with BASE_URL_PUBLIC_API and auth credentials

Steps performed:

  1. Reads object type definitions from object-types.ts
  2. For each type, checks if it already exists in Configurator (idempotency)
  3. Creates new types or updates existing ones via the orchestrate endpoint
  4. Reports per-type results (created / updated / skipped)
  5. 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 },
});
caution

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.ts base hook exists

Steps performed:

  1. Reads object type definitions to identify entities
  2. Maps each property's field type to a TypeScript type
  3. Generates a typed interface for each entity
  4. Creates a hook file (src/hooks/use{Entity}s.ts) for each object type
  5. Verifies all hooks compile without errors

Type mapping applied:

Object Type FieldTypeScript Type
textstring
numbernumber
booleanboolean
datestring (ISO 8601)
selectUnion of option values (e.g., 'draft' | 'submitted')
jsonRecord<string, unknown>
filestring
relationshipstring

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:

  1. What type of AI assistant is needed? (RAG chat, document classifier, both)
  2. What domain knowledge should the chat have?
  3. What document types will be uploaded?
  4. What workflow stages are needed? (e.g., chat, review, summary)

Steps performed:

  1. Creates a workflow record in Configurator with stage definitions and system prompts
  2. Sets the WORKFLOW_*_ID environment variable
  3. Wires the useChat hook for SSE streaming
  4. Wires the useDocuments hook for file uploads and classification
  5. Verifies the SSE stream proxy exists at src/app/api/eai/stream/[[...rest]]/route.ts
  6. 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 */);
}
Important field names
  • Use message (not chat_input) for the chat message
  • conversation_id is required -- generate with crypto.randomUUID()
  • params is 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:

  1. Sets APP_NAME in .github/workflows/deploy-demo.yml
  2. Configures GitHub secrets (Azure credentials, resource group, webapp name)
  3. Registers the app in the demo-infra verticalApps array
  4. Seeds Azure App Configuration with environment-specific values
  5. Triggers deployment (push to main or manual workflow dispatch)
  6. Verifies the deployment is accessible and platform connectivity works

What APP_NAME determines:

AspectValue
URL pathhttps://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:

SecretDescription
AZUREAPPSERVICE_CLIENTIDAzure AD app registration client ID
AZUREAPPSERVICE_TENANTIDCorporate Azure AD tenant ID
AZUREAPPSERVICE_SUBSCRIPTIONIDAzure subscription ID
AZURE_RESOURCE_GROUPResource group name (e.g., rg-demo-infrastructure)
AZURE_WEBAPP_NAMEShared 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.