Skip to content

Commit fc7643b

Browse files
committed
refactor(cli): convert stats command to effectCmd
bootstrap() → effectCmd + Effect.ensuring(store.dispose(ctx)). The aggregateSessionStats helper now takes the current project as an explicit parameter (was reading Instance.project from ALS, which the effectCmd handler doesn't bind). Other call sites of the helper pass undefined for currentProject — which matches existing behavior since the ALS-read only fired when projectFilter was the empty string.
1 parent c444e97 commit fc7643b

1 file changed

Lines changed: 33 additions & 28 deletions

File tree

packages/opencode/src/cli/cmd/stats.ts

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import type { Argv } from "yargs"
2-
import { cmd } from "./cmd"
1+
import { Effect } from "effect"
2+
import { effectCmd } from "../effect-cmd"
33
import { Session } from "@/session/session"
4-
import { bootstrap } from "../bootstrap"
54
import { Database } from "@/storage/db"
65
import { SessionTable } from "../../session/session.sql"
76
import { Project } from "@/project/project"
8-
import { Instance } from "../../project/instance"
7+
import { InstanceRef } from "@/effect/instance-ref"
8+
import { InstanceStore } from "@/project/instance-store"
99
import { AppRuntime } from "@/effect/app-runtime"
1010

1111
interface 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

9192
async 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

Comments
 (0)