Replace hardcoded PowerShell status hooks with stdin-forwarding hooks that send full Claude Code hook data (tool_input, tool_response, prompt, session_id, model, etc.) to /api/claude-hook endpoint. - PowerShell hooks read stdin JSON and POST to /api/claude-hook - Server derives status for backward-compat FAB animations - Server extracts assistant_response from transcript on Stop events - New /api/claude-permission endpoint with Promise-based allow/deny flow - HookNotifications.vue: toast system showing session, prompt, tool use, tool results, notifications, and final assistant response - WebSocket broadcast for claude-hook and claude-permission message types
332 lines
9.4 KiB
TypeScript
332 lines
9.4 KiB
TypeScript
import { optionsResponse, notFoundResponse } from '../utils/cors'
|
|
import { handleHistory } from './history'
|
|
import { handleConfig, handleHealth } from './config'
|
|
import { handleWebMCPToken, handleWebMCPRequestToken } from './webmcp'
|
|
import { handleComponents, handleComponentById, handleComponentUsage } from './components'
|
|
import { handleThemes, handleActiveTheme, handleDesignTokens, handleThemeById, handleThemeExport } from './themes'
|
|
import { handleCanvas, handleCanvasById, handleToolbarCanvas, handleDefaultCanvas, handleCanvasComponents, handleCanvasComponentById } from './canvas'
|
|
import { handleGiteaRepo, handleGiteaTree, handleGiteaFile } from './gitea'
|
|
import { handleTables, handleStats, handleTableSchema, handleTableData, handleQuery } from './database'
|
|
import { handleWhisperRoutes } from './whisper'
|
|
import { handleRecordingsRoutes } from './recordings'
|
|
import { handleClaudeStatus } from './claude-status'
|
|
import { handleClaudeHook } from './claude-hook'
|
|
import { handleClaudePermission, handleClaudePermissionRespond, handleClaudePermissionList } from './claude-permission'
|
|
import { handleSnapshots, handleSnapshotById } from './snapshots'
|
|
import { handleGitStatus, handleGitDiff, handleGitLog, handleGitLogCommit, handleGitCompare, handleGitBranches, handleGitCurrentBranch, handleGitTree, handleGitFile } from './git'
|
|
import {
|
|
handleAgents, handleAgentsFile,
|
|
handleAgentsConfig, handleAgentsKnownTools, handleAgentsSkills,
|
|
handleAgentsPlugins, handleAgentsMcpJson,
|
|
handleAgentsConfigPermissions, handleAgentsConfigHooks, handleAgentsConfigMcp
|
|
} from './agents'
|
|
|
|
export async function handleRequest(req: Request): Promise<Response> {
|
|
const url = new URL(req.url)
|
|
const path = url.pathname
|
|
|
|
// CORS preflight
|
|
if (req.method === 'OPTIONS') {
|
|
return optionsResponse()
|
|
}
|
|
|
|
// Health
|
|
if (path === '/api/health') {
|
|
return handleHealth()
|
|
}
|
|
|
|
// History
|
|
if (path === '/api/history') {
|
|
const res = await handleHistory(req, url)
|
|
if (res) return res
|
|
}
|
|
|
|
// Config
|
|
if (path === '/api/config') {
|
|
const res = await handleConfig(req, url)
|
|
if (res) return res
|
|
}
|
|
|
|
// WebMCP Token (for polling - legacy)
|
|
if (path === '/api/webmcp-token') {
|
|
const res = await handleWebMCPToken(req)
|
|
if (res) return res
|
|
}
|
|
|
|
// WebMCP Request Token (direct request to WebMCP server)
|
|
if (path === '/api/webmcp-request-token') {
|
|
const res = await handleWebMCPRequestToken(req)
|
|
if (res) return res
|
|
}
|
|
|
|
// Claude Code status (thinking/idle)
|
|
if (path === '/api/claude-status') {
|
|
const res = await handleClaudeStatus(req)
|
|
if (res) return res
|
|
}
|
|
|
|
// Claude Code hook (rich stdin data forwarding)
|
|
if (path === '/api/claude-hook') {
|
|
const res = await handleClaudeHook(req)
|
|
if (res) return res
|
|
}
|
|
|
|
// Claude Code permission request/respond
|
|
if (path === '/api/claude-permission') {
|
|
if (req.method === 'GET') {
|
|
const res = await handleClaudePermissionList(req)
|
|
if (res) return res
|
|
} else {
|
|
const res = await handleClaudePermission(req)
|
|
if (res) return res
|
|
}
|
|
}
|
|
|
|
if (path === '/api/claude-permission-respond') {
|
|
const res = await handleClaudePermissionRespond(req)
|
|
if (res) return res
|
|
}
|
|
|
|
// Components
|
|
if (path === '/api/components') {
|
|
const res = await handleComponents(req)
|
|
if (res) return res
|
|
}
|
|
|
|
// Component usage
|
|
const componentUsageMatch = path.match(/^\/api\/components\/([^/]+)\/usage$/)
|
|
if (componentUsageMatch && req.method === 'GET') {
|
|
return handleComponentUsage(componentUsageMatch[1])
|
|
}
|
|
|
|
// Component by ID
|
|
if (path.startsWith('/api/components/') && !path.includes('/usage')) {
|
|
const id = path.split('/').pop()!
|
|
const res = await handleComponentById(req, id)
|
|
if (res) return res
|
|
}
|
|
|
|
// Themes
|
|
if (path === '/api/themes') {
|
|
const res = await handleThemes(req)
|
|
if (res) return res
|
|
}
|
|
|
|
if (path === '/api/themes/active') {
|
|
return handleActiveTheme()
|
|
}
|
|
|
|
if (path === '/api/design-tokens') {
|
|
return handleDesignTokens()
|
|
}
|
|
|
|
// Theme export
|
|
if (path.startsWith('/api/themes/export/')) {
|
|
const id = path.split('/').pop()!
|
|
if (req.method === 'GET') {
|
|
return handleThemeExport(id)
|
|
}
|
|
}
|
|
|
|
// Theme by ID
|
|
if (path.startsWith('/api/themes/') && !path.includes('/active') && !path.includes('/export')) {
|
|
const pathParts = path.split('/')
|
|
const id = pathParts[3]
|
|
const action = pathParts[4]
|
|
const res = await handleThemeById(req, id, action)
|
|
if (res) return res
|
|
}
|
|
|
|
// Canvas toolbar
|
|
if (path === '/api/canvas/toolbar') {
|
|
return handleToolbarCanvas()
|
|
}
|
|
|
|
// Canvas default
|
|
if (path === '/api/canvas/default') {
|
|
return handleDefaultCanvas()
|
|
}
|
|
|
|
// Canvas list/create
|
|
if (path === '/api/canvas') {
|
|
const res = await handleCanvas(req)
|
|
if (res) return res
|
|
}
|
|
|
|
// Canvas components
|
|
const canvasComponentsMatch = path.match(/^\/api\/canvas\/([^/]+)\/components\/?$/)
|
|
if (canvasComponentsMatch) {
|
|
const res = await handleCanvasComponents(req, canvasComponentsMatch[1])
|
|
if (res) return res
|
|
}
|
|
|
|
// Canvas component by ID
|
|
const canvasComponentMatch = path.match(/^\/api\/canvas\/([^/]+)\/components\/([^/]+)$/)
|
|
if (canvasComponentMatch) {
|
|
const res = await handleCanvasComponentById(req, canvasComponentMatch[1], canvasComponentMatch[2])
|
|
if (res) return res
|
|
}
|
|
|
|
// Canvas by ID
|
|
if (path.startsWith('/api/canvas/') && !path.includes('/components')) {
|
|
const pathParts = path.split('/')
|
|
const id = pathParts[3]
|
|
const action = pathParts[4]
|
|
const res = await handleCanvasById(req, id, action)
|
|
if (res) return res
|
|
}
|
|
|
|
// Snapshots
|
|
if (path === '/api/snapshots') {
|
|
const res = await handleSnapshots(req)
|
|
if (res) return res
|
|
}
|
|
|
|
if (path.startsWith('/api/snapshots/')) {
|
|
const id = path.split('/').pop()!
|
|
const res = await handleSnapshotById(req, id)
|
|
if (res) return res
|
|
}
|
|
|
|
// Gitea
|
|
if (path === '/api/gitea/repo' && req.method === 'POST') {
|
|
return handleGiteaRepo(req)
|
|
}
|
|
|
|
if (path === '/api/gitea/tree' && req.method === 'POST') {
|
|
return handleGiteaTree(req)
|
|
}
|
|
|
|
if (path === '/api/gitea/file' && req.method === 'POST') {
|
|
return handleGiteaFile(req)
|
|
}
|
|
|
|
// Database Explorer
|
|
if (path === '/api/database/tables') {
|
|
return handleTables()
|
|
}
|
|
|
|
if (path === '/api/database/stats') {
|
|
return handleStats()
|
|
}
|
|
|
|
// Table schema
|
|
const tableSchemaMatch = path.match(/^\/api\/database\/tables\/([^/]+)\/schema$/)
|
|
if (tableSchemaMatch && req.method === 'GET') {
|
|
return handleTableSchema(decodeURIComponent(tableSchemaMatch[1]))
|
|
}
|
|
|
|
// Table data
|
|
const tableDataMatch = path.match(/^\/api\/database\/tables\/([^/]+)\/data$/)
|
|
if (tableDataMatch && req.method === 'GET') {
|
|
return handleTableData(decodeURIComponent(tableDataMatch[1]), url)
|
|
}
|
|
|
|
// Database query
|
|
if (path === '/api/database/query' && req.method === 'POST') {
|
|
return handleQuery(req)
|
|
}
|
|
|
|
// Whisper (GPU speech-to-text)
|
|
if (path.startsWith('/api/whisper/')) {
|
|
const res = await handleWhisperRoutes(req)
|
|
if (res) return res
|
|
}
|
|
|
|
// Voice recordings (for training custom models)
|
|
if (path.startsWith('/api/recordings')) {
|
|
const res = await handleRecordingsRoutes(req)
|
|
if (res) return res
|
|
}
|
|
|
|
// Git
|
|
if (path === '/api/git/status' && req.method === 'GET') {
|
|
return handleGitStatus()
|
|
}
|
|
|
|
if (path === '/api/git/diff' && req.method === 'GET') {
|
|
return handleGitDiff(url)
|
|
}
|
|
|
|
if (path === '/api/git/log' && req.method === 'GET') {
|
|
return handleGitLog(url)
|
|
}
|
|
|
|
const gitLogCommitMatch = path.match(/^\/api\/git\/log\/([a-f0-9]+)$/)
|
|
if (gitLogCommitMatch && req.method === 'GET') {
|
|
return handleGitLogCommit(gitLogCommitMatch[1])
|
|
}
|
|
|
|
if (path === '/api/git/compare' && req.method === 'POST') {
|
|
return handleGitCompare(req)
|
|
}
|
|
|
|
if (path === '/api/git/branches' && req.method === 'GET') {
|
|
return handleGitBranches()
|
|
}
|
|
|
|
if (path === '/api/git/branch/current' && req.method === 'GET') {
|
|
return handleGitCurrentBranch()
|
|
}
|
|
|
|
if (path === '/api/git/tree' && req.method === 'GET') {
|
|
return handleGitTree(url)
|
|
}
|
|
|
|
if (path === '/api/git/file' && req.method === 'GET') {
|
|
return handleGitFile(url)
|
|
}
|
|
|
|
// Agents
|
|
if (path === '/api/agents' && req.method === 'GET') {
|
|
return handleAgents(req)
|
|
}
|
|
|
|
if (path === '/api/agents/config' && req.method === 'GET') {
|
|
const res = await handleAgentsConfig(req, url)
|
|
if (res) return res
|
|
}
|
|
|
|
if (path === '/api/agents/known-tools' && req.method === 'GET') {
|
|
const res = await handleAgentsKnownTools(req)
|
|
if (res) return res
|
|
}
|
|
|
|
if (path === '/api/agents/skills' && req.method === 'GET') {
|
|
const res = await handleAgentsSkills(req, url)
|
|
if (res) return res
|
|
}
|
|
|
|
if (path === '/api/agents/plugins' && req.method === 'GET') {
|
|
const res = await handleAgentsPlugins(req)
|
|
if (res) return res
|
|
}
|
|
|
|
if (path === '/api/agents/mcp-json' && req.method === 'GET') {
|
|
const res = await handleAgentsMcpJson(req)
|
|
if (res) return res
|
|
}
|
|
|
|
if (path === '/api/agents/config/permissions' && req.method === 'POST') {
|
|
const res = await handleAgentsConfigPermissions(req)
|
|
if (res) return res
|
|
}
|
|
|
|
if (path === '/api/agents/config/hooks' && req.method === 'POST') {
|
|
const res = await handleAgentsConfigHooks(req)
|
|
if (res) return res
|
|
}
|
|
|
|
if (path === '/api/agents/config/mcp' && req.method === 'POST') {
|
|
const res = await handleAgentsConfigMcp(req)
|
|
if (res) return res
|
|
}
|
|
|
|
if (path === '/api/agents/file') {
|
|
const res = await handleAgentsFile(req, url)
|
|
if (res) return res
|
|
}
|
|
|
|
return notFoundResponse()
|
|
}
|