Files
hapi/cli/src/claude/utils/generateHookSettings.ts
T
weishu 7f9145b6de feat: add token-based authentication to session hooks
Implement hook token validation to secure session start requests.
Each hook server generates a unique token that must be provided
in the x-hapi-hook-token header. Updates hook-forwarder command
to support both positional and named arguments for port and token.
2025-12-23 16:02:04 +08:00

78 lines
2.3 KiB
TypeScript

/**
* Generate temporary settings file with Claude hooks for session tracking.
*
* Creates a settings.json file that configures Claude's SessionStart hook
* to notify our HTTP server when sessions change (new session, resume, compact, etc.).
*/
import { join } from 'node:path';
import { writeFileSync, mkdirSync, unlinkSync, existsSync } from 'node:fs';
import { configuration } from '@/configuration';
import { logger } from '@/ui/logger';
import { getHappyCliCommand } from '@/utils/spawnHappyCLI';
function shellQuote(value: string): string {
if (value.length === 0) {
return '""';
}
if (/^[A-Za-z0-9_\/:=-]+$/.test(value)) {
return value;
}
return '"' + value.replace(/(["\\$`])/g, '\\$1') + '"';
}
function shellJoin(parts: string[]): string {
return parts.map(shellQuote).join(' ');
}
/**
* Generate a temporary settings file with SessionStart hook configuration.
*/
export function generateHookSettingsFile(port: number, token: string): string {
const hooksDir = join(configuration.happyHomeDir, 'tmp', 'hooks');
mkdirSync(hooksDir, { recursive: true });
const filename = `session-hook-${process.pid}.json`;
const filepath = join(hooksDir, filename);
const { command, args } = getHappyCliCommand(['hook-forwarder', '--port', String(port), '--token', token]);
const hookCommand = shellJoin([command, ...args]);
const settings = {
hooks: {
SessionStart: [
{
matcher: '*',
hooks: [
{
type: 'command',
command: hookCommand
}
]
}
]
}
};
writeFileSync(filepath, JSON.stringify(settings, null, 4));
logger.debug(`[generateHookSettings] Created hook settings file: ${filepath}`);
return filepath;
}
/**
* Clean up the temporary hook settings file.
*/
export function cleanupHookSettingsFile(filepath: string): void {
try {
if (existsSync(filepath)) {
unlinkSync(filepath);
logger.debug(`[generateHookSettings] Cleaned up hook settings file: ${filepath}`);
}
} catch (error) {
logger.debug(`[generateHookSettings] Failed to cleanup hook settings file: ${error}`);
}
}