{"version":3,"sources":["../src/parser.ts","../src/aggregate.ts","../src/server.ts"],"names":["fs","path","records","http"],"mappings":"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","file":"index.cjs","sourcesContent":["import { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\n\n/** One assistant response's token usage, extracted from a session jsonl line. */\nexport interface UsageRecord {\n  /** Epoch milliseconds of the message. */\n  timestamp: number;\n  /** Session id (from the session header line, fallback: file name). */\n  sessionId: string;\n  /** Working directory of the session (from the session header line). */\n  cwd: string;\n  provider: string;\n  model: string;\n  input: number;\n  output: number;\n  cacheRead: number;\n  cacheWrite: number;\n  reasoning: number;\n  totalTokens: number;\n  /** Total cost in USD, as reported by the session log. */\n  cost: number;\n}\n\ninterface FileCacheEntry {\n  mtimeMs: number;\n  size: number;\n  records: UsageRecord[];\n}\n\nexport interface ScanResult {\n  records: UsageRecord[];\n  fileCount: number;\n  /** Files whose content was actually parsed in this scan. */\n  parsedFiles: number;\n  /** Milliseconds the scan took. */\n  elapsedMs: number;\n}\n\nasync function listJsonlFiles(dir: string): Promise<string[]> {\n  const out: string[] = [];\n  let entries;\n  try {\n    entries = await fs.readdir(dir, { withFileTypes: true });\n  } catch {\n    return out;\n  }\n  const jsonlNames = new Set(\n    entries\n      .filter((e) => e.isFile() && e.name.endsWith(\".jsonl\"))\n      .map((e) => e.name),\n  );\n  for (const entry of entries) {\n    const full = path.join(dir, entry.name);\n    if (entry.isDirectory()) {\n      // Nested subagent sessions live at `<session-stem>/<runId>/run-N/*.jsonl`\n      // next to their parent's `<session-stem>.jsonl`. Their usage is already\n      // accounted from the parent's subagent toolResult entries, so skip these\n      // directories to avoid double counting.\n      if (jsonlNames.has(`${entry.name}.jsonl`)) continue;\n      out.push(...(await listJsonlFiles(full)));\n    } else if (entry.isFile() && entry.name.endsWith(\".jsonl\")) {\n      out.push(full);\n    }\n  }\n  return out;\n}\n\nfunction toNumber(value: unknown): number {\n  return typeof value === \"number\" && Number.isFinite(value) ? value : 0;\n}\n\nfunction parseTimestamp(raw: unknown, messageTimestamp: unknown): number {\n  if (typeof raw === \"string\") {\n    const parsed = Date.parse(raw);\n    if (!Number.isNaN(parsed)) return parsed;\n  }\n  if (typeof messageTimestamp === \"number\" && Number.isFinite(messageTimestamp)) {\n    return messageTimestamp;\n  }\n  return 0;\n}\n\n/** Cost may be an object (`{ total }`, main messages) or a bare number (tool details). */\nfunction parseCost(raw: unknown): number {\n  if (typeof raw === \"number\") return toNumber(raw);\n  if (raw && typeof raw === \"object\") {\n    return toNumber((raw as Record<string, unknown>).total);\n  }\n  return 0;\n}\n\n/**\n * Split a subagent model string like \"opencode-go/deepseek-v4-flash:medium\"\n * into provider + model, dropping the `:thinking` suffix.\n */\nfunction parseModelString(raw: unknown): { provider: string; model: string } {\n  if (typeof raw !== \"string\" || !raw) return { provider: \"unknown\", model: \"unknown\" };\n  const slash = raw.indexOf(\"/\");\n  const provider = slash > 0 ? raw.slice(0, slash) : \"unknown\";\n  let model = slash > 0 ? raw.slice(slash + 1) : raw;\n  const colon = model.indexOf(\":\");\n  if (colon > 0) model = model.slice(0, colon);\n  return { provider, model: model || \"unknown\" };\n}\n\ninterface SubagentUsageShape {\n  input?: unknown;\n  output?: unknown;\n  cacheRead?: unknown;\n  cacheWrite?: unknown;\n  reasoning?: unknown;\n  totalTokens?: unknown;\n  cost?: unknown;\n}\n\nfunction buildSubagentRecord(\n  usage: SubagentUsageShape,\n  base: { timestamp: number; sessionId: string; cwd: string; provider: string; model: string },\n): UsageRecord {\n  const input = toNumber(usage.input);\n  const output = toNumber(usage.output);\n  const cacheRead = toNumber(usage.cacheRead);\n  const cacheWrite = toNumber(usage.cacheWrite);\n  return {\n    ...base,\n    input,\n    output,\n    cacheRead,\n    cacheWrite,\n    reasoning: toNumber(usage.reasoning),\n    totalTokens: toNumber(usage.totalTokens) || input + output + cacheRead + cacheWrite,\n    cost: parseCost(usage.cost),\n  };\n}\n\n/**\n * Extract subagent token usage from a toolResult message. Both subagent tools\n * report aggregated child usage in `message.details`:\n *   - subagent:        details.runId + details.results[] (per-child usage/model),\n *                      falling back to details.totalChildUsage\n *   - trellis_subagent: details.runs[] (per-run usage/model/agent/id)\n */\nfunction subagentRecordsFromToolResult(\n  message: Record<string, unknown>,\n  base: { timestamp: number; cwd: string },\n): UsageRecord[] {\n  const details = message.details;\n  if (!details || typeof details !== \"object\") return [];\n  const d = details as Record<string, unknown>;\n  const toolName = message.toolName;\n  const records: UsageRecord[] = [];\n\n  if (toolName === \"subagent\") {\n    const runId = typeof d.runId === \"string\" ? d.runId : \"\";\n    const results = Array.isArray(d.results) ? d.results : [];\n    results.forEach((result, index) => {\n      if (!result || typeof result !== \"object\") return;\n      const r = result as Record<string, unknown>;\n      if (!r.usage || typeof r.usage !== \"object\") return;\n      const agent = typeof r.agent === \"string\" && r.agent ? r.agent : \"subagent\";\n      const sessionId = [runId, agent, String(index)].filter(Boolean).join(\"_\");\n      if (!sessionId) return;\n      records.push(\n        buildSubagentRecord(r.usage as SubagentUsageShape, {\n          ...base,\n          sessionId,\n          ...parseModelString(r.model),\n        }),\n      );\n    });\n    // Fallback when per-child results are unavailable (e.g. older logs).\n    if (records.length === 0 && d.totalChildUsage && typeof d.totalChildUsage === \"object\") {\n      const sessionId = runId ? `${runId}_subagent` : \"\";\n      if (sessionId) {\n        records.push(\n          buildSubagentRecord(d.totalChildUsage as SubagentUsageShape, {\n            ...base,\n            sessionId,\n            provider: \"unknown\",\n            model: \"unknown\",\n          }),\n        );\n      }\n    }\n    return records;\n  }\n\n  if (toolName === \"trellis_subagent\") {\n    const runs = Array.isArray(d.runs) ? d.runs : [];\n    for (const run of runs) {\n      if (!run || typeof run !== \"object\") continue;\n      const r = run as Record<string, unknown>;\n      if (!r.usage || typeof r.usage !== \"object\") continue;\n      const agent = typeof r.agent === \"string\" && r.agent ? r.agent : \"trellis\";\n      const id = typeof r.id === \"string\" ? r.id : \"\";\n      const sessionId = [\"trellis\", agent, id].filter(Boolean).join(\"_\");\n      records.push(\n        buildSubagentRecord(r.usage as SubagentUsageShape, {\n          ...base,\n          sessionId,\n          provider: \"unknown\",\n          model: typeof r.model === \"string\" && r.model ? r.model : \"unknown\",\n        }),\n      );\n    }\n    return records;\n  }\n\n  return records;\n}\n\n/**\n * Parse one session jsonl file into usage records.\n * Two sources contribute:\n *   - assistant messages carrying a `usage` object (per-request billing data)\n *   - subagent / trellis_subagent toolResult messages, whose `details` carry\n *     the aggregated token usage of the spawned child agents\n */\nexport function parseSessionFile(content: string, fallbackSessionId: string): UsageRecord[] {\n  const records: UsageRecord[] = [];\n  const seenMessageIds = new Set<string>();\n  let sessionId = fallbackSessionId;\n  let cwd = \"\";\n\n  for (const line of content.split(\"\\n\")) {\n    if (!line || line[0] !== \"{\") continue;\n    // Cheap pre-filter: usage only appears on assistant message lines and on\n    // subagent toolResult lines (inside `details`, as \"usage\" or\n    // \"totalChildUsage\").\n    if (!line.includes('\"usage\"') && !line.includes('\"totalChildUsage\"')) {\n      // Main sessions store cwd/id in a `type: \"session\"` header line.\n      if (line.includes('\"type\":\"session\"')) {\n        try {\n          const header = JSON.parse(line) as { id?: unknown; cwd?: unknown };\n          if (typeof header.id === \"string\" && header.id) sessionId = header.id;\n          if (typeof header.cwd === \"string\") cwd = header.cwd;\n        } catch {\n          // ignore malformed header lines\n        }\n      }\n      continue;\n    }\n\n    let event: {\n      type?: unknown;\n      id?: unknown;\n      timestamp?: unknown;\n      message?: Record<string, unknown> & {\n        role?: unknown;\n        provider?: unknown;\n        model?: unknown;\n        timestamp?: unknown;\n        usage?: Record<string, unknown>;\n      };\n    };\n    try {\n      event = JSON.parse(line);\n    } catch {\n      continue;\n    }\n    if (event.type !== \"message\") continue;\n\n    const message = event.message;\n    if (!message) continue;\n\n    // De-dup by message id; skip dedup when no id is present.\n    const messageId = typeof event.id === \"string\" && event.id ? event.id : \"\";\n    if (messageId) {\n      if (seenMessageIds.has(messageId)) continue;\n      seenMessageIds.add(messageId);\n    }\n\n    const timestamp = parseTimestamp(event.timestamp, message.timestamp);\n    if (!timestamp) continue;\n\n    if (message.role === \"toolResult\") {\n      const toolName = message.toolName;\n      if (toolName === \"subagent\" || toolName === \"trellis_subagent\") {\n        records.push(...subagentRecordsFromToolResult(message, { timestamp, cwd }));\n      }\n      continue;\n    }\n\n    if (message.role !== \"assistant\" || !message.usage) continue;\n\n    const usage = message.usage;\n    const input = toNumber(usage.input);\n    const output = toNumber(usage.output);\n    const cacheRead = toNumber(usage.cacheRead);\n    const cacheWrite = toNumber(usage.cacheWrite);\n\n    records.push({\n      timestamp,\n      sessionId,\n      cwd,\n      provider: typeof message.provider === \"string\" ? message.provider : \"unknown\",\n      model: typeof message.model === \"string\" ? message.model : \"unknown\",\n      input,\n      output,\n      cacheRead,\n      cacheWrite,\n      reasoning: toNumber(usage.reasoning),\n      totalTokens: toNumber(usage.totalTokens) || input + output + cacheRead + cacheWrite,\n      cost: parseCost(usage.cost),\n    });\n  }\n  return records;\n}\n\n/**\n * Incremental scanner for the sessions directory. Caches parsed files by\n * (mtimeMs, size) so repeated scans only re-read changed files.\n */\nexport class SessionStore {\n  readonly dir: string;\n  private cache = new Map<string, FileCacheEntry>();\n\n  constructor(dir: string) {\n    this.dir = dir;\n  }\n\n  async scan(): Promise<ScanResult> {\n    const started = Date.now();\n    const files = await listJsonlFiles(this.dir);\n    const alive = new Set(files);\n    for (const cachedPath of this.cache.keys()) {\n      if (!alive.has(cachedPath)) this.cache.delete(cachedPath);\n    }\n\n    let parsedFiles = 0;\n    await Promise.all(\n      files.map(async (file) => {\n        let stat;\n        try {\n          stat = await fs.stat(file);\n        } catch {\n          return;\n        }\n        const cached = this.cache.get(file);\n        if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {\n          return;\n        }\n        let content: string;\n        try {\n          content = await fs.readFile(file, \"utf8\");\n        } catch {\n          return;\n        }\n        const fallbackSessionId = path.basename(file, \".jsonl\");\n        const records = parseSessionFile(content, fallbackSessionId);\n        this.cache.set(file, { mtimeMs: stat.mtimeMs, size: stat.size, records });\n        parsedFiles += 1;\n      }),\n    );\n\n    const records: UsageRecord[] = [];\n    for (const entry of this.cache.values()) records.push(...entry.records);\n    return {\n      records,\n      fileCount: files.length,\n      parsedFiles,\n      elapsedMs: Date.now() - started,\n    };\n  }\n}\n","import type { UsageRecord } from \"./parser.ts\";\n\nexport type RangeKey = \"day\" | \"week\" | \"month\";\n\nexport interface BucketTotals {\n  input: number;\n  output: number;\n  cacheRead: number;\n  cacheWrite: number;\n  reasoning: number;\n  totalTokens: number;\n  cost: number;\n  requests: number;\n}\n\nexport interface TimeBucket extends BucketTotals {\n  /** Bucket start, epoch ms (local-time aligned). */\n  start: number;\n  label: string;\n}\n\nexport interface ModelUsage extends BucketTotals {\n  provider: string;\n  model: string;\n  /** provider/model 组合名，便于展示。 */\n  name: string;\n  sessions: number;\n}\n\nexport interface FolderUsage extends BucketTotals {\n  /** Working directory path. */\n  cwd: string;\n  /** Short display name (last segment or full path). */\n  name: string;\n  sessions: number;\n}\n\nexport interface Overview {\n  range: RangeKey;\n  windowStart: number;\n  generatedAt: number;\n  summary: BucketTotals & { sessions: number };\n  buckets: TimeBucket[];\n  models: ModelUsage[];\n  folders: FolderUsage[];\n}\n\nexport const RANGES: Record<\n  RangeKey,\n  { bucketMs: number; bucketCount: number; unit: \"hour\" | \"day\" }\n> = {\n  day: { bucketMs: 60 * 60 * 1000, bucketCount: 24, unit: \"hour\" },\n  week: { bucketMs: 24 * 60 * 60 * 1000, bucketCount: 7, unit: \"day\" },\n  month: { bucketMs: 24 * 60 * 60 * 1000, bucketCount: 30, unit: \"day\" },\n};\n\nexport function isRangeKey(value: string): value is RangeKey {\n  return value === \"day\" || value === \"week\" || value === \"month\";\n}\n\n/** Align a timestamp down to the start of its local hour or local day. */\nfunction align(timestamp: number, unit: \"hour\" | \"day\"): number {\n  const date = new Date(timestamp);\n  if (unit === \"hour\") {\n    date.setMinutes(0, 0, 0);\n  } else {\n    date.setHours(0, 0, 0, 0);\n  }\n  return date.getTime();\n}\n\nfunction pad2(value: number): string {\n  return value < 10 ? `0${value}` : String(value);\n}\n\nfunction bucketLabel(start: number, unit: \"hour\" | \"day\"): string {\n  const date = new Date(start);\n  if (unit === \"hour\") return `${pad2(date.getHours())}:00`;\n  return `${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;\n}\n\nfunction emptyTotals(): BucketTotals {\n  return {\n    input: 0,\n    output: 0,\n    cacheRead: 0,\n    cacheWrite: 0,\n    reasoning: 0,\n    totalTokens: 0,\n    cost: 0,\n    requests: 0,\n  };\n}\n\nfunction addRecord(totals: BucketTotals, record: UsageRecord): void {\n  totals.input += record.input;\n  totals.output += record.output;\n  totals.cacheRead += record.cacheRead;\n  totals.cacheWrite += record.cacheWrite;\n  totals.reasoning += record.reasoning;\n  totals.totalTokens += record.totalTokens;\n  totals.cost += record.cost;\n  totals.requests += 1;\n}\n\n/**\n * Aggregate usage records into a fixed grid of time buckets plus a\n * per-model ranking, for the given range ending at `now`.\n */\nexport function aggregate(\n  records: UsageRecord[],\n  range: RangeKey,\n  now: number = Date.now(),\n): Overview {\n  const config = RANGES[range];\n  const lastBucketStart = align(now, config.unit);\n  const windowStart = lastBucketStart - (config.bucketCount - 1) * config.bucketMs;\n  const windowEnd = lastBucketStart + config.bucketMs;\n\n  const buckets: TimeBucket[] = [];\n  for (let i = 0; i < config.bucketCount; i += 1) {\n    const start = windowStart + i * config.bucketMs;\n    buckets.push({ start, label: bucketLabel(start, config.unit), ...emptyTotals() });\n  }\n\n  const summary = { ...emptyTotals(), sessions: 0 };\n  const summarySessions = new Set<string>();\n  const byModel = new Map<string, ModelUsage & { sessionIds: Set<string> }>();\n  const byFolder = new Map<string, FolderUsage & { sessionIds: Set<string> }>();\n\n  for (const record of records) {\n    if (record.timestamp < windowStart || record.timestamp >= windowEnd) continue;\n\n    const bucketIndex = Math.floor((record.timestamp - windowStart) / config.bucketMs);\n    addRecord(buckets[bucketIndex], record);\n    addRecord(summary, record);\n    summarySessions.add(record.sessionId);\n\n    const name = `${record.provider}/${record.model}`;\n    let model = byModel.get(name);\n    if (!model) {\n      model = {\n        provider: record.provider,\n        model: record.model,\n        name,\n        ...emptyTotals(),\n        sessions: 0,\n        sessionIds: new Set<string>(),\n      };\n      byModel.set(name, model);\n    }\n    addRecord(model, record);\n    model.sessionIds.add(record.sessionId);\n\n    // Folder aggregation\n    const folderCwd = record.cwd || \"未知目录\";\n    let folder = byFolder.get(folderCwd);\n    if (!folder) {\n      folder = {\n        cwd: folderCwd,\n        name: folderCwd === \"未知目录\" ? folderCwd : (folderCwd.split(\"/\").pop() || folderCwd),\n        ...emptyTotals(),\n        sessions: 0,\n        sessionIds: new Set<string>(),\n      };\n      byFolder.set(folderCwd, folder);\n    }\n    addRecord(folder, record);\n    folder.sessionIds.add(record.sessionId);\n  }\n\n  summary.sessions = summarySessions.size;\n  const models = [...byModel.values()]\n    .map(({ sessionIds, ...rest }) => ({ ...rest, sessions: sessionIds.size }))\n    .sort((a, b) => b.totalTokens - a.totalTokens);\n\n  const folders = [...byFolder.values()]\n    .map(({ sessionIds, ...rest }) => ({ ...rest, sessions: sessionIds.size }))\n    .sort((a, b) => b.totalTokens - a.totalTokens);\n\n  return {\n    range,\n    windowStart,\n    generatedAt: now,\n    summary,\n    buckets,\n    models,\n    folders,\n  };\n}\n","import { promises as fs } from \"node:fs\";\nimport http from \"node:http\";\nimport { aggregate, isRangeKey, type Overview } from \"./aggregate.ts\";\nimport type { ScanResult, SessionStore } from \"./parser.ts\";\n\nconst INDEX_HTML_URL = new URL(\"../public/index.html\", import.meta.url);\n\ninterface ServerState {\n  store: SessionStore;\n  scan: ScanResult;\n  scannedAt: number;\n}\n\nfunction sendJson(res: http.ServerResponse, status: number, body: unknown): void {\n  const payload = JSON.stringify(body);\n  res.writeHead(status, {\n    \"Content-Type\": \"application/json; charset=utf-8\",\n    \"Cache-Control\": \"no-cache\",\n  });\n  res.end(payload);\n}\n\nexport interface StatsServer {\n  server: http.Server;\n  /** Rescan the sessions directory and update in-memory records. */\n  refresh: () => Promise<ScanResult>;\n}\n\nexport async function createStatsServer(store: SessionStore): Promise<StatsServer> {\n  const indexHtml = await fs.readFile(INDEX_HTML_URL, \"utf8\");\n  const state: ServerState = {\n    store,\n    scan: { records: [], fileCount: 0, parsedFiles: 0, elapsedMs: 0 },\n    scannedAt: 0,\n  };\n\n  const refresh = async (): Promise<ScanResult> => {\n    state.scan = await state.store.scan();\n    state.scannedAt = Date.now();\n    return state.scan;\n  };\n  await refresh();\n\n  const server = http.createServer((req, res) => {\n    const url = new URL(req.url ?? \"/\", \"http://localhost\");\n    const pathname = url.pathname;\n\n    if (req.method === \"GET\" && (pathname === \"/\" || pathname === \"/index.html\")) {\n      res.writeHead(200, {\n        \"Content-Type\": \"text/html; charset=utf-8\",\n        \"Cache-Control\": \"no-cache\",\n      });\n      res.end(indexHtml);\n      return;\n    }\n\n    if (req.method === \"GET\" && pathname === \"/api/overview\") {\n      const rangeParam = url.searchParams.get(\"range\") ?? \"day\";\n      if (!isRangeKey(rangeParam)) {\n        sendJson(res, 400, { error: `invalid range: ${rangeParam}` });\n        return;\n      }\n      const overview: Overview = aggregate(state.scan.records, rangeParam);\n      sendJson(res, 200, {\n        ...overview,\n        scan: {\n          dir: state.store.dir,\n          fileCount: state.scan.fileCount,\n          recordCount: state.scan.records.length,\n          scannedAt: state.scannedAt,\n        },\n      });\n      return;\n    }\n\n    if (req.method === \"POST\" && pathname === \"/api/refresh\") {\n      refresh()\n        .then((scan) =>\n          sendJson(res, 200, {\n            fileCount: scan.fileCount,\n            parsedFiles: scan.parsedFiles,\n            recordCount: scan.records.length,\n            elapsedMs: scan.elapsedMs,\n            scannedAt: state.scannedAt,\n          }),\n        )\n        .catch((error: unknown) =>\n          sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) }),\n        );\n      return;\n    }\n\n    sendJson(res, 404, { error: \"not found\" });\n  });\n\n  return { server, refresh };\n}\n"]}