用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/qashsolutions/denali --skill architecture命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | architecture |
| description | System architecture, Edge functions, API design, and modularity patterns |
| version | 1.0.0 |
| context | infrastructure |
This skill defines the system architecture, including component design, Edge function patterns, and API structure.
┌─────────────────────────────────────────────────────────────────────┐
│ denali.health │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────┐ │
│ │ PWA Client │────────▶│ Edge Functions │────────▶│ Claude │ │
│ │ (Frontend) │◀────────│ (Backend) │◀────────│ (AI) │ │
│ └───────────────┘ └───────────────┘ └───────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌───────────────┐ ┌───────────┐ │
│ │ Supabase │ │ MCPs │ │
│ │ (Database) │ │ (Tools) │ │
│ └───────────────┘ └───────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
The PWA client:
Claude:
Each skill:
External APIs:
/app
├── /components
│ ├── Chat.tsx # Main chat interface
│ ├── Message.tsx # Message bubble
│ ├── Input.tsx # Text input + suggestions
│ ├── Suggestions.tsx # Tappable chips
│ └── Feedback.tsx # Thumbs up/down
├── /screens
│ ├── Welcome.tsx # Landing/greeting
│ ├── Conversation.tsx # Chat flow
│ ├── Guidance.tsx # Coverage output
│ ├── Appeal.tsx # Appeal letter view
│ ├── Settings.tsx # User preferences
│ └── History.tsx # Past conversations
├── /hooks
│ ├── useChat.ts # Chat state management
│ ├── useAuth.ts # Authentication
│ └── useTheme.ts # Theme management
└── /lib
├── supabase.ts # Supabase client
└── api.ts # Edge function calls
/supabase/functions
├── /chat
│ └── index.ts # Main conversation handler
├── /coverage-check
│ └── index.ts # NCD/LCD lookup
├── /generate-appeal
│ └── index.ts # Appeal letter generation
├── /process-feedback
│ └── index.ts # Handle thumbs up/down
├── /report-outcome
│ └── index.ts # Appeal result tracking
├── /auth-otp
│ └── index.ts # OTP send/verify
├── /stripe-webhook
│ └── index.ts # Payment processing
└── /nightly-learning
└── index.ts # Scheduled learning jobs
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { createClient } from '@supabase/supabase-js'
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
serve(async (req) => {
// Handle CORS preflight
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
try {
// Parse request
const { message, conversationId } = await req.json()
// Initialize Supabase
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
// Get user from auth header (optional)
const authHeader = req.headers.get('Authorization')
let user = null
if (authHeader) {
const { data } = await supabase..(
authHeader.(, )
)
user = data.
}
result = (message, conversationId, user)
(
.(result),
{ : { ...corsHeaders, : } }
)
} (error) {
(
.({ : error. }),
{ : , : { ...corsHeaders, : } }
)
}
})
import Anthropic from '@anthropic-ai/sdk'
async function processMessage(
message: string,
conversationId: string,
context: ConversationContext
): Promise<ChatResponse> {
const anthropic = new Anthropic({
apiKey: Deno.env.get('ANTHROPIC_API_KEY')!
})
// Load relevant skills
const skills = await loadSkills(context)
// Get learning context
const learningContext = await getLearningContext(
context.symptoms,
context.procedures
)
// Build system prompt
const systemPrompt = buildSystemPrompt(skills, learningContext)
// Get conversation history
const history = await getConversationHistory(conversationId)
// Call Claude
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: ,
: systemPrompt,
: [
...history,
{ : , : message }
],
: (context)
})
(response. === ) {
toolResults = (response.)
}
entities = (response.)
(conversationId, , response., entities)
(entities)
{
: response.,
: (context),
entities
}
}
| Endpoint | Method | Auth | Purpose |
|---|---|---|---|
/chat | POST | Optional | Send message, get response |
/coverage-check | POST | Optional | Check Medicare coverage |
/generate-appeal | POST | Required | Generate appeal letter |
/feedback | POST | Required | Submit thumbs up/down |
/outcome | POST | Required | Report appeal result |
/history | GET | Required | Get conversation history |
/settings | GET/PUT | Required | User preferences |
Chat Request:
interface ChatRequest {
message: string
conversationId?: string // null for new conversation
context?: {
symptoms?: string[]
procedures?: string[]
provider?: {
name: string
npi?: string
}
}
}
Chat Response:
interface ChatResponse {
conversationId: string
message: {
id: string
role: 'assistant'
content: string
}
suggestions: string[]
state: {
stage: 'intake' | 'coverage' | 'guidance' | 'appeal'
hasGuidance: boolean
canPrint: boolean
}
}
interface MCPTool {
name: string
description: string
execute: (params: Record<string, any>) => Promise<any>
}
const tools: MCPTool[] = [
{
name: 'search_icd10',
description: 'Search ICD-10 diagnosis codes',
execute: async ({ query }) => {
// Call ICD-10 MCP
return await icd10MCP.search(query)
}
},
{
name: 'search_npi',
description: 'Search NPI registry for providers',
execute: async ({ name, state, specialty }) => {
// Call NPI Registry MCP
return await npiMCP.search({ name, state, specialty })
}
},
{
name: 'search_coverage',
description: 'Search Medicare NCDs and LCDs',
execute: async ({ icd10, cpt, state }) => {
// Call CMS Coverage MCP
return await cmsMCP.searchCoverage({ icd10, cpt, state })
}
}
]
async function executeTools(
toolCalls: ToolCall[]
): Promise<ToolResult[]> {
const results: ToolResult[] = []
for (const call of toolCalls) {
const tool = tools.find(t => t.name === call.name)
if (!tool) {
results.push({
id: call.id,
error: `Unknown tool: ${call.name}`
})
continue
}
try {
const result = await tool.execute(call.params)
results.push({
id: call.id,
result
})
} catch (error) {
results.push({
id: call.id,
error: error.message
})
}
}
return results
}
enum ErrorCode {
// Client errors
INVALID_REQUEST = 'INVALID_REQUEST',
UNAUTHORIZED = 'UNAUTHORIZED',
PAYMENT_REQUIRED = 'PAYMENT_REQUIRED',
// Server errors
INTERNAL_ERROR = 'INTERNAL_ERROR',
CLAUDE_ERROR = 'CLAUDE_ERROR',
TOOL_ERROR = 'TOOL_ERROR',
DATABASE_ERROR = 'DATABASE_ERROR',
// Business errors
APPEAL_LIMIT_REACHED = 'APPEAL_LIMIT_REACHED',
SERVICE_NOT_COVERED = 'SERVICE_NOT_COVERED'
}
interface ErrorResponse {
error: {
code: ErrorCode
message: string
details?: Record<string, any>
}
}
// Cache NCDs/LCDs for 24 hours
const POLICY_CACHE_TTL = 24 * 60 * 60 * 1000
async function getCoveragePolicy(icd10: string, cpt: string, state: string) {
const cacheKey = `policy:${icd10}:${cpt}:${state}`
// Check cache
const cached = await cache.get(cacheKey)
if (cached && !isExpired(cached)) {
return cached.data
}
// Fetch fresh
const policy = await cmsMCP.searchCoverage({ icd10, cpt, state })
// Update cache
await cache.set(cacheKey, {
data: policy,
timestamp: Date.now()
}, POLICY_CACHE_TTL)
return policy
}
# Supabase
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_ANON_KEY=xxx
SUPABASE_SERVICE_ROLE_KEY=xxx
# Anthropic
ANTHROPIC_API_KEY=xxx
# Stripe
STRIPE_SECRET_KEY=xxx
STRIPE_WEBHOOK_SECRET=xxx
# MCPs
ICD10_MCP_URL=xxx
NPI_MCP_URL=xxx
CMS_MCP_URL=xxx
PUBMED_MCP_URL=xxx
# Deploy all functions
supabase functions deploy
# Deploy specific function
supabase functions deploy chat
# Deploy database changes
supabase db push