import { escapeHtml } from './format-telegram.js'
import {
readDailyNote,
parseFrontmatter,
ensureDailyNote,
todayIso,
vaultRelFrom,
} from './vault.js'
import { listVaultNotes } from './memory-graph.js'
import { calendarBetween } from './calendar.js'
import { gmailSince, gmailTopByImportance } from './gmail.js'
import {
googleAuthConfigured,
googleTokenSaved,
} from './google-auth.js'
import { taskRows, shortTaskId } from './tasks.js'
export type BriefSummary = {
html: string
markdown: string
}
function fmtTime(ms: number | null): string {
if (!ms) return '—'
return new Date(ms).toLocaleTimeString('en-IN', { hour: '2-digit', minute: '2-digit', hour12: false })
}
export async function composeMorningBrief(): Promise {
const date = todayIso()
await ensureDailyNote(date)
const raw = (await readDailyNote(date)) ?? ''
const { frontmatter } = parseFrontmatter(raw)
const required: string[] = []
if (frontmatter.gym_required === 'true') required.push('gym')
if (frontmatter.thesis_required === 'true') required.push('thesis')
required.push('kit', 'meditation')
const now = Date.now()
const lastDay = now - 24 * 3600_000
const nextDay = now + 24 * 3600_000
// Calendar
let calendarLines: string[]
if (googleAuthConfigured() && googleTokenSaved()) {
const events = calendarBetween(now - 3600_000, nextDay)
calendarLines = events.length === 0
? ['no events in next 24h']
: events.slice(0, 10).map(e => {
const time = fmtTime(e.starts_at)
const sum = escapeHtml(e.summary ?? '(no title)')
const link = e.meet_link ? ` · meet` : ''
return `• ${time} ${sum}${link}`
})
} else {
calendarLines = ['not configured — run `npx tsx scripts/setup-google.ts`']
}
// Gmail — ranked by LLM-scored importance, not user labels.
let gmailLines: string[]
if (googleAuthConfigured() && googleTokenSaved()) {
const ranked = gmailTopByImportance(lastDay, 8)
const items = ranked.length > 0 ? ranked : gmailSince(lastDay, 8)
gmailLines = items.length === 0
? ['no inbox activity in last 24h']
: items.map(m => {
const sender = escapeHtml((m.sender ?? '').split('<')[0]?.trim() || 'unknown')
const subj = escapeHtml(m.subject ?? '(no subject)')
const score = m.importance !== null && m.importance !== undefined ? ` · ${m.importance}` : ''
const why = m.importance_reason ? ` ${escapeHtml(m.importance_reason)}` : ''
return `• ${sender} — ${subj}${score}${why}`
})
} else {
gmailLines = ['not configured — run `npm run setup:google`']
}
const tasks = taskRows(8).filter(t => t.status !== 'completed')
const taskLines = tasks.length === 0
? ['no open tasks tracked']
: tasks.map(t => {
const due = t.due_ts ? ` · ${escapeHtml(new Date(t.due_ts).toLocaleDateString('en-IN'))}` : ''
return `• ${escapeHtml(shortTaskId(t.id))} ${escapeHtml(t.title)}${due}`
})
// Vault
const inboxFiles = listVaultNotes('04_Notes/inbox')
const recentInbox = inboxFiles
.sort((a, b) => b.mtime - a.mtime)
.slice(0, 5)
.map(n => vaultRelFrom(n.absPath))
let openIdeas: string[] = []
try {
openIdeas = listVaultNotes('06_Projects/ideas')
.filter(n => n.relPath.endsWith('/index.md'))
.sort((a, b) => b.mtime - a.mtime)
.slice(0, 3)
.map(n => vaultRelFrom(n.absPath))
} catch {
/* ignore */
}
const htmlLines: string[] = [
`Morning brief · ${date} · ${escapeHtml(frontmatter.weekday ?? '?')}`,
'',
`Required today: ${required.map(escapeHtml).join(', ')}`,
'',
'Calendar · next 24h',
...calendarLines,
'',
'Inbox · priority · 24h',
...gmailLines,
'',
'Google Tasks · open',
...taskLines,
'',
`Vault inbox (recent) · ${recentInbox.length}`,
...recentInbox.map(p => `• ${escapeHtml(p)}`),
]
if (openIdeas.length > 0) {
htmlLines.push('', `Open ideas · ${openIdeas.length}`)
htmlLines.push(...openIdeas.map(p => `• ${escapeHtml(p)}`))
}
htmlLines.push('', `Daily note → ${escapeHtml(`03_Daily/${date}.md`)}`)
const html = htmlLines.join('\n')
const markdownLines = [
`## Brief — ${date}`,
'',
`- Required: ${required.join(', ')}`,
`- Calendar: ${calendarLines.length === 1 && calendarLines[0]?.includes('not configured') ? 'n/a' : `${calendarLines.length} items`}`,
`- Gmail priority: ${gmailLines.length === 1 && gmailLines[0]?.includes('not configured') ? 'n/a' : `${gmailLines.length} items`}`,
`- Google Tasks: ${tasks.length} open`,
`- Vault inbox (${recentInbox.length}):`,
...recentInbox.map(p => ` - [[${p}]]`),
]
if (openIdeas.length > 0) {
markdownLines.push(`- Open ideas (${openIdeas.length}):`)
markdownLines.push(...openIdeas.map(p => ` - [[${p}]]`))
}
const markdown = markdownLines.join('\n')
return { html, markdown }
}