1- import type { Argv } from "yargs "
2- import { cmd } from "./ cmd"
1+ import { Effect } from "effect "
2+ import { effectCmd } from "../effect- cmd"
33import { Session } from "@/session/session"
4- import { bootstrap } from "../bootstrap"
54import { Database } from "@/storage/db"
65import { SessionTable } from "../../session/session.sql"
76import { Project } from "@/project/project"
8- import { Instance } from "../../project/instance"
7+ import { InstanceRef } from "@/effect/instance-ref"
8+ import { InstanceStore } from "@/project/instance-store"
99import { AppRuntime } from "@/effect/app-runtime"
1010
1111interface SessionStats {
@@ -47,11 +47,11 @@ interface SessionStats {
4747 medianTokensPerSession : number
4848}
4949
50- export const StatsCommand = cmd ( {
50+ export const StatsCommand = effectCmd ( {
5151 command : "stats" ,
5252 describe : "show token usage and cost statistics" ,
53- builder : ( yargs : Argv ) => {
54- return yargs
53+ builder : ( yargs ) =>
54+ yargs
5555 . option ( "days" , {
5656 describe : "show stats for the last N days (default: all time)" ,
5757 type : "number" ,
@@ -66,34 +66,39 @@ export const StatsCommand = cmd({
6666 . option ( "project" , {
6767 describe : "filter by project (default: all projects, empty string: current project)" ,
6868 type : "string" ,
69- } )
70- } ,
71- handler : async ( args ) => {
72- await bootstrap ( process . cwd ( ) , async ( ) => {
73- const stats = await aggregateSessionStats ( args . days , args . project )
74-
75- let modelLimit : number | undefined
76- if ( args . models === true ) {
77- modelLimit = Infinity
78- } else if ( typeof args . models === "number" ) {
79- modelLimit = args . models
80- }
81-
82- displayStats ( stats , args . tools , modelLimit )
83- } )
84- } ,
69+ } ) ,
70+ handler : Effect . fn ( "Cli.stats" ) ( function * ( args ) {
71+ const ctx = yield * InstanceRef
72+ if ( ! ctx ) return
73+ const store = yield * InstanceStore . Service
74+ return yield * run ( args , ctx . project ) . pipe ( Effect . ensuring ( store . dispose ( ctx ) ) )
75+ } ) ,
8576} )
8677
87- async function getCurrentProject ( ) : Promise < Project . Info > {
88- return Instance . project
89- }
78+ const run = ( args : { days ?: number ; tools ?: number ; models ?: unknown ; project ?: string } , currentProject : Project . Info ) =>
79+ Effect . promise ( async ( ) => {
80+ const stats = await aggregateSessionStats ( args . days , args . project , currentProject )
81+
82+ let modelLimit : number | undefined
83+ if ( args . models === true ) {
84+ modelLimit = Infinity
85+ } else if ( typeof args . models === "number" ) {
86+ modelLimit = args . models
87+ }
88+
89+ displayStats ( stats , args . tools , modelLimit )
90+ } )
9091
9192async function getAllSessions ( ) : Promise < Session . Info [ ] > {
9293 const rows = Database . use ( ( db ) => db . select ( ) . from ( SessionTable ) . all ( ) )
9394 return rows . map ( ( row ) => Session . fromRow ( row ) )
9495}
9596
96- export async function aggregateSessionStats ( days ?: number , projectFilter ?: string ) : Promise < SessionStats > {
97+ export async function aggregateSessionStats (
98+ days ?: number ,
99+ projectFilter ?: string ,
100+ currentProject ?: Project . Info ,
101+ ) : Promise < SessionStats > {
97102 const sessions = await getAllSessions ( )
98103 const MS_IN_DAY = 24 * 60 * 60 * 1000
99104
@@ -117,7 +122,7 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin
117122
118123 if ( projectFilter !== undefined ) {
119124 if ( projectFilter === "" ) {
120- const currentProject = await getCurrentProject ( )
125+ if ( ! currentProject ) throw new Error ( "currentProject required when projectFilter is empty string" )
121126 filteredSessions = filteredSessions . filter ( ( session ) => session . projectID === currentProject . id )
122127 } else {
123128 filteredSessions = filteredSessions . filter ( ( session ) => session . projectID === projectFilter )
0 commit comments