|
| 1 | +import { readFileSync } from 'fs'; |
| 2 | +import { basename, join } from 'path'; |
| 3 | + |
| 4 | +import type { RenderContext } from '../types/RenderContext'; |
| 5 | +import { getTaskDir } from '../widgets/TaskObjective'; |
| 6 | + |
| 7 | +import { runGit } from './git'; |
| 8 | + |
| 9 | +function getTaskFromFile(sessionId: string): string | null { |
| 10 | + try { |
| 11 | + const content = readFileSync(join(getTaskDir(), `claude-task-${sessionId}`), 'utf8').trim(); |
| 12 | + if (!content) return null; |
| 13 | + try { |
| 14 | + const data = JSON.parse(content) as { task?: string }; |
| 15 | + return data.task ?? null; |
| 16 | + } catch { |
| 17 | + return content.split('\n')[0] ?? null; |
| 18 | + } |
| 19 | + } catch { |
| 20 | + return null; |
| 21 | + } |
| 22 | +} |
| 23 | + |
| 24 | +/** |
| 25 | + * Resolve a terminal title template string. |
| 26 | + * |
| 27 | + * Supported placeholders: |
| 28 | + * {task} - task objective from the session task file |
| 29 | + * {repo} - git repository name |
| 30 | + * {branch} - current git branch |
| 31 | + * {model} - model display name |
| 32 | + * {dir} - current working directory basename |
| 33 | + * |
| 34 | + * Segments separated by ' | ' are dropped if all their placeholders resolved |
| 35 | + * to empty, so "Task: {task} | {repo}/{branch}" gracefully falls back to |
| 36 | + * just "{repo}/{branch}" when no task is set. |
| 37 | + */ |
| 38 | +export function resolveTerminalTitle(template: string, context: RenderContext): string | null { |
| 39 | + const sessionId = context.data?.session_id; |
| 40 | + const task = sessionId ? getTaskFromFile(sessionId) : null; |
| 41 | + const branch = runGit('branch --show-current', context); |
| 42 | + const repoPath = runGit('rev-parse --show-toplevel', context); |
| 43 | + const repo = repoPath ? basename(repoPath) : null; |
| 44 | + const model = typeof context.data?.model === 'string' |
| 45 | + ? context.data.model |
| 46 | + : context.data?.model?.display_name ?? null; |
| 47 | + const dir = context.data?.workspace?.current_dir |
| 48 | + ? basename(context.data.workspace.current_dir) |
| 49 | + : null; |
| 50 | + |
| 51 | + const vars: Record<string, string | null> = { |
| 52 | + task, repo, branch, model, dir |
| 53 | + }; |
| 54 | + |
| 55 | + // Split by ' | ' segments, resolve each, drop empty segments |
| 56 | + const segments = template.split(' | ').map(segment => { |
| 57 | + const resolved = segment.replace(/\{(\w+)\}/g, (_, key: string) => vars[key] ?? ''); |
| 58 | + // If the segment is empty after resolving (all placeholders were empty), skip it |
| 59 | + return resolved.trim(); |
| 60 | + }).filter(s => s.length > 0); |
| 61 | + |
| 62 | + if (segments.length === 0) return null; |
| 63 | + return segments.join(' | '); |
| 64 | +} |
0 commit comments