YeonGyu-Kim 490c0b626f Add auto-slash-command hook for intercepting and replacing slash commands
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)
2026-01-01 21:03:27 +09:00

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(" ")
}