Metadata-Version: 2.4
Name: chrome-navtree
Version: 0.2.0
Summary: Turn a Chrome profile's browsing history into a cleaned navigation forest.
Project-URL: Homepage, https://github.com/technicalpickles/chrome-navtree
Project-URL: Repository, https://github.com/technicalpickles/chrome-navtree
Project-URL: Issues, https://github.com/technicalpickles/chrome-navtree/issues
Author: Josh Nichols
License: MIT License
        
        Copyright (c) 2026 Josh Nichols
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: browsing,chrome,forest,history,navigation,sqlite
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Utilities
Requires-Python: >=3.11
Provides-Extra: dev
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Provides-Extra: playwright
Requires-Dist: playwright>=1.44; extra == 'playwright'
Description-Content-Type: text/markdown

# chrome-navtree

Turn a Chrome profile's browsing history into a cleaned **navigation forest**: the tree of pages you actually visited, with single-page-app churn collapsed, auth redirects spliced out, and junk destinations pruned.

Chrome's on-disk history already records how you got from page to page (referrer and tab-opener edges, plus a transition type per visit). This reconstructs that into a forest of trees, then cleans it so what is left is the shape of your actual journeys instead of every redirect and tab refresh.

This package is the **engine**. What you build on top of it (a viewer, an archiver, a note-taking integration) is yours to write. A self-contained HTML viewer ships in the box so you can see the result immediately.

## Install

Run it with no install at all (needs [uv](https://docs.astral.sh/uv/)):

```bash
uvx --from git+https://github.com/technicalpickles/chrome-navtree chrome-navtree profiles
```

Or install it as a CLI and library:

```bash
pip install chrome-navtree      # or: uv add chrome-navtree
uv tool install chrome-navtree  # isolated, on your PATH
```

Python 3.11+ (uses stdlib `tomllib`). No runtime dependencies.

## Quick start

```bash
# List the Chrome profiles on this machine
chrome-navtree profiles

# Open an HTML viewer of the last 7 days for your "work" profile
chrome-navtree preview --profile work --since 7d --open

# Emit the cleaned tree as JSON for something else to consume
chrome-navtree dump --profile work --since 24h > forest.json
```

`--since` takes `7d`, `24h`, or an ISO date like `2026-07-01`. `--profile` takes a profile key (from `chrome-navtree profiles`) or a raw directory name like `Default`.

The live Chrome database is never touched. It gets copied to a temp file and opened read-only, so it is safe to run while Chrome is open.

## Library

Every consumer goes through the same three steps, so the viewer and the JSON output can never drift:

```python
from datetime import timedelta
from chrome_navtree import discover_profiles, load_visits, build_forest, clean, Rules

prof   = discover_profiles()["work"]
visits = load_visits(prof.history_path, since=timedelta(days=7))
raw    = build_forest(visits)                      # faithful, unfiltered
forest = clean(raw, Rules.load("rules.toml"))      # collapsed / spliced / pruned

forest.to_dict()   # JSON-serializable: {"roots": [...], "stats": {...}}
```

`clean` is a pure function: it returns a new forest and never mutates its input.

## What cleaning does

Three passes, in order:

- **collapse** folds churn to one node. A single-page app that keeps rewriting its URL, or a GitHub PR you clicked through 40 tabs of, becomes one node with a `visit_count`. Folding is driven by rules (drop query and fragment, plus per-host path folds).
- **splice** removes auth interstitials and reconnects their children to the parent, so a `real page -> SSO -> real page` chain stays one unbroken path instead of three.
- **prune** drops junk destinations along with their whole subtree.

## Rules

The engine carries no knowledge of your specific tools or workplace. What counts as junk, what counts as auth plumbing, and how paths fold all live in TOML.

The package ships [`default_rules.toml`](chrome_navtree/default_rules.toml) with generic defaults: query/fragment dropping, GitHub PR/issue folding, and common SSO providers (Google, Microsoft, Okta, WorkOS). Your own `rules.toml` layers on top, where list values append to the defaults so you only list your own additions:

```toml
[prune]
junk_hosts = ["calendar.example.com", "internal-dashboard.example.com"]

[splice]
auth_hosts = ["sso.mycorp.example"]

[[collapse.fold]]
host = "tickets.example.com"
pattern = "^(/browse/[A-Z]+-[0-9]+)"
```

See [`examples/rules.example.toml`](examples/rules.example.toml) for a fuller template. Pass it with `--rules rules.toml` or `Rules.load("rules.toml")`.

## Data model

- **`Visit`** is one row from Chrome's `visits` table, joined to its URL, with the timestamp and transition decoded.
- **`NavNode`** is a node in the tree: `url`, `title`, `host`, `transition`, `dwell_s`, `visit_count`, `key`, `children`.
- **`NavForest`** is `roots` plus summary `stats` (raw vs cleaned node counts, distinct hosts, spliced and pruned tallies).

## Notes and limits

- Chrome expires history after about 90 days, so that is the practical window. Archiving beyond it is a job for a consumer built on this engine, not the engine itself.
- Auth detection is deliberately aggressive: anything titled "sign in" or "log in", plus common auth URL shapes and identity-provider hosts, gets spliced. For a work browsing corpus that is the right trade. If a real page ever vanishes, narrow the `splice` rules.

## Development

```bash
uv sync --extra dev     # ruff, mypy, pytest
uv run --extra dev pytest
mise run check          # ruff + ruff format + mypy + pytest (what CI runs)
```

Tooling is pinned with [mise](https://mise.jdx.dev/) and git hooks run through [hk](https://hk.jdx.dev/). After cloning, `mise install` then `hk install` gives you ruff + mypy on every commit. If you would rather not use mise, the checks are plain `uv run --extra dev ...` commands.

### Test data, isolated from your real Chrome

The live Chrome database is never touched (it is copied to a temp file and opened read-only), and none of the dev tooling reads or writes a real Chrome profile. Tests build synthetic Chrome databases in a temp dir and assert an exact cleaned forest, so they are deterministic and need no real browser data.

To poke at the CLI by hand, scaffold a fake Chrome profile (`--chrome-root` goes after the subcommand):

```bash
python scripts/fake_chrome.py /tmp/fake-chrome
chrome-navtree profiles --chrome-root /tmp/fake-chrome
chrome-navtree dump --chrome-root /tmp/fake-chrome --profile work
```

For realistic data, `scripts/seed_realistic.py` drives a throwaway Chromium profile (its own `--user-data-dir`, never your real one) with Playwright and snapshots the history into `tests/fixtures/realistic/`:

```bash
uv sync --extra playwright
uv run playwright install chromium
uv run python scripts/seed_realistic.py --scripted   # or --manual to click around yourself
```

Playwright is an opt-in extra and never runs in CI.

## License

MIT. See [LICENSE](LICENSE).
