#!/usr/bin/env node // session-title.mjs — UserPromptSubmit hook (CC v2.1.94+) // // Sets a sessionTitle when the user invokes one of the four voyage commands, // so multi-session headless runs are easy to identify in process lists and // session pickers. // // Title format: voyage:: // - ∈ {brief, research, plan, execute, review} // - ∈ first 30 chars of project slug, or "ad-hoc" when no // --project / --brief context is detected // // Fail-open invariant: any error → exit 0 with no output. We never block // the user's prompt. import { stdin } from 'node:process'; import { resolve, basename } from 'node:path'; const COMMANDS = { '/trekbrief': 'brief', '/trekresearch': 'research', '/trekplan': 'plan', '/trekexecute': 'execute', '/trekreview': 'review', '/trekcontinue': 'continue', '/trekendsession': 'endsession', }; function slugify(s) { return String(s) .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, '') .slice(0, 30) || 'ad-hoc'; } function detectSlug(prompt) { const projectMatch = prompt.match(/--project[=\s]+(\S+)/); if (projectMatch) { const dir = projectMatch[1].replace(/['"]/g, ''); const base = basename(resolve(dir)); const dateStripped = base.replace(/^\d{4}-\d{2}-\d{2}-/, ''); return slugify(dateStripped); } const briefMatch = prompt.match(/--brief[=\s]+(\S+)/); if (briefMatch) { const file = briefMatch[1].replace(/['"]/g, ''); return slugify(basename(file, '.md')); } return 'ad-hoc'; } async function readStdin() { let data = ''; for await (const chunk of stdin) data += chunk; return data; } (async () => { try { const raw = await readStdin(); if (!raw.trim()) return; const payload = JSON.parse(raw); const prompt = String(payload.prompt || '').trim(); if (!prompt) return; let matchedCmd = null; for (const [cmd, short] of Object.entries(COMMANDS)) { if (prompt.startsWith(cmd)) { matchedCmd = short; break; } } if (!matchedCmd) return; const slug = detectSlug(prompt); const title = `voyage:${matchedCmd}:${slug}`; const out = { hookSpecificOutput: { hookEventName: 'UserPromptSubmit', sessionTitle: title, }, }; process.stdout.write(JSON.stringify(out) + '\n'); } catch { // fail open } })();