Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ local Amp thread JSON files.
| Forge | `~/.forge/` |
| Gemini CLI | `~/.gemini/` |
| Goose | `~/.local/share/goose/sessions/` (macOS and Linux), `%APPDATA%\\Block\\goose\\data\\sessions\\` (Windows) |
| Crush | `~/.local/share/crush/projects.json` registry pointing at per-project `~/<project>/.crush/crush.db` stores (macOS and Linux), `%LOCALAPPDATA%\\crush\\projects.json` (Windows) |
| gptme | `~/.local/share/gptme/logs/` |
| Grok | `~/.grok/sessions/` |
| Hermes Agent | `~/.hermes/sessions/` |
Expand Down Expand Up @@ -417,6 +418,14 @@ models, token usage, and recorded costs. Set `GOOSE_PATH_ROOT` to a Goose path
root (sessions are read from `<root>/data/sessions/`), or `agents.goose.dirs` to
one or more data or sessions directories.

Crush sessions are read from each project's SQLite `.crush/crush.db`, including
transcript content, thinking, tool calls and results, session relationships,
models, and recorded session costs. The project registry lives at
`~/.local/share/crush/projects.json` (macOS and Linux) or
`%LOCALAPPDATA%\crush\projects.json` (Windows). Set `CRUSH_DIR` or
`agents.crush.dirs` to one or more Crush data directories, `.crush`
directories, or `crush.db` files.

