Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 27 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,15 @@ 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
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 <path> # List sessions matching directory path
ocv session <id> # Detailed info for a specific session
ocv search <query> # Search sessions by title or directory
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 <path>` | Filtered session list matching a directory path |
| `session <id>` | Single session detail with messages and todo breakdown |
| `search <query>` | Full-text search over session titles and directories |
| `rename --from-dir <old> -d <new>` | 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 <path>` | Filtered session list matching a directory path |
| `session <id>` | Single session detail with messages and todo breakdown |
| `search <query>` | Full-text search over session titles and directories |
| `rename --from-dir <old> -d <new>` | 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

Expand Down
31 changes: 29 additions & 2 deletions lib/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export interface DashOptions {
exclude?: string;
names?: string[];
jsonMode: boolean;
mergeSameNames?: boolean;
}

// ── Dashboard ────────────────────────────────────────────────────
Expand Down Expand Up @@ -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<string, typeof overview[0]>();
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);
Expand Down Expand Up @@ -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],
Expand Down
41 changes: 41 additions & 0 deletions main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,42 @@ function formatOutput<T>(
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 });
Expand Down Expand Up @@ -89,6 +125,10 @@ async function main() {
"--name <dirs:string>",
"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((
Expand All @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const VERSION = '1.3.7';
export const VERSION = "1.3.7";