Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
0342462
feat: detailed ingest output with skip reasons, progress bar, and totals
krsnaa May 8, 2026
94e167f
feat: add Cursor session support (#17)
GradientCake Jun 24, 2026
567ca34
docs: session groups design spec
artcashin Jul 19, 2026
e6c6a60
docs: session groups implementation plan
artcashin Jul 19, 2026
20fef48
feat(groups): add groups and session_groups tables
artcashin Jul 19, 2026
fbd7734
feat(groups): add group helper module (create/list/rename/delete/assign)
artcashin Jul 19, 2026
ad479cc
feat(groups): add Groups tab to nav
artcashin Jul 19, 2026
4c212d1
feat(groups): add groups index and detail views
artcashin Jul 19, 2026
a770550
feat(groups): add group + session-assign routes
artcashin Jul 19, 2026
1bbe0b5
feat(groups): add 'Add to group' control to session view
artcashin Jul 19, 2026
f9206c9
fix(groups): harden assign route, use SQLITE_CONSTRAINT_UNIQUE, test …
artcashin Jul 19, 2026
054537a
docs: Desktop group import (Phase 1) design spec
artcashin Jul 19, 2026
8c116bf
docs: Desktop group import (Phase 1) implementation plan
artcashin Jul 19, 2026
4191bd4
docs: Show/Hide Thinking & Tools session-display design spec
artcashin Jul 19, 2026
6286884
docs: Show/Hide Thinking & Tools implementation plan
artcashin Jul 19, 2026
5ab1ab3
feat(session): add transcript parser (text/thinking/tool blocks)
artcashin Jul 19, 2026
1a38bf0
feat(session): render thinking/tools on demand with toggle controls
artcashin Jul 19, 2026
a7dd804
feat(session): wire thinking/tools query params into /session/:id
artcashin Jul 19, 2026
2a118c1
fix(session): encode & in toggle href, dimension-specific no-data not…
artcashin Jul 19, 2026
16e431f
docs: transcript visual-match (React viewer) design spec
artcashin Jul 19, 2026
692cfa4
docs: transcript visual-match implementation plan
artcashin Jul 19, 2026
c688f02
feat(session): capture tool id/input and tool_result id in transcript…
artcashin Jul 19, 2026
f2f7236
feat(session): collapsible tool calls, thinking bubbles, teal accent …
artcashin Jul 19, 2026
5f8b13e
fix(session): pair tool results order-independently; style orphan res…
artcashin Jul 19, 2026
8900ee2
chore(session): drop dead inline style; align spec on duplicate-resul…
artcashin Jul 19, 2026
0652709
docs: combined-app vision & roadmap (React frontend on notebook backend)
artcashin Jul 19, 2026
56a154e
docs: Phase 1 spec — foundation + session display parity
artcashin Jul 19, 2026
96ed3e5
docs: Phase 1 spec — exact subagent mapping via .meta.json (spike)
artcashin Jul 19, 2026
52835d0
docs: Phase 1 implementation plan (foundation + session display)
artcashin Jul 19, 2026
8bcefa9
feat(react): scaffold web/ Vite app + Hono /api router + wiring
artcashin Jul 19, 2026
e58956f
feat(api): structured transcript parser + /api/sessions/:id/transcript
artcashin Jul 19, 2026
3f8ea1b
feat(api): subagent discovery via .meta.json + endpoints
artcashin Jul 19, 2026
2104904
fix(api): prevent path traversal via agentId in /api/subagent route
artcashin Jul 19, 2026
6b3c720
feat(api): session list endpoint
artcashin Jul 19, 2026
9deced6
fix(api): clamp session-list limit/offset to non-negative
artcashin Jul 19, 2026
f5fd961
feat(react): typed API layer + session list view + routing
artcashin Jul 19, 2026
924c0f2
feat(react): session viewer — collapsible tools, thinking, subagents,…
artcashin Jul 19, 2026
831e0fb
feat(react): engineering-notebook shell — top nav + 3-panel Journal
artcashin Jul 19, 2026
2e33ef1
feat(react): port claude-session-viewer's session display into panel 3
artcashin Jul 19, 2026
fd1353a
feat(react): session title (first prompt) + meta row in panel 3
artcashin Jul 19, 2026
afd0e38
fix(react): derive session title from the real first prompt
artcashin Jul 19, 2026
ee23716
fix(react): hide synthetic command records from the transcript
artcashin Jul 19, 2026
d9e6c38
fix(react): match claude-session-viewer's filtering exactly
artcashin Jul 19, 2026
60f5611
feat(react): Projects, Calendar, and Groups views (+ Desktop group im…
artcashin Jul 19, 2026
da6fe77
feat(react): resolve active conversation branch (port viewer tree.ts)
artcashin Jul 19, 2026
ef10ca9
feat(react): show a 'redacted thinking' marker for signature-only blocks
artcashin Jul 19, 2026
22c238c
perf(react): incremental rendering for large transcripts
artcashin Jul 19, 2026
d57c004
feat(react): move Show/Hide thinking & tools to the top bar
artcashin Jul 19, 2026
99e6324
feat(sessions): session titles — Desktop titles + LLM-generated fallback
artcashin Jul 19, 2026
18f52b1
fix(titles): exclude subagents from title backfill
artcashin Jul 19, 2026
1e8b1bc
fix(api): include session titles in journal-entries endpoint too
artcashin Jul 19, 2026
f0c99b1
feat(titles): panel-3 title, summarize generates titles, sticky user …
artcashin Jul 19, 2026
d3fa01d
fix(transcript): hide local-command-stdout messages (command output)
artcashin Jul 19, 2026
cefba3b
feat(subagents): link subagent sessions to their originating parent
artcashin Jul 20, 2026
1f4c422
feat(api): expose subagent links and keep subagents out of session lists
artcashin Jul 20, 2026
5b30dfb
feat(web): surface subagents across the 3-panel viewer
artcashin Jul 20, 2026
f540656
fix(web): escape the date in legacy summary-card views
artcashin Jul 20, 2026
d0e88f9
fix(sync): ingest local sources even when no remote source is configured
artcashin Jul 20, 2026
c6242ac
feat(transcript): uncompacted-by-default view + compacted/uncompacted…
artcashin Jul 20, 2026
45751c9
style(transcript): put the compacted toggle first in the top bar
artcashin Jul 20, 2026
1023a33
feat(transcript): disable the compacted toggle when the session isn't…
artcashin Jul 20, 2026
22e9392
style(transcript): color the compacted toggle by availability
artcashin Jul 20, 2026
fec99dc
feat(groups): drag a session onto a group to assign it (drop on Ungro…
artcashin Jul 20, 2026
660aad8
fix(ci): scope bun test to the backend and run the web suite in CI
artcashin Aug 2, 2026
b30b853
Merge #16 (verbose ingest output)
artcashin Aug 2, 2026
7754159
Merge #18 (Cursor session support)
artcashin Aug 2, 2026
2ad49f2
feat: import OpenCode sessions and make the summary model configurable
artcashin Aug 2, 2026
a2304a2
Merge OpenCode import + configurable model (rebased on #16, #18)
artcashin Aug 2, 2026
44b4f26
feat(titles): generate session titles with the configured provider
artcashin Aug 2, 2026
b09ccb0
fix(ingest): repair the counter lost when #16 and #19 merge
artcashin Aug 2, 2026
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
4 changes: 2 additions & 2 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ set -euo pipefail

echo "Running pre-commit checks..."

echo ">> bun test"
bun test
echo ">> bun test (backend)"
bun test ./src

echo ">> typecheck"
bun run typecheck
Expand Down
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@ jobs:
- run: bun install --frozen-lockfile
- run: bun test

web:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- uses: oven-sh/setup-bun@v2
with:
bun-version: latest

# web/ is not a workspace of the root package, so its dependencies have
# to be installed separately before vitest can run.
- run: bun install --frozen-lockfile
working-directory: web
- run: bunx vitest run
working-directory: web

typecheck:
runs-on: ubuntu-latest
steps:
Expand Down
86 changes: 84 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Engineering Notebook

A CLI tool that ingests [Claude Code](https://docs.anthropic.com/en/docs/claude-code) and [Codex](https://openai.com/index/introducing-codex/) session transcripts, generates LLM-powered daily summaries, and serves a web UI for browsing your engineering journal.
A CLI tool that ingests [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Codex](https://openai.com/index/introducing-codex/), and [Cursor](https://cursor.com) session transcripts, generates LLM-powered daily summaries, and serves a web UI for browsing your engineering journal.

Think of it as an automatic engineering diary — it watches your AI coding sessions and distills them into a searchable, browsable narrative of what you built, what problems you hit, and what decisions you made.

![Journal view — three-panel layout with date index, entry detail, and conversation transcript](docs/screenshots/journal-view.gif)

## How It Works

1. **Ingest** — Scans directories of Claude Code and Codex JSONL session files, parses out the human-readable conversation (stripping tool calls, thinking blocks, etc.), and stores them in SQLite.
1. **Ingest** — Scans directories of Claude Code, Codex, and Cursor JSONL session files, parses out the human-readable conversation (stripping tool calls, thinking blocks, etc.), and stores them in SQLite.
2. **Summarize** — Groups sessions by date and project, then uses Claude to write concise engineering journal entries with headlines, summaries, topics, and open questions.
3. **Serve** — Runs a web server with a browsable UI: daily journal, project timelines, calendar/Gantt view, session transcripts, full-text search, and an iCal feed.

Expand Down Expand Up @@ -114,6 +114,57 @@ Config lives at `~/.config/engineering-notebook/config.json`:
| `summary_instructions` | Custom instructions appended to the LLM summarization prompt | `""` |
| `remote_sources` | SSH remote sources to sync before ingesting | `[]` |
| `auto_sync_interval` | Seconds between auto-syncs when serving | `60` |
| `opencode` | OpenCode session import (opt-in) | absent |
| `summary_provider` | Which model writes summaries and session titles | Claude Haiku |

### OpenCode sessions

OpenCode keeps its sessions in a single SQLite database rather than one file per
session, so they cannot be scanned like Claude Code and Codex sources. Enabling
this block exports each session to a staging directory of JSONL files during
`ingest`, which the normal scanner then picks up:

```json
{
"opencode": {
"enabled": true,
"staging_dir": "~/.cache/engineering-notebook/opencode",
"max_count": 200
}
}
```

Sessions are enumerated from OpenCode's database (`opencode session list` only
reports the current directory's project, so it cannot see them all) and their
transcripts are exported with `opencode export`. A manifest of last-seen update
times keeps repeat syncs cheap — only changed sessions are re-exported. Omit
`max_count` to take everything.

Projects are keyed off each session's working directory, so OpenCode and Claude
Code work in the same repo on the same day lands in one journal entry.

### Choosing a model

Journal summaries and session titles are written by Claude Haiku through the
Agent SDK by default, which needs no API key. Any OpenAI-compatible endpoint can be used
instead — including a local llama.cpp server:

```json
{
"summary_provider": {
"type": "openai",
"base_url": "http://your-host:8001/v1",
"model": "gemma-4",
"api_key_env": "SPARK_API_KEY",
"max_tokens": 4000
}
}
```

`api_key_env` names the environment variable holding the key — the key itself is
never stored in the config file. Reasoning models spend completion tokens
thinking before emitting any content, so keep `max_tokens` generous (it defaults
to 4000); too small a budget returns an empty response rather than a summary.

### Remote Sources

Expand Down Expand Up @@ -142,6 +193,37 @@ webcal://localhost:3000/api/calendar.ics

This creates calendar events for each journal entry, viewable in Apple Calendar, Google Calendar, Outlook, etc.

## Cursor support

Cursor sessions live under `~/.cursor/projects` (Cursor's `agent-transcripts/`
layout). This source is **not** scanned by default — add it explicitly:

```sh
engineering-notebook ingest --source ~/.cursor/projects
```

Or add `~/.cursor/projects` to the `sources` array in your config.

Cursor's transcript format is leaner than Claude Code's or Codex's, so a few
caveats apply:

- **Timestamps come from file modification times.** Cursor transcripts contain no
per-message timestamps, so a session's start and end are taken from the file's
creation and last-modified times, and per-message times are approximate. Copying
or restoring transcript files can reset these.
- **Project names are the raw encoded directory string** (for example
`Users-username-GitRepos-my-repo`). Cursor does not record the working directory,
and its directory names encode the path lossily — both `/` and `.` collapse to
`-` — so the name is shown verbatim rather than guessed at.
- **Cursor sessions do not auto-merge** with the same repository's Claude Code or
Codex sessions, which group by the real working-directory name.
- **Some Cursor projects appear under an opaque numeric id** (for example
`1700000000000`) when Cursor stored no recoverable path.

Planned improvements (not yet implemented): stripping `<attached_files>` and
terminal-selection wrappers from Cursor messages, and recovering real project
names via Cursor's `workspaceStorage` mapping.

## Development

```sh
Expand Down
25 changes: 25 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Bun's test runner owns the backend suite only. The web/ tests are React
# components that need a DOM, which is configured for vitest in web/vite.config.ts
# and is not visible to `bun test` — without this scope a bare `bun test` collects
# them and fails with "document is not defined". Run them with `bun run test:web`.
[test]
root = "src"
Loading