diff --git a/README.md b/README.md index d807848..775a704 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ deno task compile-all # Produces ocv-x86_64-linux, ocv-aarch64-macos, ocv-aarc ## Usage ```bash +ocv . # Dashboard filtered to current directory ocv # Show per-directory session overview (default) ocv stats # Detailed statistics ocv dash # Dashboard with bars and charts @@ -71,6 +72,7 @@ ocv dash --top=5 # Show top 5 items per section ocv dash --exclude=simstore,infra # Exclude directories ocv dash --name=surrealdb-orm,woss.io # Focus on specific directories ocv dash --all # Show all items +ocv dash --merge-same-names # Merge same-named directories into one row ocv sessions # List sessions matching directory path ocv session # Detailed info for a specific session ocv search # Search sessions by title or directory @@ -123,6 +125,20 @@ Works on all dashboard panels — directories, models, providers, weekly activit and costs. The directory section adapts to show exactly the named directories (instead of the top N). Accepts comma-separated directory names (not paths). +### Merging same-named directories + +If you have projects in multiple locations (different clones, worktrees) with +the same directory name, the dashboard shows each path as a separate row. Use +`--merge-same-names` to collapse them into a single row by basename: + +```bash +ocv dash --merge-same-names +ocv dash --name=my-project --merge-same-names +``` + +The path-arg shortcut (`ocv .`) enables this automatically — current-directory +sessions from all clones/worktrees are merged into one row. + ### Session types The `sessions` and `search` commands show a "Type" column: @@ -166,16 +182,17 @@ more through structured tool calls. ## Commands -| Command | Description | -| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| `(no args)` / `overview` | Per-directory session overview table | -| `stats` | Full statistics: sessions (active/archived), projects, tokens, cost, most-used model, app version range | -| `dash` | ANSI dashboard with bars per directory, model, provider, weekly activity. Supports `--top`, `--all`, `--name`, `--exclude` | -| `sessions ` | Filtered session list matching a directory path | -| `session ` | Single session detail with messages and todo breakdown | -| `search ` | Full-text search over session titles and directories | -| `rename --from-dir -d ` | Batch-rename session directory and path fields when project moves | -| `mcp` | Start MCP stdio server — exposes DB queries as structured tools for AI agents | +| Command | Description | +| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| `(no args)` / `overview` | Per-directory session overview table | +| `stats` | Full statistics: sessions (active/archived), projects, tokens, cost, most-used model, app version range | +| `dash` | ANSI dashboard with bars per directory, model, provider, weekly activity. Supports `--top`, `--all`, `--name`, `--exclude`, `--merge-same-names` | +| `.` (path arg) | Dashboard filtered to the given directory's sessions — resolve `.`, `./x`, `/abs/path` to dir basename, auto-merges same-named dirs | +| `sessions ` | Filtered session list matching a directory path | +| `session ` | Single session detail with messages and todo breakdown | +| `search ` | Full-text search over session titles and directories | +| `rename --from-dir -d ` | Batch-rename session directory and path fields when project moves | +| `mcp` | Start MCP stdio server — exposes DB queries as structured tools for AI agents | ## Semantic versioning diff --git a/lib/dashboard.ts b/lib/dashboard.ts index 31cebdb..94a4878 100644 --- a/lib/dashboard.ts +++ b/lib/dashboard.ts @@ -117,6 +117,7 @@ export interface DashOptions { exclude?: string; names?: string[]; jsonMode: boolean; + mergeSameNames?: boolean; } // ── Dashboard ──────────────────────────────────────────────────── @@ -167,6 +168,32 @@ export async function showDashboard( return true; }); } + + // Merge directories with same basename into a single row + if (opts.mergeSameNames) { + const merged = new Map(); + for (const row of overview) { + const name = row.directory.split("/").pop() || row.directory; + const existing = merged.get(name); + if (existing) { + existing.total += row.total; + existing.active += row.active; + existing.main_count += row.main_count; + existing.sub_count += row.sub_count; + existing.tokens_input += row.tokens_input; + existing.tokens_output += row.tokens_output; + existing.tokens_reasoning += row.tokens_reasoning; + existing.tokens_cache_read += row.tokens_cache_read; + existing.tokens_cache_write += row.tokens_cache_write; + existing.cost += row.cost; + existing.last_active = Math.max(existing.last_active, row.last_active); + } else { + merged.set(name, { ...row, directory: name }); + } + } + overview = Array.from(merged.values()) + .sort((a, b) => b.total - a.total); + } const weekly = getSessionsByWeek(db, filterNames); const topModels = getTopModels(db, topCount, filterNames); const topProviders = getTopProviders(db, topCount, filterNames); @@ -209,8 +236,8 @@ export async function showDashboard( // ── Prepare directory data ─────────────────────────────── const dirTopCount = filterNames ? filterNames.length : topCount; - const maxDisplay = dirTopCount; - const hasMore = overview.length > maxDisplay; + const maxDisplay = filterNames ? overview.length : dirTopCount; + const hasMore = !filterNames && overview.length > maxDisplay; const displayList = hasMore ? overview.slice(0, maxDisplay - 1).concat({ ...overview[maxDisplay - 1], diff --git a/main.ts b/main.ts index 1c4f21d..77af0fb 100644 --- a/main.ts +++ b/main.ts @@ -61,6 +61,42 @@ function formatOutput( async function main() { const dbPath = resolveDbPath(); + // Single path arg → show dashboard filtered to that directory + const firstArg = Deno.args[0]; + if ( + Deno.args.length >= 1 && + (firstArg === "." || firstArg === ".." || + firstArg.startsWith("./") || firstArg.startsWith("../") || + firstArg.startsWith("/") || firstArg.startsWith("~")) + ) { + // Detect --output json from remaining args + let jsonMode = false; + for (let i = 1; i < Deno.args.length; i++) { + const a = Deno.args[i]; + if (a === "--output" || a === "-o") { + if (i + 1 < Deno.args.length && Deno.args[i + 1] === "json") { + jsonMode = true; + } + break; + } + if (a.startsWith("--output=")) { + jsonMode = a.slice(9) === "json"; + break; + } + } + + const abs = resolve(firstArg); + const dirName = abs.split("/").pop() || abs; + await showDashboard(dbPath, { + top: 10, + all: false, + names: [dirName], + jsonMode, + mergeSameNames: true, + }); + return; + } + // No args → show dashboard (default behavior) if (Deno.args.length === 0) { await showDashboard(dbPath, { top: 10, all: false, jsonMode: false }); @@ -89,6 +125,10 @@ async function main() { "--name ", "Filter all panels to specific directories (comma-separated names)", ) + .option( + "--merge-same-names", + "Merge directories with same basename into one row", + ) .action(async (options) => { const names = options.name ? options.name.split(",").map((n: string) => n.trim()).filter(( @@ -101,6 +141,7 @@ async function main() { exclude: options.exclude, names, jsonMode: options.output === "json", + mergeSameNames: options.mergeSameNames ?? false, }); }) .command("sessions", "List sessions matching a directory path pattern") diff --git a/version.ts b/version.ts index 01d6001..217e2a0 100644 --- a/version.ts +++ b/version.ts @@ -1 +1 @@ -export const VERSION = '1.3.7'; +export const VERSION = "1.3.7";