/** * pi-stats — pi 확장 진입점 * * Commands: * /stats [기간] [옵션] * 기간: (없음)=오늘 | YYYY-MM-DD | A..B(양끝 포함) | A.. | ..B * 옵션: --project 현재 프로젝트 세션만, --daily 일자별 집계, --json JSON 출력, * --help 도움말 * * 환경변수: PI_STATS_SESSIONS_DIR — 세션 디렉터리 override (기본 ~/.pi/agent/sessions) */ import { collectSessionFiles, aggregate, parseRange, defaultSessionsDir } from "./src/aggregate.ts"; import { renderReport, renderJson } from "./src/format.ts"; /** pi ExtensionAPI 중 이 패키지가 쓰는 최소 모양 (구조적 타이핑 — pi 임포트 없음) */ interface UsageCtx { cwd: string; ui: { notify(message: string, type?: "info" | "warning" | "error"): void; }; } interface UsagePi { registerCommand( name: string, cmd: { description?: string; handler: (args: string | undefined, ctx: UsageCtx) => Promise | void; }, ): void; } const HELP = `사용법: /stats [기간] [--project] [--daily] [--json] 기간: (없음) 오늘 2026-08-21 특정 날짜 하루 A..B 기간 (양끝 날짜 포함), 예: 2026-08-01..2026-08-07 A.. A부터 지금까지, 예: 2026-08-20.. ..B 처음부터 B까지, 예: ..2026-08-20 옵션: --project 현재 작업 디렉터리(cwd)의 세션만 집계 --daily 일자별 집계 추가 표시 --json JSON으로 출력 --help 이 도움말 날짜 경계는 단말 로컬 타임존 기준입니다.`; function parseArgs(args: string | undefined): { flags: Set; positional: string[]; } { const flags = new Set(); const positional: string[] = []; for (const tok of (args ?? "").split(/\s+/).filter(Boolean)) { if (tok.startsWith("-")) flags.add(tok); else positional.push(tok); } return { flags, positional }; } export default function register(pi: UsagePi): void { pi.registerCommand("stats", { description: "세션 토큰/비용 집계 — /stats [YYYY-MM-DD|A..B] [--project] [--daily] [--json]", handler: async (args, ctx) => { try { const { flags, positional } = parseArgs(args); if (flags.has("--help") || flags.has("-h")) { ctx.ui.notify(HELP, "info"); return; } if (positional.length > 1) { throw new Error(`기간 인자는 하나만 가능합니다: ${positional.join(" ")}\n\n${HELP}`); } const range = parseRange(positional[0]); const project = flags.has("--project") || flags.has("-p"); const daily = flags.has("--daily") || flags.has("-d"); const json = flags.has("--json"); const dir = defaultSessionsDir(); const files = collectSessionFiles(dir); if (files.length === 0) { ctx.ui.notify( `세션 파일을 찾을 수 없습니다: ${dir}\nPI_STATS_SESSIONS_DIR 로 다른 경로를 지정할 수 있습니다.`, "warning", ); return; } const result = aggregate(files, { fromMs: range.fromMs, toMs: range.toMs, cwdFilter: project ? ctx.cwd : undefined, }); result.rangeLabel = range.label; if (result.byModel.size === 0) { ctx.ui.notify( `해당 기간(${range.label})에 사용 기록이 없습니다.`, "info", ); return; } const text = json ? renderJson(result, { rangeLabel: range.label, project: project ? ctx.cwd : undefined, daily }) : renderReport(result, { rangeLabel: range.label, project: project ? ctx.cwd : undefined, daily }); ctx.ui.notify(text, "info"); } catch (err) { ctx.ui.notify(err instanceof Error ? err.message : String(err), "error"); } }, }); }