-
-
Notifications
You must be signed in to change notification settings - Fork 320
feat(droid): add Factory Droid usage tracker #795
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
iipanda
wants to merge
7
commits into
ryoppippi:main
Choose a base branch
from
iipanda:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
c0d8784
feat(droid): add Factory Droid usage tracker
iipanda 567fe89
docs(droid): add README
iipanda df23338
fix(droid): align engines and logger
iipanda ec151bb
fix(terminal): measure multiline cells by max line
iipanda df35e1d
docs(droid): add docstrings
iipanda 7a5c24e
docs(droid): improve docstring coverage
iipanda 9b12dd0
chore: sync pnpm lockfile
iipanda File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| <div align="center"> | ||
| <img src="https://cdn.jsdelivr.net/gh/ryoppippi/ccusage@main/docs/public/logo.svg" alt="ccusage logo" width="256" height="256"> | ||
| <h1>@ccusage/droid</h1> | ||
| </div> | ||
|
|
||
| > Analyze Factory Droid usage logs with the same reporting experience as `ccusage`. | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ```bash | ||
| # Recommended - always include @latest | ||
| npx @ccusage/droid@latest --help | ||
| bunx @ccusage/droid@latest --help | ||
|
|
||
| # Alternative package runners | ||
| pnpm dlx @ccusage/droid | ||
| pnpx @ccusage/droid | ||
| ``` | ||
|
|
||
| ## Common Commands | ||
|
|
||
| ```bash | ||
| # Daily usage grouped by date (default command) | ||
| npx @ccusage/droid@latest daily | ||
|
|
||
| # Monthly usage grouped by month | ||
| npx @ccusage/droid@latest monthly | ||
|
|
||
| # Session-level usage grouped by Factory session | ||
| npx @ccusage/droid@latest session | ||
|
|
||
| # JSON output for scripting | ||
| npx @ccusage/droid@latest daily --json | ||
|
|
||
| # Filter by date range | ||
| npx @ccusage/droid@latest daily --since 2026-01-01 --until 2026-01-10 | ||
|
|
||
| # Read from a custom Factory data dir | ||
| npx @ccusage/droid@latest daily --factoryDir /path/to/.factory | ||
| ``` | ||
|
|
||
| ## Data Source | ||
|
|
||
| This CLI reads Factory Droid logs from: | ||
|
|
||
| - `~/.factory/logs/droid-log-*.log` | ||
|
|
||
| You can override the Factory data directory via: | ||
|
|
||
| - `--factoryDir /path/to/.factory` | ||
| - `FACTORY_DIR=/path/to/.factory` | ||
|
|
||
| ## Pricing | ||
|
|
||
| Costs are calculated from token counts using LiteLLM's pricing dataset. | ||
|
|
||
| - Use `--offline` to avoid fetching updated pricing. | ||
| - If a model is missing pricing data, its cost is treated as `$0` and reported as a warning. | ||
|
|
||
| ## Custom Models | ||
|
|
||
| Factory supports custom model IDs (often prefixed with `custom:`). This CLI resolves them using: | ||
|
|
||
| - `~/.factory/settings.json` → `customModels[]` | ||
|
|
||
| Example: | ||
|
|
||
| ```json | ||
| { | ||
| "customModels": [ | ||
| { | ||
| "id": "custom:GPT-5.2-(High)-18", | ||
| "model": "gpt-5.2(high)", | ||
| "provider": "openai" | ||
| } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| In tables, custom models are displayed as `gpt-5.2(high) [custom]`. | ||
|
|
||
| When a log line is missing a model tag, the CLI resolves the model from the session settings file and marks it as `[...] [inferred]`. | ||
|
|
||
| ## Environment Variables | ||
|
|
||
| - `FACTORY_DIR` - override the Factory data directory | ||
| - `LOG_LEVEL` - control log verbosity (0 silent … 5 trace) | ||
|
|
||
| ## License | ||
|
|
||
| MIT © [@ryoppippi](https://github.com/ryoppippi) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { ryoppippi } from '@ryoppippi/eslint-config'; | ||
|
|
||
| /** @type {import('eslint').Linter.FlatConfig[]} */ | ||
| const config = ryoppippi( | ||
| { | ||
| type: 'app', | ||
| stylistic: false, | ||
| }, | ||
| { | ||
| rules: { | ||
| 'test/no-importing-vitest-globals': 'error', | ||
| }, | ||
| }, | ||
| ); | ||
|
|
||
| export default config; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| { | ||
| "name": "@ccusage/droid", | ||
| "type": "module", | ||
| "version": "18.0.5", | ||
| "description": "Usage analysis tool for Factory Droid sessions", | ||
| "author": "ryoppippi", | ||
| "license": "MIT", | ||
| "funding": "https://github.com/ryoppippi/ccusage?sponsor=1", | ||
| "homepage": "https://github.com/ryoppippi/ccusage#readme", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/ryoppippi/ccusage.git", | ||
| "directory": "apps/droid" | ||
| }, | ||
| "bugs": { | ||
| "url": "https://github.com/ryoppippi/ccusage/issues" | ||
| }, | ||
| "main": "./dist/index.js", | ||
| "module": "./dist/index.js", | ||
| "bin": { | ||
| "ccusage-droid": "./src/index.ts" | ||
| }, | ||
| "files": [ | ||
| "dist" | ||
| ], | ||
| "publishConfig": { | ||
| "bin": { | ||
| "ccusage-droid": "./dist/index.js" | ||
| } | ||
| }, | ||
| "engines": { | ||
| "node": ">=20.19.4" | ||
| }, | ||
| "scripts": { | ||
| "build": "tsdown", | ||
| "format": "pnpm run lint --fix", | ||
| "lint": "eslint --cache .", | ||
| "prepack": "pnpm run build && clean-pkg-json", | ||
| "prerelease": "pnpm run lint && pnpm run typecheck && pnpm run build", | ||
| "start": "bun ./src/index.ts", | ||
| "test": "TZ=UTC vitest", | ||
| "typecheck": "tsgo --noEmit" | ||
| }, | ||
| "devDependencies": { | ||
| "@ccusage/internal": "workspace:*", | ||
| "@ccusage/terminal": "workspace:*", | ||
| "@praha/byethrow": "catalog:runtime", | ||
| "@ryoppippi/eslint-config": "catalog:lint", | ||
| "@typescript/native-preview": "catalog:types", | ||
| "clean-pkg-json": "catalog:release", | ||
| "eslint": "catalog:lint", | ||
| "fast-sort": "catalog:runtime", | ||
| "fs-fixture": "catalog:testing", | ||
| "gunshi": "catalog:runtime", | ||
| "path-type": "catalog:runtime", | ||
| "picocolors": "catalog:runtime", | ||
| "tinyglobby": "catalog:runtime", | ||
| "tsdown": "catalog:build", | ||
| "unplugin-macros": "catalog:build", | ||
| "unplugin-unused": "catalog:build", | ||
| "valibot": "catalog:runtime", | ||
| "vitest": "catalog:testing" | ||
| }, | ||
| "devEngines": { | ||
| "runtime": [ | ||
| { | ||
| "name": "node", | ||
| "version": ">=20.19.4", | ||
| "onFail": "download" | ||
| }, | ||
| { | ||
| "name": "bun", | ||
| "version": "^1.3.2", | ||
| "onFail": "download" | ||
| } | ||
| ] | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| /** | ||
| * @fileoverview Default paths and constants for Factory Droid usage tracking. | ||
| */ | ||
|
|
||
| import os from 'node:os'; | ||
| import path from 'node:path'; | ||
|
|
||
| export const FACTORY_DIR_ENV = 'FACTORY_DIR'; | ||
| export const DEFAULT_FACTORY_DIR = path.join(os.homedir(), '.factory'); | ||
| export const DEFAULT_TIMEZONE = Intl.DateTimeFormat().resolvedOptions().timeZone ?? 'UTC'; | ||
| export const DEFAULT_LOCALE = 'en-CA'; | ||
|
|
||
| export const DROID_LOG_GLOB = 'droid-log-*.log'; | ||
| export const FACTORY_LOGS_SUBDIR = 'logs'; | ||
| export const FACTORY_SESSIONS_SUBDIR = 'sessions'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| /** | ||
| * @fileoverview Lightweight helper for prefetching Factory model pricing. | ||
| */ | ||
|
|
||
| import type { LiteLLMModelPricing } from '@ccusage/internal/pricing'; | ||
| import { | ||
| createPricingDataset, | ||
| fetchLiteLLMPricingDataset, | ||
| filterPricingDataset, | ||
| } from '@ccusage/internal/pricing-fetch-utils'; | ||
| import { logger } from './logger.ts'; | ||
|
|
||
| const FACTORY_MODEL_PREFIXES = [ | ||
| 'openai/', | ||
| 'azure/', | ||
| 'anthropic/', | ||
| 'openrouter/', | ||
| 'gpt-', | ||
| 'claude-', | ||
| 'gemini-', | ||
| 'google/', | ||
| 'vertex_ai/', | ||
| ]; | ||
|
|
||
| function isFactoryModel(modelName: string, _pricing: LiteLLMModelPricing): boolean { | ||
| return FACTORY_MODEL_PREFIXES.some((prefix) => modelName.startsWith(prefix)); | ||
| } | ||
|
|
||
| export async function prefetchFactoryPricing(): Promise<Record<string, LiteLLMModelPricing>> { | ||
| try { | ||
| const dataset = await fetchLiteLLMPricingDataset(); | ||
| return filterPricingDataset(dataset, isFactoryModel); | ||
| } catch (error) { | ||
| logger.warn('Failed to prefetch Factory pricing data, proceeding with empty cache.', error); | ||
| return createPricingDataset(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| /** | ||
| * @fileoverview Shared CLI arguments for `@ccusage/droid` commands. | ||
| */ | ||
|
|
||
| import type { Args } from 'gunshi'; | ||
| import { DEFAULT_LOCALE, DEFAULT_TIMEZONE } from './_consts.ts'; | ||
|
|
||
| /** | ||
| * Common CLI args shared by `daily`, `monthly`, and `session` commands. | ||
| */ | ||
| export const sharedArgs = { | ||
| json: { | ||
| type: 'boolean', | ||
| short: 'j', | ||
| description: 'Output report as JSON', | ||
| default: false, | ||
| }, | ||
| since: { | ||
| type: 'string', | ||
| short: 's', | ||
| description: 'Filter from date (YYYY-MM-DD or YYYYMMDD)', | ||
| }, | ||
| until: { | ||
| type: 'string', | ||
| short: 'u', | ||
| description: 'Filter until date (inclusive)', | ||
| }, | ||
| timezone: { | ||
| type: 'string', | ||
| short: 'z', | ||
| description: 'Timezone for date grouping (IANA)', | ||
| default: DEFAULT_TIMEZONE, | ||
| }, | ||
| locale: { | ||
| type: 'string', | ||
| short: 'l', | ||
| description: 'Locale for formatting', | ||
| default: DEFAULT_LOCALE, | ||
| }, | ||
| offline: { | ||
| type: 'boolean', | ||
| short: 'O', | ||
| description: 'Use cached pricing data instead of fetching from LiteLLM', | ||
| default: false, | ||
| negatable: true, | ||
| }, | ||
| compact: { | ||
| type: 'boolean', | ||
| description: 'Force compact table layout for narrow terminals', | ||
| default: false, | ||
| }, | ||
| factoryDir: { | ||
| type: 'string', | ||
| description: 'Path to Factory data directory (default: ~/.factory)', | ||
| }, | ||
| color: { | ||
| type: 'boolean', | ||
| description: 'Enable colored output (default: auto). FORCE_COLOR=1 has the same effect.', | ||
| }, | ||
| noColor: { | ||
| type: 'boolean', | ||
| description: 'Disable colored output (default: auto). NO_COLOR=1 has the same effect.', | ||
| }, | ||
| } as const satisfies Args; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.