Each directory can be overridden with an environment variable. See the
[configuration docs](https://agentsview.io/configuration/) for details. Cursor
attribution stats are a live, machine-local read from
Expand Down
15 changes: 15 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ keeps its default directories.
| Codex | `~/.codex/sessions/` and `~/.codex/archived_sessions/` | JSONL per session |
| Command Code | `~/.commandcode/projects/` | JSONL per session, optional `.meta.json` sidecar |
| Copilot CLI | `~/.copilot/` | JSONL per session under `session-state/` |
| Crush | (platform-specific, see below) | Per-project SQLite `crush.db` with transcripts, tool activity, relationships, and recorded session costs |
| Devin CLI | `~/.local/share/devin/` (Linux), `~/Library/Application Support/devin/` (macOS) | Local CLI data rooted at the directory that contains `cli/`; session data is discovered under `<root>/cli/...` |
| Evener | `~/.local/state/evener/` (or `$XDG_STATE_HOME/evener/`) | Semantic v2 `*.transcript.jsonl` and optional `*.meta.json` |
| Cortex Code | `~/.snowflake/cortex/conversations/` | JSON / JSONL per session |
Expand Down Expand Up @@ -447,6 +448,19 @@ directory.
directly to that sessions directory, its parent data directory, or the database
file.

**Crush default directories** are:

- **macOS and Linux:** `~/.local/share/crush/`
- **Windows:** `%LOCALAPPDATA%/crush/`

AgentsView reads `projects.json` in that directory and discovers one SQLite
`crush.db` per project under each listed `<project>/.crush/` data directory.
Set `CRUSH_DIR` or `agents.crush.dirs` to override the default with one or more
directories: each entry may be a Crush data directory (containing
`projects.json`), a `<project>/.crush` directory, or a `crush.db` file. A
project added after AgentsView starts is picked up by the next scheduled
reconciliation pass.

Omnigent sessions are read from `~/.omnigent/chat.db`. Set `OMNIGENT_DIR` or
`agents.omnigent.dirs` to override the default directory. AgentsView creates one
session per conversation and supports the split text-ID and current binary-UUID
Expand Down Expand Up @@ -779,6 +793,7 @@ export CODEX_SESSIONS_DIR=~/custom/codex
export CODEX_HOME=~/custom/codex-home # re-roots the default sessions/ paths
export COMMANDCODE_PROJECTS_DIR=~/custom/commandcode
export COPILOT_DIR=~/custom/copilot
export CRUSH_DIR=~/custom/crush
export DEVIN_DIR=~/Library/Application\ Support/devin
export CORTEX_DIR=~/custom/cortex
export CURSOR_PROJECTS_DIR=~/custom/cursor
Expand Down
45 changes: 45 additions & 0 deletions docs/internal/session-format-sources.md
Original file line number Diff line number Diff line change
Expand Up @@ -2902,6 +2902,51 @@ preservation of archived messages for OpenCode, Kilo, MiMoCode, and Icodemate.
distinguish machines with identical root paths. No legacy Tau v1 conversion,
native transfer, or index metadata synchronization is included.

## Charm Crush (`crush`)

- **Format:** One SQLite `crush.db` per project under the project's `.crush`
data directory (configurable with `options.data_directory` or `--data-dir`).
Session metadata, cumulative token totals, and cost live in `sessions`;
ordered role messages with a JSON `parts` array (`text`, `reasoning`,
`tool_call`, `tool_result`, `finish`) live in `messages`. A project registry
at the global data directory maps project paths to data directories.
- **Evidence:** `source`.
- **Upstream:** Clone `https://github.com/charmbracelet/crush.git` at
`ce980ada68444b7591d8dfa631af7e94b2aba0b3`; see the pinned
[initial schema](https://github.com/charmbracelet/crush/blob/ce980ada68444b7591d8dfa631af7e94b2aba0b3/internal/db/migrations/20250424200609_initial.sql),
[project registry](https://github.com/charmbracelet/crush/blob/ce980ada68444b7591d8dfa631af7e94b2aba0b3/internal/projects/projects.go),
and
[data-directory resolution](https://github.com/charmbracelet/crush/blob/ce980ada68444b7591d8dfa631af7e94b2aba0b3/internal/config/load.go).
The registry is `projects.json` next to the global config file:
`~/.local/share/crush/projects.json` (or `$XDG_DATA_HOME/crush/`,
`$CRUSH_GLOBAL_DATA/`) on macOS and Linux and
`%LOCALAPPDATA%\crush\projects.json` on Windows. Timestamps are Unix
seconds despite older schema comments claiming milliseconds; the
`updated_at` triggers write `strftime('%s','now')`. Later migrations add
`summary_message_id`, `todos`, `provider`, `is_summary_message`,
`read_files`, and Prism/Hyper metadata columns. The schema was reverified
against a live 2026-09 Crush store on 2026-09-11.
- **Usage and cost:** `sessions.prompt_tokens` and `sessions.completion_tokens`
are cumulative session totals and `sessions.cost` is a recorded provider
cost, with no per-request breakdown and no per-message token fields.
Agentsview emits exactly one aggregate `session` usage event per session,
tagged with the most recent assistant message's model, and reports no
per-message token data.
- **Agentsview:** `internal/parser/crush.go` and
`internal/parser/crush_provider.go` require the `messages.parts` column as
the format marker (the `goose_db_version` table belongs to Crush's vendored
goose migration tool and proves nothing), expand `projects.json` entries
into provider roots, attribute each session to the project directory above
the store, pair `tool_result` parts into system tool-result messages keyed
by call ID, emit `is_summary_message` rows as compact-boundary system
messages, and fingerprint the session and message rows so same-second
edits still invalidate freshness (`FingerprintHashRequiredForFreshness`).
Watcher events use bounded rowid cursors over `sessions` and `messages` so
work stays proportional to inserted rows, with a periodic reconciliation
pass covering metadata-only edits and row deletes. A malformed `parts`
value fails that session's parse rather than degrading silently, matching
the goose parser's policy.

[evener-source-1]: https://github.com/prime-radiant-inc/evener/blob/da7c06396c9848abfae362dcffce3861a6a0c95a/agent/transcript/transcript.go
[evener-source-2]: https://github.com/prime-radiant-inc/evener/blob/da7c06396c9848abfae362dcffce3861a6a0c95a/agent/schema/turn.go
[evener-source-3]: https://github.com/prime-radiant-inc/evener/blob/da7c06396c9848abfae362dcffce3861a6a0c95a/llm/types.go
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lib/utils/agents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ describe("KNOWN_AGENTS", () => {
"omnigent",
"codebuff",
"freebuff",
"crush",
]);
});

Expand Down Expand Up @@ -101,6 +102,7 @@ describe("agentColor", () => {
expect(agentColor("piebald")).toBe("var(--accent-orange)");
expect(agentColor("roocode")).toBe("var(--accent-rose)");
expect(agentColor("omnigent")).toBe("var(--accent-teal)");
expect(agentColor("crush")).toBe("var(--accent-coral)");
});

it("falls back to blue for unknown agents", () => {
Expand Down Expand Up @@ -172,6 +174,7 @@ describe("agentLabel", () => {
expect(agentLabel("omnigent")).toBe("Omnigent");
expect(agentLabel("traex")).toBe("TraeX");
expect(agentLabel("opencodereview")).toBe("Open Code Review");
expect(agentLabel("crush")).toBe("Charm Crush");
});

it("capitalizes simple agent names", () => {
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/lib/utils/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ export const KNOWN_AGENTS: readonly AgentMeta[] = [
{ name: "omnigent", color: "var(--accent-teal)", label: "Omnigent" },
{ name: "codebuff", color: "var(--accent-amber)", label: "Codebuff" },
{ name: "freebuff", color: "var(--accent-sky)", label: "Freebuff" },
{
name: "crush",
color: "var(--accent-coral)",
label: "Charm Crush",
},
];

const agentColorMap = new Map(KNOWN_AGENTS.map((a) => [a.name, a.color]));
Expand Down
9 changes: 9 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,15 @@ func Default() (Config, error) {
dirs = []string{filepath.Join(stateHome, "evener")}
}
}
// CRUSH_GLOBAL_DATA and XDG_DATA_HOME override Crush's default
// data directory following the upstream XDG conventions.
if def.Type == parser.AgentCrush && root == "" {
if crushGlobal := os.Getenv("CRUSH_GLOBAL_DATA"); crushGlobal != "" && filepath.IsAbs(crushGlobal) {
dirs = []string{crushGlobal}
} else if xdgData := os.Getenv("XDG_DATA_HOME"); xdgData != "" && filepath.IsAbs(xdgData) {
dirs = []string{filepath.Join(xdgData, "crush")}
}
}
// Keep the Hermes profiles container as a stable provider root. The
// provider enumerates its children on every discovery pass, so profiles
// created after startup become visible without rebuilding Config or the
Expand Down
4 changes: 4 additions & 0 deletions internal/parser/capabilities_sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ func TestProviderSyncSemanticsDeclarations(t *testing.T) {
FingerprintHashInCacheKey: true,
FingerprintHashRequiredForFreshness: true,
},
AgentCrush: {
FingerprintHashInCacheKey: true,
FingerprintHashRequiredForFreshness: true,
},
AgentZed: {
UnchangedResults: UnchangedResultMTime,
},
Expand Down
Loading
Loading