| 335 | } |
| 336 | |
| 337 | async function cmdStats(port: number, days: number): Promise<void> { |
| 338 | try { |
| 339 | const data = (await queryProxy(`/stats?days=${days}`, port)) as Record<string, unknown>; |
| 340 | const stats = data as { |
| 341 | totalRequests?: number; |
| 342 | totalCostUsd?: number; |
| 343 | savedUsd?: number; |
| 344 | savingsPercent?: number; |
| 345 | topModels?: Array<{ model: string; requests: number; costUsd: number }>; |
| 346 | }; |
| 347 | |
| 348 | console.log(`\nUsage Stats (last ${days} days)\n`); |
| 349 | console.log(` Requests: ${stats.totalRequests ?? 0}`); |
| 350 | console.log(` Cost: $${(stats.totalCostUsd ?? 0).toFixed(4)}`); |
| 351 | if (stats.savedUsd) { |
| 352 | console.log( |
| 353 | ` Saved: $${stats.savedUsd.toFixed(4)} (${stats.savingsPercent?.toFixed(0) ?? 0}% vs Opus)`, |
| 354 | ); |
| 355 | } |
| 356 | |
| 357 | if (stats.topModels && stats.topModels.length > 0) { |
| 358 | console.log(); |
| 359 | console.log(` Top Models:`); |
| 360 | for (const m of stats.topModels.slice(0, 10)) { |
| 361 | console.log( |
| 362 | ` ${m.model.padEnd(40)} ${String(m.requests).padStart(5)} reqs $${m.costUsd.toFixed(4)}`, |
| 363 | ); |
| 364 | } |
| 365 | } |
| 366 | console.log(); |
| 367 | } catch { |
| 368 | console.error(`✗ Cannot connect to ClawRouter on port ${port}`); |
| 369 | process.exit(1); |
| 370 | } |
| 371 | } |
| 372 | |
| 373 | /** |
| 374 | * `clawrouter phone ...` — wallet-facing phone CLI. |