This hook intercepts user messages starting with '/' and REPLACES them with the actual command template output instead of injecting instructions. The implementation includes: - Slash command detection (detector.ts) - identifies messages starting with '/' - Command discovery and execution (executor.ts) - loads templates from ~/.claude/commands/ or similar - Hook integration (index.ts) - registers with chat.message event to replace output.parts - Comprehensive test coverage - 37 tests covering detection, replacement, error handling, and command exclusions - Configuration support in HookNameSchema Key features: - Supports excluded commands to skip processing - Loads command templates from user's command directory - Replaces user input before reaching the LLM - Tests all edge cases including missing files, malformed templates, and special commands 🤖 Generated with assistance of OhMyOpenCode (https://github.com/code-yeongyu/oh-my-opencode)
66 lines
1.3 KiB
TypeScript
66 lines
1.3 KiB
TypeScript
import {
|
|
SLASH_COMMAND_PATTERN,
|
|
EXCLUDED_COMMANDS,
|
|
} from "./constants"
|
|
import type { ParsedSlashCommand } from "./types"
|
|
|
|
const CODE_BLOCK_PATTERN = /```[\s\S]*?```/g
|
|
|
|
export function removeCodeBlocks(text: string): string {
|
|
return text.replace(CODE_BLOCK_PATTERN, "")
|
|
}
|
|
|
|
export function parseSlashCommand(text: string): ParsedSlashCommand | null {
|
|
const trimmed = text.trim()
|
|
|
|
if (!trimmed.startsWith("/")) {
|
|
return null
|
|
}
|
|
|
|
const match = trimmed.match(SLASH_COMMAND_PATTERN)
|
|
if (!match) {
|
|
return null
|
|
}
|
|
|
|
const [raw, command, args] = match
|
|
return {
|
|
command: command.toLowerCase(),
|
|
args: args.trim(),
|
|
raw,
|
|
}
|
|
}
|
|
|
|
export function isExcludedCommand(command: string): boolean {
|
|
return EXCLUDED_COMMANDS.has(command.toLowerCase())
|
|
}
|
|
|
|
export function detectSlashCommand(text: string): ParsedSlashCommand | null {
|
|
const textWithoutCodeBlocks = removeCodeBlocks(text)
|
|
const trimmed = textWithoutCodeBlocks.trim()
|
|
|
|
if (!trimmed.startsWith("/")) {
|
|
return null
|
|
}
|
|
|
|
const parsed = parseSlashCommand(trimmed)
|
|
|
|
if (!parsed) {
|
|
return null
|
|
}
|
|
|
|
if (isExcludedCommand(parsed.command)) {
|
|
return null
|
|
}
|
|
|
|
return parsed
|
|
}
|
|
|
|
export function extractPromptText(
|
|
parts: Array<{ type: string; text?: string }>
|
|
): string {
|
|
return parts
|
|
.filter((p) => p.type === "text")
|
|
.map((p) => p.text || "")
|
|
.join(" ")
|
|
}
|