diff --git a/dist/cli.js b/dist/cli.js index ce857f4..d7fca74 100755 --- a/dist/cli.js +++ b/dist/cli.js @@ -2,7 +2,7 @@ // src/cli.ts import { Command } from "commander"; -import chalk6 from "chalk"; +import chalk10 from "chalk"; // src/commands/dashboard.ts import chalk2 from "chalk"; @@ -113,6 +113,61 @@ import { existsSync as existsSync2 } from "fs"; // src/core/parser.ts import { createReadStream } from "fs"; import { createInterface } from "readline"; + +// src/core/pricing.ts +var PRICING = { + "claude-opus-4-6": { input: 15, output: 75, cacheCreation: 18.75, cacheRead: 1.5 }, + "claude-opus-4-5-20251101": { input: 15, output: 75, cacheCreation: 18.75, cacheRead: 1.5 }, + "claude-sonnet-4-6": { input: 3, output: 15, cacheCreation: 3.75, cacheRead: 0.3 }, + "claude-sonnet-4-5-20241022": { input: 3, output: 15, cacheCreation: 3.75, cacheRead: 0.3 }, + "claude-haiku-4-5-20251001": { input: 0.8, output: 4, cacheCreation: 1, cacheRead: 0.08 } +}; +var FALLBACK = { input: 3, output: 15, cacheCreation: 3.75, cacheRead: 0.3 }; +function findPricing(model) { + if (PRICING[model]) return PRICING[model]; + for (const [key, val] of Object.entries(PRICING)) { + if (model.includes(key) || key.includes(model)) return val; + } + if (model.includes("opus")) return PRICING["claude-opus-4-6"]; + if (model.includes("haiku")) return PRICING["claude-haiku-4-5-20251001"]; + if (model.includes("sonnet")) return PRICING["claude-sonnet-4-6"]; + return FALLBACK; +} +function computeCost(model, usage) { + const p = findPricing(model); + const perM = 1e6; + let cost = 0; + cost += (usage.input_tokens || 0) * p.input / perM; + cost += (usage.output_tokens || 0) * p.output / perM; + cost += (usage.cache_creation_input_tokens || 0) * p.cacheCreation / perM; + cost += (usage.cache_read_input_tokens || 0) * p.cacheRead / perM; + return cost; +} + +// src/core/parser.ts +var SKIP_TYPES = /* @__PURE__ */ new Set([ + "progress", + "file-history-snapshot", + "queue-operation" +]); +function resolveRole(event) { + const t = event.type; + if (t === "human" || t === "user") return "human"; + if (t === "assistant") return "assistant"; + return null; +} +function getContent(event) { + if (event.message && typeof event.message === "object" && event.message.content != null) { + return event.message.content; + } + return event.content; +} +function getModel(event) { + if (event.message && typeof event.message === "object" && event.message.model) { + return event.message.model; + } + return event.model; +} async function scanSession(filePath) { const meta = { messageCount: 0, @@ -127,7 +182,8 @@ async function scanSession(filePath) { firstUserMessage: "", lastActivity: /* @__PURE__ */ new Date(0), firstActivity: /* @__PURE__ */ new Date(), - errorCount: 0 + errorCount: 0, + costByModel: {} }; const toolSet = /* @__PURE__ */ new Set(); const fileSet = /* @__PURE__ */ new Set(); @@ -140,41 +196,60 @@ async function scanSession(filePath) { if (!line.trim()) continue; try { const event = JSON.parse(line); + if (SKIP_TYPES.has(event.type)) continue; const ts = event.timestamp ? new Date(event.timestamp) : null; if (ts) { if (ts < meta.firstActivity) meta.firstActivity = ts; if (ts > meta.lastActivity) meta.lastActivity = ts; } - if (event.type === "human") { + const role = resolveRole(event); + if (role === "human") { meta.humanTurns++; meta.messageCount++; } - if (event.type === "assistant") { + if (role === "assistant") { meta.assistantTurns++; meta.messageCount++; } - if (event.type === "human" && !meta.firstUserMessage) { + if (role === "human" && !meta.firstUserMessage) { meta.firstUserMessage = extractTextContent(event); } - if (event.costUSD && typeof event.costUSD === "number") { + const model = getModel(event); + const usage = event.message && typeof event.message === "object" ? event.message.usage : void 0; + if (model && usage) { + const cost = computeCost(model, usage); + meta.totalCostUSD += cost; + meta.costByModel[model] = (meta.costByModel[model] || 0) + cost; + } else if (event.costUSD && typeof event.costUSD === "number") { meta.totalCostUSD += event.costUSD; + const m = model || "unknown"; + meta.costByModel[m] = (meta.costByModel[m] || 0) + event.costUSD; + } + if (event.type === "system" && event.subtype === "turn_duration") { + const dur = event.durationMs; + if (typeof dur === "number") { + meta.totalDurationMs += dur; + } } if (event.durationMs && typeof event.durationMs === "number") { meta.totalDurationMs += event.durationMs; } - if (event.model && typeof event.model === "string") { - modelSet.add(event.model); + if (model) { + modelSet.add(model); } - if (Array.isArray(event.content)) { - for (const block of event.content) { + const content = getContent(event); + if (Array.isArray(content)) { + for (const block of content) { if (block.type === "tool_use") { const tb = block; meta.toolCalls++; toolSet.add(tb.name); const input = tb.input; - for (const key of ["file_path", "path", "filePath"]) { - if (input[key] && typeof input[key] === "string") { - fileSet.add(input[key]); + if (input) { + for (const key of ["file_path", "path", "filePath"]) { + if (input[key] && typeof input[key] === "string") { + fileSet.add(input[key]); + } } } } @@ -205,6 +280,7 @@ async function parseSessionFile(filePath, sessionId) { lineIndex++; try { const raw = JSON.parse(line); + if (SKIP_TYPES.has(raw.type)) continue; const parsed = normalizeEvent(raw, sessionId, lineIndex); if (parsed.length > 0) { events.push(...parsed); @@ -219,8 +295,11 @@ function normalizeEvent(raw, sessionId, lineIndex) { const events = []; const timestamp = raw.timestamp ? new Date(raw.timestamp) : /* @__PURE__ */ new Date(); const baseId = raw.uuid || `${sessionId}-${lineIndex}`; - if ((raw.type === "human" || raw.type === "assistant") && Array.isArray(raw.content)) { - const blocks = raw.content; + const role = resolveRole(raw); + const content = getContent(raw); + const model = getModel(raw); + if (role && Array.isArray(content)) { + const blocks = content; let blockIndex = 0; for (const block of blocks) { blockIndex++; @@ -231,12 +310,12 @@ function normalizeEvent(raw, sessionId, lineIndex) { id: eventId, sessionId, timestamp, - role: raw.type, + role, type: "text", content: textBlock.text, costUSD: raw.costUSD, durationMs: raw.durationMs, - model: raw.model, + model, raw }); } else if (block.type === "tool_use") { @@ -268,14 +347,14 @@ function normalizeEvent(raw, sessionId, lineIndex) { }); } } - if (typeof raw.content === "string") { + if (typeof content === "string") { events.push({ id: baseId, sessionId, timestamp, - role: raw.type, + role, type: "message", - content: raw.content, + content, raw }); } @@ -284,20 +363,20 @@ function normalizeEvent(raw, sessionId, lineIndex) { id: baseId, sessionId, timestamp, - role: raw.type, + role, type: "message", content: extractTextContent(raw), raw }); } - } else if (raw.type === "human" && typeof raw.content === "string") { + } else if (role === "human" && typeof content === "string") { events.push({ id: baseId, sessionId, timestamp, role: "human", type: "message", - content: raw.content, + content, raw }); } else if (raw.type === "summary") { @@ -314,6 +393,14 @@ function normalizeEvent(raw, sessionId, lineIndex) { return events; } function extractTextContent(event) { + const msgContent = event.message && typeof event.message === "object" ? event.message.content : void 0; + if (typeof msgContent === "string") return msgContent; + if (Array.isArray(msgContent)) { + const textBlocks = msgContent.filter( + (b) => b.type === "text" + ); + if (textBlocks.length > 0) return textBlocks.map((b) => b.text).join("\n"); + } if (typeof event.content === "string") return event.content; if (Array.isArray(event.content)) { const textBlocks = event.content.filter( @@ -321,7 +408,6 @@ function extractTextContent(event) { ); if (textBlocks.length > 0) return textBlocks.map((b) => b.text).join("\n"); } - if (event.message && typeof event.message === "string") return event.message; return ""; } function summarizeToolInput(input) { @@ -537,12 +623,6 @@ function truncate(str, maxLen) { if (str.length <= maxLen) return str; return str.slice(0, maxLen - 1) + "\u2026"; } -function formatCost(usd) { - if (usd === 0) return chalk.dim("\u2014"); - if (usd < 0.01) return chalk.green(`$${usd.toFixed(4)}`); - if (usd < 1) return chalk.green(`$${usd.toFixed(3)}`); - return chalk.yellow(`$${usd.toFixed(2)}`); -} function formatNumber(n) { return n.toLocaleString(); } @@ -550,13 +630,142 @@ function printSuccess(msg) { console.log(chalk.green(" \u2713 ") + msg); } function printError(msg) { - console.log(chalk.red(" \u2717 ") + msg); + console.error(chalk.red(" \u2717 ") + msg); +} +var TOOL_NAMES = { + Bash: "ran a command", + Read: "read a file", + Write: "wrote a file", + Edit: "edited a file", + Glob: "searched for files", + Grep: "searched code", + Agent: "used an agent", + WebSearch: "searched the web", + WebFetch: "fetched a page", + TodoWrite: "updated todos", + TodoRead: "checked todos" +}; +function humanizeToolName(name) { + if (TOOL_NAMES[name]) return TOOL_NAMES[name]; + return name.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase(); +} +var TOOL_PLURALS = { + Bash: { singular: "ran a command", plural: "ran commands" }, + Read: { singular: "read a file", plural: "read files" }, + Write: { singular: "wrote a file", plural: "wrote files" }, + Edit: { singular: "edited a file", plural: "edited files" }, + Glob: { singular: "searched for files", plural: "searched for files" }, + Grep: { singular: "searched code", plural: "searched code" }, + Agent: { singular: "used an agent", plural: "used agents" } +}; +function humanizeToolSummary(tools) { + const counts = /* @__PURE__ */ new Map(); + for (const t of tools) { + counts.set(t, (counts.get(t) || 0) + 1); + } + const parts = []; + for (const [name, count] of counts) { + const p = TOOL_PLURALS[name]; + if (p) { + parts.push(count === 1 ? p.singular : `${p.plural}`); + } else { + const human = humanizeToolName(name); + parts.push(count === 1 ? human : `${human} (\xD7${count})`); + } + } + if (parts.length === 0) return ""; + if (parts.length === 1) return `Claude ${parts[0]}`; + const last = parts.pop(); + return `Claude ${parts.join(", ")}, and ${last}`; +} +function costWithContext(usd) { + if (usd === 0) return chalk.dim("\u2014"); + const formatted = usd < 0.01 ? `$${usd.toFixed(4)}` : usd < 1 ? `$${usd.toFixed(3)}` : `$${usd.toFixed(2)}`; + let context = ""; + if (usd < 0.01) context = " \u2014 barely a penny"; + else if (usd < 0.05) context = " \u2014 pocket change"; + else if (usd < 0.15) context = " \u2014 pretty efficient"; + else if (usd < 0.5) context = " \u2014 a good session"; + else if (usd < 1) context = " \u2014 solid investment"; + else if (usd < 5) context = " \u2014 heavy session"; + else context = " \u2014 serious work"; + return chalk.yellow(formatted) + chalk.dim(context); +} +function messageCountContext(turns) { + if (turns <= 2) return chalk.dim("quick question"); + if (turns <= 4) return chalk.dim("quick chat"); + if (turns <= 10) return chalk.dim(`${turns} messages`); + if (turns <= 25) return chalk.white(`${turns} messages`) + chalk.dim(" \u2014 solid session"); + if (turns <= 50) return chalk.white(`${turns} messages`) + chalk.dim(" \u2014 deep dive"); + return chalk.white(`${turns} messages`) + chalk.dim(" \u2014 marathon"); +} +function fileCountContext(count) { + if (count === 0) return ""; + if (count === 1) return chalk.blue("touched 1 file"); + if (count <= 5) return chalk.blue(`touched ${count} files`); + if (count <= 15) return chalk.blue(`touched ${count} files`) + chalk.dim(" \u2014 broad changes"); + return chalk.blue(`touched ${count} files`) + chalk.dim(" \u2014 major refactor"); +} +function toolCountContext(count) { + if (count === 0) return ""; + if (count <= 10) return chalk.green(`ran ${count} commands`); + if (count <= 30) return chalk.green(`ran ${count} commands`) + chalk.dim(" \u2014 busy session"); + return chalk.green(`ran ${count} commands`) + chalk.dim(" \u2014 heavy automation"); +} +function levenshtein(a, b) { + const m = a.length; + const n = b.length; + const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0)); + for (let i = 0; i <= m; i++) dp[i][0] = i; + for (let j = 0; j <= n; j++) dp[0][j] = j; + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]); + } + } + return dp[m][n]; +} + +// src/utils/output.ts +var ctx = { json: false, quiet: false }; +function initOutput(opts) { + ctx = opts; +} +function outputJson(data) { + process.stdout.write(JSON.stringify(data, null, 2) + "\n"); +} +function isJsonMode() { + return ctx.json; +} +function isQuietMode() { + return ctx.quiet; +} + +// src/commands/shared.ts +function toSessionJson(session) { + return { + id: session.id, + projectName: session.projectName, + projectPath: session.projectPath, + createdAt: session.createdAt.toISOString(), + updatedAt: session.updatedAt.toISOString(), + messageCount: session.meta.messageCount, + toolCalls: session.meta.toolCalls, + filesTouched: session.meta.filesReferenced.length, + costUSD: Math.round(session.meta.totalCostUSD * 1e6) / 1e6, + firstMessage: session.meta.firstUserMessage + }; } // src/commands/dashboard.ts -async function dashboardCommand() { +var VERSION = "0.3.0"; +async function dashboardCommand(globalOpts) { const { config, isFirstRun } = ensureInit(); if (!existsSync3(config.claudeDir)) { + if (isJsonMode()) { + outputJson({ error: "Claude Code not found", path: getClaudeProjectsDir() }); + process.exit(1); + } console.log(); console.log( chalk2.bold.cyan(" \u258C") + chalk2.bold.white(" DevLog") @@ -584,16 +793,24 @@ async function dashboardCommand() { console.log(); return; } - const spinner = ora({ - text: chalk2.dim(" Reading your Claude Code history..."), - spinner: "dots", - color: "cyan" - }).start(); + let spinner = null; + if (!isJsonMode() && !isQuietMode()) { + spinner = ora({ + text: chalk2.dim(" Reading your Claude Code history..."), + spinner: "dots", + color: "cyan", + stream: process.stderr + }).start(); + } const projects = await discoverProjects(config.claudeDir, (msg) => { - spinner.text = chalk2.dim(` ${msg}`); + if (spinner) spinner.text = chalk2.dim(` ${msg}`); }); - spinner.stop(); + spinner?.stop(); if (projects.length === 0) { + if (isJsonMode()) { + outputJson({ version: VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString(), summary: "No sessions found", stats: { totalProjects: 0, totalSessions: 0, totalToolCalls: 0, totalFilesTouched: 0, totalCostUSD: 0, todaySessions: 0, todayCostUSD: 0 }, recentSessions: [] }); + return; + } console.log(); console.log( chalk2.bold.cyan(" \u258C") + chalk2.bold.white(" DevLog") @@ -610,15 +827,43 @@ async function dashboardCommand() { } const stats = computeStats(projects); const groups = groupSessionsByTime(projects); + if (isJsonMode()) { + const allSessions = projects.flatMap((p) => p.sessions); + allSessions.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()); + const recent = allSessions.slice(0, 10); + const projectWord = stats.totalProjects === 1 ? "project" : "projects"; + const sessionWord = stats.totalSessions === 1 ? "conversation" : "conversations"; + const data = { + version: VERSION, + timestamp: (/* @__PURE__ */ new Date()).toISOString(), + summary: `You and Claude had ${stats.totalSessions} ${sessionWord} across ${stats.totalProjects} ${projectWord}`, + stats: { + totalProjects: stats.totalProjects, + totalSessions: stats.totalSessions, + totalToolCalls: stats.totalToolCalls, + totalFilesTouched: stats.allFilesReferenced.length, + totalCostUSD: Math.round(stats.totalCostUSD * 1e6) / 1e6, + todaySessions: stats.todaySessions, + todayCostUSD: Math.round(stats.todayCostUSD * 1e6) / 1e6 + }, + recentSessions: recent.map(toSessionJson) + }; + outputJson(data); + return; + } console.log(); - if (isFirstRun) { - renderWelcome(stats); - } else { - renderBanner(); + if (!isQuietMode()) { + if (isFirstRun) { + renderWelcome(stats); + } else { + renderBanner(); + } } renderNarrativeStats(stats); renderSessionGroups(groups, stats); - renderNextSteps(stats, isFirstRun); + if (!isQuietMode()) { + renderNextSteps(stats, isFirstRun); + } } function renderWelcome(stats) { console.log( @@ -717,25 +962,12 @@ function renderSessionCard(session, accentColor) { console.log( chalk2.dim(" ") + accentColor(timeStr.padEnd(16)) + chalk2.bold.white(session.projectName.padEnd(14)) + chalk2.white(preview) ); - const parts = []; const turns = m.humanTurns + m.assistantTurns; - if (turns <= 4) { - parts.push(chalk2.dim("quick chat")); - } else if (turns <= 10) { - parts.push(chalk2.dim(`${turns} messages`)); - } else { - parts.push(chalk2.white(`${turns} messages`)); - } - if (m.toolCalls > 0) { - parts.push(chalk2.green(`ran ${m.toolCalls} commands`)); - } - if (m.filesReferenced.length > 0) { - const fileWord = m.filesReferenced.length === 1 ? "file" : "files"; - parts.push(chalk2.blue(`wrote ${m.filesReferenced.length} ${fileWord}`)); - } - if (m.totalCostUSD > 0) { - parts.push(formatCost(m.totalCostUSD)); - } + const parts = []; + parts.push(messageCountContext(turns)); + if (m.toolCalls > 0) parts.push(toolCountContext(m.toolCalls)); + if (m.filesReferenced.length > 0) parts.push(fileCountContext(m.filesReferenced.length)); + if (m.totalCostUSD > 0) parts.push(costWithContext(m.totalCostUSD)); if (m.errorCount > 0) { const errWord = m.errorCount === 1 ? "error" : "errors"; parts.push(chalk2.red(`${m.errorCount} ${errWord}`)); @@ -752,21 +984,24 @@ function renderNextSteps(stats, isFirstRun) { console.log(chalk2.white(" What you can do next:")); console.log(); console.log( - chalk2.cyan(" devlog sessions") + chalk2.dim(" Browse all sessions by project") + chalk2.cyan(" devlog today") + chalk2.dim(" What did I do today?") ); console.log( - chalk2.cyan(" devlog sessions -p chat") + chalk2.dim(" Filter to a specific project") + chalk2.cyan(" devlog sessions") + chalk2.dim(" Browse all sessions by project") ); console.log( chalk2.cyan(" devlog show ") + chalk2.dim(" View a full conversation") ); + console.log( + chalk2.cyan(' devlog search "auth"') + chalk2.dim(" Find a conversation") + ); console.log(); console.log( chalk2.dim(" Tip: Just run ") + chalk2.cyan("devlog") + chalk2.dim(" anytime to see this dashboard.") ); } else { console.log( - chalk2.dim(" ") + chalk2.cyan("devlog sessions") + chalk2.dim(" all sessions \xB7 ") + chalk2.cyan("devlog show ") + chalk2.dim(" view conversation \xB7 ") + chalk2.cyan("--help") + chalk2.dim(" ") + chalk2.cyan("devlog today") + chalk2.dim(" today \xB7 ") + chalk2.cyan("devlog sessions") + chalk2.dim(" all \xB7 ") + chalk2.cyan("devlog show ") + chalk2.dim(" view \xB7 ") + chalk2.cyan("devlog search") + chalk2.dim(" find") ); } console.log(); @@ -776,30 +1011,40 @@ function renderNextSteps(stats, isFirstRun) { import { existsSync as existsSync4 } from "fs"; import chalk3 from "chalk"; import ora2 from "ora"; -async function initCommand() { - console.log(); - console.log( - chalk3.bold.cyan(" \u258C") + chalk3.bold.white(" DevLog Setup") - ); - console.log(); - if (isInitialized()) { - const config2 = loadConfig(); - printSuccess("DevLog is already set up"); - console.log( - chalk3.dim(" Config: ") + chalk3.white(getDevlogDir() + "/config.toml") - ); +async function initCommand(globalOpts) { + if (!isJsonMode()) { + console.log(); console.log( - chalk3.dim(" Claude: ") + chalk3.white(config2.claudeDir) + chalk3.bold.cyan(" \u258C") + chalk3.bold.white(" DevLog Setup") ); console.log(); - const spinner2 = ora2({ - text: chalk3.dim(" Checking your sessions..."), - spinner: "dots", - color: "cyan" - }).start(); + } + if (isInitialized()) { + const config2 = loadConfig(); + let spinner2 = null; + if (!isJsonMode() && !isQuietMode()) { + printSuccess("DevLog is already set up"); + console.log( + chalk3.dim(" Config: ") + chalk3.white(getDevlogDir() + "/config.toml") + ); + console.log( + chalk3.dim(" Claude: ") + chalk3.white(config2.claudeDir) + ); + console.log(); + spinner2 = ora2({ + text: chalk3.dim(" Checking your sessions..."), + spinner: "dots", + color: "cyan", + stream: process.stderr + }).start(); + } const projects2 = await discoverProjects(config2.claudeDir); const stats2 = computeStats(projects2); - spinner2.stop(); + spinner2?.stop(); + if (isJsonMode()) { + outputJson({ status: "already_initialized", configPath: getDevlogDir() + "/config.toml", stats: { totalProjects: stats2.totalProjects, totalSessions: stats2.totalSessions, totalMessages: stats2.totalMessages } }); + return; + } renderStatsBox(stats2); console.log( chalk3.dim(" Everything looks good! Run ") + chalk3.cyan("devlog") + chalk3.dim(" to see your dashboard.") @@ -809,6 +1054,10 @@ async function initCommand() { } const claudeDir = getClaudeProjectsDir(); if (!existsSync4(claudeDir)) { + if (isJsonMode()) { + outputJson({ error: "Claude Code not found", path: claudeDir }); + process.exit(1); + } printError("Claude Code not found"); console.log(); console.log( @@ -828,17 +1077,29 @@ async function initCommand() { console.log(); return; } - printSuccess("Found Claude Code"); + if (!isJsonMode()) { + printSuccess("Found Claude Code"); + } const config = initConfig(); - printSuccess("Created config at " + chalk3.dim("~/.devlog/")); - const spinner = ora2({ - text: chalk3.dim(" Scanning your Claude Code history..."), - spinner: "dots", - color: "cyan" - }).start(); + if (!isJsonMode()) { + printSuccess("Created config at " + chalk3.dim("~/.devlog/")); + } + let spinner = null; + if (!isJsonMode() && !isQuietMode()) { + spinner = ora2({ + text: chalk3.dim(" Scanning your Claude Code history..."), + spinner: "dots", + color: "cyan", + stream: process.stderr + }).start(); + } const projects = await discoverProjects(config.claudeDir); const stats = computeStats(projects); - spinner.stop(); + spinner?.stop(); + if (isJsonMode()) { + outputJson({ status: "initialized", configPath: getDevlogDir() + "/config.toml", stats: { totalProjects: stats.totalProjects, totalSessions: stats.totalSessions, totalMessages: stats.totalMessages } }); + return; + } printSuccess("Scan complete"); console.log(); renderStatsBox(stats); @@ -853,6 +1114,7 @@ function renderStatsBox(stats) { const padding = w - label.length - value.length - 6; return chalk3.dim(" \u2502 ") + chalk3.white(label) + " ".repeat(Math.max(1, padding)) + chalk3.cyan.bold(value) + chalk3.dim(" \u2502"); }; + const costStr = stats.totalCostUSD > 0 ? stats.totalCostUSD < 1 ? `$${stats.totalCostUSD.toFixed(3)}` : `$${stats.totalCostUSD.toFixed(2)}` : ""; console.log(chalk3.dim(" \u250C" + "\u2500".repeat(w) + "\u2510")); console.log(line("Projects", formatNumber(stats.totalProjects))); console.log(line("Conversations", formatNumber(stats.totalSessions))); @@ -861,8 +1123,8 @@ function renderStatsBox(stats) { console.log( line("Files touched", formatNumber(stats.allFilesReferenced.length)) ); - if (stats.totalCostUSD > 0) { - console.log(line("Total cost", `$${stats.totalCostUSD.toFixed(3)}`)); + if (costStr) { + console.log(line("Total cost", costStr)); } console.log(chalk3.dim(" \u2514" + "\u2500".repeat(w) + "\u2518")); console.log(); @@ -871,18 +1133,26 @@ function renderStatsBox(stats) { // src/commands/sessions.ts import chalk4 from "chalk"; import ora3 from "ora"; -async function sessionsCommand(options) { - const { config } = ensureInit(); +async function sessionsCommand(options, globalOpts) { + const { config, isFirstRun } = ensureInit(); const limit = options.all ? Infinity : parseInt(options.limit || "30", 10); - const spinner = ora3({ - text: chalk4.dim(" Scanning sessions..."), - spinner: "dots", - color: "cyan" - }).start(); + let spinner = null; + if (!isJsonMode() && !isQuietMode()) { + spinner = ora3({ + text: chalk4.dim(" Scanning sessions..."), + spinner: "dots", + color: "cyan", + stream: process.stderr + }).start(); + } const projects = await discoverProjects(config.claudeDir); const stats = computeStats(projects); - spinner.stop(); + spinner?.stop(); if (projects.length === 0) { + if (isJsonMode()) { + outputJson({ projects: [] }); + return; + } console.log(); console.log(chalk4.white(" No sessions found yet.")); console.log( @@ -894,6 +1164,18 @@ async function sessionsCommand(options) { const filteredProjects = options.project ? projects.filter( (p) => p.name.toLowerCase().includes(options.project.toLowerCase()) || p.path.toLowerCase().includes(options.project.toLowerCase()) ) : projects; + if (isJsonMode()) { + const data = { + projects: filteredProjects.map((p) => ({ + name: p.name, + path: p.path, + sessionCount: p.sessionCount, + sessions: p.sessions.map(toSessionJson) + })) + }; + outputJson(data); + return; + } if (filteredProjects.length === 0) { console.log(); console.log( @@ -923,6 +1205,15 @@ async function sessionsCommand(options) { ) ); console.log(); + if (isFirstRun) { + console.log( + chalk4.dim(" Each project shows your Claude conversations, newest first.") + ); + console.log( + chalk4.dim(" Pick one with ") + chalk4.cyan("devlog show ") + chalk4.dim(" to see the full chat.") + ); + console.log(); + } let displayedCount = 0; let sessionIndex = 0; for (const project of filteredProjects) { @@ -962,23 +1253,12 @@ function renderSessionRow(session, index) { console.log( chalk4.dim(" ") + indexStr + chalk4.white(time.padEnd(16)) + chalk4.white(preview) ); - const parts = []; const turns = m.humanTurns + m.assistantTurns; - if (turns <= 4) { - parts.push(chalk4.dim("quick chat")); - } else { - parts.push(chalk4.dim(`${turns} messages`)); - } - if (m.toolCalls > 0) { - parts.push(chalk4.green(`ran ${m.toolCalls} commands`)); - } - if (m.filesReferenced.length > 0) { - const fileWord = m.filesReferenced.length === 1 ? "file" : "files"; - parts.push(chalk4.blue(`wrote ${m.filesReferenced.length} ${fileWord}`)); - } - if (m.totalCostUSD > 0) { - parts.push(formatCost(m.totalCostUSD)); - } + const parts = []; + parts.push(messageCountContext(turns)); + if (m.toolCalls > 0) parts.push(toolCountContext(m.toolCalls)); + if (m.filesReferenced.length > 0) parts.push(fileCountContext(m.filesReferenced.length)); + if (m.totalCostUSD > 0) parts.push(costWithContext(m.totalCostUSD)); if (m.errorCount > 0) { parts.push(chalk4.red(`${m.errorCount} error${m.errorCount === 1 ? "" : "s"}`)); } @@ -991,22 +1271,30 @@ function renderSessionRow(session, index) { // src/commands/show.ts import chalk5 from "chalk"; import ora4 from "ora"; -async function showCommand(sessionRef, options) { +async function showCommand(sessionRef, options, globalOpts) { const { config } = ensureInit(); const limit = parseInt(options.limit || "50", 10); - const spinner = ora4({ - text: chalk5.dim(" Finding session..."), - spinner: "dots", - color: "cyan" - }).start(); + let spinner = null; + if (!isJsonMode() && !isQuietMode()) { + spinner = ora4({ + text: chalk5.dim(" Finding session..."), + spinner: "dots", + color: "cyan", + stream: process.stderr + }).start(); + } const projects = await discoverProjects(config.claudeDir); - spinner.stop(); + spinner?.stop(); const allSessions = []; for (const project of projects) { allSessions.push(...project.sessions); } allSessions.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()); if (allSessions.length === 0) { + if (isJsonMode()) { + outputJson({ error: "No sessions found" }); + process.exit(1); + } console.log(); console.log(chalk5.yellow(" No sessions found.")); console.log(); @@ -1021,8 +1309,17 @@ async function showCommand(sessionRef, options) { session = allSessions.find( (s) => s.id.toLowerCase().startsWith(ref) ); + if (!session) { + session = allSessions.find( + (s) => s.id.toLowerCase().includes(ref) + ); + } } if (!session) { + if (isJsonMode()) { + outputJson({ error: `No session matching: ${sessionRef}` }); + process.exit(1); + } console.log(); console.log( chalk5.yellow(" Couldn't find a session matching: ") + chalk5.white(sessionRef) @@ -1041,18 +1338,45 @@ async function showCommand(sessionRef, options) { chalk5.dim(" Use ") + chalk5.cyan("devlog show 1") + chalk5.dim(" to view the most recent session.") ); console.log(); - return; + process.exit(1); + } + let parseSpinner = null; + if (!isJsonMode() && !isQuietMode()) { + parseSpinner = ora4({ + text: chalk5.dim(" Reading conversation..."), + spinner: "dots", + color: "cyan", + stream: process.stderr + }).start(); } - const parseSpinner = ora4({ - text: chalk5.dim(" Reading conversation..."), - spinner: "dots", - color: "cyan" - }).start(); const events = await parseSessionFile(session.filePath, session.id); - parseSpinner.stop(); - renderSessionHeader(session); - renderConversation(events, limit); - renderSessionFooter(session, events, limit); + parseSpinner?.stop(); + if (isJsonMode()) { + const data = { + session: { + id: session.id, + projectName: session.projectName, + meta: session.meta + }, + events: events.map((e) => ({ + timestamp: e.timestamp.toISOString(), + role: e.role, + type: e.type, + content: e.content, + ...e.toolName ? { toolName: e.toolName } : {}, + ...e.isError ? { isError: e.isError } : {} + })) + }; + outputJson(data); + return; + } + if (options.summary) { + renderSummary(session, events); + } else { + renderSessionHeader(session); + renderConversation(events, limit); + renderSessionFooter(session, events, limit); + } } function renderSessionHeader(session) { const m = session.meta; @@ -1061,33 +1385,65 @@ function renderSessionHeader(session) { chalk5.bold.cyan(" \u258C") + chalk5.bold.white(` ${session.projectName}`) + chalk5.dim(" \xB7 ") + chalk5.dim(formatSmartTime(session.updatedAt)) ); console.log(); - const parts = []; const turns = m.humanTurns + m.assistantTurns; - parts.push(`${turns} messages`); - if (m.toolCalls > 0) { - parts.push(`${m.toolCalls} commands run`); - } - if (m.filesReferenced.length > 0) { - parts.push(`${m.filesReferenced.length} files touched`); - } - if (m.totalCostUSD > 0) { - parts.push(`$${m.totalCostUSD.toFixed(3)} cost`); - } - console.log(chalk5.dim(" " + parts.join(" \xB7 "))); + const parts = []; + parts.push(messageCountContext(turns)); + if (m.toolCalls > 0) parts.push(toolCountContext(m.toolCalls)); + if (m.filesReferenced.length > 0) parts.push(fileCountContext(m.filesReferenced.length)); + if (m.totalCostUSD > 0) parts.push(costWithContext(m.totalCostUSD)); + console.log(chalk5.dim(" ") + parts.join(chalk5.dim(" \xB7 "))); if (m.uniqueTools.length > 0) { console.log( - chalk5.dim(" Tools: ") + m.uniqueTools.map((t) => chalk5.green(t)).join(chalk5.dim(", ")) + chalk5.dim(" ") + humanizeToolSummary(m.uniqueTools) ); } console.log(); console.log(chalk5.dim(" " + "\u2500".repeat(60))); console.log(); } +function formatToolLine(event) { + const name = event.toolName || "tool"; + const input = event.toolInput || {}; + if (name === "Read" && input.file_path) return `read ${input.file_path}`; + if (name === "Write" && input.file_path) return `wrote ${input.file_path}`; + if (name === "Edit" && input.file_path) return `edited ${input.file_path}`; + if (name === "Bash" && input.command) return `ran: ${truncate(String(input.command), 60)}`; + if (name === "Grep" && input.pattern) return `searched for "${truncate(String(input.pattern), 40)}"`; + if (name === "Glob" && input.pattern) return `found files matching ${truncate(String(input.pattern), 40)}`; + return name.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase(); +} +function flushToolGroup(group) { + if (group.length === 0) return; + const firstName = group[0].toolName || "tool"; + const allSame = group.every((e) => e.toolName === firstName); + if (allSame && group.length > 2 && (firstName === "Read" || firstName === "Write" || firstName === "Edit")) { + const verb = firstName === "Read" ? "read" : firstName === "Write" ? "wrote" : "edited"; + const files = group.map((e) => { + const p = String(e.toolInput?.file_path || ""); + return p.split("/").pop() || p; + }).filter(Boolean); + const fileWord = group.length === 1 ? "file" : "files"; + console.log( + chalk5.green(" \u25B8 ") + chalk5.dim(`${verb} ${group.length} ${fileWord}: ${truncate(files.join(", "), 60)}`) + ); + } else { + for (const event of group) { + console.log( + chalk5.green(" \u25B8 ") + chalk5.dim(formatToolLine(event)) + ); + } + } +} function renderConversation(events, limit) { let shown = 0; + let toolGroup = []; for (const event of events) { if (shown >= limit) break; - if (event.role === "human" && event.type === "text") { + if (event.role !== "tool_use" && event.role !== "tool_result" && toolGroup.length > 0) { + flushToolGroup(toolGroup); + toolGroup = []; + } + if (event.role === "human" && (event.type === "text" || event.type === "message")) { console.log( chalk5.blue.bold(" You: ") + chalk5.white(truncate(event.content.trim(), 76)) ); @@ -1106,29 +1462,23 @@ function renderConversation(events, limit) { console.log(); shown++; } else if (event.role === "tool_use") { - console.log( - chalk5.green(" \u26A1 ") + chalk5.green(event.toolName || "tool") + chalk5.dim(" ") + chalk5.dim(truncate(event.content.replace(event.toolName + "(", "").replace(/\)$/, ""), 60)) - ); + toolGroup.push(event); shown++; } else if (event.role === "tool_result") { if (event.isError) { console.log( chalk5.red(" \u2717 Error: ") + chalk5.dim(truncate(event.content, 60)) ); - } else { - const resultPreview = event.content.trim(); - if (resultPreview.length > 0 && resultPreview.length < 80) { - console.log( - chalk5.dim(" \u2192 ") + chalk5.dim(truncate(resultPreview, 70)) - ); - } } } } + if (toolGroup.length > 0) { + flushToolGroup(toolGroup); + } } function renderSessionFooter(session, events, limit) { const totalEvents = events.filter( - (e) => e.role === "human" && e.type === "text" || e.role === "assistant" && e.type === "text" || e.role === "tool_use" + (e) => e.role === "human" && (e.type === "text" || e.type === "message") || e.role === "assistant" && e.type === "text" || e.role === "tool_use" ).length; console.log(); console.log(chalk5.dim(" " + "\u2500".repeat(60))); @@ -1137,73 +1487,712 @@ function renderSessionFooter(session, events, limit) { chalk5.dim(` Showing ${limit} of ${totalEvents} events. Use `) + chalk5.cyan(`devlog show ${session.id.slice(0, 8)} -n ${totalEvents}`) + chalk5.dim(" to see all.") ); } + console.log(); + console.log( + chalk5.dim(" ") + chalk5.cyan(`devlog sessions -p ${session.projectName}`) + chalk5.dim(" other sessions in this project") + ); + console.log( + chalk5.dim(" ") + chalk5.cyan("devlog") + chalk5.dim(" back to dashboard") + ); + console.log(); +} +function renderSummary(session, events) { + const m = session.meta; + console.log(); + console.log( + chalk5.bold.cyan(" \u258C") + chalk5.bold.white(` ${session.projectName}`) + chalk5.dim(" \xB7 ") + chalk5.dim(formatSmartTime(session.updatedAt)) + ); + console.log(); + const firstMsg = m.firstUserMessage ? truncate(m.firstUserMessage.replace(/\n/g, " ").trim(), 70) : "(empty)"; + console.log(chalk5.white(" Summary:")); + console.log( + chalk5.dim(" You asked Claude: ") + chalk5.white(`"${firstMsg}"`) + ); + console.log(); + const toolGroups = /* @__PURE__ */ new Map(); + const filesChanged = /* @__PURE__ */ new Set(); + const commandsRun = []; + let errorCount = 0; + for (const event of events) { + if (event.role === "tool_use") { + const name = event.toolName || "tool"; + toolGroups.set(name, (toolGroups.get(name) || 0) + 1); + const input = event.toolInput || {}; + if (input.file_path && (name === "Write" || name === "Edit")) { + filesChanged.add(String(input.file_path)); + } + if (name === "Bash" && input.command) { + commandsRun.push(truncate(String(input.command), 50)); + } + } + if (event.role === "tool_result" && event.isError) { + errorCount++; + } + } + console.log(chalk5.white(" What happened:")); + for (const [name, count] of toolGroups) { + let desc; + if (name === "Read") desc = `Read ${count} file${count === 1 ? "" : "s"} to understand the codebase`; + else if (name === "Write") desc = `Created ${count} file${count === 1 ? "" : "s"}`; + else if (name === "Edit") desc = `Edited ${count} file${count === 1 ? "" : "s"}`; + else if (name === "Bash") desc = `Ran ${count} command${count === 1 ? "" : "s"}` + (commandsRun.length > 0 ? ` (${truncate(commandsRun.slice(0, 3).join(", "), 40)})` : ""); + else if (name === "Grep") desc = `Searched code ${count} time${count === 1 ? "" : "s"}`; + else if (name === "Glob") desc = `Searched for files ${count} time${count === 1 ? "" : "s"}`; + else desc = `${name} \xD7${count}`; + console.log(chalk5.dim(" - ") + chalk5.dim(desc)); + } + if (filesChanged.size > 0) { + const fileNames = [...filesChanged].map((f) => f.split("/").pop() || f); + console.log( + chalk5.dim(" - ") + chalk5.dim(`Files changed: ${truncate(fileNames.join(", "), 50)}`) + ); + } + if (errorCount > 0) { + console.log( + chalk5.dim(" - ") + chalk5.red(`Fixed ${errorCount} error${errorCount === 1 ? "" : "s"}`) + ); + } + console.log(); + const turns = m.humanTurns + m.assistantTurns; + const parts = []; + parts.push(messageCountContext(turns)); + if (m.toolCalls > 0) parts.push(toolCountContext(m.toolCalls)); + if (m.filesReferenced.length > 0) parts.push(fileCountContext(m.filesReferenced.length)); + if (m.totalCostUSD > 0) parts.push(costWithContext(m.totalCostUSD)); + console.log(chalk5.dim(" ") + parts.join(chalk5.dim(" \xB7 "))); + console.log(); + console.log(chalk5.dim(" " + "\u2500".repeat(60))); + console.log(); + console.log( + chalk5.dim(" ") + chalk5.cyan(`devlog show ${session.id.slice(0, 8)}`) + chalk5.dim(" see full conversation \xB7 ") + chalk5.cyan(`devlog sessions -p ${session.projectName}`) + chalk5.dim(" other sessions") + ); + console.log(); +} + +// src/commands/today.ts +import chalk6 from "chalk"; +import ora5 from "ora"; +import dayjs3 from "dayjs"; +import isToday2 from "dayjs/plugin/isToday.js"; +dayjs3.extend(isToday2); +async function todayCommand(globalOpts) { + const { config } = ensureInit(); + let spinner = null; + if (!isJsonMode() && !isQuietMode()) { + spinner = ora5({ + text: chalk6.dim(" Checking today's sessions..."), + spinner: "dots", + color: "cyan", + stream: process.stderr + }).start(); + } + const projects = await discoverProjects(config.claudeDir); + spinner?.stop(); + const todaySessions = []; + const projectNames = /* @__PURE__ */ new Set(); + for (const project of projects) { + for (const session of project.sessions) { + if (dayjs3(session.updatedAt).isToday()) { + todaySessions.push(session); + projectNames.add(session.projectName); + } + } + } + todaySessions.sort((a, b) => a.updatedAt.getTime() - b.updatedAt.getTime()); + if (isJsonMode()) { + const totalCost2 = todaySessions.reduce((s, sess) => s + sess.meta.totalCostUSD, 0); + const totalTools2 = todaySessions.reduce((s, sess) => s + sess.meta.toolCalls, 0); + const allFiles2 = /* @__PURE__ */ new Set(); + for (const s of todaySessions) { + for (const f of s.meta.filesReferenced) allFiles2.add(f); + } + outputJson({ + date: dayjs3().format("YYYY-MM-DD"), + sessionCount: todaySessions.length, + projectCount: projectNames.size, + totalCostUSD: Math.round(totalCost2 * 1e6) / 1e6, + totalToolCalls: totalTools2, + totalFilesTouched: allFiles2.size, + sessions: todaySessions.map(toSessionJson) + }); + return; + } + if (todaySessions.length === 0) { + console.log(); + console.log( + chalk6.bold.cyan(" \u258C") + chalk6.bold.white(" Your day so far") + ); + console.log(); + console.log(chalk6.dim(" No sessions yet today.")); + const allSessions = []; + for (const p of projects) allSessions.push(...p.sessions); + allSessions.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()); + if (allSessions.length > 0) { + const last = allSessions[0]; + console.log( + chalk6.dim(" Your last session was ") + chalk6.white(formatSmartTime(last.updatedAt)) + chalk6.dim(" in ") + chalk6.cyan(last.projectName) + ); + } + console.log(); + console.log( + chalk6.dim(" ") + chalk6.cyan("devlog sessions") + chalk6.dim(" browse all \xB7 ") + chalk6.cyan("devlog") + chalk6.dim(" dashboard") + ); + console.log(); + return; + } + const totalCost = todaySessions.reduce((s, sess) => s + sess.meta.totalCostUSD, 0); + const totalTools = todaySessions.reduce((s, sess) => s + sess.meta.toolCalls, 0); + const allFiles = /* @__PURE__ */ new Set(); + for (const s of todaySessions) { + for (const f of s.meta.filesReferenced) allFiles.add(f); + } + const sessionWord = todaySessions.length === 1 ? "conversation" : "conversations"; + const projectWord = projectNames.size === 1 ? "project" : "projects"; + console.log(); + console.log( + chalk6.bold.cyan(" \u258C") + chalk6.bold.white(" Your day so far") + ); + console.log(); + console.log( + chalk6.dim(" ") + chalk6.white(`${todaySessions.length} ${sessionWord} across ${projectNames.size} ${projectWord}.`) + ); + const summaryParts = []; + if (totalTools > 0) summaryParts.push(toolCountContext(totalTools)); + if (allFiles.size > 0) summaryParts.push(fileCountContext(allFiles.size)); + if (summaryParts.length > 0) { + console.log(chalk6.dim(" ") + chalk6.white("Claude ") + summaryParts.join(chalk6.dim(" and "))); + } + if (totalCost > 0) { + console.log(chalk6.dim(" Cost: ") + costWithContext(totalCost)); + } + console.log(); + console.log(chalk6.dim(" " + "\u2500".repeat(60))); + console.log(); + for (const session of todaySessions) { + const m = session.meta; + const time = formatSmartTime(session.updatedAt); + const preview = m.firstUserMessage ? truncate(m.firstUserMessage.replace(/\n/g, " ").trim(), 42) : chalk6.dim("(empty)"); + console.log( + chalk6.dim(" ") + chalk6.white(time.padEnd(12)) + chalk6.cyan(session.projectName.padEnd(14)) + chalk6.white(`"${preview}"`) + ); + const turns = m.humanTurns + m.assistantTurns; + const parts = []; + parts.push(messageCountContext(turns)); + if (m.toolCalls > 0) parts.push(toolCountContext(m.toolCalls)); + if (m.totalCostUSD > 0) parts.push(costWithContext(m.totalCostUSD)); + console.log( + chalk6.dim(" ") + " ".repeat(26) + parts.join(chalk6.dim(" \xB7 ")) + ); + console.log(); + } + console.log(chalk6.dim(" " + "\u2500".repeat(60))); + console.log(); console.log( - chalk5.dim(" Session ID: ") + chalk5.dim(session.id) + chalk6.dim(" ") + chalk6.cyan("devlog show 1") + chalk6.dim(" view most recent \xB7 ") + chalk6.cyan("devlog sessions") + chalk6.dim(" browse all") + ); + console.log(); +} + +// src/commands/search.ts +import chalk7 from "chalk"; +import ora6 from "ora"; +async function searchCommand(query, globalOpts) { + const { config } = ensureInit(); + let spinner = null; + if (!isJsonMode() && !isQuietMode()) { + spinner = ora6({ + text: chalk7.dim(" Searching sessions..."), + spinner: "dots", + color: "cyan", + stream: process.stderr + }).start(); + } + const projects = await discoverProjects(config.claudeDir); + spinner?.stop(); + const q = query.toLowerCase(); + const matches = []; + for (const project of projects) { + for (const session of project.sessions) { + const m = session.meta; + if (session.projectName.toLowerCase().includes(q) || m.firstUserMessage.toLowerCase().includes(q) || m.uniqueTools.some((t) => t.toLowerCase().includes(q))) { + matches.push(session); + } + } + } + matches.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()); + if (isJsonMode()) { + outputJson({ + query, + matchCount: matches.length, + sessions: matches.map(toSessionJson) + }); + return; + } + console.log(); + console.log( + chalk7.bold.cyan(" \u258C") + chalk7.bold.white(` Search: `) + chalk7.white(`"${query}"`) + ); + console.log(); + if (matches.length === 0) { + console.log(chalk7.dim(" No conversations found matching that query.")); + console.log(); + console.log( + chalk7.dim(" Try a shorter term, or use ") + chalk7.cyan("devlog sessions") + chalk7.dim(" to browse all.") + ); + console.log(); + return; + } + const matchWord = matches.length === 1 ? "conversation" : "conversations"; + console.log( + chalk7.dim(` Found ${matches.length} ${matchWord} matching "${query}"`) + ); + console.log(); + const shown = matches.slice(0, 20); + for (let i = 0; i < shown.length; i++) { + const session = shown[i]; + const m = session.meta; + const time = formatSmartTime(session.updatedAt); + const preview = m.firstUserMessage ? truncate(m.firstUserMessage.replace(/\n/g, " ").trim(), 42) : chalk7.dim("(empty)"); + console.log( + chalk7.dim(` ${(i + 1).toString().padEnd(3)} `) + chalk7.white(time.padEnd(16)) + chalk7.cyan(session.projectName.padEnd(14)) + chalk7.white(`"${preview}"`) + ); + const turns = m.humanTurns + m.assistantTurns; + const parts = []; + parts.push(messageCountContext(turns)); + if (m.totalCostUSD > 0) parts.push(costWithContext(m.totalCostUSD)); + console.log( + chalk7.dim(" ") + " " + " ".repeat(16) + parts.join(chalk7.dim(" \xB7 ")) + ); + console.log(); + } + if (matches.length > 20) { + console.log(chalk7.dim(` + ${matches.length - 20} more matches`)); + console.log(); + } + console.log(chalk7.dim(" " + "\u2500".repeat(60))); + console.log(); + console.log( + chalk7.dim(" ") + chalk7.cyan("devlog show 1") + chalk7.dim(" view match \xB7 ") + chalk7.cyan(`devlog search "${query} ..."`) + chalk7.dim(" refine") + ); + console.log(); +} + +// src/commands/stats.ts +import chalk8 from "chalk"; +import ora7 from "ora"; +import dayjs4 from "dayjs"; +import isToday3 from "dayjs/plugin/isToday.js"; +dayjs4.extend(isToday3); +function filterByPeriod(sessions, period) { + const now = dayjs4(); + return sessions.filter((s) => { + const d = dayjs4(s.updatedAt); + switch (period) { + case "today": + return d.isToday(); + case "week": + return now.diff(d, "day") < 7; + case "month": + return now.diff(d, "day") < 30; + default: + return true; + } + }); +} +async function statsCommand(options, globalOpts) { + const { config } = ensureInit(); + const period = options.period || "all"; + let spinner = null; + if (!isJsonMode() && !isQuietMode()) { + spinner = ora7({ + text: chalk8.dim(" Crunching numbers..."), + spinner: "dots", + color: "cyan", + stream: process.stderr + }).start(); + } + const projects = await discoverProjects(config.claudeDir); + spinner?.stop(); + const allSessions = []; + for (const p of projects) allSessions.push(...p.sessions); + const filtered = filterByPeriod(allSessions, period); + let totalCost = 0; + let totalTools = 0; + let totalMessages = 0; + const fileSet = /* @__PURE__ */ new Set(); + const toolCounts = /* @__PURE__ */ new Map(); + const projectCounts = /* @__PURE__ */ new Map(); + const costByModel = {}; + for (const s of filtered) { + totalCost += s.meta.totalCostUSD; + totalTools += s.meta.toolCalls; + totalMessages += s.meta.humanTurns + s.meta.assistantTurns; + for (const f of s.meta.filesReferenced) fileSet.add(f); + for (const t of s.meta.uniqueTools) { + toolCounts.set(t, (toolCounts.get(t) || 0) + 1); + } + projectCounts.set( + s.projectName, + (projectCounts.get(s.projectName) || 0) + 1 + ); + for (const [model, cost] of Object.entries(s.meta.costByModel)) { + costByModel[model] = (costByModel[model] || 0) + cost; + } + } + const periodLabel = period === "today" ? "Today" : period === "week" ? "This Week" : period === "month" ? "This Month" : "All Time"; + if (isJsonMode()) { + outputJson({ + period: periodLabel, + sessionCount: filtered.length, + totalMessages, + totalToolCalls: totalTools, + totalFilesTouched: fileSet.size, + totalCostUSD: Math.round(totalCost * 1e6) / 1e6, + topTools: [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10).map(([name, count]) => ({ name, count })), + projectBreakdown: [...projectCounts.entries()].sort((a, b) => b[1] - a[1]).map(([name, count]) => ({ name, sessions: count })), + costByModel + }); + return; + } + if (filtered.length === 0) { + console.log(); + console.log( + chalk8.bold.cyan(" \u258C") + chalk8.bold.white(` Stats \u2014 ${periodLabel}`) + ); + console.log(); + console.log(chalk8.dim(" No sessions in this period.")); + console.log(); + return; + } + console.log(); + console.log( + chalk8.bold.cyan(" \u258C") + chalk8.bold.white(` Stats \u2014 ${periodLabel}`) + ); + console.log(); + const w = 42; + const line = (label, value) => { + const padding = w - label.length - value.length - 6; + return chalk8.dim(" \u2502 ") + chalk8.white(label) + " ".repeat(Math.max(1, padding)) + chalk8.cyan.bold(value) + chalk8.dim(" \u2502"); + }; + console.log(chalk8.dim(" \u250C" + "\u2500".repeat(w) + "\u2510")); + console.log(line("Sessions", formatNumber(filtered.length))); + console.log(line("Messages", formatNumber(totalMessages))); + console.log(line("Commands run", formatNumber(totalTools))); + console.log(line("Files touched", formatNumber(fileSet.size))); + if (totalCost > 0) { + const costStr = totalCost < 1 ? `$${totalCost.toFixed(3)}` : `$${totalCost.toFixed(2)}`; + console.log(line("Total cost", costStr)); + } + console.log(chalk8.dim(" \u2514" + "\u2500".repeat(w) + "\u2518")); + console.log(); + const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5); + if (topTools.length > 0) { + console.log(chalk8.white(" Most used tools:")); + for (const [name, count] of topTools) { + console.log( + chalk8.dim(" ") + chalk8.green(name.padEnd(16)) + chalk8.dim(`used in ${count} session${count === 1 ? "" : "s"}`) + ); + } + console.log(); + } + const topProjects = [...projectCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3); + if (topProjects.length > 0) { + console.log(chalk8.white(" Most active projects:")); + for (const [name, count] of topProjects) { + const sessionWord = count === 1 ? "session" : "sessions"; + console.log( + chalk8.dim(" ") + chalk8.cyan(name.padEnd(16)) + chalk8.dim(`${count} ${sessionWord}`) + ); + } + console.log(); + } + if (totalCost > 0) { + console.log( + chalk8.dim(" Total cost: ") + costWithContext(totalCost) + ); + console.log(); + } + console.log(chalk8.dim(" " + "\u2500".repeat(60))); + console.log(); + console.log( + chalk8.dim(" ") + chalk8.cyan("devlog cost") + chalk8.dim(" cost breakdown \xB7 ") + chalk8.cyan("devlog stats --period week") + chalk8.dim(" filter") + ); + console.log(); +} + +// src/commands/cost.ts +import chalk9 from "chalk"; +import ora8 from "ora"; +import dayjs5 from "dayjs"; +import isToday4 from "dayjs/plugin/isToday.js"; +dayjs5.extend(isToday4); +function filterByPeriod2(sessions, period) { + const now = dayjs5(); + return sessions.filter((s) => { + const d = dayjs5(s.updatedAt); + switch (period) { + case "today": + return d.isToday(); + case "week": + return now.diff(d, "day") < 7; + case "month": + return now.diff(d, "day") < 30; + default: + return true; + } + }); +} +function renderBar(fraction, width = 16) { + const filled = Math.round(fraction * width); + return chalk9.green("\u2588".repeat(filled)) + chalk9.dim("\u2591".repeat(width - filled)); +} +async function costCommand(options, globalOpts) { + const { config } = ensureInit(); + const period = options.period || "all"; + let spinner = null; + if (!isJsonMode() && !isQuietMode()) { + spinner = ora8({ + text: chalk9.dim(" Calculating costs..."), + spinner: "dots", + color: "cyan", + stream: process.stderr + }).start(); + } + const projects = await discoverProjects(config.claudeDir); + spinner?.stop(); + const allSessions = []; + for (const p of projects) allSessions.push(...p.sessions); + const filtered = filterByPeriod2(allSessions, period); + const byProject = /* @__PURE__ */ new Map(); + const byModel = /* @__PURE__ */ new Map(); + let totalCost = 0; + for (const s of filtered) { + totalCost += s.meta.totalCostUSD; + byProject.set( + s.projectName, + (byProject.get(s.projectName) || 0) + s.meta.totalCostUSD + ); + for (const [model, cost] of Object.entries(s.meta.costByModel)) { + byModel.set(model, (byModel.get(model) || 0) + cost); + } + } + const periodLabel = period === "today" ? "Today" : period === "week" ? "This Week" : period === "month" ? "This Month" : "All Time"; + if (isJsonMode()) { + outputJson({ + period: periodLabel, + totalCostUSD: Math.round(totalCost * 1e6) / 1e6, + byProject: [...byProject.entries()].sort((a, b) => b[1] - a[1]).map(([name, cost]) => ({ name, costUSD: Math.round(cost * 1e6) / 1e6 })), + byModel: [...byModel.entries()].sort((a, b) => b[1] - a[1]).map(([name, cost]) => ({ name, costUSD: Math.round(cost * 1e6) / 1e6 })) + }); + return; + } + console.log(); + console.log( + chalk9.bold.cyan(" \u258C") + chalk9.bold.white(` Cost Breakdown \u2014 ${periodLabel}`) + ); + console.log(); + if (totalCost === 0) { + console.log(chalk9.dim(" No costs recorded in this period.")); + console.log(); + return; + } + console.log( + chalk9.dim(" Total: ") + costWithContext(totalCost) + ); + console.log(); + const projectEntries = [...byProject.entries()].sort((a, b) => b[1] - a[1]); + if (projectEntries.length > 0) { + console.log(chalk9.white(" By project:")); + for (const [name, cost] of projectEntries) { + const pct = totalCost > 0 ? Math.round(cost / totalCost * 100) : 0; + const costStr = cost < 1 ? `$${cost.toFixed(3)}` : `$${cost.toFixed(2)}`; + console.log( + chalk9.dim(" ") + chalk9.cyan(name.padEnd(18)) + chalk9.yellow(costStr.padEnd(10)) + chalk9.dim(`(${pct}%)`.padEnd(7)) + renderBar(cost / totalCost) + ); + } + console.log(); + } + const modelEntries = [...byModel.entries()].sort((a, b) => b[1] - a[1]); + if (modelEntries.length > 0) { + console.log(chalk9.white(" By model:")); + for (const [name, cost] of modelEntries) { + const pct = totalCost > 0 ? Math.round(cost / totalCost * 100) : 0; + const costStr = cost < 1 ? `$${cost.toFixed(3)}` : `$${cost.toFixed(2)}`; + let label = name; + if (name.includes("opus")) label = "claude-opus"; + else if (name.includes("sonnet")) label = "claude-sonnet"; + else if (name.includes("haiku")) label = "claude-haiku"; + let context = ""; + if (pct > 70) context = " most of your work"; + else if (pct < 20) context = " for the hard stuff"; + console.log( + chalk9.dim(" ") + chalk9.white(label.padEnd(18)) + chalk9.yellow(costStr.padEnd(10)) + chalk9.dim(`(${pct}%)`) + chalk9.dim(context) + ); + } + console.log(); + } + console.log(chalk9.dim(" " + "\u2500".repeat(60))); + console.log(); + console.log( + chalk9.dim(" ") + chalk9.cyan("devlog stats") + chalk9.dim(" usage trends \xB7 ") + chalk9.cyan("devlog cost --period week") + chalk9.dim(" filter") ); console.log(); } // src/cli.ts -var VERSION = "0.1.0"; +var VERSION2 = "0.3.0"; var HELP_TEXT = ` -${chalk6.bold.cyan(" \u258C")} ${chalk6.bold.white("DevLog")} ${chalk6.dim(`v${VERSION}`)} -${chalk6.dim(" Your Claude Code work journal")} +${chalk10.bold.cyan(" \u258C")} ${chalk10.bold.white("DevLog")} ${chalk10.dim(`v${VERSION2}`)} +${chalk10.dim(" Your Claude Code work journal")} + +${chalk10.bold.white(" Quick Start:")} +${chalk10.dim(" Just run")} ${chalk10.cyan("devlog")} ${chalk10.dim("\u2014 that's it. No setup needed.")} -${chalk6.bold.white(" Quick Start:")} -${chalk6.dim(" Just run")} ${chalk6.cyan("devlog")} ${chalk6.dim("\u2014 that's it. No setup needed.")} +${chalk10.bold.white(" Examples:")} +${chalk10.cyan(" devlog")}${chalk10.dim(" See your dashboard")} +${chalk10.cyan(" devlog today")}${chalk10.dim(" What did I do today?")} +${chalk10.cyan(" devlog sessions")}${chalk10.dim(" Browse all sessions by project")} +${chalk10.cyan(" devlog sessions -p chatbot")}${chalk10.dim(" Filter to a specific project")} +${chalk10.cyan(" devlog show 1")}${chalk10.dim(" View your most recent conversation")} +${chalk10.cyan(" devlog show 1 --summary")}${chalk10.dim(" Quick narrative summary")} +${chalk10.cyan(" devlog show abc123")}${chalk10.dim(" View a specific session by ID")} +${chalk10.cyan(' devlog search "auth bug"')}${chalk10.dim(" Find a conversation")} +${chalk10.cyan(" devlog stats")}${chalk10.dim(" Usage trends")} +${chalk10.cyan(" devlog cost")}${chalk10.dim(" Cost breakdown")} -${chalk6.bold.white(" Examples:")} -${chalk6.cyan(" devlog")}${chalk6.dim(" See your dashboard")} -${chalk6.cyan(" devlog sessions")}${chalk6.dim(" Browse all sessions by project")} -${chalk6.cyan(" devlog sessions -p chatbot")}${chalk6.dim(" Filter to a specific project")} -${chalk6.cyan(" devlog show 1")}${chalk6.dim(" View your most recent conversation")} -${chalk6.cyan(" devlog show abc123")}${chalk6.dim(" View a specific session by ID")} +${chalk10.bold.white(" Output Modes:")} +${chalk10.cyan(" devlog --json")}${chalk10.dim(" JSON output for scripts/agents")} +${chalk10.cyan(" devlog -q")}${chalk10.dim(" Quiet mode (no spinners/banners)")} +${chalk10.cyan(" devlog --no-color")}${chalk10.dim(" Plain text, no ANSI escapes")} `; var program = new Command(); -program.name("devlog").description("Your Claude Code work journal \u2014 auto-generated dev logs").version(VERSION).addHelpText("before", HELP_TEXT); +var KNOWN_COMMANDS = [ + "init", + "sessions", + "show", + "today", + "search", + "stats", + "cost" +]; +function getGlobalOpts() { + const opts = program.opts(); + return { json: !!opts.json, quiet: !!opts.quiet }; +} +function handleError(err, globalOpts) { + const message = err instanceof Error ? err.message : String(err); + if (globalOpts.json) { + outputJson({ error: message }); + } else { + console.error( + chalk10.red("\n Error:"), + message + ); + } + process.exit(1); +} +program.name("devlog").description("Your Claude Code work journal \u2014 auto-generated dev logs").version(VERSION2).option("--json", "Output as JSON for scripts and agents").option("-q, --quiet", "Suppress non-essential output").option("--no-color", "Disable colored output").addHelpText("before", HELP_TEXT); +program.hook("preAction", () => { + const opts = program.opts(); + if (opts.color === false) { + process.env.NO_COLOR = "1"; + } + initOutput({ json: !!opts.json, quiet: !!opts.quiet }); +}); program.action(async () => { + const globalOpts = getGlobalOpts(); try { - await dashboardCommand(); + await dashboardCommand(globalOpts); } catch (err) { - console.error( - chalk6.red("\n Error:"), - err instanceof Error ? err.message : err - ); - process.exit(1); + handleError(err, globalOpts); } }); program.command("init").description("Set up DevLog (usually auto-detected, you rarely need this)").action(async () => { + const globalOpts = getGlobalOpts(); try { - await initCommand(); + await initCommand(globalOpts); } catch (err) { - console.error( - chalk6.red("\n Error:"), - err instanceof Error ? err.message : err - ); - process.exit(1); + handleError(err, globalOpts); } }); program.command("sessions").description("Browse all sessions grouped by project").option("-p, --project ", "Filter by project name (fuzzy match)").option("-n, --limit ", "Max sessions to display", "30").option("-a, --all", "Show all sessions").action(async (options) => { + const globalOpts = getGlobalOpts(); try { - await sessionsCommand(options); + await sessionsCommand(options, globalOpts); } catch (err) { - console.error( - chalk6.red("\n Error:"), - err instanceof Error ? err.message : err - ); - process.exit(1); + handleError(err, globalOpts); } }); -program.command("show ").description("View a full conversation (use a number like 1, 2, 3 or a session ID)").option("-n, --limit ", "Max events to display", "50").action(async (sessionRef, options) => { +program.command("show ").description("View a full conversation (use a number like 1, 2, 3 or a session ID)").option("-n, --limit ", "Max events to display", "50").option("-s, --summary", "Show a narrative summary instead of the full conversation").action(async (sessionRef, options) => { + const globalOpts = getGlobalOpts(); try { - await showCommand(sessionRef, options); + await showCommand(sessionRef, options, globalOpts); } catch (err) { - console.error( - chalk6.red("\n Error:"), - err instanceof Error ? err.message : err - ); - process.exit(1); + handleError(err, globalOpts); + } +}); +program.command("today").description("What did I do today?").action(async () => { + const globalOpts = getGlobalOpts(); + try { + await todayCommand(globalOpts); + } catch (err) { + handleError(err, globalOpts); + } +}); +program.command("search ").description("Search sessions by message, project, or tool name").action(async (query) => { + const globalOpts = getGlobalOpts(); + try { + await searchCommand(query, globalOpts); + } catch (err) { + handleError(err, globalOpts); + } +}); +program.command("stats").description("Aggregated usage statistics").option("--period ", "Filter: today, week, month, all", "all").action(async (options) => { + const globalOpts = getGlobalOpts(); + try { + await statsCommand(options, globalOpts); + } catch (err) { + handleError(err, globalOpts); } }); +program.command("cost").description("Cost breakdown by project and model").option("--period ", "Filter: today, week, month, all", "all").action(async (options) => { + const globalOpts = getGlobalOpts(); + try { + await costCommand(options, globalOpts); + } catch (err) { + handleError(err, globalOpts); + } +}); +var userArgs = process.argv.slice(2).filter((a) => !a.startsWith("-")); +if (userArgs.length === 1 && !KNOWN_COMMANDS.includes(userArgs[0])) { + const candidate = userArgs[0]; + const isNumber = /^\d+$/.test(candidate); + const isSessionId = /^[0-9a-f]{6,}$/i.test(candidate); + if (!isNumber && !isSessionId) { + let suggestion = ""; + let bestDist = Infinity; + for (const cmd of KNOWN_COMMANDS) { + const dist = levenshtein(candidate, cmd); + if (dist < bestDist) { + bestDist = dist; + suggestion = cmd; + } + } + if (bestDist <= 3) { + console.error(); + console.error( + chalk10.yellow(` Unknown command: ${candidate}`) + ); + console.error( + chalk10.dim(" Did you mean: ") + chalk10.cyan(suggestion) + chalk10.dim("?") + ); + console.error( + chalk10.dim(" Run ") + chalk10.cyan("devlog --help") + chalk10.dim(" to see all commands.") + ); + console.error(); + process.exit(1); + } + } +} program.parse(); //# sourceMappingURL=cli.js.map \ No newline at end of file diff --git a/dist/cli.js.map b/dist/cli.js.map index 81405eb..a115e5c 100644 --- a/dist/cli.js.map +++ b/dist/cli.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/cli.ts","../src/commands/dashboard.ts","../src/core/config.ts","../src/utils/paths.ts","../src/core/discovery.ts","../src/core/parser.ts","../src/utils/format.ts","../src/commands/init.ts","../src/commands/sessions.ts","../src/commands/show.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { dashboardCommand } from \"./commands/dashboard.js\";\nimport { initCommand } from \"./commands/init.js\";\nimport { sessionsCommand } from \"./commands/sessions.js\";\nimport { showCommand } from \"./commands/show.js\";\n\nconst VERSION = \"0.1.0\";\n\nconst HELP_TEXT = `\n${chalk.bold.cyan(\" ▌\")} ${chalk.bold.white(\"DevLog\")} ${chalk.dim(`v${VERSION}`)}\n${chalk.dim(\" Your Claude Code work journal\")}\n\n${chalk.bold.white(\" Quick Start:\")}\n${chalk.dim(\" Just run\")} ${chalk.cyan(\"devlog\")} ${chalk.dim(\"— that's it. No setup needed.\")}\n\n${chalk.bold.white(\" Examples:\")}\n${chalk.cyan(\" devlog\")}${chalk.dim(\" See your dashboard\")}\n${chalk.cyan(\" devlog sessions\")}${chalk.dim(\" Browse all sessions by project\")}\n${chalk.cyan(\" devlog sessions -p chatbot\")}${chalk.dim(\" Filter to a specific project\")}\n${chalk.cyan(\" devlog show 1\")}${chalk.dim(\" View your most recent conversation\")}\n${chalk.cyan(\" devlog show abc123\")}${chalk.dim(\" View a specific session by ID\")}\n`;\n\nconst program = new Command();\n\nprogram\n .name(\"devlog\")\n .description(\"Your Claude Code work journal — auto-generated dev logs\")\n .version(VERSION)\n .addHelpText(\"before\", HELP_TEXT);\n\n// ── Default: `devlog` with no args → dashboard ──────────\nprogram.action(async () => {\n try {\n await dashboardCommand();\n } catch (err) {\n console.error(\n chalk.red(\"\\n Error:\"),\n err instanceof Error ? err.message : err\n );\n process.exit(1);\n }\n});\n\n// ── devlog init ──────────────────────────────────────────\nprogram\n .command(\"init\")\n .description(\"Set up DevLog (usually auto-detected, you rarely need this)\")\n .action(async () => {\n try {\n await initCommand();\n } catch (err) {\n console.error(\n chalk.red(\"\\n Error:\"),\n err instanceof Error ? err.message : err\n );\n process.exit(1);\n }\n });\n\n// ── devlog sessions ──────────────────────────────────────\nprogram\n .command(\"sessions\")\n .description(\"Browse all sessions grouped by project\")\n .option(\"-p, --project \", \"Filter by project name (fuzzy match)\")\n .option(\"-n, --limit \", \"Max sessions to display\", \"30\")\n .option(\"-a, --all\", \"Show all sessions\")\n .action(async (options) => {\n try {\n await sessionsCommand(options);\n } catch (err) {\n console.error(\n chalk.red(\"\\n Error:\"),\n err instanceof Error ? err.message : err\n );\n process.exit(1);\n }\n });\n\n// ── devlog show ────────────────────────────────\nprogram\n .command(\"show \")\n .description(\"View a full conversation (use a number like 1, 2, 3 or a session ID)\")\n .option(\"-n, --limit \", \"Max events to display\", \"50\")\n .action(async (sessionRef: string, options) => {\n try {\n await showCommand(sessionRef, options);\n } catch (err) {\n console.error(\n chalk.red(\"\\n Error:\"),\n err instanceof Error ? err.message : err\n );\n process.exit(1);\n }\n });\n\nprogram.parse();\n","import chalk from \"chalk\";\nimport ora from \"ora\";\nimport { existsSync } from \"fs\";\nimport { ensureInit } from \"../core/config.js\";\nimport {\n discoverProjects,\n computeStats,\n groupSessionsByTime,\n} from \"../core/discovery.js\";\nimport type { Session, AggregateStats } from \"../core/types.js\";\nimport {\n formatSmartTime,\n formatCost,\n formatDuration,\n formatNumber,\n truncate,\n} from \"../utils/format.js\";\nimport { getClaudeProjectsDir } from \"../utils/paths.js\";\n\n/**\n * The default command. This IS the product.\n * Run `devlog` → see your world with Claude.\n */\nexport async function dashboardCommand(): Promise {\n const { config, isFirstRun } = ensureInit();\n\n // ── No Claude Code installed ──────────────────────\n if (!existsSync(config.claudeDir)) {\n console.log();\n console.log(\n chalk.bold.cyan(\" ▌\") + chalk.bold.white(\" DevLog\")\n );\n console.log();\n console.log(\n chalk.white(\" Hmm, I can't find Claude Code on this machine.\")\n );\n console.log(\n chalk.dim(\" I looked for: \") + chalk.white(getClaudeProjectsDir())\n );\n console.log();\n console.log(\n chalk.white(\" To get started:\")\n );\n console.log(\n chalk.dim(\" 1. Install Claude Code → \") + chalk.cyan(\"https://claude.ai/code\")\n );\n console.log(\n chalk.dim(\" 2. Have a conversation with Claude in any project\")\n );\n console.log(\n chalk.dim(\" 3. Come back and run \") + chalk.cyan(\"devlog\") + chalk.dim(\" again\")\n );\n console.log();\n return;\n }\n\n // ── Scan ──────────────────────────────────────────\n const spinner = ora({\n text: chalk.dim(\" Reading your Claude Code history...\"),\n spinner: \"dots\",\n color: \"cyan\",\n }).start();\n\n const projects = await discoverProjects(config.claudeDir, (msg) => {\n spinner.text = chalk.dim(` ${msg}`);\n });\n\n spinner.stop();\n\n // ── No sessions yet ───────────────────────────────\n if (projects.length === 0) {\n console.log();\n console.log(\n chalk.bold.cyan(\" ▌\") + chalk.bold.white(\" DevLog\")\n );\n console.log();\n console.log(\n chalk.white(\" No conversations found yet.\")\n );\n console.log(\n chalk.dim(\" Start a Claude Code session in any project, then come back!\")\n );\n console.log();\n return;\n }\n\n const stats = computeStats(projects);\n const groups = groupSessionsByTime(projects);\n\n // ── Render ────────────────────────────────────────\n console.log();\n\n if (isFirstRun) {\n renderWelcome(stats);\n } else {\n renderBanner();\n }\n\n renderNarrativeStats(stats);\n renderSessionGroups(groups, stats);\n renderNextSteps(stats, isFirstRun);\n}\n\n// ─────────────────────────────────────────────────────\n// Render helpers\n// ─────────────────────────────────────────────────────\n\nfunction renderWelcome(stats: AggregateStats): void {\n console.log(\n chalk.bold.cyan(\" ▌\") + chalk.bold.white(\" Welcome to DevLog!\")\n );\n console.log();\n console.log(\n chalk.white(\" I found your Claude Code history — let me show you what\") +\n chalk.white(\" you've been building.\")\n );\n console.log();\n}\n\nfunction renderBanner(): void {\n console.log(\n chalk.bold.cyan(\" ▌\") +\n chalk.bold.white(\" DevLog\")\n );\n console.log();\n}\n\nfunction renderNarrativeStats(stats: AggregateStats): void {\n // Main narrative: \"You and Claude\" story\n const projectWord = stats.totalProjects === 1 ? \"project\" : \"projects\";\n const sessionWord = stats.totalSessions === 1 ? \"conversation\" : \"conversations\";\n\n console.log(\n chalk.dim(\" \") +\n chalk.white(\"You and Claude had \") +\n chalk.cyan.bold(String(stats.totalSessions)) +\n chalk.white(` ${sessionWord} across `) +\n chalk.cyan.bold(String(stats.totalProjects)) +\n chalk.white(` ${projectWord}.`)\n );\n\n // Activity line — what actually happened\n const activityParts: string[] = [];\n\n if (stats.totalToolCalls > 0) {\n activityParts.push(\n chalk.white(\"Claude ran \") +\n chalk.green.bold(formatNumber(stats.totalToolCalls)) +\n chalk.white(\" commands\")\n );\n }\n\n if (stats.allFilesReferenced.length > 0) {\n activityParts.push(\n chalk.white(\"touched \") +\n chalk.blue.bold(String(stats.allFilesReferenced.length)) +\n chalk.white(\" files\")\n );\n }\n\n if (activityParts.length > 0) {\n console.log(chalk.dim(\" \") + activityParts.join(chalk.dim(\" and \")));\n }\n\n // Cost line — only if there's data, with context\n if (stats.totalCostUSD > 0) {\n const costStr = stats.totalCostUSD < 1\n ? `$${stats.totalCostUSD.toFixed(3)}`\n : `$${stats.totalCostUSD.toFixed(2)}`;\n\n let costContext = \"\";\n if (stats.totalCostUSD < 0.10) costContext = \" — less than a cup of coffee\";\n else if (stats.totalCostUSD < 1) costContext = \" — pretty efficient\";\n else if (stats.totalCostUSD < 5) costContext = \" — solid investment\";\n\n console.log(\n chalk.dim(\" \") +\n chalk.white(\"Total cost: \") +\n chalk.yellow.bold(costStr) +\n chalk.dim(costContext)\n );\n }\n\n // Today highlight\n if (stats.todaySessions > 0) {\n const todayWord = stats.todaySessions === 1 ? \"session\" : \"sessions\";\n console.log(\n chalk.dim(\" \") +\n chalk.green(\"▸ \") +\n chalk.white.bold(`${stats.todaySessions} ${todayWord} today`) +\n (stats.todayCostUSD > 0\n ? chalk.dim(` · $${stats.todayCostUSD.toFixed(3)}`)\n : \"\")\n );\n }\n\n console.log();\n console.log(chalk.dim(\" \" + \"─\".repeat(60)));\n console.log();\n}\n\nfunction renderSessionGroups(\n groups: {\n today: Session[];\n yesterday: Session[];\n thisWeek: Session[];\n older: Session[];\n },\n stats: AggregateStats\n): void {\n const hasToday = groups.today.length > 0;\n const hasYesterday = groups.yesterday.length > 0;\n const hasThisWeek = groups.thisWeek.length > 0;\n const hasOlder = groups.older.length > 0;\n\n if (hasToday) {\n renderGroup(\"Today\", groups.today, chalk.green);\n }\n\n if (hasYesterday) {\n renderGroup(\"Yesterday\", groups.yesterday, chalk.blue);\n }\n\n if (hasThisWeek) {\n renderGroup(\"This Week\", groups.thisWeek, chalk.white);\n }\n\n if (hasOlder) {\n const count = groups.older.length;\n console.log(\n chalk.dim(` + ${count} older session${count === 1 ? \"\" : \"s\"}`)\n );\n console.log();\n }\n\n if (!hasToday && !hasYesterday && !hasThisWeek && !hasOlder) {\n console.log(chalk.dim(\" No sessions found.\"));\n console.log();\n }\n}\n\nfunction renderGroup(\n label: string,\n sessions: Session[],\n accentColor: typeof chalk\n): void {\n console.log(accentColor.bold(` ${label}`));\n console.log();\n\n for (const session of sessions) {\n renderSessionCard(session, accentColor);\n }\n}\n\nfunction renderSessionCard(session: Session, accentColor: typeof chalk): void {\n const m = session.meta;\n const time = formatSmartTime(session.updatedAt);\n const preview = m.firstUserMessage\n ? truncate(m.firstUserMessage.replace(/\\n/g, \" \").trim(), 50)\n : chalk.dim(\"(empty session)\");\n\n // Line 1: Time + Project name + What you asked\n const timeStr = time.length > 14 ? time.slice(0, 14) : time;\n console.log(\n chalk.dim(\" \") +\n accentColor(timeStr.padEnd(16)) +\n chalk.bold.white(session.projectName.padEnd(14)) +\n chalk.white(preview)\n );\n\n // Line 2: Human-readable activity summary\n const parts: string[] = [];\n\n // Conversation depth\n const turns = m.humanTurns + m.assistantTurns;\n if (turns <= 4) {\n parts.push(chalk.dim(\"quick chat\"));\n } else if (turns <= 10) {\n parts.push(chalk.dim(`${turns} messages`));\n } else {\n parts.push(chalk.white(`${turns} messages`));\n }\n\n // What Claude did (in plain language)\n if (m.toolCalls > 0) {\n parts.push(chalk.green(`ran ${m.toolCalls} commands`));\n }\n\n if (m.filesReferenced.length > 0) {\n const fileWord = m.filesReferenced.length === 1 ? \"file\" : \"files\";\n parts.push(chalk.blue(`wrote ${m.filesReferenced.length} ${fileWord}`));\n }\n\n if (m.totalCostUSD > 0) {\n parts.push(formatCost(m.totalCostUSD));\n }\n\n if (m.errorCount > 0) {\n const errWord = m.errorCount === 1 ? \"error\" : \"errors\";\n parts.push(chalk.red(`${m.errorCount} ${errWord}`));\n }\n\n console.log(\n chalk.dim(\" \") +\n \" \".repeat(30) +\n parts.join(chalk.dim(\" · \"))\n );\n console.log();\n}\n\nfunction renderNextSteps(stats: AggregateStats, isFirstRun: boolean): void {\n console.log(chalk.dim(\" \" + \"─\".repeat(60)));\n console.log();\n\n if (isFirstRun) {\n // First run: teach the user what they can do\n console.log(chalk.white(\" What you can do next:\"));\n console.log();\n console.log(\n chalk.cyan(\" devlog sessions\") +\n chalk.dim(\" Browse all sessions by project\")\n );\n console.log(\n chalk.cyan(\" devlog sessions -p chat\") +\n chalk.dim(\" Filter to a specific project\")\n );\n console.log(\n chalk.cyan(\" devlog show \") +\n chalk.dim(\" View a full conversation\")\n );\n console.log();\n console.log(\n chalk.dim(\" Tip: Just run \") +\n chalk.cyan(\"devlog\") +\n chalk.dim(\" anytime to see this dashboard.\")\n );\n } else {\n // Returning user: compact hints\n console.log(\n chalk.dim(\" \") +\n chalk.cyan(\"devlog sessions\") +\n chalk.dim(\" all sessions · \") +\n chalk.cyan(\"devlog show \") +\n chalk.dim(\" view conversation · \") +\n chalk.cyan(\"--help\")\n );\n }\n console.log();\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { join } from \"path\";\nimport toml from \"toml\";\nimport type { DevLogConfig } from \"./types.js\";\nimport {\n getClaudeProjectsDir,\n getDevlogDir,\n getConfigPath,\n} from \"../utils/paths.js\";\n\nconst DEFAULT_CONFIG = `# DevLog Configuration\n# Auto-generated by devlog\n\n[paths]\n# Claude Code projects directory\nclaude_dir = \"{{claudeDir}}\"\n\n# DevLog data directory\ndevlog_dir = \"{{devlogDir}}\"\n\n[display]\n# Maximum number of sessions to show in list\nmax_sessions = 50\n\n# Truncate message preview to this length\npreview_length = 80\n`;\n\nexport function isInitialized(): boolean {\n return existsSync(getConfigPath());\n}\n\nexport function initConfig(): DevLogConfig {\n const devlogDir = getDevlogDir();\n const claudeDir = getClaudeProjectsDir();\n const configPath = getConfigPath();\n\n mkdirSync(devlogDir, { recursive: true });\n mkdirSync(join(devlogDir, \"daily\"), { recursive: true });\n mkdirSync(join(devlogDir, \"reports\"), { recursive: true });\n mkdirSync(join(devlogDir, \"db\"), { recursive: true });\n\n const configContent = DEFAULT_CONFIG.replace(\n \"{{claudeDir}}\",\n claudeDir\n ).replace(\"{{devlogDir}}\", devlogDir);\n\n writeFileSync(configPath, configContent, \"utf-8\");\n\n return { claudeDir, devlogDir, version: \"0.1.0\" };\n}\n\n/**\n * Ensure DevLog is initialized. Returns config + whether this was the first run.\n */\nexport function ensureInit(): { config: DevLogConfig; isFirstRun: boolean } {\n if (!isInitialized()) {\n return { config: initConfig(), isFirstRun: true };\n }\n return { config: loadConfig(), isFirstRun: false };\n}\n\nexport function loadConfig(): DevLogConfig {\n const configPath = getConfigPath();\n\n if (!existsSync(configPath)) {\n return {\n claudeDir: getClaudeProjectsDir(),\n devlogDir: getDevlogDir(),\n version: \"0.1.0\",\n };\n }\n\n try {\n const raw = readFileSync(configPath, \"utf-8\");\n const parsed = toml.parse(raw);\n\n return {\n claudeDir: parsed.paths?.claude_dir || getClaudeProjectsDir(),\n devlogDir: parsed.paths?.devlog_dir || getDevlogDir(),\n version: \"0.1.0\",\n };\n } catch {\n return {\n claudeDir: getClaudeProjectsDir(),\n devlogDir: getDevlogDir(),\n version: \"0.1.0\",\n };\n }\n}\n","import { homedir } from \"os\";\nimport { join } from \"path\";\n\n/**\n * Default Claude Code projects directory\n */\nexport function getClaudeProjectsDir(): string {\n return join(homedir(), \".claude\", \"projects\");\n}\n\n/**\n * Default DevLog data directory\n */\nexport function getDevlogDir(): string {\n return join(homedir(), \".devlog\");\n}\n\n/**\n * Default DevLog config file path\n */\nexport function getConfigPath(): string {\n return join(getDevlogDir(), \"config.toml\");\n}\n\n/**\n * Decode Claude Code's path encoding:\n * -Users-dong-projects-myapp → /Users/dong/projects/myapp\n *\n * Claude Code encodes the project path by replacing '/' with '-'\n * and prepending a '-' to the path.\n */\nexport function decodePath(encoded: string): string {\n // The encoded path starts with the drive/root indicator\n // e.g., \"-Users-dong-xxx\" → \"/Users/dong/xxx\"\n // Handle both macOS and Linux paths\n if (encoded.startsWith(\"-\")) {\n return encoded.replace(/-/g, \"/\");\n }\n return encoded;\n}\n\n/**\n * Get a human-readable project name from the decoded path\n */\nexport function getProjectName(decodedPath: string): string {\n const parts = decodedPath.split(\"/\").filter(Boolean);\n // Return the last meaningful directory name\n return parts[parts.length - 1] || decodedPath;\n}\n","import { readdir, stat } from \"fs/promises\";\nimport { join } from \"path\";\nimport { existsSync } from \"fs\";\nimport type { Project, Session, AggregateStats } from \"./types.js\";\nimport {\n decodePath,\n getProjectName,\n getClaudeProjectsDir,\n} from \"../utils/paths.js\";\nimport { scanSession } from \"./parser.js\";\nimport dayjs from \"dayjs\";\n\n/**\n * Discover all Claude Code projects and sessions with rich metadata.\n * Accepts an optional progress callback for spinner updates.\n */\nexport async function discoverProjects(\n claudeDir?: string,\n onProgress?: (msg: string) => void\n): Promise {\n const projectsDir = claudeDir || getClaudeProjectsDir();\n\n if (!existsSync(projectsDir)) {\n return [];\n }\n\n const entries = await readdir(projectsDir, { withFileTypes: true });\n const projects: Project[] = [];\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n const projectDir = join(projectsDir, entry.name);\n const decodedPath = decodePath(entry.name);\n const projectName = getProjectName(decodedPath);\n\n onProgress?.(`Scanning ${projectName}...`);\n\n const sessions = await discoverSessions(\n projectDir,\n decodedPath,\n projectName\n );\n\n if (sessions.length > 0) {\n projects.push({\n path: decodedPath,\n name: projectName,\n encodedPath: entry.name,\n sessionCount: sessions.length,\n sessions,\n });\n }\n }\n\n // Sort projects by most recent session\n projects.sort((a, b) => {\n const aLatest = Math.max(\n ...a.sessions.map((s) => s.updatedAt.getTime())\n );\n const bLatest = Math.max(\n ...b.sessions.map((s) => s.updatedAt.getTime())\n );\n return bLatest - aLatest;\n });\n\n return projects;\n}\n\nasync function discoverSessions(\n projectDir: string,\n decodedPath: string,\n projectName: string\n): Promise {\n const sessions: Session[] = [];\n\n try {\n const entries = await readdir(projectDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith(\".jsonl\")) continue;\n\n const filePath = join(projectDir, entry.name);\n const sessionId = entry.name.replace(\".jsonl\", \"\");\n\n try {\n const fileStat = await stat(filePath);\n const meta = await scanSession(filePath);\n\n // Use internal timestamps when available (more accurate than fs mtime)\n const createdAt = meta.firstActivity.getTime() > 0 ? meta.firstActivity : fileStat.birthtime;\n const updatedAt = meta.lastActivity.getTime() > 0 ? meta.lastActivity : fileStat.mtime;\n\n sessions.push({\n id: sessionId,\n projectPath: decodedPath,\n projectName,\n filePath,\n createdAt,\n updatedAt,\n meta,\n });\n } catch {\n continue;\n }\n }\n } catch {\n return [];\n }\n\n sessions.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());\n return sessions;\n}\n\n/**\n * Compute aggregate stats with today-awareness for the emotional dashboard.\n */\nexport function computeStats(projects: Project[]): AggregateStats {\n const today = dayjs().startOf(\"day\");\n let totalSessions = 0;\n let totalMessages = 0;\n let totalHumanTurns = 0;\n let totalAssistantTurns = 0;\n let totalToolCalls = 0;\n let totalCostUSD = 0;\n let totalDurationMs = 0;\n let todaySessions = 0;\n let todayMessages = 0;\n let todayCostUSD = 0;\n\n const allTools = new Set();\n const allFiles = new Set();\n const allModels = new Set();\n\n let mostActiveProject = \"\";\n let mostActiveProjectSessions = 0;\n\n for (const project of projects) {\n totalSessions += project.sessionCount;\n\n if (project.sessionCount > mostActiveProjectSessions) {\n mostActiveProjectSessions = project.sessionCount;\n mostActiveProject = project.name;\n }\n\n for (const session of project.sessions) {\n const m = session.meta;\n totalMessages += m.messageCount;\n totalHumanTurns += m.humanTurns;\n totalAssistantTurns += m.assistantTurns;\n totalToolCalls += m.toolCalls;\n totalCostUSD += m.totalCostUSD;\n totalDurationMs += m.totalDurationMs;\n\n m.uniqueTools.forEach((t) => allTools.add(t));\n m.filesReferenced.forEach((f) => allFiles.add(f));\n m.models.forEach((model) => allModels.add(model));\n\n // Today tracking\n if (dayjs(session.updatedAt).isAfter(today)) {\n todaySessions++;\n todayMessages += m.messageCount;\n todayCostUSD += m.totalCostUSD;\n }\n }\n }\n\n return {\n totalProjects: projects.length,\n totalSessions,\n totalMessages,\n totalHumanTurns,\n totalAssistantTurns,\n totalToolCalls,\n totalCostUSD,\n totalDurationMs,\n uniqueToolsUsed: [...allTools],\n allFilesReferenced: [...allFiles],\n modelsUsed: [...allModels],\n todaySessions,\n todayMessages,\n todayCostUSD,\n mostActiveProject,\n mostActiveProjectSessions,\n };\n}\n\n/**\n * Group sessions by time period for smart display\n */\nexport function groupSessionsByTime(\n projects: Project[]\n): {\n today: Session[];\n yesterday: Session[];\n thisWeek: Session[];\n older: Session[];\n} {\n const now = dayjs();\n const todayStart = now.startOf(\"day\");\n const yesterdayStart = todayStart.subtract(1, \"day\");\n const weekStart = now.startOf(\"week\");\n\n const groups = {\n today: [] as Session[],\n yesterday: [] as Session[],\n thisWeek: [] as Session[],\n older: [] as Session[],\n };\n\n for (const project of projects) {\n for (const session of project.sessions) {\n const d = dayjs(session.updatedAt);\n if (d.isAfter(todayStart)) {\n groups.today.push(session);\n } else if (d.isAfter(yesterdayStart)) {\n groups.yesterday.push(session);\n } else if (d.isAfter(weekStart)) {\n groups.thisWeek.push(session);\n } else {\n groups.older.push(session);\n }\n }\n }\n\n // Sort each group by most recent first\n for (const group of Object.values(groups)) {\n group.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());\n }\n\n return groups;\n}\n","import { createReadStream } from \"fs\";\nimport { createInterface } from \"readline\";\nimport type {\n RawJsonlEvent,\n DevLogEvent,\n ContentBlock,\n TextBlock,\n ToolUseBlock,\n ToolResultBlock,\n SessionMeta,\n} from \"./types.js\";\n\n/**\n * Rich scan: extract full metadata from a session file in a single streaming pass.\n * This replaces the old quickScanSession — same performance, 10x more insight.\n */\nexport async function scanSession(filePath: string): Promise {\n const meta: SessionMeta = {\n messageCount: 0,\n humanTurns: 0,\n assistantTurns: 0,\n toolCalls: 0,\n uniqueTools: [],\n filesReferenced: [],\n totalCostUSD: 0,\n totalDurationMs: 0,\n models: [],\n firstUserMessage: \"\",\n lastActivity: new Date(0),\n firstActivity: new Date(),\n errorCount: 0,\n };\n\n const toolSet = new Set();\n const fileSet = new Set();\n const modelSet = new Set();\n\n const rl = createInterface({\n input: createReadStream(filePath, { encoding: \"utf-8\" }),\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n if (!line.trim()) continue;\n\n try {\n const event = JSON.parse(line) as RawJsonlEvent;\n const ts = event.timestamp ? new Date(event.timestamp) : null;\n\n if (ts) {\n if (ts < meta.firstActivity) meta.firstActivity = ts;\n if (ts > meta.lastActivity) meta.lastActivity = ts;\n }\n\n // Count messages\n if (event.type === \"human\") {\n meta.humanTurns++;\n meta.messageCount++;\n }\n if (event.type === \"assistant\") {\n meta.assistantTurns++;\n meta.messageCount++;\n }\n\n // First user message\n if (event.type === \"human\" && !meta.firstUserMessage) {\n meta.firstUserMessage = extractTextContent(event);\n }\n\n // Cost & duration tracking\n if (event.costUSD && typeof event.costUSD === \"number\") {\n meta.totalCostUSD += event.costUSD;\n }\n if (event.durationMs && typeof event.durationMs === \"number\") {\n meta.totalDurationMs += event.durationMs;\n }\n\n // Model tracking\n if (event.model && typeof event.model === \"string\") {\n modelSet.add(event.model);\n }\n\n // Tool call extraction from content blocks\n if (Array.isArray(event.content)) {\n for (const block of event.content as ContentBlock[]) {\n if (block.type === \"tool_use\") {\n const tb = block as ToolUseBlock;\n meta.toolCalls++;\n toolSet.add(tb.name);\n\n // Extract file references from tool inputs\n const input = tb.input;\n for (const key of [\"file_path\", \"path\", \"filePath\"]) {\n if (input[key] && typeof input[key] === \"string\") {\n fileSet.add(input[key] as string);\n }\n }\n }\n if (block.type === \"tool_result\") {\n const rb = block as ToolResultBlock;\n if (rb.is_error) meta.errorCount++;\n }\n }\n }\n } catch {\n continue;\n }\n }\n\n meta.uniqueTools = [...toolSet];\n meta.filesReferenced = [...fileSet];\n meta.models = [...modelSet];\n\n return meta;\n}\n\n/**\n * Fully parse a session file into DevLogEvent array.\n */\nexport async function parseSessionFile(\n filePath: string,\n sessionId: string\n): Promise {\n const events: DevLogEvent[] = [];\n let lineIndex = 0;\n\n const rl = createInterface({\n input: createReadStream(filePath, { encoding: \"utf-8\" }),\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n if (!line.trim()) continue;\n lineIndex++;\n\n try {\n const raw = JSON.parse(line) as RawJsonlEvent;\n const parsed = normalizeEvent(raw, sessionId, lineIndex);\n if (parsed.length > 0) {\n events.push(...parsed);\n }\n } catch {\n continue;\n }\n }\n\n return events;\n}\n\nfunction normalizeEvent(\n raw: RawJsonlEvent,\n sessionId: string,\n lineIndex: number\n): DevLogEvent[] {\n const events: DevLogEvent[] = [];\n const timestamp = raw.timestamp ? new Date(raw.timestamp) : new Date();\n const baseId = raw.uuid || `${sessionId}-${lineIndex}`;\n\n if (\n (raw.type === \"human\" || raw.type === \"assistant\") &&\n Array.isArray(raw.content)\n ) {\n const blocks = raw.content as ContentBlock[];\n let blockIndex = 0;\n\n for (const block of blocks) {\n blockIndex++;\n const eventId = `${baseId}-${blockIndex}`;\n\n if (block.type === \"text\") {\n const textBlock = block as TextBlock;\n events.push({\n id: eventId,\n sessionId,\n timestamp,\n role: raw.type as \"human\" | \"assistant\",\n type: \"text\",\n content: textBlock.text,\n costUSD: raw.costUSD,\n durationMs: raw.durationMs,\n model: raw.model,\n raw,\n });\n } else if (block.type === \"tool_use\") {\n const toolBlock = block as ToolUseBlock;\n events.push({\n id: eventId,\n sessionId,\n timestamp,\n role: \"tool_use\",\n type: \"tool_use\",\n content: `${toolBlock.name}(${summarizeToolInput(toolBlock.input)})`,\n toolName: toolBlock.name,\n toolInput: toolBlock.input,\n toolUseId: toolBlock.id,\n raw,\n });\n } else if (block.type === \"tool_result\") {\n const resultBlock = block as ToolResultBlock;\n events.push({\n id: eventId,\n sessionId,\n timestamp,\n role: \"tool_result\",\n type: \"tool_result\",\n content: extractToolResultContent(resultBlock),\n toolUseId: resultBlock.tool_use_id,\n isError: resultBlock.is_error,\n raw,\n });\n }\n }\n\n if (typeof raw.content === \"string\") {\n events.push({\n id: baseId,\n sessionId,\n timestamp,\n role: raw.type as \"human\" | \"assistant\",\n type: \"message\",\n content: raw.content,\n raw,\n });\n }\n\n if (events.length === 0) {\n events.push({\n id: baseId,\n sessionId,\n timestamp,\n role: raw.type as \"human\" | \"assistant\",\n type: \"message\",\n content: extractTextContent(raw),\n raw,\n });\n }\n } else if (raw.type === \"human\" && typeof raw.content === \"string\") {\n events.push({\n id: baseId,\n sessionId,\n timestamp,\n role: \"human\",\n type: \"message\",\n content: raw.content,\n raw,\n });\n } else if (raw.type === \"summary\") {\n events.push({\n id: baseId,\n sessionId,\n timestamp,\n role: \"system\",\n type: \"summary\",\n content: raw.summary || \"\",\n raw,\n });\n }\n\n return events;\n}\n\nfunction extractTextContent(event: RawJsonlEvent): string {\n if (typeof event.content === \"string\") return event.content;\n if (Array.isArray(event.content)) {\n const textBlocks = event.content.filter(\n (b): b is TextBlock => b.type === \"text\"\n );\n if (textBlocks.length > 0) return textBlocks.map((b) => b.text).join(\"\\n\");\n }\n if (event.message && typeof event.message === \"string\") return event.message;\n return \"\";\n}\n\nfunction summarizeToolInput(input: Record): string {\n const parts: string[] = [];\n if (input.command && typeof input.command === \"string\")\n parts.push(truncateStr(input.command, 60));\n if (input.file_path && typeof input.file_path === \"string\")\n parts.push(input.file_path as string);\n if (input.path && typeof input.path === \"string\")\n parts.push(input.path as string);\n if (input.query && typeof input.query === \"string\")\n parts.push(truncateStr(input.query, 40));\n\n if (parts.length === 0) {\n return Object.keys(input).slice(0, 3).join(\", \");\n }\n return parts.join(\", \");\n}\n\nfunction extractToolResultContent(block: ToolResultBlock): string {\n if (typeof block.content === \"string\") return block.content;\n if (Array.isArray(block.content)) {\n return block.content\n .filter((item) => item.text)\n .map((item) => item.text)\n .join(\"\\n\");\n }\n return \"\";\n}\n\nfunction truncateStr(str: string, max: number): string {\n if (str.length <= max) return str;\n return str.slice(0, max - 1) + \"…\";\n}\n","import chalk from \"chalk\";\nimport dayjs from \"dayjs\";\nimport relativeTime from \"dayjs/plugin/relativeTime.js\";\nimport isToday from \"dayjs/plugin/isToday.js\";\nimport isYesterday from \"dayjs/plugin/isYesterday.js\";\n\ndayjs.extend(relativeTime);\ndayjs.extend(isToday);\ndayjs.extend(isYesterday);\n\n/**\n * Smart time formatting:\n * Today: \"2:30 PM\" (just the time)\n * Yesterday: \"Yesterday 2:30 PM\"\n * This week: \"Mon 2:30 PM\"\n * Older: \"Jan 15, 2:30 PM\"\n */\nexport function formatSmartTime(date: Date): string {\n const d = dayjs(date);\n if (d.isToday()) return d.format(\"h:mm A\");\n if (d.isYesterday()) return \"Yest \" + d.format(\"h:mm A\");\n\n const now = dayjs();\n const diffDays = now.diff(d, \"day\");\n if (diffDays < 7) return d.format(\"ddd h:mm A\");\n\n return d.format(\"MMM D, h:mm A\");\n}\n\nexport function formatDate(date: Date): string {\n return dayjs(date).format(\"YYYY-MM-DD HH:mm\");\n}\n\nexport function formatRelative(date: Date): string {\n return dayjs(date).fromNow();\n}\n\nexport function truncate(str: string, maxLen: number): string {\n if (str.length <= maxLen) return str;\n return str.slice(0, maxLen - 1) + \"…\";\n}\n\n/**\n * Format cost with appropriate precision\n */\nexport function formatCost(usd: number): string {\n if (usd === 0) return chalk.dim(\"—\");\n if (usd < 0.01) return chalk.green(`$${usd.toFixed(4)}`);\n if (usd < 1) return chalk.green(`$${usd.toFixed(3)}`);\n return chalk.yellow(`$${usd.toFixed(2)}`);\n}\n\n/**\n * Format duration in human-readable form\n */\nexport function formatDuration(ms: number): string {\n if (ms === 0) return chalk.dim(\"—\");\n const seconds = Math.round(ms / 1000);\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = seconds % 60;\n if (minutes < 60) return `${minutes}m ${remainingSeconds}s`;\n const hours = Math.floor(minutes / 60);\n const remainingMinutes = minutes % 60;\n return `${hours}h ${remainingMinutes}m`;\n}\n\nexport function formatRole(role: string): string {\n switch (role) {\n case \"human\":\n return chalk.blue.bold(\"You\");\n case \"assistant\":\n return chalk.gray.bold(\"Claude\");\n case \"tool_use\":\n return chalk.green.bold(\"Tool\");\n case \"tool_result\":\n return chalk.yellow.bold(\"Result\");\n default:\n return chalk.dim(role);\n }\n}\n\nexport function formatNumber(n: number): string {\n return n.toLocaleString();\n}\n\n// ── Styled printing ─────────────────────────────────────\n\nexport function printHeader(title: string): void {\n console.log();\n console.log(chalk.bold.white(title));\n console.log(chalk.dim(\"─\".repeat(Math.min(title.length + 10, 60))));\n}\n\nexport function printSuccess(msg: string): void {\n console.log(chalk.green(\" ✓ \") + msg);\n}\n\nexport function printWarn(msg: string): void {\n console.log(chalk.yellow(\" ⚠ \") + msg);\n}\n\nexport function printError(msg: string): void {\n console.log(chalk.red(\" ✗ \") + msg);\n}\n\nexport function printInfo(msg: string): void {\n console.log(chalk.cyan(\" ℹ \") + msg);\n}\n\n/**\n * Render a compact stat badge: \" label value\"\n */\nexport function statBadge(\n label: string,\n value: string | number,\n color: typeof chalk = chalk\n): string {\n return (\n chalk.dim(\" \") +\n chalk.dim(label + \" \") +\n color(typeof value === \"number\" ? formatNumber(value) : value)\n );\n}\n","import { existsSync } from \"fs\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { initConfig, isInitialized, loadConfig } from \"../core/config.js\";\nimport { getClaudeProjectsDir, getDevlogDir } from \"../utils/paths.js\";\nimport { discoverProjects, computeStats } from \"../core/discovery.js\";\nimport {\n printSuccess,\n printError,\n formatNumber,\n} from \"../utils/format.js\";\n\nexport async function initCommand(): Promise {\n console.log();\n console.log(\n chalk.bold.cyan(\" ▌\") + chalk.bold.white(\" DevLog Setup\")\n );\n console.log();\n\n // Already initialized\n if (isInitialized()) {\n const config = loadConfig();\n printSuccess(\"DevLog is already set up\");\n console.log(\n chalk.dim(\" Config: \") + chalk.white(getDevlogDir() + \"/config.toml\")\n );\n console.log(\n chalk.dim(\" Claude: \") + chalk.white(config.claudeDir)\n );\n console.log();\n\n const spinner = ora({\n text: chalk.dim(\" Checking your sessions...\"),\n spinner: \"dots\",\n color: \"cyan\",\n }).start();\n\n const projects = await discoverProjects(config.claudeDir);\n const stats = computeStats(projects);\n spinner.stop();\n\n renderStatsBox(stats);\n\n console.log(\n chalk.dim(\" Everything looks good! Run \") +\n chalk.cyan(\"devlog\") +\n chalk.dim(\" to see your dashboard.\")\n );\n console.log();\n return;\n }\n\n // Check Claude Code\n const claudeDir = getClaudeProjectsDir();\n\n if (!existsSync(claudeDir)) {\n printError(\"Claude Code not found\");\n console.log();\n console.log(\n chalk.white(\" I looked for: \") + chalk.dim(\"~/.claude/projects/\")\n );\n console.log();\n console.log(chalk.white(\" To get started:\"));\n console.log(\n chalk.dim(\" 1. Install Claude Code → \") + chalk.cyan(\"https://claude.ai/code\")\n );\n console.log(\n chalk.dim(\" 2. Have a conversation with Claude in any project\")\n );\n console.log(\n chalk.dim(\" 3. Come back and run \") + chalk.cyan(\"devlog\") + chalk.dim(\" again\")\n );\n console.log();\n return;\n }\n\n printSuccess(\"Found Claude Code\");\n\n const config = initConfig();\n printSuccess(\"Created config at \" + chalk.dim(\"~/.devlog/\"));\n\n const spinner = ora({\n text: chalk.dim(\" Scanning your Claude Code history...\"),\n spinner: \"dots\",\n color: \"cyan\",\n }).start();\n\n const projects = await discoverProjects(config.claudeDir);\n const stats = computeStats(projects);\n spinner.stop();\n\n printSuccess(\"Scan complete\");\n console.log();\n\n renderStatsBox(stats);\n\n console.log(\n chalk.white(\" You're all set! Run \") +\n chalk.cyan.bold(\"devlog\") +\n chalk.white(\" to see your dashboard.\")\n );\n console.log();\n}\n\nfunction renderStatsBox(stats: ReturnType): void {\n const w = 42;\n const line = (label: string, value: string) => {\n const padding = w - label.length - value.length - 6;\n return (\n chalk.dim(\" │ \") +\n chalk.white(label) +\n \" \".repeat(Math.max(1, padding)) +\n chalk.cyan.bold(value) +\n chalk.dim(\" │\")\n );\n };\n\n console.log(chalk.dim(\" ┌\" + \"─\".repeat(w) + \"┐\"));\n console.log(line(\"Projects\", formatNumber(stats.totalProjects)));\n console.log(line(\"Conversations\", formatNumber(stats.totalSessions)));\n console.log(line(\"Messages\", formatNumber(stats.totalMessages)));\n console.log(line(\"Commands run\", formatNumber(stats.totalToolCalls)));\n console.log(\n line(\"Files touched\", formatNumber(stats.allFilesReferenced.length))\n );\n if (stats.totalCostUSD > 0) {\n console.log(line(\"Total cost\", `$${stats.totalCostUSD.toFixed(3)}`));\n }\n console.log(chalk.dim(\" └\" + \"─\".repeat(w) + \"┘\"));\n console.log();\n}\n","import chalk from \"chalk\";\nimport ora from \"ora\";\nimport { ensureInit } from \"../core/config.js\";\nimport { discoverProjects, computeStats } from \"../core/discovery.js\";\nimport type { Session } from \"../core/types.js\";\nimport {\n printWarn,\n formatSmartTime,\n formatCost,\n truncate,\n formatNumber,\n} from \"../utils/format.js\";\n\ninterface SessionsOptions {\n project?: string;\n limit?: string;\n all?: boolean;\n}\n\nexport async function sessionsCommand(options: SessionsOptions): Promise {\n const { config } = ensureInit();\n const limit = options.all ? Infinity : parseInt(options.limit || \"30\", 10);\n\n const spinner = ora({\n text: chalk.dim(\" Scanning sessions...\"),\n spinner: \"dots\",\n color: \"cyan\",\n }).start();\n\n const projects = await discoverProjects(config.claudeDir);\n const stats = computeStats(projects);\n spinner.stop();\n\n if (projects.length === 0) {\n console.log();\n console.log(chalk.white(\" No sessions found yet.\"));\n console.log(\n chalk.dim(\" Start a Claude Code session in any project, then come back!\")\n );\n console.log();\n return;\n }\n\n // Filter by project if specified\n const filteredProjects = options.project\n ? projects.filter(\n (p) =>\n p.name.toLowerCase().includes(options.project!.toLowerCase()) ||\n p.path.toLowerCase().includes(options.project!.toLowerCase())\n )\n : projects;\n\n if (filteredProjects.length === 0) {\n console.log();\n console.log(\n chalk.yellow(\" No projects matching \") +\n chalk.white(`\"${options.project}\"`)\n );\n console.log();\n console.log(chalk.white(\" Your projects:\"));\n for (const p of projects) {\n console.log(\n chalk.cyan(` ${p.name}`) +\n chalk.dim(` — ${p.sessionCount} session${p.sessionCount === 1 ? \"\" : \"s\"}`)\n );\n }\n console.log();\n console.log(\n chalk.dim(\" Try: \") +\n chalk.cyan(`devlog sessions -p ${projects[0].name}`)\n );\n console.log();\n return;\n }\n\n // Header\n console.log();\n console.log(\n chalk.bold.cyan(\" ▌\") +\n chalk.bold.white(\" All Sessions\") +\n (options.project ? chalk.dim(` matching \"${options.project}\"`) : \"\")\n );\n console.log(\n chalk.dim(\n ` ${formatNumber(stats.totalProjects)} projects · ${formatNumber(stats.totalSessions)} conversations · ${formatNumber(stats.totalMessages)} messages`\n )\n );\n console.log();\n\n let displayedCount = 0;\n let sessionIndex = 0;\n\n for (const project of filteredProjects) {\n if (displayedCount >= limit) break;\n\n // Project header\n const sessionWord = project.sessionCount === 1 ? \"session\" : \"sessions\";\n console.log(\n chalk.bold.white(\" 📁 \" + project.name) +\n chalk.dim(` — ${project.sessionCount} ${sessionWord}`)\n );\n console.log(chalk.dim(\" \" + project.path));\n console.log();\n\n const sessionsToShow = project.sessions.slice(0, limit - displayedCount);\n\n for (const session of sessionsToShow) {\n sessionIndex++;\n renderSessionRow(session, sessionIndex);\n displayedCount++;\n }\n\n if (project.sessions.length > sessionsToShow.length) {\n const remaining = project.sessions.length - sessionsToShow.length;\n console.log(\n chalk.dim(` + ${remaining} more — use `) +\n chalk.cyan(\"--all\") +\n chalk.dim(\" to show all\")\n );\n console.log();\n }\n }\n\n // Footer\n console.log(chalk.dim(\" \" + \"─\".repeat(60)));\n console.log();\n console.log(\n chalk.dim(\" \") +\n chalk.cyan(\"devlog show 1\") +\n chalk.dim(\" view most recent · \") +\n chalk.cyan(\"devlog sessions -p \") +\n chalk.dim(\" filter\")\n );\n console.log();\n}\n\nfunction renderSessionRow(session: Session, index: number): void {\n const m = session.meta;\n const time = formatSmartTime(session.updatedAt);\n const preview = m.firstUserMessage\n ? truncate(m.firstUserMessage.replace(/\\n/g, \" \").trim(), 46)\n : chalk.dim(\"(empty)\");\n\n // Line 1: index + time + first message\n const indexStr = chalk.dim(`${index}.`.padEnd(4));\n console.log(\n chalk.dim(\" \") + indexStr + chalk.white(time.padEnd(16)) + chalk.white(preview)\n );\n\n // Line 2: human-readable activity summary\n const parts: string[] = [];\n const turns = m.humanTurns + m.assistantTurns;\n\n if (turns <= 4) {\n parts.push(chalk.dim(\"quick chat\"));\n } else {\n parts.push(chalk.dim(`${turns} messages`));\n }\n\n if (m.toolCalls > 0) {\n parts.push(chalk.green(`ran ${m.toolCalls} commands`));\n }\n if (m.filesReferenced.length > 0) {\n const fileWord = m.filesReferenced.length === 1 ? \"file\" : \"files\";\n parts.push(chalk.blue(`wrote ${m.filesReferenced.length} ${fileWord}`));\n }\n if (m.totalCostUSD > 0) {\n parts.push(formatCost(m.totalCostUSD));\n }\n if (m.errorCount > 0) {\n parts.push(chalk.red(`${m.errorCount} error${m.errorCount === 1 ? \"\" : \"s\"}`));\n }\n\n console.log(\n chalk.dim(\" \") + \" \" + \" \".repeat(16) + parts.join(chalk.dim(\" · \"))\n );\n console.log();\n}\n","import chalk from \"chalk\";\nimport ora from \"ora\";\nimport { ensureInit } from \"../core/config.js\";\nimport { discoverProjects } from \"../core/discovery.js\";\nimport { parseSessionFile } from \"../core/parser.js\";\nimport type { Session, DevLogEvent } from \"../core/types.js\";\nimport {\n formatSmartTime,\n formatCost,\n formatDuration,\n truncate,\n} from \"../utils/format.js\";\n\ninterface ShowOptions {\n limit?: string;\n}\n\n/**\n * `devlog show ` — view a full conversation.\n * Accepts partial session IDs (first 6+ chars) for convenience.\n * Also accepts a number (1, 2, 3...) to pick from the most recent sessions.\n */\nexport async function showCommand(\n sessionRef: string,\n options: ShowOptions\n): Promise {\n const { config } = ensureInit();\n const limit = parseInt(options.limit || \"50\", 10);\n\n const spinner = ora({\n text: chalk.dim(\" Finding session...\"),\n spinner: \"dots\",\n color: \"cyan\",\n }).start();\n\n const projects = await discoverProjects(config.claudeDir);\n spinner.stop();\n\n // Flatten all sessions\n const allSessions: Session[] = [];\n for (const project of projects) {\n allSessions.push(...project.sessions);\n }\n allSessions.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());\n\n if (allSessions.length === 0) {\n console.log();\n console.log(chalk.yellow(\" No sessions found.\"));\n console.log();\n return;\n }\n\n // Find the session — by number index or by ID prefix\n let session: Session | undefined;\n\n const asNumber = parseInt(sessionRef, 10);\n if (!isNaN(asNumber) && asNumber >= 1 && asNumber <= allSessions.length) {\n // Numeric reference: \"devlog show 1\" = most recent session\n session = allSessions[asNumber - 1];\n } else {\n // ID prefix match\n const ref = sessionRef.toLowerCase();\n session = allSessions.find((s) =>\n s.id.toLowerCase().startsWith(ref)\n );\n }\n\n if (!session) {\n console.log();\n console.log(\n chalk.yellow(\" Couldn't find a session matching: \") +\n chalk.white(sessionRef)\n );\n console.log();\n console.log(chalk.dim(\" Recent sessions:\"));\n const recent = allSessions.slice(0, 5);\n recent.forEach((s, i) => {\n const preview = s.meta.firstUserMessage\n ? truncate(s.meta.firstUserMessage.replace(/\\n/g, \" \").trim(), 40)\n : \"(empty)\";\n console.log(\n chalk.cyan(` ${i + 1}. `) +\n chalk.white(s.projectName.padEnd(14)) +\n chalk.dim(preview) +\n chalk.dim(` [${s.id.slice(0, 8)}]`)\n );\n });\n console.log();\n console.log(\n chalk.dim(\" Use \") +\n chalk.cyan(\"devlog show 1\") +\n chalk.dim(\" to view the most recent session.\")\n );\n console.log();\n return;\n }\n\n // Parse the full session\n const parseSpinner = ora({\n text: chalk.dim(\" Reading conversation...\"),\n spinner: \"dots\",\n color: \"cyan\",\n }).start();\n\n const events = await parseSessionFile(session.filePath, session.id);\n parseSpinner.stop();\n\n // Render\n renderSessionHeader(session);\n renderConversation(events, limit);\n renderSessionFooter(session, events, limit);\n}\n\nfunction renderSessionHeader(session: Session): void {\n const m = session.meta;\n console.log();\n console.log(\n chalk.bold.cyan(\" ▌\") +\n chalk.bold.white(` ${session.projectName}`) +\n chalk.dim(\" · \") +\n chalk.dim(formatSmartTime(session.updatedAt))\n );\n console.log();\n\n // Session summary in plain language\n const parts: string[] = [];\n const turns = m.humanTurns + m.assistantTurns;\n parts.push(`${turns} messages`);\n\n if (m.toolCalls > 0) {\n parts.push(`${m.toolCalls} commands run`);\n }\n if (m.filesReferenced.length > 0) {\n parts.push(`${m.filesReferenced.length} files touched`);\n }\n if (m.totalCostUSD > 0) {\n parts.push(`$${m.totalCostUSD.toFixed(3)} cost`);\n }\n\n console.log(chalk.dim(\" \" + parts.join(\" · \")));\n\n // Show which tools were used\n if (m.uniqueTools.length > 0) {\n console.log(\n chalk.dim(\" Tools: \") +\n m.uniqueTools.map((t) => chalk.green(t)).join(chalk.dim(\", \"))\n );\n }\n\n console.log();\n console.log(chalk.dim(\" \" + \"─\".repeat(60)));\n console.log();\n}\n\nfunction renderConversation(events: DevLogEvent[], limit: number): void {\n let shown = 0;\n\n for (const event of events) {\n if (shown >= limit) break;\n\n if (event.role === \"human\" && event.type === \"text\") {\n // User message\n console.log(\n chalk.blue.bold(\" You: \") +\n chalk.white(truncate(event.content.trim(), 76))\n );\n console.log();\n shown++;\n } else if (event.role === \"assistant\" && event.type === \"text\") {\n // Claude's response — show first few lines\n const lines = event.content.trim().split(\"\\n\");\n const preview = lines.slice(0, 3);\n console.log(chalk.dim.bold(\" Claude: \"));\n for (const line of preview) {\n console.log(chalk.dim(\" \") + chalk.white(truncate(line, 74)));\n }\n if (lines.length > 3) {\n console.log(chalk.dim(` ... (${lines.length - 3} more lines)`));\n }\n console.log();\n shown++;\n } else if (event.role === \"tool_use\") {\n // Tool call — compact display\n console.log(\n chalk.green(\" ⚡ \") +\n chalk.green(event.toolName || \"tool\") +\n chalk.dim(\" \") +\n chalk.dim(truncate(event.content.replace(event.toolName + \"(\", \"\").replace(/\\)$/, \"\"), 60))\n );\n shown++;\n } else if (event.role === \"tool_result\") {\n // Tool result — very compact\n if (event.isError) {\n console.log(\n chalk.red(\" ✗ Error: \") +\n chalk.dim(truncate(event.content, 60))\n );\n } else {\n const resultPreview = event.content.trim();\n if (resultPreview.length > 0 && resultPreview.length < 80) {\n console.log(\n chalk.dim(\" → \") +\n chalk.dim(truncate(resultPreview, 70))\n );\n }\n }\n // Don't increment shown for tool results (they're part of the tool call)\n }\n }\n}\n\nfunction renderSessionFooter(\n session: Session,\n events: DevLogEvent[],\n limit: number\n): void {\n const totalEvents = events.filter(\n (e) =>\n (e.role === \"human\" && e.type === \"text\") ||\n (e.role === \"assistant\" && e.type === \"text\") ||\n e.role === \"tool_use\"\n ).length;\n\n console.log();\n console.log(chalk.dim(\" \" + \"─\".repeat(60)));\n\n if (totalEvents > limit) {\n console.log(\n chalk.dim(` Showing ${limit} of ${totalEvents} events. Use `) +\n chalk.cyan(`devlog show ${session.id.slice(0, 8)} -n ${totalEvents}`) +\n chalk.dim(\" to see all.\")\n );\n }\n\n console.log(\n chalk.dim(\" Session ID: \") + chalk.dim(session.id)\n );\n console.log();\n}\n"],"mappings":"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","names":["chalk","chalk","existsSync","join","join","join","existsSync","existsSync","join","dayjs","existsSync","chalk","existsSync","chalk","ora","chalk","config","spinner","ora","projects","stats","existsSync","chalk","ora","ora","chalk","chalk","ora","ora","chalk","chalk"]} \ No newline at end of file +{"version":3,"sources":["../src/cli.ts","../src/commands/dashboard.ts","../src/core/config.ts","../src/utils/paths.ts","../src/core/discovery.ts","../src/core/parser.ts","../src/core/pricing.ts","../src/utils/format.ts","../src/utils/output.ts","../src/commands/shared.ts","../src/commands/init.ts","../src/commands/sessions.ts","../src/commands/show.ts","../src/commands/today.ts","../src/commands/search.ts","../src/commands/stats.ts","../src/commands/cost.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { dashboardCommand } from \"./commands/dashboard.js\";\nimport { initCommand } from \"./commands/init.js\";\nimport { sessionsCommand } from \"./commands/sessions.js\";\nimport { showCommand } from \"./commands/show.js\";\nimport { todayCommand } from \"./commands/today.js\";\nimport { searchCommand } from \"./commands/search.js\";\nimport { statsCommand } from \"./commands/stats.js\";\nimport { costCommand } from \"./commands/cost.js\";\nimport { initOutput, outputJson } from \"./utils/output.js\";\nimport { levenshtein } from \"./utils/format.js\";\nimport type { GlobalOptions } from \"./core/types.js\";\n\nconst VERSION = \"0.3.0\";\n\nconst HELP_TEXT = `\n${chalk.bold.cyan(\" ▌\")} ${chalk.bold.white(\"DevLog\")} ${chalk.dim(`v${VERSION}`)}\n${chalk.dim(\" Your Claude Code work journal\")}\n\n${chalk.bold.white(\" Quick Start:\")}\n${chalk.dim(\" Just run\")} ${chalk.cyan(\"devlog\")} ${chalk.dim(\"— that's it. No setup needed.\")}\n\n${chalk.bold.white(\" Examples:\")}\n${chalk.cyan(\" devlog\")}${chalk.dim(\" See your dashboard\")}\n${chalk.cyan(\" devlog today\")}${chalk.dim(\" What did I do today?\")}\n${chalk.cyan(\" devlog sessions\")}${chalk.dim(\" Browse all sessions by project\")}\n${chalk.cyan(\" devlog sessions -p chatbot\")}${chalk.dim(\" Filter to a specific project\")}\n${chalk.cyan(\" devlog show 1\")}${chalk.dim(\" View your most recent conversation\")}\n${chalk.cyan(\" devlog show 1 --summary\")}${chalk.dim(\" Quick narrative summary\")}\n${chalk.cyan(\" devlog show abc123\")}${chalk.dim(\" View a specific session by ID\")}\n${chalk.cyan(\" devlog search \\\"auth bug\\\"\")}${chalk.dim(\" Find a conversation\")}\n${chalk.cyan(\" devlog stats\")}${chalk.dim(\" Usage trends\")}\n${chalk.cyan(\" devlog cost\")}${chalk.dim(\" Cost breakdown\")}\n\n${chalk.bold.white(\" Output Modes:\")}\n${chalk.cyan(\" devlog --json\")}${chalk.dim(\" JSON output for scripts/agents\")}\n${chalk.cyan(\" devlog -q\")}${chalk.dim(\" Quiet mode (no spinners/banners)\")}\n${chalk.cyan(\" devlog --no-color\")}${chalk.dim(\" Plain text, no ANSI escapes\")}\n`;\n\nconst program = new Command();\n\nconst KNOWN_COMMANDS = [\n \"init\",\n \"sessions\",\n \"show\",\n \"today\",\n \"search\",\n \"stats\",\n \"cost\",\n];\n\nfunction getGlobalOpts(): GlobalOptions {\n const opts = program.opts();\n return { json: !!opts.json, quiet: !!opts.quiet };\n}\n\nfunction handleError(err: unknown, globalOpts: GlobalOptions): never {\n const message = err instanceof Error ? err.message : String(err);\n if (globalOpts.json) {\n outputJson({ error: message });\n } else {\n console.error(\n chalk.red(\"\\n Error:\"),\n message\n );\n }\n process.exit(1);\n}\n\nprogram\n .name(\"devlog\")\n .description(\"Your Claude Code work journal — auto-generated dev logs\")\n .version(VERSION)\n .option(\"--json\", \"Output as JSON for scripts and agents\")\n .option(\"-q, --quiet\", \"Suppress non-essential output\")\n .option(\"--no-color\", \"Disable colored output\")\n .addHelpText(\"before\", HELP_TEXT);\n\n// Initialize output context before every command\nprogram.hook(\"preAction\", () => {\n const opts = program.opts();\n if (opts.color === false) {\n process.env.NO_COLOR = \"1\";\n }\n initOutput({ json: !!opts.json, quiet: !!opts.quiet });\n});\n\n// ── Default: `devlog` with no args → dashboard ──────────\nprogram.action(async () => {\n const globalOpts = getGlobalOpts();\n try {\n await dashboardCommand(globalOpts);\n } catch (err) {\n handleError(err, globalOpts);\n }\n});\n\n// ── devlog init ──────────────────────────────────────────\nprogram\n .command(\"init\")\n .description(\"Set up DevLog (usually auto-detected, you rarely need this)\")\n .action(async () => {\n const globalOpts = getGlobalOpts();\n try {\n await initCommand(globalOpts);\n } catch (err) {\n handleError(err, globalOpts);\n }\n });\n\n// ── devlog sessions ──────────────────────────────────────\nprogram\n .command(\"sessions\")\n .description(\"Browse all sessions grouped by project\")\n .option(\"-p, --project \", \"Filter by project name (fuzzy match)\")\n .option(\"-n, --limit \", \"Max sessions to display\", \"30\")\n .option(\"-a, --all\", \"Show all sessions\")\n .action(async (options) => {\n const globalOpts = getGlobalOpts();\n try {\n await sessionsCommand(options, globalOpts);\n } catch (err) {\n handleError(err, globalOpts);\n }\n });\n\n// ── devlog show ────────────────────────────────\nprogram\n .command(\"show \")\n .description(\"View a full conversation (use a number like 1, 2, 3 or a session ID)\")\n .option(\"-n, --limit \", \"Max events to display\", \"50\")\n .option(\"-s, --summary\", \"Show a narrative summary instead of the full conversation\")\n .action(async (sessionRef: string, options) => {\n const globalOpts = getGlobalOpts();\n try {\n await showCommand(sessionRef, options, globalOpts);\n } catch (err) {\n handleError(err, globalOpts);\n }\n });\n\n// ── devlog today ─────────────────────────────────────────\nprogram\n .command(\"today\")\n .description(\"What did I do today?\")\n .action(async () => {\n const globalOpts = getGlobalOpts();\n try {\n await todayCommand(globalOpts);\n } catch (err) {\n handleError(err, globalOpts);\n }\n });\n\n// ── devlog search ────────────────────────────────\nprogram\n .command(\"search \")\n .description(\"Search sessions by message, project, or tool name\")\n .action(async (query: string) => {\n const globalOpts = getGlobalOpts();\n try {\n await searchCommand(query, globalOpts);\n } catch (err) {\n handleError(err, globalOpts);\n }\n });\n\n// ── devlog stats ─────────────────────────────────────────\nprogram\n .command(\"stats\")\n .description(\"Aggregated usage statistics\")\n .option(\"--period \", \"Filter: today, week, month, all\", \"all\")\n .action(async (options) => {\n const globalOpts = getGlobalOpts();\n try {\n await statsCommand(options, globalOpts);\n } catch (err) {\n handleError(err, globalOpts);\n }\n });\n\n// ── devlog cost ──────────────────────────────────────────\nprogram\n .command(\"cost\")\n .description(\"Cost breakdown by project and model\")\n .option(\"--period \", \"Filter: today, week, month, all\", \"all\")\n .action(async (options) => {\n const globalOpts = getGlobalOpts();\n try {\n await costCommand(options, globalOpts);\n } catch (err) {\n handleError(err, globalOpts);\n }\n });\n\n// ── \"Did you mean?\" for unknown commands (Principle 3) ───\n// Commander treats unknown words as args to default command.\n// We intercept by checking process.argv before parse.\nconst userArgs = process.argv.slice(2).filter((a) => !a.startsWith(\"-\"));\nif (userArgs.length === 1 && !KNOWN_COMMANDS.includes(userArgs[0])) {\n // Check if it looks like a mistyped command (not a session ID or number)\n const candidate = userArgs[0];\n const isNumber = /^\\d+$/.test(candidate);\n const isSessionId = /^[0-9a-f]{6,}$/i.test(candidate);\n\n if (!isNumber && !isSessionId) {\n let suggestion = \"\";\n let bestDist = Infinity;\n\n for (const cmd of KNOWN_COMMANDS) {\n const dist = levenshtein(candidate, cmd);\n if (dist < bestDist) {\n bestDist = dist;\n suggestion = cmd;\n }\n }\n\n if (bestDist <= 3) {\n console.error();\n console.error(\n chalk.yellow(` Unknown command: ${candidate}`)\n );\n console.error(\n chalk.dim(\" Did you mean: \") + chalk.cyan(suggestion) + chalk.dim(\"?\")\n );\n console.error(\n chalk.dim(\" Run \") +\n chalk.cyan(\"devlog --help\") +\n chalk.dim(\" to see all commands.\")\n );\n console.error();\n process.exit(1);\n }\n }\n}\n\nprogram.parse();\n","import chalk from \"chalk\";\nimport ora from \"ora\";\nimport { existsSync } from \"fs\";\nimport { ensureInit } from \"../core/config.js\";\nimport {\n discoverProjects,\n computeStats,\n groupSessionsByTime,\n} from \"../core/discovery.js\";\nimport type { Session, AggregateStats, GlobalOptions, DashboardJson } from \"../core/types.js\";\nimport {\n formatSmartTime,\n formatNumber,\n truncate,\n costWithContext,\n messageCountContext,\n toolCountContext,\n fileCountContext,\n} from \"../utils/format.js\";\nimport { getClaudeProjectsDir } from \"../utils/paths.js\";\nimport { outputJson, isJsonMode, isQuietMode } from \"../utils/output.js\";\nimport { toSessionJson } from \"./shared.js\";\n\nconst VERSION = \"0.3.0\";\n\n/**\n * The default command. This IS the product.\n * Run `devlog` → see your world with Claude.\n */\nexport async function dashboardCommand(globalOpts: GlobalOptions): Promise {\n const { config, isFirstRun } = ensureInit();\n\n // ── No Claude Code installed ──────────────────────\n if (!existsSync(config.claudeDir)) {\n if (isJsonMode()) {\n outputJson({ error: \"Claude Code not found\", path: getClaudeProjectsDir() });\n process.exit(1);\n }\n console.log();\n console.log(\n chalk.bold.cyan(\" ▌\") + chalk.bold.white(\" DevLog\")\n );\n console.log();\n console.log(\n chalk.white(\" Hmm, I can't find Claude Code on this machine.\")\n );\n console.log(\n chalk.dim(\" I looked for: \") + chalk.white(getClaudeProjectsDir())\n );\n console.log();\n console.log(\n chalk.white(\" To get started:\")\n );\n console.log(\n chalk.dim(\" 1. Install Claude Code → \") + chalk.cyan(\"https://claude.ai/code\")\n );\n console.log(\n chalk.dim(\" 2. Have a conversation with Claude in any project\")\n );\n console.log(\n chalk.dim(\" 3. Come back and run \") + chalk.cyan(\"devlog\") + chalk.dim(\" again\")\n );\n console.log();\n return;\n }\n\n // ── Scan ──────────────────────────────────────────\n let spinner: ReturnType | null = null;\n if (!isJsonMode() && !isQuietMode()) {\n spinner = ora({\n text: chalk.dim(\" Reading your Claude Code history...\"),\n spinner: \"dots\",\n color: \"cyan\",\n stream: process.stderr,\n }).start();\n }\n\n const projects = await discoverProjects(config.claudeDir, (msg) => {\n if (spinner) spinner.text = chalk.dim(` ${msg}`);\n });\n\n spinner?.stop();\n\n // ── No sessions yet ───────────────────────────────\n if (projects.length === 0) {\n if (isJsonMode()) {\n outputJson({ version: VERSION, timestamp: new Date().toISOString(), summary: \"No sessions found\", stats: { totalProjects: 0, totalSessions: 0, totalToolCalls: 0, totalFilesTouched: 0, totalCostUSD: 0, todaySessions: 0, todayCostUSD: 0 }, recentSessions: [] });\n return;\n }\n console.log();\n console.log(\n chalk.bold.cyan(\" ▌\") + chalk.bold.white(\" DevLog\")\n );\n console.log();\n console.log(\n chalk.white(\" No conversations found yet.\")\n );\n console.log(\n chalk.dim(\" Start a Claude Code session in any project, then come back!\")\n );\n console.log();\n return;\n }\n\n const stats = computeStats(projects);\n const groups = groupSessionsByTime(projects);\n\n // ── JSON output ────────────────────────────────────\n if (isJsonMode()) {\n const allSessions = projects.flatMap((p) => p.sessions);\n allSessions.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());\n const recent = allSessions.slice(0, 10);\n\n const projectWord = stats.totalProjects === 1 ? \"project\" : \"projects\";\n const sessionWord = stats.totalSessions === 1 ? \"conversation\" : \"conversations\";\n\n const data: DashboardJson = {\n version: VERSION,\n timestamp: new Date().toISOString(),\n summary: `You and Claude had ${stats.totalSessions} ${sessionWord} across ${stats.totalProjects} ${projectWord}`,\n stats: {\n totalProjects: stats.totalProjects,\n totalSessions: stats.totalSessions,\n totalToolCalls: stats.totalToolCalls,\n totalFilesTouched: stats.allFilesReferenced.length,\n totalCostUSD: Math.round(stats.totalCostUSD * 1000000) / 1000000,\n todaySessions: stats.todaySessions,\n todayCostUSD: Math.round(stats.todayCostUSD * 1000000) / 1000000,\n },\n recentSessions: recent.map(toSessionJson),\n };\n outputJson(data);\n return;\n }\n\n // ── Render ────────────────────────────────────────\n console.log();\n\n if (!isQuietMode()) {\n if (isFirstRun) {\n renderWelcome(stats);\n } else {\n renderBanner();\n }\n }\n\n renderNarrativeStats(stats);\n renderSessionGroups(groups, stats);\n\n if (!isQuietMode()) {\n renderNextSteps(stats, isFirstRun);\n }\n}\n\n// ─────────────────────────────────────────────────────\n// Render helpers\n// ─────────────────────────────────────────────────────\n\nfunction renderWelcome(stats: AggregateStats): void {\n console.log(\n chalk.bold.cyan(\" ▌\") + chalk.bold.white(\" Welcome to DevLog!\")\n );\n console.log();\n console.log(\n chalk.white(\" I found your Claude Code history — let me show you what\") +\n chalk.white(\" you've been building.\")\n );\n console.log();\n}\n\nfunction renderBanner(): void {\n console.log(\n chalk.bold.cyan(\" ▌\") +\n chalk.bold.white(\" DevLog\")\n );\n console.log();\n}\n\nfunction renderNarrativeStats(stats: AggregateStats): void {\n const projectWord = stats.totalProjects === 1 ? \"project\" : \"projects\";\n const sessionWord = stats.totalSessions === 1 ? \"conversation\" : \"conversations\";\n\n console.log(\n chalk.dim(\" \") +\n chalk.white(\"You and Claude had \") +\n chalk.cyan.bold(String(stats.totalSessions)) +\n chalk.white(` ${sessionWord} across `) +\n chalk.cyan.bold(String(stats.totalProjects)) +\n chalk.white(` ${projectWord}.`)\n );\n\n const activityParts: string[] = [];\n\n if (stats.totalToolCalls > 0) {\n activityParts.push(\n chalk.white(\"Claude ran \") +\n chalk.green.bold(formatNumber(stats.totalToolCalls)) +\n chalk.white(\" commands\")\n );\n }\n\n if (stats.allFilesReferenced.length > 0) {\n activityParts.push(\n chalk.white(\"touched \") +\n chalk.blue.bold(String(stats.allFilesReferenced.length)) +\n chalk.white(\" files\")\n );\n }\n\n if (activityParts.length > 0) {\n console.log(chalk.dim(\" \") + activityParts.join(chalk.dim(\" and \")));\n }\n\n if (stats.totalCostUSD > 0) {\n const costStr = stats.totalCostUSD < 1\n ? `$${stats.totalCostUSD.toFixed(3)}`\n : `$${stats.totalCostUSD.toFixed(2)}`;\n\n let costContext = \"\";\n if (stats.totalCostUSD < 0.10) costContext = \" — less than a cup of coffee\";\n else if (stats.totalCostUSD < 1) costContext = \" — pretty efficient\";\n else if (stats.totalCostUSD < 5) costContext = \" — solid investment\";\n\n console.log(\n chalk.dim(\" \") +\n chalk.white(\"Total cost: \") +\n chalk.yellow.bold(costStr) +\n chalk.dim(costContext)\n );\n }\n\n if (stats.todaySessions > 0) {\n const todayWord = stats.todaySessions === 1 ? \"session\" : \"sessions\";\n console.log(\n chalk.dim(\" \") +\n chalk.green(\"▸ \") +\n chalk.white.bold(`${stats.todaySessions} ${todayWord} today`) +\n (stats.todayCostUSD > 0\n ? chalk.dim(` · $${stats.todayCostUSD.toFixed(3)}`)\n : \"\")\n );\n }\n\n console.log();\n console.log(chalk.dim(\" \" + \"─\".repeat(60)));\n console.log();\n}\n\nfunction renderSessionGroups(\n groups: {\n today: Session[];\n yesterday: Session[];\n thisWeek: Session[];\n older: Session[];\n },\n stats: AggregateStats\n): void {\n const hasToday = groups.today.length > 0;\n const hasYesterday = groups.yesterday.length > 0;\n const hasThisWeek = groups.thisWeek.length > 0;\n const hasOlder = groups.older.length > 0;\n\n if (hasToday) {\n renderGroup(\"Today\", groups.today, chalk.green);\n }\n\n if (hasYesterday) {\n renderGroup(\"Yesterday\", groups.yesterday, chalk.blue);\n }\n\n if (hasThisWeek) {\n renderGroup(\"This Week\", groups.thisWeek, chalk.white);\n }\n\n if (hasOlder) {\n const count = groups.older.length;\n console.log(\n chalk.dim(` + ${count} older session${count === 1 ? \"\" : \"s\"}`)\n );\n console.log();\n }\n\n if (!hasToday && !hasYesterday && !hasThisWeek && !hasOlder) {\n console.log(chalk.dim(\" No sessions found.\"));\n console.log();\n }\n}\n\nfunction renderGroup(\n label: string,\n sessions: Session[],\n accentColor: typeof chalk\n): void {\n console.log(accentColor.bold(` ${label}`));\n console.log();\n\n for (const session of sessions) {\n renderSessionCard(session, accentColor);\n }\n}\n\nfunction renderSessionCard(session: Session, accentColor: typeof chalk): void {\n const m = session.meta;\n const time = formatSmartTime(session.updatedAt);\n const preview = m.firstUserMessage\n ? truncate(m.firstUserMessage.replace(/\\n/g, \" \").trim(), 50)\n : chalk.dim(\"(empty session)\");\n\n const timeStr = time.length > 14 ? time.slice(0, 14) : time;\n console.log(\n chalk.dim(\" \") +\n accentColor(timeStr.padEnd(16)) +\n chalk.bold.white(session.projectName.padEnd(14)) +\n chalk.white(preview)\n );\n\n const turns = m.humanTurns + m.assistantTurns;\n const parts: string[] = [];\n parts.push(messageCountContext(turns));\n if (m.toolCalls > 0) parts.push(toolCountContext(m.toolCalls));\n if (m.filesReferenced.length > 0) parts.push(fileCountContext(m.filesReferenced.length));\n if (m.totalCostUSD > 0) parts.push(costWithContext(m.totalCostUSD));\n if (m.errorCount > 0) {\n const errWord = m.errorCount === 1 ? \"error\" : \"errors\";\n parts.push(chalk.red(`${m.errorCount} ${errWord}`));\n }\n\n console.log(\n chalk.dim(\" \") +\n \" \".repeat(30) +\n parts.join(chalk.dim(\" · \"))\n );\n console.log();\n}\n\nfunction renderNextSteps(stats: AggregateStats, isFirstRun: boolean): void {\n console.log(chalk.dim(\" \" + \"─\".repeat(60)));\n console.log();\n\n if (isFirstRun) {\n console.log(chalk.white(\" What you can do next:\"));\n console.log();\n console.log(\n chalk.cyan(\" devlog today\") +\n chalk.dim(\" What did I do today?\")\n );\n console.log(\n chalk.cyan(\" devlog sessions\") +\n chalk.dim(\" Browse all sessions by project\")\n );\n console.log(\n chalk.cyan(\" devlog show \") +\n chalk.dim(\" View a full conversation\")\n );\n console.log(\n chalk.cyan(\" devlog search \\\"auth\\\"\") +\n chalk.dim(\" Find a conversation\")\n );\n console.log();\n console.log(\n chalk.dim(\" Tip: Just run \") +\n chalk.cyan(\"devlog\") +\n chalk.dim(\" anytime to see this dashboard.\")\n );\n } else {\n console.log(\n chalk.dim(\" \") +\n chalk.cyan(\"devlog today\") +\n chalk.dim(\" today · \") +\n chalk.cyan(\"devlog sessions\") +\n chalk.dim(\" all · \") +\n chalk.cyan(\"devlog show \") +\n chalk.dim(\" view · \") +\n chalk.cyan(\"devlog search\") +\n chalk.dim(\" find\")\n );\n }\n console.log();\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { join } from \"path\";\nimport toml from \"toml\";\nimport type { DevLogConfig } from \"./types.js\";\nimport {\n getClaudeProjectsDir,\n getDevlogDir,\n getConfigPath,\n} from \"../utils/paths.js\";\n\nconst DEFAULT_CONFIG = `# DevLog Configuration\n# Auto-generated by devlog\n\n[paths]\n# Claude Code projects directory\nclaude_dir = \"{{claudeDir}}\"\n\n# DevLog data directory\ndevlog_dir = \"{{devlogDir}}\"\n\n[display]\n# Maximum number of sessions to show in list\nmax_sessions = 50\n\n# Truncate message preview to this length\npreview_length = 80\n`;\n\nexport function isInitialized(): boolean {\n return existsSync(getConfigPath());\n}\n\nexport function initConfig(): DevLogConfig {\n const devlogDir = getDevlogDir();\n const claudeDir = getClaudeProjectsDir();\n const configPath = getConfigPath();\n\n mkdirSync(devlogDir, { recursive: true });\n mkdirSync(join(devlogDir, \"daily\"), { recursive: true });\n mkdirSync(join(devlogDir, \"reports\"), { recursive: true });\n mkdirSync(join(devlogDir, \"db\"), { recursive: true });\n\n const configContent = DEFAULT_CONFIG.replace(\n \"{{claudeDir}}\",\n claudeDir\n ).replace(\"{{devlogDir}}\", devlogDir);\n\n writeFileSync(configPath, configContent, \"utf-8\");\n\n return { claudeDir, devlogDir, version: \"0.1.0\" };\n}\n\n/**\n * Ensure DevLog is initialized. Returns config + whether this was the first run.\n */\nexport function ensureInit(): { config: DevLogConfig; isFirstRun: boolean } {\n if (!isInitialized()) {\n return { config: initConfig(), isFirstRun: true };\n }\n return { config: loadConfig(), isFirstRun: false };\n}\n\nexport function loadConfig(): DevLogConfig {\n const configPath = getConfigPath();\n\n if (!existsSync(configPath)) {\n return {\n claudeDir: getClaudeProjectsDir(),\n devlogDir: getDevlogDir(),\n version: \"0.1.0\",\n };\n }\n\n try {\n const raw = readFileSync(configPath, \"utf-8\");\n const parsed = toml.parse(raw);\n\n return {\n claudeDir: parsed.paths?.claude_dir || getClaudeProjectsDir(),\n devlogDir: parsed.paths?.devlog_dir || getDevlogDir(),\n version: \"0.1.0\",\n };\n } catch {\n return {\n claudeDir: getClaudeProjectsDir(),\n devlogDir: getDevlogDir(),\n version: \"0.1.0\",\n };\n }\n}\n","import { homedir } from \"os\";\nimport { join } from \"path\";\n\n/**\n * Default Claude Code projects directory\n */\nexport function getClaudeProjectsDir(): string {\n return join(homedir(), \".claude\", \"projects\");\n}\n\n/**\n * Default DevLog data directory\n */\nexport function getDevlogDir(): string {\n return join(homedir(), \".devlog\");\n}\n\n/**\n * Default DevLog config file path\n */\nexport function getConfigPath(): string {\n return join(getDevlogDir(), \"config.toml\");\n}\n\n/**\n * Decode Claude Code's path encoding:\n * -Users-dong-projects-myapp → /Users/dong/projects/myapp\n *\n * Claude Code encodes the project path by replacing '/' with '-'\n * and prepending a '-' to the path.\n */\nexport function decodePath(encoded: string): string {\n // The encoded path starts with the drive/root indicator\n // e.g., \"-Users-dong-xxx\" → \"/Users/dong/xxx\"\n // Handle both macOS and Linux paths\n if (encoded.startsWith(\"-\")) {\n return encoded.replace(/-/g, \"/\");\n }\n return encoded;\n}\n\n/**\n * Get a human-readable project name from the decoded path\n */\nexport function getProjectName(decodedPath: string): string {\n const parts = decodedPath.split(\"/\").filter(Boolean);\n // Return the last meaningful directory name\n return parts[parts.length - 1] || decodedPath;\n}\n","import { readdir, stat } from \"fs/promises\";\nimport { join } from \"path\";\nimport { existsSync } from \"fs\";\nimport type { Project, Session, AggregateStats } from \"./types.js\";\nimport {\n decodePath,\n getProjectName,\n getClaudeProjectsDir,\n} from \"../utils/paths.js\";\nimport { scanSession } from \"./parser.js\";\nimport dayjs from \"dayjs\";\n\n/**\n * Discover all Claude Code projects and sessions with rich metadata.\n * Accepts an optional progress callback for spinner updates.\n */\nexport async function discoverProjects(\n claudeDir?: string,\n onProgress?: (msg: string) => void\n): Promise {\n const projectsDir = claudeDir || getClaudeProjectsDir();\n\n if (!existsSync(projectsDir)) {\n return [];\n }\n\n const entries = await readdir(projectsDir, { withFileTypes: true });\n const projects: Project[] = [];\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n const projectDir = join(projectsDir, entry.name);\n const decodedPath = decodePath(entry.name);\n const projectName = getProjectName(decodedPath);\n\n onProgress?.(`Scanning ${projectName}...`);\n\n const sessions = await discoverSessions(\n projectDir,\n decodedPath,\n projectName\n );\n\n if (sessions.length > 0) {\n projects.push({\n path: decodedPath,\n name: projectName,\n encodedPath: entry.name,\n sessionCount: sessions.length,\n sessions,\n });\n }\n }\n\n // Sort projects by most recent session\n projects.sort((a, b) => {\n const aLatest = Math.max(\n ...a.sessions.map((s) => s.updatedAt.getTime())\n );\n const bLatest = Math.max(\n ...b.sessions.map((s) => s.updatedAt.getTime())\n );\n return bLatest - aLatest;\n });\n\n return projects;\n}\n\nasync function discoverSessions(\n projectDir: string,\n decodedPath: string,\n projectName: string\n): Promise {\n const sessions: Session[] = [];\n\n try {\n const entries = await readdir(projectDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith(\".jsonl\")) continue;\n\n const filePath = join(projectDir, entry.name);\n const sessionId = entry.name.replace(\".jsonl\", \"\");\n\n try {\n const fileStat = await stat(filePath);\n const meta = await scanSession(filePath);\n\n // Use internal timestamps when available (more accurate than fs mtime)\n const createdAt = meta.firstActivity.getTime() > 0 ? meta.firstActivity : fileStat.birthtime;\n const updatedAt = meta.lastActivity.getTime() > 0 ? meta.lastActivity : fileStat.mtime;\n\n sessions.push({\n id: sessionId,\n projectPath: decodedPath,\n projectName,\n filePath,\n createdAt,\n updatedAt,\n meta,\n });\n } catch {\n continue;\n }\n }\n } catch {\n return [];\n }\n\n sessions.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());\n return sessions;\n}\n\n/**\n * Compute aggregate stats with today-awareness for the emotional dashboard.\n */\nexport function computeStats(projects: Project[]): AggregateStats {\n const today = dayjs().startOf(\"day\");\n let totalSessions = 0;\n let totalMessages = 0;\n let totalHumanTurns = 0;\n let totalAssistantTurns = 0;\n let totalToolCalls = 0;\n let totalCostUSD = 0;\n let totalDurationMs = 0;\n let todaySessions = 0;\n let todayMessages = 0;\n let todayCostUSD = 0;\n\n const allTools = new Set();\n const allFiles = new Set();\n const allModels = new Set();\n\n let mostActiveProject = \"\";\n let mostActiveProjectSessions = 0;\n\n for (const project of projects) {\n totalSessions += project.sessionCount;\n\n if (project.sessionCount > mostActiveProjectSessions) {\n mostActiveProjectSessions = project.sessionCount;\n mostActiveProject = project.name;\n }\n\n for (const session of project.sessions) {\n const m = session.meta;\n totalMessages += m.messageCount;\n totalHumanTurns += m.humanTurns;\n totalAssistantTurns += m.assistantTurns;\n totalToolCalls += m.toolCalls;\n totalCostUSD += m.totalCostUSD;\n totalDurationMs += m.totalDurationMs;\n\n m.uniqueTools.forEach((t) => allTools.add(t));\n m.filesReferenced.forEach((f) => allFiles.add(f));\n m.models.forEach((model) => allModels.add(model));\n\n // Today tracking\n if (dayjs(session.updatedAt).isAfter(today)) {\n todaySessions++;\n todayMessages += m.messageCount;\n todayCostUSD += m.totalCostUSD;\n }\n }\n }\n\n return {\n totalProjects: projects.length,\n totalSessions,\n totalMessages,\n totalHumanTurns,\n totalAssistantTurns,\n totalToolCalls,\n totalCostUSD,\n totalDurationMs,\n uniqueToolsUsed: [...allTools],\n allFilesReferenced: [...allFiles],\n modelsUsed: [...allModels],\n todaySessions,\n todayMessages,\n todayCostUSD,\n mostActiveProject,\n mostActiveProjectSessions,\n };\n}\n\n/**\n * Group sessions by time period for smart display\n */\nexport function groupSessionsByTime(\n projects: Project[]\n): {\n today: Session[];\n yesterday: Session[];\n thisWeek: Session[];\n older: Session[];\n} {\n const now = dayjs();\n const todayStart = now.startOf(\"day\");\n const yesterdayStart = todayStart.subtract(1, \"day\");\n const weekStart = now.startOf(\"week\");\n\n const groups = {\n today: [] as Session[],\n yesterday: [] as Session[],\n thisWeek: [] as Session[],\n older: [] as Session[],\n };\n\n for (const project of projects) {\n for (const session of project.sessions) {\n const d = dayjs(session.updatedAt);\n if (d.isAfter(todayStart)) {\n groups.today.push(session);\n } else if (d.isAfter(yesterdayStart)) {\n groups.yesterday.push(session);\n } else if (d.isAfter(weekStart)) {\n groups.thisWeek.push(session);\n } else {\n groups.older.push(session);\n }\n }\n }\n\n // Sort each group by most recent first\n for (const group of Object.values(groups)) {\n group.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());\n }\n\n return groups;\n}\n","import { createReadStream } from \"fs\";\nimport { createInterface } from \"readline\";\nimport type {\n RawJsonlEvent,\n DevLogEvent,\n ContentBlock,\n TextBlock,\n ToolUseBlock,\n ToolResultBlock,\n SessionMeta,\n} from \"./types.js\";\nimport { computeCost } from \"./pricing.js\";\n\n// Types to skip entirely\nconst SKIP_TYPES = new Set([\n \"progress\",\n \"file-history-snapshot\",\n \"queue-operation\",\n]);\n\n/**\n * Resolve the effective role from the event.\n * Real Claude Code uses type:\"user\"/\"assistant\", legacy uses type:\"human\"/\"assistant\".\n */\nfunction resolveRole(event: RawJsonlEvent): \"human\" | \"assistant\" | null {\n const t = event.type;\n if (t === \"human\" || t === \"user\") return \"human\";\n if (t === \"assistant\") return \"assistant\";\n return null;\n}\n\n/**\n * Get content blocks from the event, supporting both real and legacy formats.\n * Real: event.message.content\n * Legacy: event.content\n */\nfunction getContent(event: RawJsonlEvent): ContentBlock[] | string | undefined {\n if (event.message && typeof event.message === \"object\" && event.message.content != null) {\n return event.message.content;\n }\n return event.content;\n}\n\n/**\n * Get model from the event, supporting both formats.\n */\nfunction getModel(event: RawJsonlEvent): string | undefined {\n if (event.message && typeof event.message === \"object\" && event.message.model) {\n return event.message.model;\n }\n return event.model;\n}\n\n/**\n * Rich scan: extract full metadata from a session file in a single streaming pass.\n */\nexport async function scanSession(filePath: string): Promise {\n const meta: SessionMeta = {\n messageCount: 0,\n humanTurns: 0,\n assistantTurns: 0,\n toolCalls: 0,\n uniqueTools: [],\n filesReferenced: [],\n totalCostUSD: 0,\n totalDurationMs: 0,\n models: [],\n firstUserMessage: \"\",\n lastActivity: new Date(0),\n firstActivity: new Date(),\n errorCount: 0,\n costByModel: {},\n };\n\n const toolSet = new Set();\n const fileSet = new Set();\n const modelSet = new Set();\n\n const rl = createInterface({\n input: createReadStream(filePath, { encoding: \"utf-8\" }),\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n if (!line.trim()) continue;\n\n try {\n const event = JSON.parse(line) as RawJsonlEvent;\n\n if (SKIP_TYPES.has(event.type)) continue;\n\n const ts = event.timestamp ? new Date(event.timestamp) : null;\n\n if (ts) {\n if (ts < meta.firstActivity) meta.firstActivity = ts;\n if (ts > meta.lastActivity) meta.lastActivity = ts;\n }\n\n const role = resolveRole(event);\n\n // Count messages\n if (role === \"human\") {\n meta.humanTurns++;\n meta.messageCount++;\n }\n if (role === \"assistant\") {\n meta.assistantTurns++;\n meta.messageCount++;\n }\n\n // First user message\n if (role === \"human\" && !meta.firstUserMessage) {\n meta.firstUserMessage = extractTextContent(event);\n }\n\n // Cost: compute from usage tokens (costUSD is null in real data)\n const model = getModel(event);\n const usage = event.message && typeof event.message === \"object\" ? event.message.usage : undefined;\n if (model && usage) {\n const cost = computeCost(model, usage);\n meta.totalCostUSD += cost;\n meta.costByModel[model] = (meta.costByModel[model] || 0) + cost;\n } else if (event.costUSD && typeof event.costUSD === \"number\") {\n // Legacy fallback\n meta.totalCostUSD += event.costUSD;\n const m = model || \"unknown\";\n meta.costByModel[m] = (meta.costByModel[m] || 0) + event.costUSD;\n }\n\n // Duration from system turn_duration events\n if (event.type === \"system\" && event.subtype === \"turn_duration\") {\n const dur = (event as Record).durationMs;\n if (typeof dur === \"number\") {\n meta.totalDurationMs += dur;\n }\n }\n if (event.durationMs && typeof event.durationMs === \"number\") {\n meta.totalDurationMs += event.durationMs;\n }\n\n // Model tracking\n if (model) {\n modelSet.add(model);\n }\n\n // Tool call extraction from content blocks\n const content = getContent(event);\n if (Array.isArray(content)) {\n for (const block of content as ContentBlock[]) {\n if (block.type === \"tool_use\") {\n const tb = block as ToolUseBlock;\n meta.toolCalls++;\n toolSet.add(tb.name);\n\n // Extract file references from tool inputs\n const input = tb.input;\n if (input) {\n for (const key of [\"file_path\", \"path\", \"filePath\"]) {\n if (input[key] && typeof input[key] === \"string\") {\n fileSet.add(input[key] as string);\n }\n }\n }\n }\n if (block.type === \"tool_result\") {\n const rb = block as ToolResultBlock;\n if (rb.is_error) meta.errorCount++;\n }\n }\n }\n } catch {\n continue;\n }\n }\n\n meta.uniqueTools = [...toolSet];\n meta.filesReferenced = [...fileSet];\n meta.models = [...modelSet];\n\n return meta;\n}\n\n/**\n * Fully parse a session file into DevLogEvent array.\n */\nexport async function parseSessionFile(\n filePath: string,\n sessionId: string\n): Promise {\n const events: DevLogEvent[] = [];\n let lineIndex = 0;\n\n const rl = createInterface({\n input: createReadStream(filePath, { encoding: \"utf-8\" }),\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n if (!line.trim()) continue;\n lineIndex++;\n\n try {\n const raw = JSON.parse(line) as RawJsonlEvent;\n if (SKIP_TYPES.has(raw.type)) continue;\n const parsed = normalizeEvent(raw, sessionId, lineIndex);\n if (parsed.length > 0) {\n events.push(...parsed);\n }\n } catch {\n continue;\n }\n }\n\n return events;\n}\n\nfunction normalizeEvent(\n raw: RawJsonlEvent,\n sessionId: string,\n lineIndex: number\n): DevLogEvent[] {\n const events: DevLogEvent[] = [];\n const timestamp = raw.timestamp ? new Date(raw.timestamp) : new Date();\n const baseId = raw.uuid || `${sessionId}-${lineIndex}`;\n const role = resolveRole(raw);\n const content = getContent(raw);\n const model = getModel(raw);\n\n if (role && Array.isArray(content)) {\n const blocks = content as ContentBlock[];\n let blockIndex = 0;\n\n for (const block of blocks) {\n blockIndex++;\n const eventId = `${baseId}-${blockIndex}`;\n\n if (block.type === \"text\") {\n const textBlock = block as TextBlock;\n events.push({\n id: eventId,\n sessionId,\n timestamp,\n role: role,\n type: \"text\",\n content: textBlock.text,\n costUSD: raw.costUSD,\n durationMs: raw.durationMs,\n model,\n raw,\n });\n } else if (block.type === \"tool_use\") {\n const toolBlock = block as ToolUseBlock;\n events.push({\n id: eventId,\n sessionId,\n timestamp,\n role: \"tool_use\",\n type: \"tool_use\",\n content: `${toolBlock.name}(${summarizeToolInput(toolBlock.input)})`,\n toolName: toolBlock.name,\n toolInput: toolBlock.input,\n toolUseId: toolBlock.id,\n raw,\n });\n } else if (block.type === \"tool_result\") {\n const resultBlock = block as ToolResultBlock;\n events.push({\n id: eventId,\n sessionId,\n timestamp,\n role: \"tool_result\",\n type: \"tool_result\",\n content: extractToolResultContent(resultBlock),\n toolUseId: resultBlock.tool_use_id,\n isError: resultBlock.is_error,\n raw,\n });\n }\n }\n\n if (typeof content === \"string\") {\n events.push({\n id: baseId,\n sessionId,\n timestamp,\n role: role,\n type: \"message\",\n content: content,\n raw,\n });\n }\n\n if (events.length === 0) {\n events.push({\n id: baseId,\n sessionId,\n timestamp,\n role: role,\n type: \"message\",\n content: extractTextContent(raw),\n raw,\n });\n }\n } else if (role === \"human\" && typeof content === \"string\") {\n events.push({\n id: baseId,\n sessionId,\n timestamp,\n role: \"human\",\n type: \"message\",\n content: content,\n raw,\n });\n } else if (raw.type === \"summary\") {\n events.push({\n id: baseId,\n sessionId,\n timestamp,\n role: \"system\",\n type: \"summary\",\n content: raw.summary || \"\",\n raw,\n });\n }\n\n return events;\n}\n\nfunction extractTextContent(event: RawJsonlEvent): string {\n // Check message.content first (real Claude Code format)\n const msgContent = event.message && typeof event.message === \"object\" ? event.message.content : undefined;\n if (typeof msgContent === \"string\") return msgContent;\n if (Array.isArray(msgContent)) {\n const textBlocks = (msgContent as ContentBlock[]).filter(\n (b): b is TextBlock => b.type === \"text\"\n );\n if (textBlocks.length > 0) return textBlocks.map((b) => b.text).join(\"\\n\");\n }\n\n // Legacy format\n if (typeof event.content === \"string\") return event.content;\n if (Array.isArray(event.content)) {\n const textBlocks = event.content.filter(\n (b): b is TextBlock => b.type === \"text\"\n );\n if (textBlocks.length > 0) return textBlocks.map((b) => b.text).join(\"\\n\");\n }\n return \"\";\n}\n\nfunction summarizeToolInput(input: Record): string {\n const parts: string[] = [];\n if (input.command && typeof input.command === \"string\")\n parts.push(truncateStr(input.command, 60));\n if (input.file_path && typeof input.file_path === \"string\")\n parts.push(input.file_path as string);\n if (input.path && typeof input.path === \"string\")\n parts.push(input.path as string);\n if (input.query && typeof input.query === \"string\")\n parts.push(truncateStr(input.query, 40));\n\n if (parts.length === 0) {\n return Object.keys(input).slice(0, 3).join(\", \");\n }\n return parts.join(\", \");\n}\n\nfunction extractToolResultContent(block: ToolResultBlock): string {\n if (typeof block.content === \"string\") return block.content;\n if (Array.isArray(block.content)) {\n return block.content\n .filter((item) => item.text)\n .map((item) => item.text)\n .join(\"\\n\");\n }\n return \"\";\n}\n\nfunction truncateStr(str: string, max: number): string {\n if (str.length <= max) return str;\n return str.slice(0, max - 1) + \"\\u2026\";\n}\n","import type { TokenUsage } from \"./types.js\";\n\n// Pricing per million tokens (USD)\nconst PRICING: Record = {\n \"claude-opus-4-6\": { input: 15, output: 75, cacheCreation: 18.75, cacheRead: 1.5 },\n \"claude-opus-4-5-20251101\": { input: 15, output: 75, cacheCreation: 18.75, cacheRead: 1.5 },\n \"claude-sonnet-4-6\": { input: 3, output: 15, cacheCreation: 3.75, cacheRead: 0.3 },\n \"claude-sonnet-4-5-20241022\": { input: 3, output: 15, cacheCreation: 3.75, cacheRead: 0.3 },\n \"claude-haiku-4-5-20251001\": { input: 0.80, output: 4, cacheCreation: 1, cacheRead: 0.08 },\n};\n\nconst FALLBACK = { input: 3, output: 15, cacheCreation: 3.75, cacheRead: 0.3 };\n\nfunction findPricing(model: string) {\n if (PRICING[model]) return PRICING[model];\n // Fuzzy match: check if model string contains a known key\n for (const [key, val] of Object.entries(PRICING)) {\n if (model.includes(key) || key.includes(model)) return val;\n }\n // Match by family\n if (model.includes(\"opus\")) return PRICING[\"claude-opus-4-6\"];\n if (model.includes(\"haiku\")) return PRICING[\"claude-haiku-4-5-20251001\"];\n if (model.includes(\"sonnet\")) return PRICING[\"claude-sonnet-4-6\"];\n return FALLBACK;\n}\n\nexport function computeCost(model: string, usage: TokenUsage): number {\n const p = findPricing(model);\n const perM = 1_000_000;\n\n let cost = 0;\n cost += (usage.input_tokens || 0) * p.input / perM;\n cost += (usage.output_tokens || 0) * p.output / perM;\n cost += (usage.cache_creation_input_tokens || 0) * p.cacheCreation / perM;\n cost += (usage.cache_read_input_tokens || 0) * p.cacheRead / perM;\n\n return cost;\n}\n","import chalk from \"chalk\";\nimport dayjs from \"dayjs\";\nimport relativeTime from \"dayjs/plugin/relativeTime.js\";\nimport isToday from \"dayjs/plugin/isToday.js\";\nimport isYesterday from \"dayjs/plugin/isYesterday.js\";\n\ndayjs.extend(relativeTime);\ndayjs.extend(isToday);\ndayjs.extend(isYesterday);\n\n/**\n * Smart time formatting:\n * Today: \"2:30 PM\" (just the time)\n * Yesterday: \"Yesterday 2:30 PM\"\n * This week: \"Mon 2:30 PM\"\n * Older: \"Jan 15, 2:30 PM\"\n */\nexport function formatSmartTime(date: Date): string {\n const d = dayjs(date);\n if (d.isToday()) return d.format(\"h:mm A\");\n if (d.isYesterday()) return \"Yest \" + d.format(\"h:mm A\");\n\n const now = dayjs();\n const diffDays = now.diff(d, \"day\");\n if (diffDays < 7) return d.format(\"ddd h:mm A\");\n\n return d.format(\"MMM D, h:mm A\");\n}\n\nexport function formatDate(date: Date): string {\n return dayjs(date).format(\"YYYY-MM-DD HH:mm\");\n}\n\nexport function formatRelative(date: Date): string {\n return dayjs(date).fromNow();\n}\n\nexport function truncate(str: string, maxLen: number): string {\n if (str.length <= maxLen) return str;\n return str.slice(0, maxLen - 1) + \"…\";\n}\n\n/**\n * Format cost with appropriate precision\n */\nexport function formatCost(usd: number): string {\n if (usd === 0) return chalk.dim(\"—\");\n if (usd < 0.01) return chalk.green(`$${usd.toFixed(4)}`);\n if (usd < 1) return chalk.green(`$${usd.toFixed(3)}`);\n return chalk.yellow(`$${usd.toFixed(2)}`);\n}\n\n/**\n * Format duration in human-readable form\n */\nexport function formatDuration(ms: number): string {\n if (ms === 0) return chalk.dim(\"—\");\n const seconds = Math.round(ms / 1000);\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = seconds % 60;\n if (minutes < 60) return `${minutes}m ${remainingSeconds}s`;\n const hours = Math.floor(minutes / 60);\n const remainingMinutes = minutes % 60;\n return `${hours}h ${remainingMinutes}m`;\n}\n\nexport function formatRole(role: string): string {\n switch (role) {\n case \"human\":\n return chalk.blue.bold(\"You\");\n case \"assistant\":\n return chalk.gray.bold(\"Claude\");\n case \"tool_use\":\n return chalk.green.bold(\"Tool\");\n case \"tool_result\":\n return chalk.yellow.bold(\"Result\");\n default:\n return chalk.dim(role);\n }\n}\n\nexport function formatNumber(n: number): string {\n return n.toLocaleString();\n}\n\n// ── Styled printing ─────────────────────────────────────\n\nexport function printHeader(title: string): void {\n console.log();\n console.log(chalk.bold.white(title));\n console.log(chalk.dim(\"─\".repeat(Math.min(title.length + 10, 60))));\n}\n\nexport function printSuccess(msg: string): void {\n console.log(chalk.green(\" ✓ \") + msg);\n}\n\nexport function printWarn(msg: string): void {\n console.error(chalk.yellow(\" ⚠ \") + msg);\n}\n\nexport function printError(msg: string): void {\n console.error(chalk.red(\" ✗ \") + msg);\n}\n\nexport function printInfo(msg: string): void {\n console.log(chalk.cyan(\" ℹ \") + msg);\n}\n\n/**\n * Render a compact stat badge: \" label value\"\n */\nexport function statBadge(\n label: string,\n value: string | number,\n color: typeof chalk = chalk\n): string {\n return (\n chalk.dim(\" \") +\n chalk.dim(label + \" \") +\n color(typeof value === \"number\" ? formatNumber(value) : value)\n );\n}\n\n// ── Humanize helpers (Principle 1: 说人话) ──────────────\n\nconst TOOL_NAMES: Record = {\n Bash: \"ran a command\",\n Read: \"read a file\",\n Write: \"wrote a file\",\n Edit: \"edited a file\",\n Glob: \"searched for files\",\n Grep: \"searched code\",\n Agent: \"used an agent\",\n WebSearch: \"searched the web\",\n WebFetch: \"fetched a page\",\n TodoWrite: \"updated todos\",\n TodoRead: \"checked todos\",\n};\n\n/**\n * Map a raw tool name to a human-readable description.\n */\nexport function humanizeToolName(name: string): string {\n if (TOOL_NAMES[name]) return TOOL_NAMES[name];\n // CamelCase → space-separated lowercase\n return name.replace(/([a-z])([A-Z])/g, \"$1 $2\").toLowerCase();\n}\n\nconst TOOL_PLURALS: Record = {\n Bash: { singular: \"ran a command\", plural: \"ran commands\" },\n Read: { singular: \"read a file\", plural: \"read files\" },\n Write: { singular: \"wrote a file\", plural: \"wrote files\" },\n Edit: { singular: \"edited a file\", plural: \"edited files\" },\n Glob: { singular: \"searched for files\", plural: \"searched for files\" },\n Grep: { singular: \"searched code\", plural: \"searched code\" },\n Agent: { singular: \"used an agent\", plural: \"used agents\" },\n};\n\n/**\n * Group tool arrays into a summary like \"read 3 files, ran 2 commands, edited 1 file\".\n */\nexport function humanizeToolSummary(tools: string[]): string {\n const counts = new Map();\n for (const t of tools) {\n counts.set(t, (counts.get(t) || 0) + 1);\n }\n\n const parts: string[] = [];\n for (const [name, count] of counts) {\n const p = TOOL_PLURALS[name];\n if (p) {\n parts.push(count === 1 ? p.singular : `${p.plural}`);\n } else {\n const human = humanizeToolName(name);\n parts.push(count === 1 ? human : `${human} (×${count})`);\n }\n }\n\n if (parts.length === 0) return \"\";\n if (parts.length === 1) return `Claude ${parts[0]}`;\n const last = parts.pop()!;\n return `Claude ${parts.join(\", \")}, and ${last}`;\n}\n\n// ── Context helpers (Principle 5: 数字要有上下文) ────────\n\n/**\n * Cost with context suffix.\n */\nexport function costWithContext(usd: number): string {\n if (usd === 0) return chalk.dim(\"—\");\n\n const formatted =\n usd < 0.01\n ? `$${usd.toFixed(4)}`\n : usd < 1\n ? `$${usd.toFixed(3)}`\n : `$${usd.toFixed(2)}`;\n\n let context = \"\";\n if (usd < 0.01) context = \" — barely a penny\";\n else if (usd < 0.05) context = \" — pocket change\";\n else if (usd < 0.15) context = \" — pretty efficient\";\n else if (usd < 0.50) context = \" — a good session\";\n else if (usd < 1) context = \" — solid investment\";\n else if (usd < 5) context = \" — heavy session\";\n else context = \" — serious work\";\n\n return chalk.yellow(formatted) + chalk.dim(context);\n}\n\n/**\n * Message count with context.\n */\nexport function messageCountContext(turns: number): string {\n if (turns <= 2) return chalk.dim(\"quick question\");\n if (turns <= 4) return chalk.dim(\"quick chat\");\n if (turns <= 10) return chalk.dim(`${turns} messages`);\n if (turns <= 25) return chalk.white(`${turns} messages`) + chalk.dim(\" — solid session\");\n if (turns <= 50) return chalk.white(`${turns} messages`) + chalk.dim(\" — deep dive\");\n return chalk.white(`${turns} messages`) + chalk.dim(\" — marathon\");\n}\n\n/**\n * File count with context.\n */\nexport function fileCountContext(count: number): string {\n if (count === 0) return \"\";\n if (count === 1) return chalk.blue(\"touched 1 file\");\n if (count <= 5) return chalk.blue(`touched ${count} files`);\n if (count <= 15) return chalk.blue(`touched ${count} files`) + chalk.dim(\" — broad changes\");\n return chalk.blue(`touched ${count} files`) + chalk.dim(\" — major refactor\");\n}\n\n/**\n * Tool count with context.\n */\nexport function toolCountContext(count: number): string {\n if (count === 0) return \"\";\n if (count <= 10) return chalk.green(`ran ${count} commands`);\n if (count <= 30) return chalk.green(`ran ${count} commands`) + chalk.dim(\" — busy session\");\n return chalk.green(`ran ${count} commands`) + chalk.dim(\" — heavy automation\");\n}\n\n// ── Fuzzy matching (Principle 3: 永远不卡住) ────────────\n\n/**\n * Simple Levenshtein distance for \"did you mean?\" suggestions.\n */\nexport function levenshtein(a: string, b: string): number {\n const m = a.length;\n const n = b.length;\n const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));\n for (let i = 0; i <= m; i++) dp[i][0] = i;\n for (let j = 0; j <= n; j++) dp[0][j] = j;\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n dp[i][j] =\n a[i - 1] === b[j - 1]\n ? dp[i - 1][j - 1]\n : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);\n }\n }\n return dp[m][n];\n}\n","export interface OutputContext {\n json: boolean;\n quiet: boolean;\n}\n\nlet ctx: OutputContext = { json: false, quiet: false };\n\nexport function initOutput(opts: OutputContext): void {\n ctx = opts;\n}\n\nexport function getOutputContext(): OutputContext {\n return ctx;\n}\n\n/** Write JSON to stdout */\nexport function outputJson(data: unknown): void {\n process.stdout.write(JSON.stringify(data, null, 2) + \"\\n\");\n}\n\n/** Progress info to stderr (suppressed in quiet mode) */\nexport function logStatus(msg: string): void {\n if (!ctx.quiet) {\n process.stderr.write(msg + \"\\n\");\n }\n}\n\n/** Errors always go to stderr */\nexport function logError(msg: string): void {\n process.stderr.write(msg + \"\\n\");\n}\n\n/** Whether we are in JSON output mode */\nexport function isJsonMode(): boolean {\n return ctx.json;\n}\n\n/** Whether we are in quiet mode */\nexport function isQuietMode(): boolean {\n return ctx.quiet;\n}\n","import type { Session, SessionJson } from \"../core/types.js\";\n\nexport function toSessionJson(session: Session): SessionJson {\n return {\n id: session.id,\n projectName: session.projectName,\n projectPath: session.projectPath,\n createdAt: session.createdAt.toISOString(),\n updatedAt: session.updatedAt.toISOString(),\n messageCount: session.meta.messageCount,\n toolCalls: session.meta.toolCalls,\n filesTouched: session.meta.filesReferenced.length,\n costUSD: Math.round(session.meta.totalCostUSD * 1000000) / 1000000,\n firstMessage: session.meta.firstUserMessage,\n };\n}\n","import { existsSync } from \"fs\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { initConfig, isInitialized, loadConfig } from \"../core/config.js\";\nimport { getClaudeProjectsDir, getDevlogDir } from \"../utils/paths.js\";\nimport { discoverProjects, computeStats } from \"../core/discovery.js\";\nimport {\n printSuccess,\n printError,\n formatNumber,\n} from \"../utils/format.js\";\nimport type { GlobalOptions } from \"../core/types.js\";\nimport { outputJson, isJsonMode, isQuietMode } from \"../utils/output.js\";\n\nexport async function initCommand(globalOpts: GlobalOptions): Promise {\n if (!isJsonMode()) {\n console.log();\n console.log(\n chalk.bold.cyan(\" ▌\") + chalk.bold.white(\" DevLog Setup\")\n );\n console.log();\n }\n\n // Already initialized\n if (isInitialized()) {\n const config = loadConfig();\n\n let spinner: ReturnType | null = null;\n if (!isJsonMode() && !isQuietMode()) {\n printSuccess(\"DevLog is already set up\");\n console.log(\n chalk.dim(\" Config: \") + chalk.white(getDevlogDir() + \"/config.toml\")\n );\n console.log(\n chalk.dim(\" Claude: \") + chalk.white(config.claudeDir)\n );\n console.log();\n\n spinner = ora({\n text: chalk.dim(\" Checking your sessions...\"),\n spinner: \"dots\",\n color: \"cyan\",\n stream: process.stderr,\n }).start();\n }\n\n const projects = await discoverProjects(config.claudeDir);\n const stats = computeStats(projects);\n spinner?.stop();\n\n if (isJsonMode()) {\n outputJson({ status: \"already_initialized\", configPath: getDevlogDir() + \"/config.toml\", stats: { totalProjects: stats.totalProjects, totalSessions: stats.totalSessions, totalMessages: stats.totalMessages } });\n return;\n }\n\n renderStatsBox(stats);\n\n console.log(\n chalk.dim(\" Everything looks good! Run \") +\n chalk.cyan(\"devlog\") +\n chalk.dim(\" to see your dashboard.\")\n );\n console.log();\n return;\n }\n\n // Check Claude Code\n const claudeDir = getClaudeProjectsDir();\n\n if (!existsSync(claudeDir)) {\n if (isJsonMode()) {\n outputJson({ error: \"Claude Code not found\", path: claudeDir });\n process.exit(1);\n }\n printError(\"Claude Code not found\");\n console.log();\n console.log(\n chalk.white(\" I looked for: \") + chalk.dim(\"~/.claude/projects/\")\n );\n console.log();\n console.log(chalk.white(\" To get started:\"));\n console.log(\n chalk.dim(\" 1. Install Claude Code → \") + chalk.cyan(\"https://claude.ai/code\")\n );\n console.log(\n chalk.dim(\" 2. Have a conversation with Claude in any project\")\n );\n console.log(\n chalk.dim(\" 3. Come back and run \") + chalk.cyan(\"devlog\") + chalk.dim(\" again\")\n );\n console.log();\n return;\n }\n\n if (!isJsonMode()) {\n printSuccess(\"Found Claude Code\");\n }\n\n const config = initConfig();\n\n if (!isJsonMode()) {\n printSuccess(\"Created config at \" + chalk.dim(\"~/.devlog/\"));\n }\n\n let spinner: ReturnType | null = null;\n if (!isJsonMode() && !isQuietMode()) {\n spinner = ora({\n text: chalk.dim(\" Scanning your Claude Code history...\"),\n spinner: \"dots\",\n color: \"cyan\",\n stream: process.stderr,\n }).start();\n }\n\n const projects = await discoverProjects(config.claudeDir);\n const stats = computeStats(projects);\n spinner?.stop();\n\n if (isJsonMode()) {\n outputJson({ status: \"initialized\", configPath: getDevlogDir() + \"/config.toml\", stats: { totalProjects: stats.totalProjects, totalSessions: stats.totalSessions, totalMessages: stats.totalMessages } });\n return;\n }\n\n printSuccess(\"Scan complete\");\n console.log();\n\n renderStatsBox(stats);\n\n console.log(\n chalk.white(\" You're all set! Run \") +\n chalk.cyan.bold(\"devlog\") +\n chalk.white(\" to see your dashboard.\")\n );\n console.log();\n}\n\nfunction renderStatsBox(stats: ReturnType): void {\n const w = 42;\n const line = (label: string, value: string) => {\n const padding = w - label.length - value.length - 6;\n return (\n chalk.dim(\" │ \") +\n chalk.white(label) +\n \" \".repeat(Math.max(1, padding)) +\n chalk.cyan.bold(value) +\n chalk.dim(\" │\")\n );\n };\n\n const costStr = stats.totalCostUSD > 0\n ? stats.totalCostUSD < 1\n ? `$${stats.totalCostUSD.toFixed(3)}`\n : `$${stats.totalCostUSD.toFixed(2)}`\n : \"\";\n\n console.log(chalk.dim(\" ┌\" + \"─\".repeat(w) + \"┐\"));\n console.log(line(\"Projects\", formatNumber(stats.totalProjects)));\n console.log(line(\"Conversations\", formatNumber(stats.totalSessions)));\n console.log(line(\"Messages\", formatNumber(stats.totalMessages)));\n console.log(line(\"Commands run\", formatNumber(stats.totalToolCalls)));\n console.log(\n line(\"Files touched\", formatNumber(stats.allFilesReferenced.length))\n );\n if (costStr) {\n console.log(line(\"Total cost\", costStr));\n }\n console.log(chalk.dim(\" └\" + \"─\".repeat(w) + \"┘\"));\n console.log();\n}\n","import chalk from \"chalk\";\nimport ora from \"ora\";\nimport { ensureInit } from \"../core/config.js\";\nimport { discoverProjects, computeStats } from \"../core/discovery.js\";\nimport type { Session, GlobalOptions, SessionsJson } from \"../core/types.js\";\nimport {\n formatSmartTime,\n truncate,\n formatNumber,\n costWithContext,\n messageCountContext,\n toolCountContext,\n fileCountContext,\n} from \"../utils/format.js\";\nimport { outputJson, isJsonMode, isQuietMode } from \"../utils/output.js\";\nimport { toSessionJson } from \"./shared.js\";\n\ninterface SessionsOptions {\n project?: string;\n limit?: string;\n all?: boolean;\n}\n\nexport async function sessionsCommand(options: SessionsOptions, globalOpts: GlobalOptions): Promise {\n const { config, isFirstRun } = ensureInit();\n const limit = options.all ? Infinity : parseInt(options.limit || \"30\", 10);\n\n let spinner: ReturnType | null = null;\n if (!isJsonMode() && !isQuietMode()) {\n spinner = ora({\n text: chalk.dim(\" Scanning sessions...\"),\n spinner: \"dots\",\n color: \"cyan\",\n stream: process.stderr,\n }).start();\n }\n\n const projects = await discoverProjects(config.claudeDir);\n const stats = computeStats(projects);\n spinner?.stop();\n\n if (projects.length === 0) {\n if (isJsonMode()) {\n outputJson({ projects: [] });\n return;\n }\n console.log();\n console.log(chalk.white(\" No sessions found yet.\"));\n console.log(\n chalk.dim(\" Start a Claude Code session in any project, then come back!\")\n );\n console.log();\n return;\n }\n\n // Filter by project if specified\n const filteredProjects = options.project\n ? projects.filter(\n (p) =>\n p.name.toLowerCase().includes(options.project!.toLowerCase()) ||\n p.path.toLowerCase().includes(options.project!.toLowerCase())\n )\n : projects;\n\n // ── JSON output ────────────────────────────────────\n if (isJsonMode()) {\n const data: SessionsJson = {\n projects: filteredProjects.map((p) => ({\n name: p.name,\n path: p.path,\n sessionCount: p.sessionCount,\n sessions: p.sessions.map(toSessionJson),\n })),\n };\n outputJson(data);\n return;\n }\n\n if (filteredProjects.length === 0) {\n console.log();\n console.log(\n chalk.yellow(\" No projects matching \") +\n chalk.white(`\"${options.project}\"`)\n );\n console.log();\n console.log(chalk.white(\" Your projects:\"));\n for (const p of projects) {\n console.log(\n chalk.cyan(` ${p.name}`) +\n chalk.dim(` — ${p.sessionCount} session${p.sessionCount === 1 ? \"\" : \"s\"}`)\n );\n }\n console.log();\n console.log(\n chalk.dim(\" Try: \") +\n chalk.cyan(`devlog sessions -p ${projects[0].name}`)\n );\n console.log();\n return;\n }\n\n // Header\n console.log();\n console.log(\n chalk.bold.cyan(\" ▌\") +\n chalk.bold.white(\" All Sessions\") +\n (options.project ? chalk.dim(` matching \"${options.project}\"`) : \"\")\n );\n console.log(\n chalk.dim(\n ` ${formatNumber(stats.totalProjects)} projects · ${formatNumber(stats.totalSessions)} conversations · ${formatNumber(stats.totalMessages)} messages`\n )\n );\n console.log();\n\n if (isFirstRun) {\n console.log(\n chalk.dim(\" Each project shows your Claude conversations, newest first.\")\n );\n console.log(\n chalk.dim(\" Pick one with \") +\n chalk.cyan(\"devlog show \") +\n chalk.dim(\" to see the full chat.\")\n );\n console.log();\n }\n\n let displayedCount = 0;\n let sessionIndex = 0;\n\n for (const project of filteredProjects) {\n if (displayedCount >= limit) break;\n\n const sessionWord = project.sessionCount === 1 ? \"session\" : \"sessions\";\n console.log(\n chalk.bold.white(\" 📁 \" + project.name) +\n chalk.dim(` — ${project.sessionCount} ${sessionWord}`)\n );\n console.log(chalk.dim(\" \" + project.path));\n console.log();\n\n const sessionsToShow = project.sessions.slice(0, limit - displayedCount);\n\n for (const session of sessionsToShow) {\n sessionIndex++;\n renderSessionRow(session, sessionIndex);\n displayedCount++;\n }\n\n if (project.sessions.length > sessionsToShow.length) {\n const remaining = project.sessions.length - sessionsToShow.length;\n console.log(\n chalk.dim(` + ${remaining} more — use `) +\n chalk.cyan(\"--all\") +\n chalk.dim(\" to show all\")\n );\n console.log();\n }\n }\n\n // Footer\n console.log(chalk.dim(\" \" + \"─\".repeat(60)));\n console.log();\n console.log(\n chalk.dim(\" \") +\n chalk.cyan(\"devlog show 1\") +\n chalk.dim(\" view most recent · \") +\n chalk.cyan(\"devlog sessions -p \") +\n chalk.dim(\" filter\")\n );\n console.log();\n}\n\nfunction renderSessionRow(session: Session, index: number): void {\n const m = session.meta;\n const time = formatSmartTime(session.updatedAt);\n const preview = m.firstUserMessage\n ? truncate(m.firstUserMessage.replace(/\\n/g, \" \").trim(), 46)\n : chalk.dim(\"(empty)\");\n\n const indexStr = chalk.dim(`${index}.`.padEnd(4));\n console.log(\n chalk.dim(\" \") + indexStr + chalk.white(time.padEnd(16)) + chalk.white(preview)\n );\n\n const turns = m.humanTurns + m.assistantTurns;\n const parts: string[] = [];\n parts.push(messageCountContext(turns));\n if (m.toolCalls > 0) parts.push(toolCountContext(m.toolCalls));\n if (m.filesReferenced.length > 0) parts.push(fileCountContext(m.filesReferenced.length));\n if (m.totalCostUSD > 0) parts.push(costWithContext(m.totalCostUSD));\n if (m.errorCount > 0) {\n parts.push(chalk.red(`${m.errorCount} error${m.errorCount === 1 ? \"\" : \"s\"}`));\n }\n\n console.log(\n chalk.dim(\" \") + \" \" + \" \".repeat(16) + parts.join(chalk.dim(\" · \"))\n );\n console.log();\n}\n","import chalk from \"chalk\";\nimport ora from \"ora\";\nimport { ensureInit } from \"../core/config.js\";\nimport { discoverProjects } from \"../core/discovery.js\";\nimport { parseSessionFile } from \"../core/parser.js\";\nimport type { Session, DevLogEvent, GlobalOptions, ShowJson } from \"../core/types.js\";\nimport {\n formatSmartTime,\n truncate,\n costWithContext,\n messageCountContext,\n toolCountContext,\n fileCountContext,\n humanizeToolSummary,\n} from \"../utils/format.js\";\nimport { outputJson, isJsonMode, isQuietMode } from \"../utils/output.js\";\n\ninterface ShowOptions {\n limit?: string;\n summary?: boolean;\n}\n\n/**\n * `devlog show ` — view a full conversation.\n * Accepts partial session IDs (first 6+ chars) for convenience.\n * Also accepts a number (1, 2, 3...) to pick from the most recent sessions.\n */\nexport async function showCommand(\n sessionRef: string,\n options: ShowOptions,\n globalOpts: GlobalOptions\n): Promise {\n const { config } = ensureInit();\n const limit = parseInt(options.limit || \"50\", 10);\n\n let spinner: ReturnType | null = null;\n if (!isJsonMode() && !isQuietMode()) {\n spinner = ora({\n text: chalk.dim(\" Finding session...\"),\n spinner: \"dots\",\n color: \"cyan\",\n stream: process.stderr,\n }).start();\n }\n\n const projects = await discoverProjects(config.claudeDir);\n spinner?.stop();\n\n // Flatten all sessions\n const allSessions: Session[] = [];\n for (const project of projects) {\n allSessions.push(...project.sessions);\n }\n allSessions.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());\n\n if (allSessions.length === 0) {\n if (isJsonMode()) {\n outputJson({ error: \"No sessions found\" });\n process.exit(1);\n }\n console.log();\n console.log(chalk.yellow(\" No sessions found.\"));\n console.log();\n return;\n }\n\n // Find the session — by number index or by ID prefix\n let session: Session | undefined;\n\n const asNumber = parseInt(sessionRef, 10);\n if (!isNaN(asNumber) && asNumber >= 1 && asNumber <= allSessions.length) {\n session = allSessions[asNumber - 1];\n } else {\n const ref = sessionRef.toLowerCase();\n // Try prefix match first, then substring match\n session = allSessions.find((s) =>\n s.id.toLowerCase().startsWith(ref)\n );\n if (!session) {\n session = allSessions.find((s) =>\n s.id.toLowerCase().includes(ref)\n );\n }\n }\n\n if (!session) {\n if (isJsonMode()) {\n outputJson({ error: `No session matching: ${sessionRef}` });\n process.exit(1);\n }\n console.log();\n console.log(\n chalk.yellow(\" Couldn't find a session matching: \") +\n chalk.white(sessionRef)\n );\n console.log();\n console.log(chalk.dim(\" Recent sessions:\"));\n const recent = allSessions.slice(0, 5);\n recent.forEach((s, i) => {\n const preview = s.meta.firstUserMessage\n ? truncate(s.meta.firstUserMessage.replace(/\\n/g, \" \").trim(), 40)\n : \"(empty)\";\n console.log(\n chalk.cyan(` ${i + 1}. `) +\n chalk.white(s.projectName.padEnd(14)) +\n chalk.dim(preview) +\n chalk.dim(` [${s.id.slice(0, 8)}]`)\n );\n });\n console.log();\n console.log(\n chalk.dim(\" Use \") +\n chalk.cyan(\"devlog show 1\") +\n chalk.dim(\" to view the most recent session.\")\n );\n console.log();\n process.exit(1);\n }\n\n // Parse the full session\n let parseSpinner: ReturnType | null = null;\n if (!isJsonMode() && !isQuietMode()) {\n parseSpinner = ora({\n text: chalk.dim(\" Reading conversation...\"),\n spinner: \"dots\",\n color: \"cyan\",\n stream: process.stderr,\n }).start();\n }\n\n const events = await parseSessionFile(session.filePath, session.id);\n parseSpinner?.stop();\n\n // ── JSON output ────────────────────────────────────\n if (isJsonMode()) {\n const data: ShowJson = {\n session: {\n id: session.id,\n projectName: session.projectName,\n meta: session.meta,\n },\n events: events.map((e) => ({\n timestamp: e.timestamp.toISOString(),\n role: e.role,\n type: e.type,\n content: e.content,\n ...(e.toolName ? { toolName: e.toolName } : {}),\n ...(e.isError ? { isError: e.isError } : {}),\n })),\n };\n outputJson(data);\n return;\n }\n\n // Render\n if (options.summary) {\n renderSummary(session, events);\n } else {\n renderSessionHeader(session);\n renderConversation(events, limit);\n renderSessionFooter(session, events, limit);\n }\n}\n\nfunction renderSessionHeader(session: Session): void {\n const m = session.meta;\n console.log();\n console.log(\n chalk.bold.cyan(\" ▌\") +\n chalk.bold.white(` ${session.projectName}`) +\n chalk.dim(\" · \") +\n chalk.dim(formatSmartTime(session.updatedAt))\n );\n console.log();\n\n const turns = m.humanTurns + m.assistantTurns;\n const parts: string[] = [];\n parts.push(messageCountContext(turns));\n if (m.toolCalls > 0) parts.push(toolCountContext(m.toolCalls));\n if (m.filesReferenced.length > 0) parts.push(fileCountContext(m.filesReferenced.length));\n if (m.totalCostUSD > 0) parts.push(costWithContext(m.totalCostUSD));\n\n console.log(chalk.dim(\" \") + parts.join(chalk.dim(\" · \")));\n\n if (m.uniqueTools.length > 0) {\n console.log(\n chalk.dim(\" \") + humanizeToolSummary(m.uniqueTools)\n );\n }\n\n console.log();\n console.log(chalk.dim(\" \" + \"─\".repeat(60)));\n console.log();\n}\n\nfunction formatToolLine(event: DevLogEvent): string {\n const name = event.toolName || \"tool\";\n const input = event.toolInput || {};\n\n if (name === \"Read\" && input.file_path) return `read ${input.file_path}`;\n if (name === \"Write\" && input.file_path) return `wrote ${input.file_path}`;\n if (name === \"Edit\" && input.file_path) return `edited ${input.file_path}`;\n if (name === \"Bash\" && input.command) return `ran: ${truncate(String(input.command), 60)}`;\n if (name === \"Grep\" && input.pattern) return `searched for \"${truncate(String(input.pattern), 40)}\"`;\n if (name === \"Glob\" && input.pattern) return `found files matching ${truncate(String(input.pattern), 40)}`;\n\n // Fallback: humanize\n return name.replace(/([a-z])([A-Z])/g, \"$1 $2\").toLowerCase();\n}\n\nfunction flushToolGroup(group: DevLogEvent[]): void {\n if (group.length === 0) return;\n\n const firstName = group[0].toolName || \"tool\";\n const allSame = group.every((e) => e.toolName === firstName);\n\n if (allSame && group.length > 2 && (firstName === \"Read\" || firstName === \"Write\" || firstName === \"Edit\")) {\n // Group consecutive same-type file operations\n const verb = firstName === \"Read\" ? \"read\" : firstName === \"Write\" ? \"wrote\" : \"edited\";\n const files = group\n .map((e) => {\n const p = String(e.toolInput?.file_path || \"\");\n return p.split(\"/\").pop() || p;\n })\n .filter(Boolean);\n const fileWord = group.length === 1 ? \"file\" : \"files\";\n console.log(\n chalk.green(\" ▸ \") +\n chalk.dim(`${verb} ${group.length} ${fileWord}: ${truncate(files.join(\", \"), 60)}`)\n );\n } else {\n for (const event of group) {\n console.log(\n chalk.green(\" ▸ \") + chalk.dim(formatToolLine(event))\n );\n }\n }\n}\n\nfunction renderConversation(events: DevLogEvent[], limit: number): void {\n let shown = 0;\n let toolGroup: DevLogEvent[] = [];\n\n for (const event of events) {\n if (shown >= limit) break;\n\n // Flush tool group on non-tool event\n if (event.role !== \"tool_use\" && event.role !== \"tool_result\" && toolGroup.length > 0) {\n flushToolGroup(toolGroup);\n toolGroup = [];\n }\n\n if (event.role === \"human\" && (event.type === \"text\" || event.type === \"message\")) {\n console.log(\n chalk.blue.bold(\" You: \") +\n chalk.white(truncate(event.content.trim(), 76))\n );\n console.log();\n shown++;\n } else if (event.role === \"assistant\" && event.type === \"text\") {\n const lines = event.content.trim().split(\"\\n\");\n const preview = lines.slice(0, 3);\n console.log(chalk.dim.bold(\" Claude: \"));\n for (const line of preview) {\n console.log(chalk.dim(\" \") + chalk.white(truncate(line, 74)));\n }\n if (lines.length > 3) {\n console.log(chalk.dim(` ... (${lines.length - 3} more lines)`));\n }\n console.log();\n shown++;\n } else if (event.role === \"tool_use\") {\n toolGroup.push(event);\n shown++;\n } else if (event.role === \"tool_result\") {\n if (event.isError) {\n console.log(\n chalk.red(\" ✗ Error: \") +\n chalk.dim(truncate(event.content, 60))\n );\n }\n }\n }\n\n // Flush remaining tool group\n if (toolGroup.length > 0) {\n flushToolGroup(toolGroup);\n }\n}\n\nfunction renderSessionFooter(\n session: Session,\n events: DevLogEvent[],\n limit: number\n): void {\n const totalEvents = events.filter(\n (e) =>\n (e.role === \"human\" && (e.type === \"text\" || e.type === \"message\")) ||\n (e.role === \"assistant\" && e.type === \"text\") ||\n e.role === \"tool_use\"\n ).length;\n\n console.log();\n console.log(chalk.dim(\" \" + \"─\".repeat(60)));\n\n if (totalEvents > limit) {\n console.log(\n chalk.dim(` Showing ${limit} of ${totalEvents} events. Use `) +\n chalk.cyan(`devlog show ${session.id.slice(0, 8)} -n ${totalEvents}`) +\n chalk.dim(\" to see all.\")\n );\n }\n\n console.log();\n console.log(\n chalk.dim(\" \") +\n chalk.cyan(`devlog sessions -p ${session.projectName}`) +\n chalk.dim(\" other sessions in this project\")\n );\n console.log(\n chalk.dim(\" \") +\n chalk.cyan(\"devlog\") +\n chalk.dim(\" back to dashboard\")\n );\n console.log();\n}\n\nfunction renderSummary(session: Session, events: DevLogEvent[]): void {\n const m = session.meta;\n\n console.log();\n console.log(\n chalk.bold.cyan(\" ▌\") +\n chalk.bold.white(` ${session.projectName}`) +\n chalk.dim(\" · \") +\n chalk.dim(formatSmartTime(session.updatedAt))\n );\n console.log();\n\n // Extract what was asked\n const firstMsg = m.firstUserMessage\n ? truncate(m.firstUserMessage.replace(/\\n/g, \" \").trim(), 70)\n : \"(empty)\";\n console.log(chalk.white(\" Summary:\"));\n console.log(\n chalk.dim(\" You asked Claude: \") + chalk.white(`\"${firstMsg}\"`)\n );\n console.log();\n\n // What happened — group tool usage\n const toolGroups = new Map();\n const filesChanged = new Set();\n const commandsRun: string[] = [];\n let errorCount = 0;\n\n for (const event of events) {\n if (event.role === \"tool_use\") {\n const name = event.toolName || \"tool\";\n toolGroups.set(name, (toolGroups.get(name) || 0) + 1);\n\n const input = event.toolInput || {};\n if (input.file_path && (name === \"Write\" || name === \"Edit\")) {\n filesChanged.add(String(input.file_path));\n }\n if (name === \"Bash\" && input.command) {\n commandsRun.push(truncate(String(input.command), 50));\n }\n }\n if (event.role === \"tool_result\" && event.isError) {\n errorCount++;\n }\n }\n\n console.log(chalk.white(\" What happened:\"));\n\n for (const [name, count] of toolGroups) {\n let desc: string;\n if (name === \"Read\") desc = `Read ${count} file${count === 1 ? \"\" : \"s\"} to understand the codebase`;\n else if (name === \"Write\") desc = `Created ${count} file${count === 1 ? \"\" : \"s\"}`;\n else if (name === \"Edit\") desc = `Edited ${count} file${count === 1 ? \"\" : \"s\"}`;\n else if (name === \"Bash\") desc = `Ran ${count} command${count === 1 ? \"\" : \"s\"}` + (commandsRun.length > 0 ? ` (${truncate(commandsRun.slice(0, 3).join(\", \"), 40)})` : \"\");\n else if (name === \"Grep\") desc = `Searched code ${count} time${count === 1 ? \"\" : \"s\"}`;\n else if (name === \"Glob\") desc = `Searched for files ${count} time${count === 1 ? \"\" : \"s\"}`;\n else desc = `${name} ×${count}`;\n\n console.log(chalk.dim(\" - \") + chalk.dim(desc));\n }\n\n if (filesChanged.size > 0) {\n const fileNames = [...filesChanged].map((f) => f.split(\"/\").pop() || f);\n console.log(\n chalk.dim(\" - \") +\n chalk.dim(`Files changed: ${truncate(fileNames.join(\", \"), 50)}`)\n );\n }\n\n if (errorCount > 0) {\n console.log(\n chalk.dim(\" - \") +\n chalk.red(`Fixed ${errorCount} error${errorCount === 1 ? \"\" : \"s\"}`)\n );\n }\n\n console.log();\n\n // Stats line\n const turns = m.humanTurns + m.assistantTurns;\n const parts: string[] = [];\n parts.push(messageCountContext(turns));\n if (m.toolCalls > 0) parts.push(toolCountContext(m.toolCalls));\n if (m.filesReferenced.length > 0) parts.push(fileCountContext(m.filesReferenced.length));\n if (m.totalCostUSD > 0) parts.push(costWithContext(m.totalCostUSD));\n\n console.log(chalk.dim(\" \") + parts.join(chalk.dim(\" · \")));\n\n // Footer\n console.log();\n console.log(chalk.dim(\" \" + \"─\".repeat(60)));\n console.log();\n console.log(\n chalk.dim(\" \") +\n chalk.cyan(`devlog show ${session.id.slice(0, 8)}`) +\n chalk.dim(\" see full conversation · \") +\n chalk.cyan(`devlog sessions -p ${session.projectName}`) +\n chalk.dim(\" other sessions\")\n );\n console.log();\n}\n","import chalk from \"chalk\";\nimport ora from \"ora\";\nimport dayjs from \"dayjs\";\nimport isToday from \"dayjs/plugin/isToday.js\";\nimport { ensureInit } from \"../core/config.js\";\nimport { discoverProjects } from \"../core/discovery.js\";\nimport type { Session, GlobalOptions } from \"../core/types.js\";\nimport {\n formatSmartTime,\n truncate,\n costWithContext,\n messageCountContext,\n toolCountContext,\n fileCountContext,\n} from \"../utils/format.js\";\nimport { outputJson, isJsonMode, isQuietMode } from \"../utils/output.js\";\nimport { toSessionJson } from \"./shared.js\";\n\ndayjs.extend(isToday);\n\nexport async function todayCommand(globalOpts: GlobalOptions): Promise {\n const { config } = ensureInit();\n\n let spinner: ReturnType | null = null;\n if (!isJsonMode() && !isQuietMode()) {\n spinner = ora({\n text: chalk.dim(\" Checking today's sessions...\"),\n spinner: \"dots\",\n color: \"cyan\",\n stream: process.stderr,\n }).start();\n }\n\n const projects = await discoverProjects(config.claudeDir);\n spinner?.stop();\n\n // Filter to today's sessions\n const todaySessions: Session[] = [];\n const projectNames = new Set();\n\n for (const project of projects) {\n for (const session of project.sessions) {\n if (dayjs(session.updatedAt).isToday()) {\n todaySessions.push(session);\n projectNames.add(session.projectName);\n }\n }\n }\n\n todaySessions.sort((a, b) => a.updatedAt.getTime() - b.updatedAt.getTime());\n\n // JSON output\n if (isJsonMode()) {\n const totalCost = todaySessions.reduce((s, sess) => s + sess.meta.totalCostUSD, 0);\n const totalTools = todaySessions.reduce((s, sess) => s + sess.meta.toolCalls, 0);\n const allFiles = new Set();\n for (const s of todaySessions) {\n for (const f of s.meta.filesReferenced) allFiles.add(f);\n }\n\n outputJson({\n date: dayjs().format(\"YYYY-MM-DD\"),\n sessionCount: todaySessions.length,\n projectCount: projectNames.size,\n totalCostUSD: Math.round(totalCost * 1000000) / 1000000,\n totalToolCalls: totalTools,\n totalFilesTouched: allFiles.size,\n sessions: todaySessions.map(toSessionJson),\n });\n return;\n }\n\n // No sessions today\n if (todaySessions.length === 0) {\n console.log();\n console.log(\n chalk.bold.cyan(\" ▌\") + chalk.bold.white(\" Your day so far\")\n );\n console.log();\n console.log(chalk.dim(\" No sessions yet today.\"));\n\n // Find most recent session\n const allSessions: Session[] = [];\n for (const p of projects) allSessions.push(...p.sessions);\n allSessions.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());\n\n if (allSessions.length > 0) {\n const last = allSessions[0];\n console.log(\n chalk.dim(\" Your last session was \") +\n chalk.white(formatSmartTime(last.updatedAt)) +\n chalk.dim(\" in \") +\n chalk.cyan(last.projectName)\n );\n }\n\n console.log();\n console.log(\n chalk.dim(\" \") +\n chalk.cyan(\"devlog sessions\") +\n chalk.dim(\" browse all · \") +\n chalk.cyan(\"devlog\") +\n chalk.dim(\" dashboard\")\n );\n console.log();\n return;\n }\n\n // Narrative summary\n const totalCost = todaySessions.reduce((s, sess) => s + sess.meta.totalCostUSD, 0);\n const totalTools = todaySessions.reduce((s, sess) => s + sess.meta.toolCalls, 0);\n const allFiles = new Set();\n for (const s of todaySessions) {\n for (const f of s.meta.filesReferenced) allFiles.add(f);\n }\n\n const sessionWord = todaySessions.length === 1 ? \"conversation\" : \"conversations\";\n const projectWord = projectNames.size === 1 ? \"project\" : \"projects\";\n\n console.log();\n console.log(\n chalk.bold.cyan(\" ▌\") + chalk.bold.white(\" Your day so far\")\n );\n console.log();\n console.log(\n chalk.dim(\" \") +\n chalk.white(`${todaySessions.length} ${sessionWord} across ${projectNames.size} ${projectWord}.`)\n );\n\n const summaryParts: string[] = [];\n if (totalTools > 0) summaryParts.push(toolCountContext(totalTools));\n if (allFiles.size > 0) summaryParts.push(fileCountContext(allFiles.size));\n if (summaryParts.length > 0) {\n console.log(chalk.dim(\" \") + chalk.white(\"Claude \") + summaryParts.join(chalk.dim(\" and \")));\n }\n if (totalCost > 0) {\n console.log(chalk.dim(\" Cost: \") + costWithContext(totalCost));\n }\n\n console.log();\n console.log(chalk.dim(\" \" + \"─\".repeat(60)));\n console.log();\n\n // Timeline\n for (const session of todaySessions) {\n const m = session.meta;\n const time = formatSmartTime(session.updatedAt);\n const preview = m.firstUserMessage\n ? truncate(m.firstUserMessage.replace(/\\n/g, \" \").trim(), 42)\n : chalk.dim(\"(empty)\");\n\n console.log(\n chalk.dim(\" \") +\n chalk.white(time.padEnd(12)) +\n chalk.cyan(session.projectName.padEnd(14)) +\n chalk.white(`\"${preview}\"`)\n );\n\n const turns = m.humanTurns + m.assistantTurns;\n const parts: string[] = [];\n parts.push(messageCountContext(turns));\n if (m.toolCalls > 0) parts.push(toolCountContext(m.toolCalls));\n if (m.totalCostUSD > 0) parts.push(costWithContext(m.totalCostUSD));\n\n console.log(\n chalk.dim(\" \") +\n \" \".repeat(26) +\n parts.join(chalk.dim(\" · \"))\n );\n console.log();\n }\n\n console.log(chalk.dim(\" \" + \"─\".repeat(60)));\n console.log();\n console.log(\n chalk.dim(\" \") +\n chalk.cyan(\"devlog show 1\") +\n chalk.dim(\" view most recent · \") +\n chalk.cyan(\"devlog sessions\") +\n chalk.dim(\" browse all\")\n );\n console.log();\n}\n","import chalk from \"chalk\";\nimport ora from \"ora\";\nimport { ensureInit } from \"../core/config.js\";\nimport { discoverProjects } from \"../core/discovery.js\";\nimport type { Session, GlobalOptions } from \"../core/types.js\";\nimport {\n formatSmartTime,\n truncate,\n costWithContext,\n messageCountContext,\n} from \"../utils/format.js\";\nimport { outputJson, isJsonMode, isQuietMode } from \"../utils/output.js\";\nimport { toSessionJson } from \"./shared.js\";\n\nexport async function searchCommand(\n query: string,\n globalOpts: GlobalOptions\n): Promise {\n const { config } = ensureInit();\n\n let spinner: ReturnType | null = null;\n if (!isJsonMode() && !isQuietMode()) {\n spinner = ora({\n text: chalk.dim(\" Searching sessions...\"),\n spinner: \"dots\",\n color: \"cyan\",\n stream: process.stderr,\n }).start();\n }\n\n const projects = await discoverProjects(config.claudeDir);\n spinner?.stop();\n\n const q = query.toLowerCase();\n\n // Search by first message, project name, or tool names\n const matches: Session[] = [];\n for (const project of projects) {\n for (const session of project.sessions) {\n const m = session.meta;\n if (\n session.projectName.toLowerCase().includes(q) ||\n m.firstUserMessage.toLowerCase().includes(q) ||\n m.uniqueTools.some((t) => t.toLowerCase().includes(q))\n ) {\n matches.push(session);\n }\n }\n }\n\n matches.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());\n\n // JSON output\n if (isJsonMode()) {\n outputJson({\n query,\n matchCount: matches.length,\n sessions: matches.map(toSessionJson),\n });\n return;\n }\n\n console.log();\n console.log(\n chalk.bold.cyan(\" ▌\") +\n chalk.bold.white(` Search: `) +\n chalk.white(`\"${query}\"`)\n );\n console.log();\n\n if (matches.length === 0) {\n console.log(chalk.dim(\" No conversations found matching that query.\"));\n console.log();\n console.log(\n chalk.dim(\" Try a shorter term, or use \") +\n chalk.cyan(\"devlog sessions\") +\n chalk.dim(\" to browse all.\")\n );\n console.log();\n return;\n }\n\n const matchWord = matches.length === 1 ? \"conversation\" : \"conversations\";\n console.log(\n chalk.dim(` Found ${matches.length} ${matchWord} matching \"${query}\"`)\n );\n console.log();\n\n const shown = matches.slice(0, 20);\n\n for (let i = 0; i < shown.length; i++) {\n const session = shown[i];\n const m = session.meta;\n const time = formatSmartTime(session.updatedAt);\n const preview = m.firstUserMessage\n ? truncate(m.firstUserMessage.replace(/\\n/g, \" \").trim(), 42)\n : chalk.dim(\"(empty)\");\n\n console.log(\n chalk.dim(` ${(i + 1).toString().padEnd(3)} `) +\n chalk.white(time.padEnd(16)) +\n chalk.cyan(session.projectName.padEnd(14)) +\n chalk.white(`\"${preview}\"`)\n );\n\n const turns = m.humanTurns + m.assistantTurns;\n const parts: string[] = [];\n parts.push(messageCountContext(turns));\n if (m.totalCostUSD > 0) parts.push(costWithContext(m.totalCostUSD));\n\n console.log(\n chalk.dim(\" \") +\n \" \" +\n \" \".repeat(16) +\n parts.join(chalk.dim(\" · \"))\n );\n console.log();\n }\n\n if (matches.length > 20) {\n console.log(chalk.dim(` + ${matches.length - 20} more matches`));\n console.log();\n }\n\n console.log(chalk.dim(\" \" + \"─\".repeat(60)));\n console.log();\n console.log(\n chalk.dim(\" \") +\n chalk.cyan(\"devlog show 1\") +\n chalk.dim(\" view match · \") +\n chalk.cyan(`devlog search \"${query} ...\"`) +\n chalk.dim(\" refine\")\n );\n console.log();\n}\n","import chalk from \"chalk\";\nimport ora from \"ora\";\nimport dayjs from \"dayjs\";\nimport isToday from \"dayjs/plugin/isToday.js\";\nimport { ensureInit } from \"../core/config.js\";\nimport { discoverProjects, computeStats } from \"../core/discovery.js\";\nimport type { Session, GlobalOptions } from \"../core/types.js\";\nimport {\n formatNumber,\n costWithContext,\n} from \"../utils/format.js\";\nimport { outputJson, isJsonMode, isQuietMode } from \"../utils/output.js\";\n\ndayjs.extend(isToday);\n\ninterface StatsOptions {\n period?: string;\n}\n\nfunction filterByPeriod(sessions: Session[], period: string): Session[] {\n const now = dayjs();\n return sessions.filter((s) => {\n const d = dayjs(s.updatedAt);\n switch (period) {\n case \"today\":\n return d.isToday();\n case \"week\":\n return now.diff(d, \"day\") < 7;\n case \"month\":\n return now.diff(d, \"day\") < 30;\n default:\n return true;\n }\n });\n}\n\nexport async function statsCommand(\n options: StatsOptions,\n globalOpts: GlobalOptions\n): Promise {\n const { config } = ensureInit();\n const period = options.period || \"all\";\n\n let spinner: ReturnType | null = null;\n if (!isJsonMode() && !isQuietMode()) {\n spinner = ora({\n text: chalk.dim(\" Crunching numbers...\"),\n spinner: \"dots\",\n color: \"cyan\",\n stream: process.stderr,\n }).start();\n }\n\n const projects = await discoverProjects(config.claudeDir);\n spinner?.stop();\n\n const allSessions: Session[] = [];\n for (const p of projects) allSessions.push(...p.sessions);\n const filtered = filterByPeriod(allSessions, period);\n\n // Aggregate\n let totalCost = 0;\n let totalTools = 0;\n let totalMessages = 0;\n const fileSet = new Set();\n const toolCounts = new Map();\n const projectCounts = new Map();\n const costByModel: Record = {};\n\n for (const s of filtered) {\n totalCost += s.meta.totalCostUSD;\n totalTools += s.meta.toolCalls;\n totalMessages += s.meta.humanTurns + s.meta.assistantTurns;\n for (const f of s.meta.filesReferenced) fileSet.add(f);\n for (const t of s.meta.uniqueTools) {\n toolCounts.set(t, (toolCounts.get(t) || 0) + 1);\n }\n projectCounts.set(\n s.projectName,\n (projectCounts.get(s.projectName) || 0) + 1\n );\n for (const [model, cost] of Object.entries(s.meta.costByModel)) {\n costByModel[model] = (costByModel[model] || 0) + cost;\n }\n }\n\n const periodLabel =\n period === \"today\"\n ? \"Today\"\n : period === \"week\"\n ? \"This Week\"\n : period === \"month\"\n ? \"This Month\"\n : \"All Time\";\n\n // JSON\n if (isJsonMode()) {\n outputJson({\n period: periodLabel,\n sessionCount: filtered.length,\n totalMessages,\n totalToolCalls: totalTools,\n totalFilesTouched: fileSet.size,\n totalCostUSD: Math.round(totalCost * 1000000) / 1000000,\n topTools: [...toolCounts.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 10)\n .map(([name, count]) => ({ name, count })),\n projectBreakdown: [...projectCounts.entries()]\n .sort((a, b) => b[1] - a[1])\n .map(([name, count]) => ({ name, sessions: count })),\n costByModel,\n });\n return;\n }\n\n if (filtered.length === 0) {\n console.log();\n console.log(\n chalk.bold.cyan(\" ▌\") + chalk.bold.white(` Stats — ${periodLabel}`)\n );\n console.log();\n console.log(chalk.dim(\" No sessions in this period.\"));\n console.log();\n return;\n }\n\n console.log();\n console.log(\n chalk.bold.cyan(\" ▌\") + chalk.bold.white(` Stats — ${periodLabel}`)\n );\n console.log();\n\n // Stats box\n const w = 42;\n const line = (label: string, value: string) => {\n const padding = w - label.length - value.length - 6;\n return (\n chalk.dim(\" │ \") +\n chalk.white(label) +\n \" \".repeat(Math.max(1, padding)) +\n chalk.cyan.bold(value) +\n chalk.dim(\" │\")\n );\n };\n\n console.log(chalk.dim(\" ┌\" + \"─\".repeat(w) + \"┐\"));\n console.log(line(\"Sessions\", formatNumber(filtered.length)));\n console.log(line(\"Messages\", formatNumber(totalMessages)));\n console.log(line(\"Commands run\", formatNumber(totalTools)));\n console.log(line(\"Files touched\", formatNumber(fileSet.size)));\n if (totalCost > 0) {\n const costStr = totalCost < 1 ? `$${totalCost.toFixed(3)}` : `$${totalCost.toFixed(2)}`;\n console.log(line(\"Total cost\", costStr));\n }\n console.log(chalk.dim(\" └\" + \"─\".repeat(w) + \"┘\"));\n console.log();\n\n // Top tools\n const topTools = [...toolCounts.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 5);\n if (topTools.length > 0) {\n console.log(chalk.white(\" Most used tools:\"));\n for (const [name, count] of topTools) {\n console.log(\n chalk.dim(\" \") +\n chalk.green(name.padEnd(16)) +\n chalk.dim(`used in ${count} session${count === 1 ? \"\" : \"s\"}`)\n );\n }\n console.log();\n }\n\n // Most active project\n const topProjects = [...projectCounts.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 3);\n if (topProjects.length > 0) {\n console.log(chalk.white(\" Most active projects:\"));\n for (const [name, count] of topProjects) {\n const sessionWord = count === 1 ? \"session\" : \"sessions\";\n console.log(\n chalk.dim(\" \") +\n chalk.cyan(name.padEnd(16)) +\n chalk.dim(`${count} ${sessionWord}`)\n );\n }\n console.log();\n }\n\n // Cost context\n if (totalCost > 0) {\n console.log(\n chalk.dim(\" Total cost: \") + costWithContext(totalCost)\n );\n console.log();\n }\n\n console.log(chalk.dim(\" \" + \"─\".repeat(60)));\n console.log();\n console.log(\n chalk.dim(\" \") +\n chalk.cyan(\"devlog cost\") +\n chalk.dim(\" cost breakdown · \") +\n chalk.cyan(\"devlog stats --period week\") +\n chalk.dim(\" filter\")\n );\n console.log();\n}\n","import chalk from \"chalk\";\nimport ora from \"ora\";\nimport dayjs from \"dayjs\";\nimport isToday from \"dayjs/plugin/isToday.js\";\nimport { ensureInit } from \"../core/config.js\";\nimport { discoverProjects } from \"../core/discovery.js\";\nimport type { Session, GlobalOptions } from \"../core/types.js\";\nimport { costWithContext } from \"../utils/format.js\";\nimport { outputJson, isJsonMode, isQuietMode } from \"../utils/output.js\";\n\ndayjs.extend(isToday);\n\ninterface CostOptions {\n period?: string;\n}\n\nfunction filterByPeriod(sessions: Session[], period: string): Session[] {\n const now = dayjs();\n return sessions.filter((s) => {\n const d = dayjs(s.updatedAt);\n switch (period) {\n case \"today\":\n return d.isToday();\n case \"week\":\n return now.diff(d, \"day\") < 7;\n case \"month\":\n return now.diff(d, \"day\") < 30;\n default:\n return true;\n }\n });\n}\n\nfunction renderBar(fraction: number, width: number = 16): string {\n const filled = Math.round(fraction * width);\n return chalk.green(\"█\".repeat(filled)) + chalk.dim(\"░\".repeat(width - filled));\n}\n\nexport async function costCommand(\n options: CostOptions,\n globalOpts: GlobalOptions\n): Promise {\n const { config } = ensureInit();\n const period = options.period || \"all\";\n\n let spinner: ReturnType | null = null;\n if (!isJsonMode() && !isQuietMode()) {\n spinner = ora({\n text: chalk.dim(\" Calculating costs...\"),\n spinner: \"dots\",\n color: \"cyan\",\n stream: process.stderr,\n }).start();\n }\n\n const projects = await discoverProjects(config.claudeDir);\n spinner?.stop();\n\n const allSessions: Session[] = [];\n for (const p of projects) allSessions.push(...p.sessions);\n const filtered = filterByPeriod(allSessions, period);\n\n // Aggregate by project and model\n const byProject = new Map();\n const byModel = new Map();\n let totalCost = 0;\n\n for (const s of filtered) {\n totalCost += s.meta.totalCostUSD;\n byProject.set(\n s.projectName,\n (byProject.get(s.projectName) || 0) + s.meta.totalCostUSD\n );\n for (const [model, cost] of Object.entries(s.meta.costByModel)) {\n byModel.set(model, (byModel.get(model) || 0) + cost);\n }\n }\n\n const periodLabel =\n period === \"today\"\n ? \"Today\"\n : period === \"week\"\n ? \"This Week\"\n : period === \"month\"\n ? \"This Month\"\n : \"All Time\";\n\n // JSON\n if (isJsonMode()) {\n outputJson({\n period: periodLabel,\n totalCostUSD: Math.round(totalCost * 1000000) / 1000000,\n byProject: [...byProject.entries()]\n .sort((a, b) => b[1] - a[1])\n .map(([name, cost]) => ({ name, costUSD: Math.round(cost * 1000000) / 1000000 })),\n byModel: [...byModel.entries()]\n .sort((a, b) => b[1] - a[1])\n .map(([name, cost]) => ({ name, costUSD: Math.round(cost * 1000000) / 1000000 })),\n });\n return;\n }\n\n console.log();\n console.log(\n chalk.bold.cyan(\" ▌\") + chalk.bold.white(` Cost Breakdown — ${periodLabel}`)\n );\n console.log();\n\n if (totalCost === 0) {\n console.log(chalk.dim(\" No costs recorded in this period.\"));\n console.log();\n return;\n }\n\n console.log(\n chalk.dim(\" Total: \") + costWithContext(totalCost)\n );\n console.log();\n\n // By project\n const projectEntries = [...byProject.entries()]\n .sort((a, b) => b[1] - a[1]);\n if (projectEntries.length > 0) {\n console.log(chalk.white(\" By project:\"));\n for (const [name, cost] of projectEntries) {\n const pct = totalCost > 0 ? Math.round((cost / totalCost) * 100) : 0;\n const costStr = cost < 1 ? `$${cost.toFixed(3)}` : `$${cost.toFixed(2)}`;\n console.log(\n chalk.dim(\" \") +\n chalk.cyan(name.padEnd(18)) +\n chalk.yellow(costStr.padEnd(10)) +\n chalk.dim(`(${pct}%)`.padEnd(7)) +\n renderBar(cost / totalCost)\n );\n }\n console.log();\n }\n\n // By model\n const modelEntries = [...byModel.entries()]\n .sort((a, b) => b[1] - a[1]);\n if (modelEntries.length > 0) {\n console.log(chalk.white(\" By model:\"));\n for (const [name, cost] of modelEntries) {\n const pct = totalCost > 0 ? Math.round((cost / totalCost) * 100) : 0;\n const costStr = cost < 1 ? `$${cost.toFixed(3)}` : `$${cost.toFixed(2)}`;\n // Friendly model name\n let label = name;\n if (name.includes(\"opus\")) label = \"claude-opus\";\n else if (name.includes(\"sonnet\")) label = \"claude-sonnet\";\n else if (name.includes(\"haiku\")) label = \"claude-haiku\";\n\n let context = \"\";\n if (pct > 70) context = \" most of your work\";\n else if (pct < 20) context = \" for the hard stuff\";\n\n console.log(\n chalk.dim(\" \") +\n chalk.white(label.padEnd(18)) +\n chalk.yellow(costStr.padEnd(10)) +\n chalk.dim(`(${pct}%)`) +\n chalk.dim(context)\n );\n }\n console.log();\n }\n\n console.log(chalk.dim(\" \" + \"─\".repeat(60)));\n console.log();\n console.log(\n chalk.dim(\" \") +\n chalk.cyan(\"devlog stats\") +\n chalk.dim(\" usage trends · \") +\n chalk.cyan(\"devlog cost --period week\") +\n chalk.dim(\" filter\")\n );\n console.log();\n}\n"],"mappings":"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","names":["chalk","chalk","existsSync","join","join","join","existsSync","existsSync","join","dayjs","existsSync","chalk","existsSync","chalk","ora","chalk","config","spinner","ora","projects","stats","existsSync","chalk","ora","ora","chalk","chalk","ora","ora","chalk","chalk","ora","dayjs","isToday","dayjs","isToday","ora","chalk","totalCost","totalTools","allFiles","chalk","ora","ora","chalk","chalk","ora","dayjs","isToday","dayjs","isToday","ora","chalk","chalk","ora","dayjs","isToday","dayjs","isToday","filterByPeriod","chalk","ora","VERSION","chalk"]} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..30836bc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1836 @@ +{ + "name": "@anthropic/devlog", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@anthropic/devlog", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "chalk": "^5.6.2", + "cli-table3": "^0.6.5", + "commander": "^14.0.3", + "dayjs": "^1.11.19", + "ora": "^9.3.0", + "toml": "^3.0.0" + }, + "bin": { + "devlog": "dist/cli.js" + }, + "devDependencies": { + "@types/node": "^25.3.3", + "tsup": "^8.5.1", + "typescript": "^5.9.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.3.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz", + "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz", + "integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==", + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/log-symbols": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", + "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-9.3.0.tgz", + "integrity": "sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.6.2", + "cli-cursor": "^5.0.0", + "cli-spinners": "^3.2.0", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.1.0", + "log-symbols": "^7.0.1", + "stdin-discarder": "^0.3.1", + "string-width": "^8.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ora/node_modules/string-width": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.0.tgz", + "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/stdin-discarder": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.3.1.tgz", + "integrity": "sha512-reExS1kSGoElkextOcPkel4NE99S0BWxjUHQeDFnR8S993JxpPX7KU4MNmO19NXhlJp+8dmdCbKQVNgLJh2teA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==", + "license": "MIT" + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tsup": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", + "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.27.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "^0.7.6", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json index c4bf132..fcfbb0d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@moose-lab/devlog", - "version": "0.1.0", + "version": "0.3.0", "description": "Auto-generate dev logs from your Claude Code sessions", "type": "module", "bin": { diff --git a/src/cli.ts b/src/cli.ts index 9dc08d9..c3d6c2b 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -4,8 +4,15 @@ import { dashboardCommand } from "./commands/dashboard.js"; import { initCommand } from "./commands/init.js"; import { sessionsCommand } from "./commands/sessions.js"; import { showCommand } from "./commands/show.js"; +import { todayCommand } from "./commands/today.js"; +import { searchCommand } from "./commands/search.js"; +import { statsCommand } from "./commands/stats.js"; +import { costCommand } from "./commands/cost.js"; +import { initOutput, outputJson } from "./utils/output.js"; +import { levenshtein } from "./utils/format.js"; +import type { GlobalOptions } from "./core/types.js"; -const VERSION = "0.1.0"; +const VERSION = "0.3.0"; const HELP_TEXT = ` ${chalk.bold.cyan(" ▌")} ${chalk.bold.white("DevLog")} ${chalk.dim(`v${VERSION}`)} @@ -16,30 +23,77 @@ ${chalk.dim(" Just run")} ${chalk.cyan("devlog")} ${chalk.dim("— that's it. N ${chalk.bold.white(" Examples:")} ${chalk.cyan(" devlog")}${chalk.dim(" See your dashboard")} +${chalk.cyan(" devlog today")}${chalk.dim(" What did I do today?")} ${chalk.cyan(" devlog sessions")}${chalk.dim(" Browse all sessions by project")} ${chalk.cyan(" devlog sessions -p chatbot")}${chalk.dim(" Filter to a specific project")} ${chalk.cyan(" devlog show 1")}${chalk.dim(" View your most recent conversation")} +${chalk.cyan(" devlog show 1 --summary")}${chalk.dim(" Quick narrative summary")} ${chalk.cyan(" devlog show abc123")}${chalk.dim(" View a specific session by ID")} +${chalk.cyan(" devlog search \"auth bug\"")}${chalk.dim(" Find a conversation")} +${chalk.cyan(" devlog stats")}${chalk.dim(" Usage trends")} +${chalk.cyan(" devlog cost")}${chalk.dim(" Cost breakdown")} + +${chalk.bold.white(" Output Modes:")} +${chalk.cyan(" devlog --json")}${chalk.dim(" JSON output for scripts/agents")} +${chalk.cyan(" devlog -q")}${chalk.dim(" Quiet mode (no spinners/banners)")} +${chalk.cyan(" devlog --no-color")}${chalk.dim(" Plain text, no ANSI escapes")} `; const program = new Command(); +const KNOWN_COMMANDS = [ + "init", + "sessions", + "show", + "today", + "search", + "stats", + "cost", +]; + +function getGlobalOpts(): GlobalOptions { + const opts = program.opts(); + return { json: !!opts.json, quiet: !!opts.quiet }; +} + +function handleError(err: unknown, globalOpts: GlobalOptions): never { + const message = err instanceof Error ? err.message : String(err); + if (globalOpts.json) { + outputJson({ error: message }); + } else { + console.error( + chalk.red("\n Error:"), + message + ); + } + process.exit(1); +} + program .name("devlog") .description("Your Claude Code work journal — auto-generated dev logs") .version(VERSION) + .option("--json", "Output as JSON for scripts and agents") + .option("-q, --quiet", "Suppress non-essential output") + .option("--no-color", "Disable colored output") .addHelpText("before", HELP_TEXT); +// Initialize output context before every command +program.hook("preAction", () => { + const opts = program.opts(); + if (opts.color === false) { + process.env.NO_COLOR = "1"; + } + initOutput({ json: !!opts.json, quiet: !!opts.quiet }); +}); + // ── Default: `devlog` with no args → dashboard ────────── program.action(async () => { + const globalOpts = getGlobalOpts(); try { - await dashboardCommand(); + await dashboardCommand(globalOpts); } catch (err) { - console.error( - chalk.red("\n Error:"), - err instanceof Error ? err.message : err - ); - process.exit(1); + handleError(err, globalOpts); } }); @@ -48,14 +102,11 @@ program .command("init") .description("Set up DevLog (usually auto-detected, you rarely need this)") .action(async () => { + const globalOpts = getGlobalOpts(); try { - await initCommand(); + await initCommand(globalOpts); } catch (err) { - console.error( - chalk.red("\n Error:"), - err instanceof Error ? err.message : err - ); - process.exit(1); + handleError(err, globalOpts); } }); @@ -67,14 +118,11 @@ program .option("-n, --limit ", "Max sessions to display", "30") .option("-a, --all", "Show all sessions") .action(async (options) => { + const globalOpts = getGlobalOpts(); try { - await sessionsCommand(options); + await sessionsCommand(options, globalOpts); } catch (err) { - console.error( - chalk.red("\n Error:"), - err instanceof Error ? err.message : err - ); - process.exit(1); + handleError(err, globalOpts); } }); @@ -83,16 +131,109 @@ program .command("show ") .description("View a full conversation (use a number like 1, 2, 3 or a session ID)") .option("-n, --limit ", "Max events to display", "50") + .option("-s, --summary", "Show a narrative summary instead of the full conversation") .action(async (sessionRef: string, options) => { + const globalOpts = getGlobalOpts(); + try { + await showCommand(sessionRef, options, globalOpts); + } catch (err) { + handleError(err, globalOpts); + } + }); + +// ── devlog today ───────────────────────────────────────── +program + .command("today") + .description("What did I do today?") + .action(async () => { + const globalOpts = getGlobalOpts(); + try { + await todayCommand(globalOpts); + } catch (err) { + handleError(err, globalOpts); + } + }); + +// ── devlog search ──────────────────────────────── +program + .command("search ") + .description("Search sessions by message, project, or tool name") + .action(async (query: string) => { + const globalOpts = getGlobalOpts(); + try { + await searchCommand(query, globalOpts); + } catch (err) { + handleError(err, globalOpts); + } + }); + +// ── devlog stats ───────────────────────────────────────── +program + .command("stats") + .description("Aggregated usage statistics") + .option("--period ", "Filter: today, week, month, all", "all") + .action(async (options) => { + const globalOpts = getGlobalOpts(); + try { + await statsCommand(options, globalOpts); + } catch (err) { + handleError(err, globalOpts); + } + }); + +// ── devlog cost ────────────────────────────────────────── +program + .command("cost") + .description("Cost breakdown by project and model") + .option("--period ", "Filter: today, week, month, all", "all") + .action(async (options) => { + const globalOpts = getGlobalOpts(); try { - await showCommand(sessionRef, options); + await costCommand(options, globalOpts); } catch (err) { + handleError(err, globalOpts); + } + }); + +// ── "Did you mean?" for unknown commands (Principle 3) ─── +// Commander treats unknown words as args to default command. +// We intercept by checking process.argv before parse. +const userArgs = process.argv.slice(2).filter((a) => !a.startsWith("-")); +if (userArgs.length === 1 && !KNOWN_COMMANDS.includes(userArgs[0])) { + // Check if it looks like a mistyped command (not a session ID or number) + const candidate = userArgs[0]; + const isNumber = /^\d+$/.test(candidate); + const isSessionId = /^[0-9a-f]{6,}$/i.test(candidate); + + if (!isNumber && !isSessionId) { + let suggestion = ""; + let bestDist = Infinity; + + for (const cmd of KNOWN_COMMANDS) { + const dist = levenshtein(candidate, cmd); + if (dist < bestDist) { + bestDist = dist; + suggestion = cmd; + } + } + + if (bestDist <= 3) { + console.error(); + console.error( + chalk.yellow(` Unknown command: ${candidate}`) + ); console.error( - chalk.red("\n Error:"), - err instanceof Error ? err.message : err + chalk.dim(" Did you mean: ") + chalk.cyan(suggestion) + chalk.dim("?") ); + console.error( + chalk.dim(" Run ") + + chalk.cyan("devlog --help") + + chalk.dim(" to see all commands.") + ); + console.error(); process.exit(1); } - }); + } +} program.parse(); diff --git a/src/commands/cost.ts b/src/commands/cost.ts new file mode 100644 index 0000000..31b5db5 --- /dev/null +++ b/src/commands/cost.ts @@ -0,0 +1,178 @@ +import chalk from "chalk"; +import ora from "ora"; +import dayjs from "dayjs"; +import isToday from "dayjs/plugin/isToday.js"; +import { ensureInit } from "../core/config.js"; +import { discoverProjects } from "../core/discovery.js"; +import type { Session, GlobalOptions } from "../core/types.js"; +import { costWithContext } from "../utils/format.js"; +import { outputJson, isJsonMode, isQuietMode } from "../utils/output.js"; + +dayjs.extend(isToday); + +interface CostOptions { + period?: string; +} + +function filterByPeriod(sessions: Session[], period: string): Session[] { + const now = dayjs(); + return sessions.filter((s) => { + const d = dayjs(s.updatedAt); + switch (period) { + case "today": + return d.isToday(); + case "week": + return now.diff(d, "day") < 7; + case "month": + return now.diff(d, "day") < 30; + default: + return true; + } + }); +} + +function renderBar(fraction: number, width: number = 16): string { + const filled = Math.round(fraction * width); + return chalk.green("█".repeat(filled)) + chalk.dim("░".repeat(width - filled)); +} + +export async function costCommand( + options: CostOptions, + globalOpts: GlobalOptions +): Promise { + const { config } = ensureInit(); + const period = options.period || "all"; + + let spinner: ReturnType | null = null; + if (!isJsonMode() && !isQuietMode()) { + spinner = ora({ + text: chalk.dim(" Calculating costs..."), + spinner: "dots", + color: "cyan", + stream: process.stderr, + }).start(); + } + + const projects = await discoverProjects(config.claudeDir); + spinner?.stop(); + + const allSessions: Session[] = []; + for (const p of projects) allSessions.push(...p.sessions); + const filtered = filterByPeriod(allSessions, period); + + // Aggregate by project and model + const byProject = new Map(); + const byModel = new Map(); + let totalCost = 0; + + for (const s of filtered) { + totalCost += s.meta.totalCostUSD; + byProject.set( + s.projectName, + (byProject.get(s.projectName) || 0) + s.meta.totalCostUSD + ); + for (const [model, cost] of Object.entries(s.meta.costByModel)) { + byModel.set(model, (byModel.get(model) || 0) + cost); + } + } + + const periodLabel = + period === "today" + ? "Today" + : period === "week" + ? "This Week" + : period === "month" + ? "This Month" + : "All Time"; + + // JSON + if (isJsonMode()) { + outputJson({ + period: periodLabel, + totalCostUSD: Math.round(totalCost * 1000000) / 1000000, + byProject: [...byProject.entries()] + .sort((a, b) => b[1] - a[1]) + .map(([name, cost]) => ({ name, costUSD: Math.round(cost * 1000000) / 1000000 })), + byModel: [...byModel.entries()] + .sort((a, b) => b[1] - a[1]) + .map(([name, cost]) => ({ name, costUSD: Math.round(cost * 1000000) / 1000000 })), + }); + return; + } + + console.log(); + console.log( + chalk.bold.cyan(" ▌") + chalk.bold.white(` Cost Breakdown — ${periodLabel}`) + ); + console.log(); + + if (totalCost === 0) { + console.log(chalk.dim(" No costs recorded in this period.")); + console.log(); + return; + } + + console.log( + chalk.dim(" Total: ") + costWithContext(totalCost) + ); + console.log(); + + // By project + const projectEntries = [...byProject.entries()] + .sort((a, b) => b[1] - a[1]); + if (projectEntries.length > 0) { + console.log(chalk.white(" By project:")); + for (const [name, cost] of projectEntries) { + const pct = totalCost > 0 ? Math.round((cost / totalCost) * 100) : 0; + const costStr = cost < 1 ? `$${cost.toFixed(3)}` : `$${cost.toFixed(2)}`; + console.log( + chalk.dim(" ") + + chalk.cyan(name.padEnd(18)) + + chalk.yellow(costStr.padEnd(10)) + + chalk.dim(`(${pct}%)`.padEnd(7)) + + renderBar(cost / totalCost) + ); + } + console.log(); + } + + // By model + const modelEntries = [...byModel.entries()] + .sort((a, b) => b[1] - a[1]); + if (modelEntries.length > 0) { + console.log(chalk.white(" By model:")); + for (const [name, cost] of modelEntries) { + const pct = totalCost > 0 ? Math.round((cost / totalCost) * 100) : 0; + const costStr = cost < 1 ? `$${cost.toFixed(3)}` : `$${cost.toFixed(2)}`; + // Friendly model name + let label = name; + if (name.includes("opus")) label = "claude-opus"; + else if (name.includes("sonnet")) label = "claude-sonnet"; + else if (name.includes("haiku")) label = "claude-haiku"; + + let context = ""; + if (pct > 70) context = " most of your work"; + else if (pct < 20) context = " for the hard stuff"; + + console.log( + chalk.dim(" ") + + chalk.white(label.padEnd(18)) + + chalk.yellow(costStr.padEnd(10)) + + chalk.dim(`(${pct}%)`) + + chalk.dim(context) + ); + } + console.log(); + } + + console.log(chalk.dim(" " + "─".repeat(60))); + console.log(); + console.log( + chalk.dim(" ") + + chalk.cyan("devlog stats") + + chalk.dim(" usage trends · ") + + chalk.cyan("devlog cost --period week") + + chalk.dim(" filter") + ); + console.log(); +} diff --git a/src/commands/dashboard.ts b/src/commands/dashboard.ts index d8eddec..53fe668 100644 --- a/src/commands/dashboard.ts +++ b/src/commands/dashboard.ts @@ -7,25 +7,35 @@ import { computeStats, groupSessionsByTime, } from "../core/discovery.js"; -import type { Session, AggregateStats } from "../core/types.js"; +import type { Session, AggregateStats, GlobalOptions, DashboardJson } from "../core/types.js"; import { formatSmartTime, - formatCost, - formatDuration, formatNumber, truncate, + costWithContext, + messageCountContext, + toolCountContext, + fileCountContext, } from "../utils/format.js"; import { getClaudeProjectsDir } from "../utils/paths.js"; +import { outputJson, isJsonMode, isQuietMode } from "../utils/output.js"; +import { toSessionJson } from "./shared.js"; + +const VERSION = "0.3.0"; /** * The default command. This IS the product. * Run `devlog` → see your world with Claude. */ -export async function dashboardCommand(): Promise { +export async function dashboardCommand(globalOpts: GlobalOptions): Promise { const { config, isFirstRun } = ensureInit(); // ── No Claude Code installed ────────────────────── if (!existsSync(config.claudeDir)) { + if (isJsonMode()) { + outputJson({ error: "Claude Code not found", path: getClaudeProjectsDir() }); + process.exit(1); + } console.log(); console.log( chalk.bold.cyan(" ▌") + chalk.bold.white(" DevLog") @@ -55,20 +65,28 @@ export async function dashboardCommand(): Promise { } // ── Scan ────────────────────────────────────────── - const spinner = ora({ - text: chalk.dim(" Reading your Claude Code history..."), - spinner: "dots", - color: "cyan", - }).start(); + let spinner: ReturnType | null = null; + if (!isJsonMode() && !isQuietMode()) { + spinner = ora({ + text: chalk.dim(" Reading your Claude Code history..."), + spinner: "dots", + color: "cyan", + stream: process.stderr, + }).start(); + } const projects = await discoverProjects(config.claudeDir, (msg) => { - spinner.text = chalk.dim(` ${msg}`); + if (spinner) spinner.text = chalk.dim(` ${msg}`); }); - spinner.stop(); + spinner?.stop(); // ── No sessions yet ─────────────────────────────── if (projects.length === 0) { + if (isJsonMode()) { + outputJson({ version: VERSION, timestamp: new Date().toISOString(), summary: "No sessions found", stats: { totalProjects: 0, totalSessions: 0, totalToolCalls: 0, totalFilesTouched: 0, totalCostUSD: 0, todaySessions: 0, todayCostUSD: 0 }, recentSessions: [] }); + return; + } console.log(); console.log( chalk.bold.cyan(" ▌") + chalk.bold.white(" DevLog") @@ -87,18 +105,51 @@ export async function dashboardCommand(): Promise { const stats = computeStats(projects); const groups = groupSessionsByTime(projects); + // ── JSON output ──────────────────────────────────── + if (isJsonMode()) { + const allSessions = projects.flatMap((p) => p.sessions); + allSessions.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()); + const recent = allSessions.slice(0, 10); + + const projectWord = stats.totalProjects === 1 ? "project" : "projects"; + const sessionWord = stats.totalSessions === 1 ? "conversation" : "conversations"; + + const data: DashboardJson = { + version: VERSION, + timestamp: new Date().toISOString(), + summary: `You and Claude had ${stats.totalSessions} ${sessionWord} across ${stats.totalProjects} ${projectWord}`, + stats: { + totalProjects: stats.totalProjects, + totalSessions: stats.totalSessions, + totalToolCalls: stats.totalToolCalls, + totalFilesTouched: stats.allFilesReferenced.length, + totalCostUSD: Math.round(stats.totalCostUSD * 1000000) / 1000000, + todaySessions: stats.todaySessions, + todayCostUSD: Math.round(stats.todayCostUSD * 1000000) / 1000000, + }, + recentSessions: recent.map(toSessionJson), + }; + outputJson(data); + return; + } + // ── Render ──────────────────────────────────────── console.log(); - if (isFirstRun) { - renderWelcome(stats); - } else { - renderBanner(); + if (!isQuietMode()) { + if (isFirstRun) { + renderWelcome(stats); + } else { + renderBanner(); + } } renderNarrativeStats(stats); renderSessionGroups(groups, stats); - renderNextSteps(stats, isFirstRun); + + if (!isQuietMode()) { + renderNextSteps(stats, isFirstRun); + } } // ───────────────────────────────────────────────────── @@ -126,7 +177,6 @@ function renderBanner(): void { } function renderNarrativeStats(stats: AggregateStats): void { - // Main narrative: "You and Claude" story const projectWord = stats.totalProjects === 1 ? "project" : "projects"; const sessionWord = stats.totalSessions === 1 ? "conversation" : "conversations"; @@ -139,7 +189,6 @@ function renderNarrativeStats(stats: AggregateStats): void { chalk.white(` ${projectWord}.`) ); - // Activity line — what actually happened const activityParts: string[] = []; if (stats.totalToolCalls > 0) { @@ -162,7 +211,6 @@ function renderNarrativeStats(stats: AggregateStats): void { console.log(chalk.dim(" ") + activityParts.join(chalk.dim(" and "))); } - // Cost line — only if there's data, with context if (stats.totalCostUSD > 0) { const costStr = stats.totalCostUSD < 1 ? `$${stats.totalCostUSD.toFixed(3)}` @@ -181,7 +229,6 @@ function renderNarrativeStats(stats: AggregateStats): void { ); } - // Today highlight if (stats.todaySessions > 0) { const todayWord = stats.todaySessions === 1 ? "session" : "sessions"; console.log( @@ -259,7 +306,6 @@ function renderSessionCard(session: Session, accentColor: typeof chalk): void { ? truncate(m.firstUserMessage.replace(/\n/g, " ").trim(), 50) : chalk.dim("(empty session)"); - // Line 1: Time + Project name + What you asked const timeStr = time.length > 14 ? time.slice(0, 14) : time; console.log( chalk.dim(" ") + @@ -268,33 +314,12 @@ function renderSessionCard(session: Session, accentColor: typeof chalk): void { chalk.white(preview) ); - // Line 2: Human-readable activity summary - const parts: string[] = []; - - // Conversation depth const turns = m.humanTurns + m.assistantTurns; - if (turns <= 4) { - parts.push(chalk.dim("quick chat")); - } else if (turns <= 10) { - parts.push(chalk.dim(`${turns} messages`)); - } else { - parts.push(chalk.white(`${turns} messages`)); - } - - // What Claude did (in plain language) - if (m.toolCalls > 0) { - parts.push(chalk.green(`ran ${m.toolCalls} commands`)); - } - - if (m.filesReferenced.length > 0) { - const fileWord = m.filesReferenced.length === 1 ? "file" : "files"; - parts.push(chalk.blue(`wrote ${m.filesReferenced.length} ${fileWord}`)); - } - - if (m.totalCostUSD > 0) { - parts.push(formatCost(m.totalCostUSD)); - } - + const parts: string[] = []; + parts.push(messageCountContext(turns)); + if (m.toolCalls > 0) parts.push(toolCountContext(m.toolCalls)); + if (m.filesReferenced.length > 0) parts.push(fileCountContext(m.filesReferenced.length)); + if (m.totalCostUSD > 0) parts.push(costWithContext(m.totalCostUSD)); if (m.errorCount > 0) { const errWord = m.errorCount === 1 ? "error" : "errors"; parts.push(chalk.red(`${m.errorCount} ${errWord}`)); @@ -313,21 +338,24 @@ function renderNextSteps(stats: AggregateStats, isFirstRun: boolean): void { console.log(); if (isFirstRun) { - // First run: teach the user what they can do console.log(chalk.white(" What you can do next:")); console.log(); console.log( - chalk.cyan(" devlog sessions") + - chalk.dim(" Browse all sessions by project") + chalk.cyan(" devlog today") + + chalk.dim(" What did I do today?") ); console.log( - chalk.cyan(" devlog sessions -p chat") + - chalk.dim(" Filter to a specific project") + chalk.cyan(" devlog sessions") + + chalk.dim(" Browse all sessions by project") ); console.log( chalk.cyan(" devlog show ") + chalk.dim(" View a full conversation") ); + console.log( + chalk.cyan(" devlog search \"auth\"") + + chalk.dim(" Find a conversation") + ); console.log(); console.log( chalk.dim(" Tip: Just run ") + @@ -335,14 +363,16 @@ function renderNextSteps(stats: AggregateStats, isFirstRun: boolean): void { chalk.dim(" anytime to see this dashboard.") ); } else { - // Returning user: compact hints console.log( chalk.dim(" ") + + chalk.cyan("devlog today") + + chalk.dim(" today · ") + chalk.cyan("devlog sessions") + - chalk.dim(" all sessions · ") + + chalk.dim(" all · ") + chalk.cyan("devlog show ") + - chalk.dim(" view conversation · ") + - chalk.cyan("--help") + chalk.dim(" view · ") + + chalk.cyan("devlog search") + + chalk.dim(" find") ); } console.log(); diff --git a/src/commands/init.ts b/src/commands/init.ts index 6220b59..cdf3bea 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -9,35 +9,49 @@ import { printError, formatNumber, } from "../utils/format.js"; +import type { GlobalOptions } from "../core/types.js"; +import { outputJson, isJsonMode, isQuietMode } from "../utils/output.js"; -export async function initCommand(): Promise { - console.log(); - console.log( - chalk.bold.cyan(" ▌") + chalk.bold.white(" DevLog Setup") - ); - console.log(); +export async function initCommand(globalOpts: GlobalOptions): Promise { + if (!isJsonMode()) { + console.log(); + console.log( + chalk.bold.cyan(" ▌") + chalk.bold.white(" DevLog Setup") + ); + console.log(); + } // Already initialized if (isInitialized()) { const config = loadConfig(); - printSuccess("DevLog is already set up"); - console.log( - chalk.dim(" Config: ") + chalk.white(getDevlogDir() + "/config.toml") - ); - console.log( - chalk.dim(" Claude: ") + chalk.white(config.claudeDir) - ); - console.log(); - const spinner = ora({ - text: chalk.dim(" Checking your sessions..."), - spinner: "dots", - color: "cyan", - }).start(); + let spinner: ReturnType | null = null; + if (!isJsonMode() && !isQuietMode()) { + printSuccess("DevLog is already set up"); + console.log( + chalk.dim(" Config: ") + chalk.white(getDevlogDir() + "/config.toml") + ); + console.log( + chalk.dim(" Claude: ") + chalk.white(config.claudeDir) + ); + console.log(); + + spinner = ora({ + text: chalk.dim(" Checking your sessions..."), + spinner: "dots", + color: "cyan", + stream: process.stderr, + }).start(); + } const projects = await discoverProjects(config.claudeDir); const stats = computeStats(projects); - spinner.stop(); + spinner?.stop(); + + if (isJsonMode()) { + outputJson({ status: "already_initialized", configPath: getDevlogDir() + "/config.toml", stats: { totalProjects: stats.totalProjects, totalSessions: stats.totalSessions, totalMessages: stats.totalMessages } }); + return; + } renderStatsBox(stats); @@ -54,6 +68,10 @@ export async function initCommand(): Promise { const claudeDir = getClaudeProjectsDir(); if (!existsSync(claudeDir)) { + if (isJsonMode()) { + outputJson({ error: "Claude Code not found", path: claudeDir }); + process.exit(1); + } printError("Claude Code not found"); console.log(); console.log( @@ -74,20 +92,34 @@ export async function initCommand(): Promise { return; } - printSuccess("Found Claude Code"); + if (!isJsonMode()) { + printSuccess("Found Claude Code"); + } const config = initConfig(); - printSuccess("Created config at " + chalk.dim("~/.devlog/")); - const spinner = ora({ - text: chalk.dim(" Scanning your Claude Code history..."), - spinner: "dots", - color: "cyan", - }).start(); + if (!isJsonMode()) { + printSuccess("Created config at " + chalk.dim("~/.devlog/")); + } + + let spinner: ReturnType | null = null; + if (!isJsonMode() && !isQuietMode()) { + spinner = ora({ + text: chalk.dim(" Scanning your Claude Code history..."), + spinner: "dots", + color: "cyan", + stream: process.stderr, + }).start(); + } const projects = await discoverProjects(config.claudeDir); const stats = computeStats(projects); - spinner.stop(); + spinner?.stop(); + + if (isJsonMode()) { + outputJson({ status: "initialized", configPath: getDevlogDir() + "/config.toml", stats: { totalProjects: stats.totalProjects, totalSessions: stats.totalSessions, totalMessages: stats.totalMessages } }); + return; + } printSuccess("Scan complete"); console.log(); @@ -115,6 +147,12 @@ function renderStatsBox(stats: ReturnType): void { ); }; + const costStr = stats.totalCostUSD > 0 + ? stats.totalCostUSD < 1 + ? `$${stats.totalCostUSD.toFixed(3)}` + : `$${stats.totalCostUSD.toFixed(2)}` + : ""; + console.log(chalk.dim(" ┌" + "─".repeat(w) + "┐")); console.log(line("Projects", formatNumber(stats.totalProjects))); console.log(line("Conversations", formatNumber(stats.totalSessions))); @@ -123,8 +161,8 @@ function renderStatsBox(stats: ReturnType): void { console.log( line("Files touched", formatNumber(stats.allFilesReferenced.length)) ); - if (stats.totalCostUSD > 0) { - console.log(line("Total cost", `$${stats.totalCostUSD.toFixed(3)}`)); + if (costStr) { + console.log(line("Total cost", costStr)); } console.log(chalk.dim(" └" + "─".repeat(w) + "┘")); console.log(); diff --git a/src/commands/search.ts b/src/commands/search.ts new file mode 100644 index 0000000..534bc90 --- /dev/null +++ b/src/commands/search.ts @@ -0,0 +1,135 @@ +import chalk from "chalk"; +import ora from "ora"; +import { ensureInit } from "../core/config.js"; +import { discoverProjects } from "../core/discovery.js"; +import type { Session, GlobalOptions } from "../core/types.js"; +import { + formatSmartTime, + truncate, + costWithContext, + messageCountContext, +} from "../utils/format.js"; +import { outputJson, isJsonMode, isQuietMode } from "../utils/output.js"; +import { toSessionJson } from "./shared.js"; + +export async function searchCommand( + query: string, + globalOpts: GlobalOptions +): Promise { + const { config } = ensureInit(); + + let spinner: ReturnType | null = null; + if (!isJsonMode() && !isQuietMode()) { + spinner = ora({ + text: chalk.dim(" Searching sessions..."), + spinner: "dots", + color: "cyan", + stream: process.stderr, + }).start(); + } + + const projects = await discoverProjects(config.claudeDir); + spinner?.stop(); + + const q = query.toLowerCase(); + + // Search by first message, project name, or tool names + const matches: Session[] = []; + for (const project of projects) { + for (const session of project.sessions) { + const m = session.meta; + if ( + session.projectName.toLowerCase().includes(q) || + m.firstUserMessage.toLowerCase().includes(q) || + m.uniqueTools.some((t) => t.toLowerCase().includes(q)) + ) { + matches.push(session); + } + } + } + + matches.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()); + + // JSON output + if (isJsonMode()) { + outputJson({ + query, + matchCount: matches.length, + sessions: matches.map(toSessionJson), + }); + return; + } + + console.log(); + console.log( + chalk.bold.cyan(" ▌") + + chalk.bold.white(` Search: `) + + chalk.white(`"${query}"`) + ); + console.log(); + + if (matches.length === 0) { + console.log(chalk.dim(" No conversations found matching that query.")); + console.log(); + console.log( + chalk.dim(" Try a shorter term, or use ") + + chalk.cyan("devlog sessions") + + chalk.dim(" to browse all.") + ); + console.log(); + return; + } + + const matchWord = matches.length === 1 ? "conversation" : "conversations"; + console.log( + chalk.dim(` Found ${matches.length} ${matchWord} matching "${query}"`) + ); + console.log(); + + const shown = matches.slice(0, 20); + + for (let i = 0; i < shown.length; i++) { + const session = shown[i]; + const m = session.meta; + const time = formatSmartTime(session.updatedAt); + const preview = m.firstUserMessage + ? truncate(m.firstUserMessage.replace(/\n/g, " ").trim(), 42) + : chalk.dim("(empty)"); + + console.log( + chalk.dim(` ${(i + 1).toString().padEnd(3)} `) + + chalk.white(time.padEnd(16)) + + chalk.cyan(session.projectName.padEnd(14)) + + chalk.white(`"${preview}"`) + ); + + const turns = m.humanTurns + m.assistantTurns; + const parts: string[] = []; + parts.push(messageCountContext(turns)); + if (m.totalCostUSD > 0) parts.push(costWithContext(m.totalCostUSD)); + + console.log( + chalk.dim(" ") + + " " + + " ".repeat(16) + + parts.join(chalk.dim(" · ")) + ); + console.log(); + } + + if (matches.length > 20) { + console.log(chalk.dim(` + ${matches.length - 20} more matches`)); + console.log(); + } + + console.log(chalk.dim(" " + "─".repeat(60))); + console.log(); + console.log( + chalk.dim(" ") + + chalk.cyan("devlog show 1") + + chalk.dim(" view match · ") + + chalk.cyan(`devlog search "${query} ..."`) + + chalk.dim(" refine") + ); + console.log(); +} diff --git a/src/commands/sessions.ts b/src/commands/sessions.ts index d4646e4..8461e60 100644 --- a/src/commands/sessions.ts +++ b/src/commands/sessions.ts @@ -2,14 +2,18 @@ import chalk from "chalk"; import ora from "ora"; import { ensureInit } from "../core/config.js"; import { discoverProjects, computeStats } from "../core/discovery.js"; -import type { Session } from "../core/types.js"; +import type { Session, GlobalOptions, SessionsJson } from "../core/types.js"; import { - printWarn, formatSmartTime, - formatCost, truncate, formatNumber, + costWithContext, + messageCountContext, + toolCountContext, + fileCountContext, } from "../utils/format.js"; +import { outputJson, isJsonMode, isQuietMode } from "../utils/output.js"; +import { toSessionJson } from "./shared.js"; interface SessionsOptions { project?: string; @@ -17,21 +21,29 @@ interface SessionsOptions { all?: boolean; } -export async function sessionsCommand(options: SessionsOptions): Promise { - const { config } = ensureInit(); +export async function sessionsCommand(options: SessionsOptions, globalOpts: GlobalOptions): Promise { + const { config, isFirstRun } = ensureInit(); const limit = options.all ? Infinity : parseInt(options.limit || "30", 10); - const spinner = ora({ - text: chalk.dim(" Scanning sessions..."), - spinner: "dots", - color: "cyan", - }).start(); + let spinner: ReturnType | null = null; + if (!isJsonMode() && !isQuietMode()) { + spinner = ora({ + text: chalk.dim(" Scanning sessions..."), + spinner: "dots", + color: "cyan", + stream: process.stderr, + }).start(); + } const projects = await discoverProjects(config.claudeDir); const stats = computeStats(projects); - spinner.stop(); + spinner?.stop(); if (projects.length === 0) { + if (isJsonMode()) { + outputJson({ projects: [] }); + return; + } console.log(); console.log(chalk.white(" No sessions found yet.")); console.log( @@ -50,6 +62,20 @@ export async function sessionsCommand(options: SessionsOptions): Promise { ) : projects; + // ── JSON output ──────────────────────────────────── + if (isJsonMode()) { + const data: SessionsJson = { + projects: filteredProjects.map((p) => ({ + name: p.name, + path: p.path, + sessionCount: p.sessionCount, + sessions: p.sessions.map(toSessionJson), + })), + }; + outputJson(data); + return; + } + if (filteredProjects.length === 0) { console.log(); console.log( @@ -87,13 +113,24 @@ export async function sessionsCommand(options: SessionsOptions): Promise { ); console.log(); + if (isFirstRun) { + console.log( + chalk.dim(" Each project shows your Claude conversations, newest first.") + ); + console.log( + chalk.dim(" Pick one with ") + + chalk.cyan("devlog show ") + + chalk.dim(" to see the full chat.") + ); + console.log(); + } + let displayedCount = 0; let sessionIndex = 0; for (const project of filteredProjects) { if (displayedCount >= limit) break; - // Project header const sessionWord = project.sessionCount === 1 ? "session" : "sessions"; console.log( chalk.bold.white(" 📁 " + project.name) + @@ -141,32 +178,17 @@ function renderSessionRow(session: Session, index: number): void { ? truncate(m.firstUserMessage.replace(/\n/g, " ").trim(), 46) : chalk.dim("(empty)"); - // Line 1: index + time + first message const indexStr = chalk.dim(`${index}.`.padEnd(4)); console.log( chalk.dim(" ") + indexStr + chalk.white(time.padEnd(16)) + chalk.white(preview) ); - // Line 2: human-readable activity summary - const parts: string[] = []; const turns = m.humanTurns + m.assistantTurns; - - if (turns <= 4) { - parts.push(chalk.dim("quick chat")); - } else { - parts.push(chalk.dim(`${turns} messages`)); - } - - if (m.toolCalls > 0) { - parts.push(chalk.green(`ran ${m.toolCalls} commands`)); - } - if (m.filesReferenced.length > 0) { - const fileWord = m.filesReferenced.length === 1 ? "file" : "files"; - parts.push(chalk.blue(`wrote ${m.filesReferenced.length} ${fileWord}`)); - } - if (m.totalCostUSD > 0) { - parts.push(formatCost(m.totalCostUSD)); - } + const parts: string[] = []; + parts.push(messageCountContext(turns)); + if (m.toolCalls > 0) parts.push(toolCountContext(m.toolCalls)); + if (m.filesReferenced.length > 0) parts.push(fileCountContext(m.filesReferenced.length)); + if (m.totalCostUSD > 0) parts.push(costWithContext(m.totalCostUSD)); if (m.errorCount > 0) { parts.push(chalk.red(`${m.errorCount} error${m.errorCount === 1 ? "" : "s"}`)); } diff --git a/src/commands/shared.ts b/src/commands/shared.ts new file mode 100644 index 0000000..b03cac8 --- /dev/null +++ b/src/commands/shared.ts @@ -0,0 +1,16 @@ +import type { Session, SessionJson } from "../core/types.js"; + +export function toSessionJson(session: Session): SessionJson { + return { + id: session.id, + projectName: session.projectName, + projectPath: session.projectPath, + createdAt: session.createdAt.toISOString(), + updatedAt: session.updatedAt.toISOString(), + messageCount: session.meta.messageCount, + toolCalls: session.meta.toolCalls, + filesTouched: session.meta.filesReferenced.length, + costUSD: Math.round(session.meta.totalCostUSD * 1000000) / 1000000, + firstMessage: session.meta.firstUserMessage, + }; +} diff --git a/src/commands/show.ts b/src/commands/show.ts index 66634b6..1473617 100644 --- a/src/commands/show.ts +++ b/src/commands/show.ts @@ -3,16 +3,21 @@ import ora from "ora"; import { ensureInit } from "../core/config.js"; import { discoverProjects } from "../core/discovery.js"; import { parseSessionFile } from "../core/parser.js"; -import type { Session, DevLogEvent } from "../core/types.js"; +import type { Session, DevLogEvent, GlobalOptions, ShowJson } from "../core/types.js"; import { formatSmartTime, - formatCost, - formatDuration, truncate, + costWithContext, + messageCountContext, + toolCountContext, + fileCountContext, + humanizeToolSummary, } from "../utils/format.js"; +import { outputJson, isJsonMode, isQuietMode } from "../utils/output.js"; interface ShowOptions { limit?: string; + summary?: boolean; } /** @@ -22,19 +27,24 @@ interface ShowOptions { */ export async function showCommand( sessionRef: string, - options: ShowOptions + options: ShowOptions, + globalOpts: GlobalOptions ): Promise { const { config } = ensureInit(); const limit = parseInt(options.limit || "50", 10); - const spinner = ora({ - text: chalk.dim(" Finding session..."), - spinner: "dots", - color: "cyan", - }).start(); + let spinner: ReturnType | null = null; + if (!isJsonMode() && !isQuietMode()) { + spinner = ora({ + text: chalk.dim(" Finding session..."), + spinner: "dots", + color: "cyan", + stream: process.stderr, + }).start(); + } const projects = await discoverProjects(config.claudeDir); - spinner.stop(); + spinner?.stop(); // Flatten all sessions const allSessions: Session[] = []; @@ -44,6 +54,10 @@ export async function showCommand( allSessions.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()); if (allSessions.length === 0) { + if (isJsonMode()) { + outputJson({ error: "No sessions found" }); + process.exit(1); + } console.log(); console.log(chalk.yellow(" No sessions found.")); console.log(); @@ -55,17 +69,25 @@ export async function showCommand( const asNumber = parseInt(sessionRef, 10); if (!isNaN(asNumber) && asNumber >= 1 && asNumber <= allSessions.length) { - // Numeric reference: "devlog show 1" = most recent session session = allSessions[asNumber - 1]; } else { - // ID prefix match const ref = sessionRef.toLowerCase(); + // Try prefix match first, then substring match session = allSessions.find((s) => s.id.toLowerCase().startsWith(ref) ); + if (!session) { + session = allSessions.find((s) => + s.id.toLowerCase().includes(ref) + ); + } } if (!session) { + if (isJsonMode()) { + outputJson({ error: `No session matching: ${sessionRef}` }); + process.exit(1); + } console.log(); console.log( chalk.yellow(" Couldn't find a session matching: ") + @@ -92,23 +114,52 @@ export async function showCommand( chalk.dim(" to view the most recent session.") ); console.log(); - return; + process.exit(1); } // Parse the full session - const parseSpinner = ora({ - text: chalk.dim(" Reading conversation..."), - spinner: "dots", - color: "cyan", - }).start(); + let parseSpinner: ReturnType | null = null; + if (!isJsonMode() && !isQuietMode()) { + parseSpinner = ora({ + text: chalk.dim(" Reading conversation..."), + spinner: "dots", + color: "cyan", + stream: process.stderr, + }).start(); + } const events = await parseSessionFile(session.filePath, session.id); - parseSpinner.stop(); + parseSpinner?.stop(); + + // ── JSON output ──────────────────────────────────── + if (isJsonMode()) { + const data: ShowJson = { + session: { + id: session.id, + projectName: session.projectName, + meta: session.meta, + }, + events: events.map((e) => ({ + timestamp: e.timestamp.toISOString(), + role: e.role, + type: e.type, + content: e.content, + ...(e.toolName ? { toolName: e.toolName } : {}), + ...(e.isError ? { isError: e.isError } : {}), + })), + }; + outputJson(data); + return; + } // Render - renderSessionHeader(session); - renderConversation(events, limit); - renderSessionFooter(session, events, limit); + if (options.summary) { + renderSummary(session, events); + } else { + renderSessionHeader(session); + renderConversation(events, limit); + renderSessionFooter(session, events, limit); + } } function renderSessionHeader(session: Session): void { @@ -122,28 +173,18 @@ function renderSessionHeader(session: Session): void { ); console.log(); - // Session summary in plain language - const parts: string[] = []; const turns = m.humanTurns + m.assistantTurns; - parts.push(`${turns} messages`); - - if (m.toolCalls > 0) { - parts.push(`${m.toolCalls} commands run`); - } - if (m.filesReferenced.length > 0) { - parts.push(`${m.filesReferenced.length} files touched`); - } - if (m.totalCostUSD > 0) { - parts.push(`$${m.totalCostUSD.toFixed(3)} cost`); - } + const parts: string[] = []; + parts.push(messageCountContext(turns)); + if (m.toolCalls > 0) parts.push(toolCountContext(m.toolCalls)); + if (m.filesReferenced.length > 0) parts.push(fileCountContext(m.filesReferenced.length)); + if (m.totalCostUSD > 0) parts.push(costWithContext(m.totalCostUSD)); - console.log(chalk.dim(" " + parts.join(" · "))); + console.log(chalk.dim(" ") + parts.join(chalk.dim(" · "))); - // Show which tools were used if (m.uniqueTools.length > 0) { console.log( - chalk.dim(" Tools: ") + - m.uniqueTools.map((t) => chalk.green(t)).join(chalk.dim(", ")) + chalk.dim(" ") + humanizeToolSummary(m.uniqueTools) ); } @@ -152,14 +193,64 @@ function renderSessionHeader(session: Session): void { console.log(); } +function formatToolLine(event: DevLogEvent): string { + const name = event.toolName || "tool"; + const input = event.toolInput || {}; + + if (name === "Read" && input.file_path) return `read ${input.file_path}`; + if (name === "Write" && input.file_path) return `wrote ${input.file_path}`; + if (name === "Edit" && input.file_path) return `edited ${input.file_path}`; + if (name === "Bash" && input.command) return `ran: ${truncate(String(input.command), 60)}`; + if (name === "Grep" && input.pattern) return `searched for "${truncate(String(input.pattern), 40)}"`; + if (name === "Glob" && input.pattern) return `found files matching ${truncate(String(input.pattern), 40)}`; + + // Fallback: humanize + return name.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase(); +} + +function flushToolGroup(group: DevLogEvent[]): void { + if (group.length === 0) return; + + const firstName = group[0].toolName || "tool"; + const allSame = group.every((e) => e.toolName === firstName); + + if (allSame && group.length > 2 && (firstName === "Read" || firstName === "Write" || firstName === "Edit")) { + // Group consecutive same-type file operations + const verb = firstName === "Read" ? "read" : firstName === "Write" ? "wrote" : "edited"; + const files = group + .map((e) => { + const p = String(e.toolInput?.file_path || ""); + return p.split("/").pop() || p; + }) + .filter(Boolean); + const fileWord = group.length === 1 ? "file" : "files"; + console.log( + chalk.green(" ▸ ") + + chalk.dim(`${verb} ${group.length} ${fileWord}: ${truncate(files.join(", "), 60)}`) + ); + } else { + for (const event of group) { + console.log( + chalk.green(" ▸ ") + chalk.dim(formatToolLine(event)) + ); + } + } +} + function renderConversation(events: DevLogEvent[], limit: number): void { let shown = 0; + let toolGroup: DevLogEvent[] = []; for (const event of events) { if (shown >= limit) break; - if (event.role === "human" && event.type === "text") { - // User message + // Flush tool group on non-tool event + if (event.role !== "tool_use" && event.role !== "tool_result" && toolGroup.length > 0) { + flushToolGroup(toolGroup); + toolGroup = []; + } + + if (event.role === "human" && (event.type === "text" || event.type === "message")) { console.log( chalk.blue.bold(" You: ") + chalk.white(truncate(event.content.trim(), 76)) @@ -167,7 +258,6 @@ function renderConversation(events: DevLogEvent[], limit: number): void { console.log(); shown++; } else if (event.role === "assistant" && event.type === "text") { - // Claude's response — show first few lines const lines = event.content.trim().split("\n"); const preview = lines.slice(0, 3); console.log(chalk.dim.bold(" Claude: ")); @@ -180,33 +270,22 @@ function renderConversation(events: DevLogEvent[], limit: number): void { console.log(); shown++; } else if (event.role === "tool_use") { - // Tool call — compact display - console.log( - chalk.green(" ⚡ ") + - chalk.green(event.toolName || "tool") + - chalk.dim(" ") + - chalk.dim(truncate(event.content.replace(event.toolName + "(", "").replace(/\)$/, ""), 60)) - ); + toolGroup.push(event); shown++; } else if (event.role === "tool_result") { - // Tool result — very compact if (event.isError) { console.log( chalk.red(" ✗ Error: ") + chalk.dim(truncate(event.content, 60)) ); - } else { - const resultPreview = event.content.trim(); - if (resultPreview.length > 0 && resultPreview.length < 80) { - console.log( - chalk.dim(" → ") + - chalk.dim(truncate(resultPreview, 70)) - ); - } } - // Don't increment shown for tool results (they're part of the tool call) } } + + // Flush remaining tool group + if (toolGroup.length > 0) { + flushToolGroup(toolGroup); + } } function renderSessionFooter( @@ -216,7 +295,7 @@ function renderSessionFooter( ): void { const totalEvents = events.filter( (e) => - (e.role === "human" && e.type === "text") || + (e.role === "human" && (e.type === "text" || e.type === "message")) || (e.role === "assistant" && e.type === "text") || e.role === "tool_use" ).length; @@ -232,8 +311,118 @@ function renderSessionFooter( ); } + console.log(); + console.log( + chalk.dim(" ") + + chalk.cyan(`devlog sessions -p ${session.projectName}`) + + chalk.dim(" other sessions in this project") + ); + console.log( + chalk.dim(" ") + + chalk.cyan("devlog") + + chalk.dim(" back to dashboard") + ); + console.log(); +} + +function renderSummary(session: Session, events: DevLogEvent[]): void { + const m = session.meta; + + console.log(); + console.log( + chalk.bold.cyan(" ▌") + + chalk.bold.white(` ${session.projectName}`) + + chalk.dim(" · ") + + chalk.dim(formatSmartTime(session.updatedAt)) + ); + console.log(); + + // Extract what was asked + const firstMsg = m.firstUserMessage + ? truncate(m.firstUserMessage.replace(/\n/g, " ").trim(), 70) + : "(empty)"; + console.log(chalk.white(" Summary:")); + console.log( + chalk.dim(" You asked Claude: ") + chalk.white(`"${firstMsg}"`) + ); + console.log(); + + // What happened — group tool usage + const toolGroups = new Map(); + const filesChanged = new Set(); + const commandsRun: string[] = []; + let errorCount = 0; + + for (const event of events) { + if (event.role === "tool_use") { + const name = event.toolName || "tool"; + toolGroups.set(name, (toolGroups.get(name) || 0) + 1); + + const input = event.toolInput || {}; + if (input.file_path && (name === "Write" || name === "Edit")) { + filesChanged.add(String(input.file_path)); + } + if (name === "Bash" && input.command) { + commandsRun.push(truncate(String(input.command), 50)); + } + } + if (event.role === "tool_result" && event.isError) { + errorCount++; + } + } + + console.log(chalk.white(" What happened:")); + + for (const [name, count] of toolGroups) { + let desc: string; + if (name === "Read") desc = `Read ${count} file${count === 1 ? "" : "s"} to understand the codebase`; + else if (name === "Write") desc = `Created ${count} file${count === 1 ? "" : "s"}`; + else if (name === "Edit") desc = `Edited ${count} file${count === 1 ? "" : "s"}`; + else if (name === "Bash") desc = `Ran ${count} command${count === 1 ? "" : "s"}` + (commandsRun.length > 0 ? ` (${truncate(commandsRun.slice(0, 3).join(", "), 40)})` : ""); + else if (name === "Grep") desc = `Searched code ${count} time${count === 1 ? "" : "s"}`; + else if (name === "Glob") desc = `Searched for files ${count} time${count === 1 ? "" : "s"}`; + else desc = `${name} ×${count}`; + + console.log(chalk.dim(" - ") + chalk.dim(desc)); + } + + if (filesChanged.size > 0) { + const fileNames = [...filesChanged].map((f) => f.split("/").pop() || f); + console.log( + chalk.dim(" - ") + + chalk.dim(`Files changed: ${truncate(fileNames.join(", "), 50)}`) + ); + } + + if (errorCount > 0) { + console.log( + chalk.dim(" - ") + + chalk.red(`Fixed ${errorCount} error${errorCount === 1 ? "" : "s"}`) + ); + } + + console.log(); + + // Stats line + const turns = m.humanTurns + m.assistantTurns; + const parts: string[] = []; + parts.push(messageCountContext(turns)); + if (m.toolCalls > 0) parts.push(toolCountContext(m.toolCalls)); + if (m.filesReferenced.length > 0) parts.push(fileCountContext(m.filesReferenced.length)); + if (m.totalCostUSD > 0) parts.push(costWithContext(m.totalCostUSD)); + + console.log(chalk.dim(" ") + parts.join(chalk.dim(" · "))); + + // Footer + console.log(); + console.log(chalk.dim(" " + "─".repeat(60))); + console.log(); console.log( - chalk.dim(" Session ID: ") + chalk.dim(session.id) + chalk.dim(" ") + + chalk.cyan(`devlog show ${session.id.slice(0, 8)}`) + + chalk.dim(" see full conversation · ") + + chalk.cyan(`devlog sessions -p ${session.projectName}`) + + chalk.dim(" other sessions") ); console.log(); } diff --git a/src/commands/stats.ts b/src/commands/stats.ts new file mode 100644 index 0000000..34b4caf --- /dev/null +++ b/src/commands/stats.ts @@ -0,0 +1,210 @@ +import chalk from "chalk"; +import ora from "ora"; +import dayjs from "dayjs"; +import isToday from "dayjs/plugin/isToday.js"; +import { ensureInit } from "../core/config.js"; +import { discoverProjects, computeStats } from "../core/discovery.js"; +import type { Session, GlobalOptions } from "../core/types.js"; +import { + formatNumber, + costWithContext, +} from "../utils/format.js"; +import { outputJson, isJsonMode, isQuietMode } from "../utils/output.js"; + +dayjs.extend(isToday); + +interface StatsOptions { + period?: string; +} + +function filterByPeriod(sessions: Session[], period: string): Session[] { + const now = dayjs(); + return sessions.filter((s) => { + const d = dayjs(s.updatedAt); + switch (period) { + case "today": + return d.isToday(); + case "week": + return now.diff(d, "day") < 7; + case "month": + return now.diff(d, "day") < 30; + default: + return true; + } + }); +} + +export async function statsCommand( + options: StatsOptions, + globalOpts: GlobalOptions +): Promise { + const { config } = ensureInit(); + const period = options.period || "all"; + + let spinner: ReturnType | null = null; + if (!isJsonMode() && !isQuietMode()) { + spinner = ora({ + text: chalk.dim(" Crunching numbers..."), + spinner: "dots", + color: "cyan", + stream: process.stderr, + }).start(); + } + + const projects = await discoverProjects(config.claudeDir); + spinner?.stop(); + + const allSessions: Session[] = []; + for (const p of projects) allSessions.push(...p.sessions); + const filtered = filterByPeriod(allSessions, period); + + // Aggregate + let totalCost = 0; + let totalTools = 0; + let totalMessages = 0; + const fileSet = new Set(); + const toolCounts = new Map(); + const projectCounts = new Map(); + const costByModel: Record = {}; + + for (const s of filtered) { + totalCost += s.meta.totalCostUSD; + totalTools += s.meta.toolCalls; + totalMessages += s.meta.humanTurns + s.meta.assistantTurns; + for (const f of s.meta.filesReferenced) fileSet.add(f); + for (const t of s.meta.uniqueTools) { + toolCounts.set(t, (toolCounts.get(t) || 0) + 1); + } + projectCounts.set( + s.projectName, + (projectCounts.get(s.projectName) || 0) + 1 + ); + for (const [model, cost] of Object.entries(s.meta.costByModel)) { + costByModel[model] = (costByModel[model] || 0) + cost; + } + } + + const periodLabel = + period === "today" + ? "Today" + : period === "week" + ? "This Week" + : period === "month" + ? "This Month" + : "All Time"; + + // JSON + if (isJsonMode()) { + outputJson({ + period: periodLabel, + sessionCount: filtered.length, + totalMessages, + totalToolCalls: totalTools, + totalFilesTouched: fileSet.size, + totalCostUSD: Math.round(totalCost * 1000000) / 1000000, + topTools: [...toolCounts.entries()] + .sort((a, b) => b[1] - a[1]) + .slice(0, 10) + .map(([name, count]) => ({ name, count })), + projectBreakdown: [...projectCounts.entries()] + .sort((a, b) => b[1] - a[1]) + .map(([name, count]) => ({ name, sessions: count })), + costByModel, + }); + return; + } + + if (filtered.length === 0) { + console.log(); + console.log( + chalk.bold.cyan(" ▌") + chalk.bold.white(` Stats — ${periodLabel}`) + ); + console.log(); + console.log(chalk.dim(" No sessions in this period.")); + console.log(); + return; + } + + console.log(); + console.log( + chalk.bold.cyan(" ▌") + chalk.bold.white(` Stats — ${periodLabel}`) + ); + console.log(); + + // Stats box + const w = 42; + const line = (label: string, value: string) => { + const padding = w - label.length - value.length - 6; + return ( + chalk.dim(" │ ") + + chalk.white(label) + + " ".repeat(Math.max(1, padding)) + + chalk.cyan.bold(value) + + chalk.dim(" │") + ); + }; + + console.log(chalk.dim(" ┌" + "─".repeat(w) + "┐")); + console.log(line("Sessions", formatNumber(filtered.length))); + console.log(line("Messages", formatNumber(totalMessages))); + console.log(line("Commands run", formatNumber(totalTools))); + console.log(line("Files touched", formatNumber(fileSet.size))); + if (totalCost > 0) { + const costStr = totalCost < 1 ? `$${totalCost.toFixed(3)}` : `$${totalCost.toFixed(2)}`; + console.log(line("Total cost", costStr)); + } + console.log(chalk.dim(" └" + "─".repeat(w) + "┘")); + console.log(); + + // Top tools + const topTools = [...toolCounts.entries()] + .sort((a, b) => b[1] - a[1]) + .slice(0, 5); + if (topTools.length > 0) { + console.log(chalk.white(" Most used tools:")); + for (const [name, count] of topTools) { + console.log( + chalk.dim(" ") + + chalk.green(name.padEnd(16)) + + chalk.dim(`used in ${count} session${count === 1 ? "" : "s"}`) + ); + } + console.log(); + } + + // Most active project + const topProjects = [...projectCounts.entries()] + .sort((a, b) => b[1] - a[1]) + .slice(0, 3); + if (topProjects.length > 0) { + console.log(chalk.white(" Most active projects:")); + for (const [name, count] of topProjects) { + const sessionWord = count === 1 ? "session" : "sessions"; + console.log( + chalk.dim(" ") + + chalk.cyan(name.padEnd(16)) + + chalk.dim(`${count} ${sessionWord}`) + ); + } + console.log(); + } + + // Cost context + if (totalCost > 0) { + console.log( + chalk.dim(" Total cost: ") + costWithContext(totalCost) + ); + console.log(); + } + + console.log(chalk.dim(" " + "─".repeat(60))); + console.log(); + console.log( + chalk.dim(" ") + + chalk.cyan("devlog cost") + + chalk.dim(" cost breakdown · ") + + chalk.cyan("devlog stats --period week") + + chalk.dim(" filter") + ); + console.log(); +} diff --git a/src/commands/today.ts b/src/commands/today.ts new file mode 100644 index 0000000..206fffe --- /dev/null +++ b/src/commands/today.ts @@ -0,0 +1,183 @@ +import chalk from "chalk"; +import ora from "ora"; +import dayjs from "dayjs"; +import isToday from "dayjs/plugin/isToday.js"; +import { ensureInit } from "../core/config.js"; +import { discoverProjects } from "../core/discovery.js"; +import type { Session, GlobalOptions } from "../core/types.js"; +import { + formatSmartTime, + truncate, + costWithContext, + messageCountContext, + toolCountContext, + fileCountContext, +} from "../utils/format.js"; +import { outputJson, isJsonMode, isQuietMode } from "../utils/output.js"; +import { toSessionJson } from "./shared.js"; + +dayjs.extend(isToday); + +export async function todayCommand(globalOpts: GlobalOptions): Promise { + const { config } = ensureInit(); + + let spinner: ReturnType | null = null; + if (!isJsonMode() && !isQuietMode()) { + spinner = ora({ + text: chalk.dim(" Checking today's sessions..."), + spinner: "dots", + color: "cyan", + stream: process.stderr, + }).start(); + } + + const projects = await discoverProjects(config.claudeDir); + spinner?.stop(); + + // Filter to today's sessions + const todaySessions: Session[] = []; + const projectNames = new Set(); + + for (const project of projects) { + for (const session of project.sessions) { + if (dayjs(session.updatedAt).isToday()) { + todaySessions.push(session); + projectNames.add(session.projectName); + } + } + } + + todaySessions.sort((a, b) => a.updatedAt.getTime() - b.updatedAt.getTime()); + + // JSON output + if (isJsonMode()) { + const totalCost = todaySessions.reduce((s, sess) => s + sess.meta.totalCostUSD, 0); + const totalTools = todaySessions.reduce((s, sess) => s + sess.meta.toolCalls, 0); + const allFiles = new Set(); + for (const s of todaySessions) { + for (const f of s.meta.filesReferenced) allFiles.add(f); + } + + outputJson({ + date: dayjs().format("YYYY-MM-DD"), + sessionCount: todaySessions.length, + projectCount: projectNames.size, + totalCostUSD: Math.round(totalCost * 1000000) / 1000000, + totalToolCalls: totalTools, + totalFilesTouched: allFiles.size, + sessions: todaySessions.map(toSessionJson), + }); + return; + } + + // No sessions today + if (todaySessions.length === 0) { + console.log(); + console.log( + chalk.bold.cyan(" ▌") + chalk.bold.white(" Your day so far") + ); + console.log(); + console.log(chalk.dim(" No sessions yet today.")); + + // Find most recent session + const allSessions: Session[] = []; + for (const p of projects) allSessions.push(...p.sessions); + allSessions.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()); + + if (allSessions.length > 0) { + const last = allSessions[0]; + console.log( + chalk.dim(" Your last session was ") + + chalk.white(formatSmartTime(last.updatedAt)) + + chalk.dim(" in ") + + chalk.cyan(last.projectName) + ); + } + + console.log(); + console.log( + chalk.dim(" ") + + chalk.cyan("devlog sessions") + + chalk.dim(" browse all · ") + + chalk.cyan("devlog") + + chalk.dim(" dashboard") + ); + console.log(); + return; + } + + // Narrative summary + const totalCost = todaySessions.reduce((s, sess) => s + sess.meta.totalCostUSD, 0); + const totalTools = todaySessions.reduce((s, sess) => s + sess.meta.toolCalls, 0); + const allFiles = new Set(); + for (const s of todaySessions) { + for (const f of s.meta.filesReferenced) allFiles.add(f); + } + + const sessionWord = todaySessions.length === 1 ? "conversation" : "conversations"; + const projectWord = projectNames.size === 1 ? "project" : "projects"; + + console.log(); + console.log( + chalk.bold.cyan(" ▌") + chalk.bold.white(" Your day so far") + ); + console.log(); + console.log( + chalk.dim(" ") + + chalk.white(`${todaySessions.length} ${sessionWord} across ${projectNames.size} ${projectWord}.`) + ); + + const summaryParts: string[] = []; + if (totalTools > 0) summaryParts.push(toolCountContext(totalTools)); + if (allFiles.size > 0) summaryParts.push(fileCountContext(allFiles.size)); + if (summaryParts.length > 0) { + console.log(chalk.dim(" ") + chalk.white("Claude ") + summaryParts.join(chalk.dim(" and "))); + } + if (totalCost > 0) { + console.log(chalk.dim(" Cost: ") + costWithContext(totalCost)); + } + + console.log(); + console.log(chalk.dim(" " + "─".repeat(60))); + console.log(); + + // Timeline + for (const session of todaySessions) { + const m = session.meta; + const time = formatSmartTime(session.updatedAt); + const preview = m.firstUserMessage + ? truncate(m.firstUserMessage.replace(/\n/g, " ").trim(), 42) + : chalk.dim("(empty)"); + + console.log( + chalk.dim(" ") + + chalk.white(time.padEnd(12)) + + chalk.cyan(session.projectName.padEnd(14)) + + chalk.white(`"${preview}"`) + ); + + const turns = m.humanTurns + m.assistantTurns; + const parts: string[] = []; + parts.push(messageCountContext(turns)); + if (m.toolCalls > 0) parts.push(toolCountContext(m.toolCalls)); + if (m.totalCostUSD > 0) parts.push(costWithContext(m.totalCostUSD)); + + console.log( + chalk.dim(" ") + + " ".repeat(26) + + parts.join(chalk.dim(" · ")) + ); + console.log(); + } + + console.log(chalk.dim(" " + "─".repeat(60))); + console.log(); + console.log( + chalk.dim(" ") + + chalk.cyan("devlog show 1") + + chalk.dim(" view most recent · ") + + chalk.cyan("devlog sessions") + + chalk.dim(" browse all") + ); + console.log(); +} diff --git a/src/core/parser.ts b/src/core/parser.ts index f046ed0..41db5d5 100644 --- a/src/core/parser.ts +++ b/src/core/parser.ts @@ -9,10 +9,50 @@ import type { ToolResultBlock, SessionMeta, } from "./types.js"; +import { computeCost } from "./pricing.js"; + +// Types to skip entirely +const SKIP_TYPES = new Set([ + "progress", + "file-history-snapshot", + "queue-operation", +]); + +/** + * Resolve the effective role from the event. + * Real Claude Code uses type:"user"/"assistant", legacy uses type:"human"/"assistant". + */ +function resolveRole(event: RawJsonlEvent): "human" | "assistant" | null { + const t = event.type; + if (t === "human" || t === "user") return "human"; + if (t === "assistant") return "assistant"; + return null; +} + +/** + * Get content blocks from the event, supporting both real and legacy formats. + * Real: event.message.content + * Legacy: event.content + */ +function getContent(event: RawJsonlEvent): ContentBlock[] | string | undefined { + if (event.message && typeof event.message === "object" && event.message.content != null) { + return event.message.content; + } + return event.content; +} + +/** + * Get model from the event, supporting both formats. + */ +function getModel(event: RawJsonlEvent): string | undefined { + if (event.message && typeof event.message === "object" && event.message.model) { + return event.message.model; + } + return event.model; +} /** * Rich scan: extract full metadata from a session file in a single streaming pass. - * This replaces the old quickScanSession — same performance, 10x more insight. */ export async function scanSession(filePath: string): Promise { const meta: SessionMeta = { @@ -29,6 +69,7 @@ export async function scanSession(filePath: string): Promise { lastActivity: new Date(0), firstActivity: new Date(), errorCount: 0, + costByModel: {}, }; const toolSet = new Set(); @@ -45,6 +86,9 @@ export async function scanSession(filePath: string): Promise { try { const event = JSON.parse(line) as RawJsonlEvent; + + if (SKIP_TYPES.has(event.type)) continue; + const ts = event.timestamp ? new Date(event.timestamp) : null; if (ts) { @@ -52,37 +96,57 @@ export async function scanSession(filePath: string): Promise { if (ts > meta.lastActivity) meta.lastActivity = ts; } + const role = resolveRole(event); + // Count messages - if (event.type === "human") { + if (role === "human") { meta.humanTurns++; meta.messageCount++; } - if (event.type === "assistant") { + if (role === "assistant") { meta.assistantTurns++; meta.messageCount++; } // First user message - if (event.type === "human" && !meta.firstUserMessage) { + if (role === "human" && !meta.firstUserMessage) { meta.firstUserMessage = extractTextContent(event); } - // Cost & duration tracking - if (event.costUSD && typeof event.costUSD === "number") { + // Cost: compute from usage tokens (costUSD is null in real data) + const model = getModel(event); + const usage = event.message && typeof event.message === "object" ? event.message.usage : undefined; + if (model && usage) { + const cost = computeCost(model, usage); + meta.totalCostUSD += cost; + meta.costByModel[model] = (meta.costByModel[model] || 0) + cost; + } else if (event.costUSD && typeof event.costUSD === "number") { + // Legacy fallback meta.totalCostUSD += event.costUSD; + const m = model || "unknown"; + meta.costByModel[m] = (meta.costByModel[m] || 0) + event.costUSD; + } + + // Duration from system turn_duration events + if (event.type === "system" && event.subtype === "turn_duration") { + const dur = (event as Record).durationMs; + if (typeof dur === "number") { + meta.totalDurationMs += dur; + } } if (event.durationMs && typeof event.durationMs === "number") { meta.totalDurationMs += event.durationMs; } // Model tracking - if (event.model && typeof event.model === "string") { - modelSet.add(event.model); + if (model) { + modelSet.add(model); } // Tool call extraction from content blocks - if (Array.isArray(event.content)) { - for (const block of event.content as ContentBlock[]) { + const content = getContent(event); + if (Array.isArray(content)) { + for (const block of content as ContentBlock[]) { if (block.type === "tool_use") { const tb = block as ToolUseBlock; meta.toolCalls++; @@ -90,9 +154,11 @@ export async function scanSession(filePath: string): Promise { // Extract file references from tool inputs const input = tb.input; - for (const key of ["file_path", "path", "filePath"]) { - if (input[key] && typeof input[key] === "string") { - fileSet.add(input[key] as string); + if (input) { + for (const key of ["file_path", "path", "filePath"]) { + if (input[key] && typeof input[key] === "string") { + fileSet.add(input[key] as string); + } } } } @@ -135,6 +201,7 @@ export async function parseSessionFile( try { const raw = JSON.parse(line) as RawJsonlEvent; + if (SKIP_TYPES.has(raw.type)) continue; const parsed = normalizeEvent(raw, sessionId, lineIndex); if (parsed.length > 0) { events.push(...parsed); @@ -155,12 +222,12 @@ function normalizeEvent( const events: DevLogEvent[] = []; const timestamp = raw.timestamp ? new Date(raw.timestamp) : new Date(); const baseId = raw.uuid || `${sessionId}-${lineIndex}`; + const role = resolveRole(raw); + const content = getContent(raw); + const model = getModel(raw); - if ( - (raw.type === "human" || raw.type === "assistant") && - Array.isArray(raw.content) - ) { - const blocks = raw.content as ContentBlock[]; + if (role && Array.isArray(content)) { + const blocks = content as ContentBlock[]; let blockIndex = 0; for (const block of blocks) { @@ -173,12 +240,12 @@ function normalizeEvent( id: eventId, sessionId, timestamp, - role: raw.type as "human" | "assistant", + role: role, type: "text", content: textBlock.text, costUSD: raw.costUSD, durationMs: raw.durationMs, - model: raw.model, + model, raw, }); } else if (block.type === "tool_use") { @@ -211,14 +278,14 @@ function normalizeEvent( } } - if (typeof raw.content === "string") { + if (typeof content === "string") { events.push({ id: baseId, sessionId, timestamp, - role: raw.type as "human" | "assistant", + role: role, type: "message", - content: raw.content, + content: content, raw, }); } @@ -228,20 +295,20 @@ function normalizeEvent( id: baseId, sessionId, timestamp, - role: raw.type as "human" | "assistant", + role: role, type: "message", content: extractTextContent(raw), raw, }); } - } else if (raw.type === "human" && typeof raw.content === "string") { + } else if (role === "human" && typeof content === "string") { events.push({ id: baseId, sessionId, timestamp, role: "human", type: "message", - content: raw.content, + content: content, raw, }); } else if (raw.type === "summary") { @@ -260,6 +327,17 @@ function normalizeEvent( } function extractTextContent(event: RawJsonlEvent): string { + // Check message.content first (real Claude Code format) + const msgContent = event.message && typeof event.message === "object" ? event.message.content : undefined; + if (typeof msgContent === "string") return msgContent; + if (Array.isArray(msgContent)) { + const textBlocks = (msgContent as ContentBlock[]).filter( + (b): b is TextBlock => b.type === "text" + ); + if (textBlocks.length > 0) return textBlocks.map((b) => b.text).join("\n"); + } + + // Legacy format if (typeof event.content === "string") return event.content; if (Array.isArray(event.content)) { const textBlocks = event.content.filter( @@ -267,7 +345,6 @@ function extractTextContent(event: RawJsonlEvent): string { ); if (textBlocks.length > 0) return textBlocks.map((b) => b.text).join("\n"); } - if (event.message && typeof event.message === "string") return event.message; return ""; } @@ -301,5 +378,5 @@ function extractToolResultContent(block: ToolResultBlock): string { function truncateStr(str: string, max: number): string { if (str.length <= max) return str; - return str.slice(0, max - 1) + "…"; + return str.slice(0, max - 1) + "\u2026"; } diff --git a/src/core/pricing.ts b/src/core/pricing.ts new file mode 100644 index 0000000..6a9c161 --- /dev/null +++ b/src/core/pricing.ts @@ -0,0 +1,38 @@ +import type { TokenUsage } from "./types.js"; + +// Pricing per million tokens (USD) +const PRICING: Record = { + "claude-opus-4-6": { input: 15, output: 75, cacheCreation: 18.75, cacheRead: 1.5 }, + "claude-opus-4-5-20251101": { input: 15, output: 75, cacheCreation: 18.75, cacheRead: 1.5 }, + "claude-sonnet-4-6": { input: 3, output: 15, cacheCreation: 3.75, cacheRead: 0.3 }, + "claude-sonnet-4-5-20241022": { input: 3, output: 15, cacheCreation: 3.75, cacheRead: 0.3 }, + "claude-haiku-4-5-20251001": { input: 0.80, output: 4, cacheCreation: 1, cacheRead: 0.08 }, +}; + +const FALLBACK = { input: 3, output: 15, cacheCreation: 3.75, cacheRead: 0.3 }; + +function findPricing(model: string) { + if (PRICING[model]) return PRICING[model]; + // Fuzzy match: check if model string contains a known key + for (const [key, val] of Object.entries(PRICING)) { + if (model.includes(key) || key.includes(model)) return val; + } + // Match by family + if (model.includes("opus")) return PRICING["claude-opus-4-6"]; + if (model.includes("haiku")) return PRICING["claude-haiku-4-5-20251001"]; + if (model.includes("sonnet")) return PRICING["claude-sonnet-4-6"]; + return FALLBACK; +} + +export function computeCost(model: string, usage: TokenUsage): number { + const p = findPricing(model); + const perM = 1_000_000; + + let cost = 0; + cost += (usage.input_tokens || 0) * p.input / perM; + cost += (usage.output_tokens || 0) * p.output / perM; + cost += (usage.cache_creation_input_tokens || 0) * p.cacheCreation / perM; + cost += (usage.cache_read_input_tokens || 0) * p.cacheRead / perM; + + return cost; +} diff --git a/src/core/types.ts b/src/core/types.ts index 8536803..ee7d5de 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -1,6 +1,18 @@ // ── Core Types ───────────────────────────────────────────── export type MessageRole = "human" | "assistant"; + +export interface TokenUsage { + input_tokens: number; + output_tokens: number; + cache_creation_input_tokens?: number; + cache_read_input_tokens?: number; +} + +export interface GlobalOptions { + json: boolean; + quiet: boolean; +} export type ContentBlockType = "text" | "tool_use" | "tool_result"; export interface TextBlock { @@ -41,6 +53,14 @@ export interface RawJsonlEvent { cacheReadInputTokens?: number; inputTokens?: number; outputTokens?: number; + message?: { + role?: string; + content?: ContentBlock[] | string; + model?: string; + type?: string; + usage?: TokenUsage; + id?: string; + }; [key: string]: unknown; } @@ -78,6 +98,7 @@ export interface SessionMeta { lastActivity: Date; firstActivity: Date; errorCount: number; + costByModel: Record; } export interface Session { @@ -126,3 +147,59 @@ export interface AggregateStats { mostActiveProject: string; mostActiveProjectSessions: number; } + +// ── JSON Output Types ────────────────────────────────────── + +export interface SessionJson { + id: string; + projectName: string; + projectPath: string; + createdAt: string; + updatedAt: string; + messageCount: number; + toolCalls: number; + filesTouched: number; + costUSD: number; + firstMessage: string; +} + +export interface DashboardJson { + version: string; + timestamp: string; + summary: string; + stats: { + totalProjects: number; + totalSessions: number; + totalToolCalls: number; + totalFilesTouched: number; + totalCostUSD: number; + todaySessions: number; + todayCostUSD: number; + }; + recentSessions: SessionJson[]; +} + +export interface SessionsJson { + projects: { + name: string; + path: string; + sessionCount: number; + sessions: SessionJson[]; + }[]; +} + +export interface ShowJson { + session: { + id: string; + projectName: string; + meta: SessionMeta; + }; + events: { + timestamp: string; + role: string; + type: string; + content: string; + toolName?: string; + isError?: boolean; + }[]; +} diff --git a/src/index.ts b/src/index.ts index c2517a2..c81d6fb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,5 +2,7 @@ export * from "./core/types.js"; export * from "./core/config.js"; export * from "./core/discovery.js"; export * from "./core/parser.js"; +export * from "./core/pricing.js"; export * from "./utils/paths.js"; export * from "./utils/format.js"; +export * from "./utils/output.js"; diff --git a/src/utils/format.ts b/src/utils/format.ts index 2c4f4ec..74ffc0d 100644 --- a/src/utils/format.ts +++ b/src/utils/format.ts @@ -97,11 +97,11 @@ export function printSuccess(msg: string): void { } export function printWarn(msg: string): void { - console.log(chalk.yellow(" ⚠ ") + msg); + console.error(chalk.yellow(" ⚠ ") + msg); } export function printError(msg: string): void { - console.log(chalk.red(" ✗ ") + msg); + console.error(chalk.red(" ✗ ") + msg); } export function printInfo(msg: string): void { @@ -122,3 +122,146 @@ export function statBadge( color(typeof value === "number" ? formatNumber(value) : value) ); } + +// ── Humanize helpers (Principle 1: 说人话) ────────────── + +const TOOL_NAMES: Record = { + Bash: "ran a command", + Read: "read a file", + Write: "wrote a file", + Edit: "edited a file", + Glob: "searched for files", + Grep: "searched code", + Agent: "used an agent", + WebSearch: "searched the web", + WebFetch: "fetched a page", + TodoWrite: "updated todos", + TodoRead: "checked todos", +}; + +/** + * Map a raw tool name to a human-readable description. + */ +export function humanizeToolName(name: string): string { + if (TOOL_NAMES[name]) return TOOL_NAMES[name]; + // CamelCase → space-separated lowercase + return name.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase(); +} + +const TOOL_PLURALS: Record = { + Bash: { singular: "ran a command", plural: "ran commands" }, + Read: { singular: "read a file", plural: "read files" }, + Write: { singular: "wrote a file", plural: "wrote files" }, + Edit: { singular: "edited a file", plural: "edited files" }, + Glob: { singular: "searched for files", plural: "searched for files" }, + Grep: { singular: "searched code", plural: "searched code" }, + Agent: { singular: "used an agent", plural: "used agents" }, +}; + +/** + * Group tool arrays into a summary like "read 3 files, ran 2 commands, edited 1 file". + */ +export function humanizeToolSummary(tools: string[]): string { + const counts = new Map(); + for (const t of tools) { + counts.set(t, (counts.get(t) || 0) + 1); + } + + const parts: string[] = []; + for (const [name, count] of counts) { + const p = TOOL_PLURALS[name]; + if (p) { + parts.push(count === 1 ? p.singular : `${p.plural}`); + } else { + const human = humanizeToolName(name); + parts.push(count === 1 ? human : `${human} (×${count})`); + } + } + + if (parts.length === 0) return ""; + if (parts.length === 1) return `Claude ${parts[0]}`; + const last = parts.pop()!; + return `Claude ${parts.join(", ")}, and ${last}`; +} + +// ── Context helpers (Principle 5: 数字要有上下文) ──────── + +/** + * Cost with context suffix. + */ +export function costWithContext(usd: number): string { + if (usd === 0) return chalk.dim("—"); + + const formatted = + usd < 0.01 + ? `$${usd.toFixed(4)}` + : usd < 1 + ? `$${usd.toFixed(3)}` + : `$${usd.toFixed(2)}`; + + let context = ""; + if (usd < 0.01) context = " — barely a penny"; + else if (usd < 0.05) context = " — pocket change"; + else if (usd < 0.15) context = " — pretty efficient"; + else if (usd < 0.50) context = " — a good session"; + else if (usd < 1) context = " — solid investment"; + else if (usd < 5) context = " — heavy session"; + else context = " — serious work"; + + return chalk.yellow(formatted) + chalk.dim(context); +} + +/** + * Message count with context. + */ +export function messageCountContext(turns: number): string { + if (turns <= 2) return chalk.dim("quick question"); + if (turns <= 4) return chalk.dim("quick chat"); + if (turns <= 10) return chalk.dim(`${turns} messages`); + if (turns <= 25) return chalk.white(`${turns} messages`) + chalk.dim(" — solid session"); + if (turns <= 50) return chalk.white(`${turns} messages`) + chalk.dim(" — deep dive"); + return chalk.white(`${turns} messages`) + chalk.dim(" — marathon"); +} + +/** + * File count with context. + */ +export function fileCountContext(count: number): string { + if (count === 0) return ""; + if (count === 1) return chalk.blue("touched 1 file"); + if (count <= 5) return chalk.blue(`touched ${count} files`); + if (count <= 15) return chalk.blue(`touched ${count} files`) + chalk.dim(" — broad changes"); + return chalk.blue(`touched ${count} files`) + chalk.dim(" — major refactor"); +} + +/** + * Tool count with context. + */ +export function toolCountContext(count: number): string { + if (count === 0) return ""; + if (count <= 10) return chalk.green(`ran ${count} commands`); + if (count <= 30) return chalk.green(`ran ${count} commands`) + chalk.dim(" — busy session"); + return chalk.green(`ran ${count} commands`) + chalk.dim(" — heavy automation"); +} + +// ── Fuzzy matching (Principle 3: 永远不卡住) ──────────── + +/** + * Simple Levenshtein distance for "did you mean?" suggestions. + */ +export function levenshtein(a: string, b: string): number { + const m = a.length; + const n = b.length; + const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0)); + for (let i = 0; i <= m; i++) dp[i][0] = i; + for (let j = 0; j <= n; j++) dp[0][j] = j; + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + dp[i][j] = + a[i - 1] === b[j - 1] + ? dp[i - 1][j - 1] + : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]); + } + } + return dp[m][n]; +} diff --git a/src/utils/output.ts b/src/utils/output.ts new file mode 100644 index 0000000..69b9fc4 --- /dev/null +++ b/src/utils/output.ts @@ -0,0 +1,41 @@ +export interface OutputContext { + json: boolean; + quiet: boolean; +} + +let ctx: OutputContext = { json: false, quiet: false }; + +export function initOutput(opts: OutputContext): void { + ctx = opts; +} + +export function getOutputContext(): OutputContext { + return ctx; +} + +/** Write JSON to stdout */ +export function outputJson(data: unknown): void { + process.stdout.write(JSON.stringify(data, null, 2) + "\n"); +} + +/** Progress info to stderr (suppressed in quiet mode) */ +export function logStatus(msg: string): void { + if (!ctx.quiet) { + process.stderr.write(msg + "\n"); + } +} + +/** Errors always go to stderr */ +export function logError(msg: string): void { + process.stderr.write(msg + "\n"); +} + +/** Whether we are in JSON output mode */ +export function isJsonMode(): boolean { + return ctx.json; +} + +/** Whether we are in quiet mode */ +export function isQuietMode(): boolean { + return ctx.quiet; +}