scrollback 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scrollback-0.1.0/.github/workflows/ci.yml +71 -0
- scrollback-0.1.0/.github/workflows/publish.yml +98 -0
- scrollback-0.1.0/.gitignore +34 -0
- scrollback-0.1.0/CHANGELOG.md +86 -0
- scrollback-0.1.0/CONTRIBUTING.md +70 -0
- scrollback-0.1.0/LICENSE +21 -0
- scrollback-0.1.0/PKG-INFO +391 -0
- scrollback-0.1.0/README.md +332 -0
- scrollback-0.1.0/ROADMAP.md +71 -0
- scrollback-0.1.0/assets/icon-512.png +0 -0
- scrollback-0.1.0/assets/icon.icns +0 -0
- scrollback-0.1.0/assets/icon.svg +41 -0
- scrollback-0.1.0/assets/screenshots/cli.svg +91 -0
- scrollback-0.1.0/assets/screenshots/web.png +0 -0
- scrollback-0.1.0/pyproject.toml +100 -0
- scrollback-0.1.0/scripts/demo_data.py +188 -0
- scrollback-0.1.0/scripts/screenshots.py +147 -0
- scrollback-0.1.0/src/scrollback/__init__.py +8 -0
- scrollback-0.1.0/src/scrollback/assets/icon-256.png +0 -0
- scrollback-0.1.0/src/scrollback/assets/icon.icns +0 -0
- scrollback-0.1.0/src/scrollback/cli.py +1139 -0
- scrollback-0.1.0/src/scrollback/clipboard.py +34 -0
- scrollback-0.1.0/src/scrollback/export.py +293 -0
- scrollback-0.1.0/src/scrollback/fts.py +307 -0
- scrollback-0.1.0/src/scrollback/highlight.py +128 -0
- scrollback-0.1.0/src/scrollback/katexbundle.py +81 -0
- scrollback-0.1.0/src/scrollback/launcher_install.py +209 -0
- scrollback-0.1.0/src/scrollback/launchers/scrollback.bat +19 -0
- scrollback-0.1.0/src/scrollback/launchers/scrollback.command +19 -0
- scrollback-0.1.0/src/scrollback/launchers/scrollback.desktop +10 -0
- scrollback-0.1.0/src/scrollback/launchers/scrollback.sh +12 -0
- scrollback-0.1.0/src/scrollback/mathspan.py +180 -0
- scrollback-0.1.0/src/scrollback/minimd.py +205 -0
- scrollback-0.1.0/src/scrollback/models.py +135 -0
- scrollback-0.1.0/src/scrollback/serialize.py +83 -0
- scrollback-0.1.0/src/scrollback/serverconfig.py +66 -0
- scrollback-0.1.0/src/scrollback/sources/__init__.py +6 -0
- scrollback-0.1.0/src/scrollback/sources/aider.py +244 -0
- scrollback-0.1.0/src/scrollback/sources/base.py +117 -0
- scrollback-0.1.0/src/scrollback/sources/claudecode.py +631 -0
- scrollback-0.1.0/src/scrollback/sources/codex.py +281 -0
- scrollback-0.1.0/src/scrollback/sources/opencode.py +357 -0
- scrollback-0.1.0/src/scrollback/sources/registry.py +39 -0
- scrollback-0.1.0/src/scrollback/store.py +384 -0
- scrollback-0.1.0/src/scrollback/termrender.py +170 -0
- scrollback-0.1.0/src/scrollback/web/__init__.py +1 -0
- scrollback-0.1.0/src/scrollback/web/app.py +359 -0
- scrollback-0.1.0/src/scrollback/web/static/app.js +1245 -0
- scrollback-0.1.0/src/scrollback/web/static/apple-touch-icon.png +0 -0
- scrollback-0.1.0/src/scrollback/web/static/favicon.png +0 -0
- scrollback-0.1.0/src/scrollback/web/static/favicon.svg +41 -0
- scrollback-0.1.0/src/scrollback/web/static/index.html +75 -0
- scrollback-0.1.0/src/scrollback/web/static/style.css +628 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/highlight.min.js +1213 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/hljs-dark.min.css +10 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/hljs-light.min.css +10 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/katex.min.css +1 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/katex/katex.min.js +1 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/marked.min.js +6 -0
- scrollback-0.1.0/src/scrollback/web/static/vendor/purify.min.js +3 -0
- scrollback-0.1.0/src/scrollback/webopen.py +96 -0
- scrollback-0.1.0/tests/test_aider.py +87 -0
- scrollback-0.1.0/tests/test_claude_paging.py +84 -0
- scrollback-0.1.0/tests/test_claude_subagents.py +120 -0
- scrollback-0.1.0/tests/test_cli_helpers.py +66 -0
- scrollback-0.1.0/tests/test_codex.py +70 -0
- scrollback-0.1.0/tests/test_export.py +147 -0
- scrollback-0.1.0/tests/test_fts.py +151 -0
- scrollback-0.1.0/tests/test_highlight.py +41 -0
- scrollback-0.1.0/tests/test_launcher_install.py +101 -0
- scrollback-0.1.0/tests/test_minimd.py +140 -0
- scrollback-0.1.0/tests/test_models.py +63 -0
- scrollback-0.1.0/tests/test_serverconfig.py +66 -0
- scrollback-0.1.0/tests/test_sources_live.py +96 -0
- scrollback-0.1.0/tests/test_stats_resume.py +89 -0
- scrollback-0.1.0/tests/test_store_filters.py +120 -0
- scrollback-0.1.0/tests/test_web_api.py +240 -0
- scrollback-0.1.0/tests/test_webopen.py +45 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
name: test (py${{ matrix.python-version }})
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
strategy:
|
|
16
|
+
fail-fast: false
|
|
17
|
+
matrix:
|
|
18
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
23
|
+
uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: ${{ matrix.python-version }}
|
|
26
|
+
|
|
27
|
+
- name: Install (dev extra)
|
|
28
|
+
run: |
|
|
29
|
+
python -m pip install --upgrade pip
|
|
30
|
+
# The dev extra is self-sufficient for the full suite (fastapi +
|
|
31
|
+
# uvicorn + httpx for the web-API tests). pywebview is intentionally
|
|
32
|
+
# excluded -- it needs a GUI backend CI runners lack, and no test
|
|
33
|
+
# imports it.
|
|
34
|
+
python -m pip install -e ".[dev]"
|
|
35
|
+
|
|
36
|
+
- name: Lint (ruff)
|
|
37
|
+
run: ruff check src tests
|
|
38
|
+
|
|
39
|
+
- name: Test (pytest)
|
|
40
|
+
run: pytest -q
|
|
41
|
+
|
|
42
|
+
build:
|
|
43
|
+
name: build wheel + sdist
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v4
|
|
47
|
+
- uses: actions/setup-python@v5
|
|
48
|
+
with:
|
|
49
|
+
python-version: "3.12"
|
|
50
|
+
- name: Build
|
|
51
|
+
run: |
|
|
52
|
+
python -m pip install --upgrade pip build
|
|
53
|
+
python -m build
|
|
54
|
+
- name: Check artifacts contain data files
|
|
55
|
+
run: |
|
|
56
|
+
python - <<'PY'
|
|
57
|
+
import glob, zipfile
|
|
58
|
+
whl = sorted(glob.glob("dist/*.whl"))[-1]
|
|
59
|
+
names = zipfile.ZipFile(whl).namelist()
|
|
60
|
+
need = ["web/static/app.js", "web/static/vendor/marked.min.js",
|
|
61
|
+
"web/static/vendor/katex/katex.min.js",
|
|
62
|
+
"web/static/vendor/katex/fonts/KaTeX_Main-Regular.woff2",
|
|
63
|
+
"launchers/scrollback.command", "assets/icon.icns"]
|
|
64
|
+
missing = [n for n in need if not any(n in x for x in names)]
|
|
65
|
+
assert not missing, f"wheel missing data files: {missing}"
|
|
66
|
+
print("wheel OK:", whl.split('/')[-1])
|
|
67
|
+
PY
|
|
68
|
+
- uses: actions/upload-artifact@v4
|
|
69
|
+
with:
|
|
70
|
+
name: dist
|
|
71
|
+
path: dist/*
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Tag-gated release. Push a version tag (e.g. `v0.1.0`) to build, validate, and
|
|
4
|
+
# publish the sdist + wheel to PyPI via Trusted Publishing (OIDC) -- no API
|
|
5
|
+
# token or password is stored in the repo. Configure the trusted publisher once
|
|
6
|
+
# at https://pypi.org/manage/project/scrollback/settings/publishing/ (or via
|
|
7
|
+
# "pending publisher" before the first upload), matching:
|
|
8
|
+
# owner = a-attia, repo = scrollback, workflow = publish.yml, env = pypi.
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
push:
|
|
12
|
+
tags: ["v*"]
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
# Re-run the full test matrix + lint before releasing, so a tag can never
|
|
19
|
+
# publish a red build.
|
|
20
|
+
test:
|
|
21
|
+
name: test (py${{ matrix.python-version }})
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
strategy:
|
|
24
|
+
fail-fast: false
|
|
25
|
+
matrix:
|
|
26
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
- uses: actions/setup-python@v5
|
|
30
|
+
with:
|
|
31
|
+
python-version: ${{ matrix.python-version }}
|
|
32
|
+
- run: |
|
|
33
|
+
python -m pip install --upgrade pip
|
|
34
|
+
python -m pip install -e ".[dev]"
|
|
35
|
+
- run: ruff check src tests
|
|
36
|
+
- run: pytest -q
|
|
37
|
+
|
|
38
|
+
build:
|
|
39
|
+
name: build + validate
|
|
40
|
+
needs: test
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
steps:
|
|
43
|
+
- uses: actions/checkout@v4
|
|
44
|
+
- uses: actions/setup-python@v5
|
|
45
|
+
with:
|
|
46
|
+
python-version: "3.12"
|
|
47
|
+
- name: Build sdist + wheel
|
|
48
|
+
run: |
|
|
49
|
+
python -m pip install --upgrade pip build twine
|
|
50
|
+
python -m build
|
|
51
|
+
- name: Verify the tag matches the package version
|
|
52
|
+
run: |
|
|
53
|
+
python - <<'PY'
|
|
54
|
+
import os, tomllib, pathlib, re
|
|
55
|
+
# version is dynamic from src/scrollback/__init__.py
|
|
56
|
+
init = pathlib.Path("src/scrollback/__init__.py").read_text()
|
|
57
|
+
ver = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', init).group(1)
|
|
58
|
+
tag = os.environ["GITHUB_REF_NAME"]
|
|
59
|
+
assert tag == f"v{ver}", f"tag {tag!r} does not match package version v{ver}"
|
|
60
|
+
print(f"tag {tag} matches version {ver}")
|
|
61
|
+
PY
|
|
62
|
+
- name: Validate metadata (twine check)
|
|
63
|
+
run: python -m twine check dist/*
|
|
64
|
+
- name: Verify wheel ships its data files
|
|
65
|
+
run: |
|
|
66
|
+
python - <<'PY'
|
|
67
|
+
import glob, zipfile
|
|
68
|
+
whl = sorted(glob.glob("dist/*.whl"))[-1]
|
|
69
|
+
names = zipfile.ZipFile(whl).namelist()
|
|
70
|
+
need = ["web/static/app.js", "web/static/vendor/marked.min.js",
|
|
71
|
+
"web/static/vendor/katex/katex.min.js",
|
|
72
|
+
"web/static/vendor/katex/fonts/KaTeX_Main-Regular.woff2",
|
|
73
|
+
"launchers/scrollback.command", "assets/icon.icns"]
|
|
74
|
+
missing = [n for n in need if not any(n in x for x in names)]
|
|
75
|
+
assert not missing, f"wheel missing data files: {missing}"
|
|
76
|
+
print("wheel OK:", whl.split('/')[-1])
|
|
77
|
+
PY
|
|
78
|
+
- uses: actions/upload-artifact@v4
|
|
79
|
+
with:
|
|
80
|
+
name: dist
|
|
81
|
+
path: dist/*
|
|
82
|
+
|
|
83
|
+
publish:
|
|
84
|
+
name: publish to PyPI
|
|
85
|
+
needs: build
|
|
86
|
+
runs-on: ubuntu-latest
|
|
87
|
+
environment:
|
|
88
|
+
name: pypi
|
|
89
|
+
url: https://pypi.org/p/scrollback
|
|
90
|
+
permissions:
|
|
91
|
+
id-token: write # OIDC token for Trusted Publishing
|
|
92
|
+
steps:
|
|
93
|
+
- uses: actions/download-artifact@v4
|
|
94
|
+
with:
|
|
95
|
+
name: dist
|
|
96
|
+
path: dist
|
|
97
|
+
- name: Publish
|
|
98
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
*.egg
|
|
9
|
+
|
|
10
|
+
# Test / coverage / lint
|
|
11
|
+
.pytest_cache/
|
|
12
|
+
.ruff_cache/
|
|
13
|
+
.coverage
|
|
14
|
+
htmlcov/
|
|
15
|
+
.tox/
|
|
16
|
+
.nox/
|
|
17
|
+
|
|
18
|
+
# Virtual envs
|
|
19
|
+
.venv/
|
|
20
|
+
venv/
|
|
21
|
+
env/
|
|
22
|
+
|
|
23
|
+
# Editors / OS
|
|
24
|
+
.DS_Store
|
|
25
|
+
.idea/
|
|
26
|
+
.vscode/
|
|
27
|
+
|
|
28
|
+
# scrollback: never commit anyone's exported sessions
|
|
29
|
+
*.session.md
|
|
30
|
+
*.session.html
|
|
31
|
+
*.session.json
|
|
32
|
+
|
|
33
|
+
# Generated icon intermediates (the SVG source + .icns are kept)
|
|
34
|
+
assets/icon.iconset/
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to scrollback are documented here. The format is based
|
|
4
|
+
on [Keep a Changelog](https://keepachangelog.com/), and the project aims to
|
|
5
|
+
follow [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-06-30
|
|
10
|
+
|
|
11
|
+
The first release. scrollback reads AI coding-agent session history
|
|
12
|
+
(opencode + Claude Code) read-only and lets you browse, search, copy, and
|
|
13
|
+
export it from a CLI and a local web app.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **CLI** (`scrollback`): `sources`, `list`, `show`, `search`, `export`
|
|
18
|
+
(markdown / json / html / text), `copy`, `stats`, `resume`, `web`,
|
|
19
|
+
`index`, `doctor`, and `install-launcher`.
|
|
20
|
+
- **Source adapters** (pluggable, read-only): opencode (SQLite), Claude Code
|
|
21
|
+
(JSONL, with subagent sidechains folded under their parent), Codex
|
|
22
|
+
(`rollout-*.jsonl`), and Aider (`.aider.chat.history.md`). More are queued
|
|
23
|
+
in `ROADMAP.md`.
|
|
24
|
+
- `stats` aggregates session/message/token/cost totals plus top projects;
|
|
25
|
+
`resume` prints the native command to continue a session in its own agent.
|
|
26
|
+
- Listing filters: `--source`, `--dir`, `--query`, `--since` / `--until`,
|
|
27
|
+
pagination (`--offset` / `--page`), usage columns (`--usage`), and
|
|
28
|
+
subagent folding (on by default; `--no-fold`). Optional coloured output
|
|
29
|
+
via `rich`.
|
|
30
|
+
- **Web app** (`scrollback web`): local, read-only, served on
|
|
31
|
+
`127.0.0.1`. Session list with source filters, date filters, and a
|
|
32
|
+
`titles | contents` search scope; lazy, windowed transcript loading so
|
|
33
|
+
very large sessions open instantly; in-transcript find; per-message and
|
|
34
|
+
per-session copy; export and print; light/dark theme; keyboard
|
|
35
|
+
navigation; a frozen session header with a scrolling message body.
|
|
36
|
+
- **Markdown rendering**: assistant/user text renders as Markdown with code
|
|
37
|
+
highlighting -- in the browser (vendored marked + highlight.js) and in
|
|
38
|
+
the static HTML export (a dependency-free Python renderer + highlighter).
|
|
39
|
+
- **Math / equation rendering**: delimited LaTeX (`$...$`, `$$...$$`,
|
|
40
|
+
`\(...\)`, `\[...\]`) is detected and shielded from the Markdown pass so
|
|
41
|
+
`\`, `_`, `*`, `^` survive intact in both renderers. A render mode --
|
|
42
|
+
`raw` (verbatim source), `latex` (verbatim, never typeset, paste-ready),
|
|
43
|
+
or `rendered` (typeset) -- is a toggle in the web transcript header
|
|
44
|
+
(persisted like the theme) and an `--math {raw,latex,rendered}` flag on
|
|
45
|
+
`scrollback export` / `copy`. Typesetting uses vendored KaTeX (no CDN);
|
|
46
|
+
the self-contained HTML export embeds KaTeX with its fonts inlined so
|
|
47
|
+
saved/printed files typeset offline. The single-`$` form is recognised
|
|
48
|
+
conservatively so currency (`$5 to $10`) and code are left alone.
|
|
49
|
+
- **Optional full-text search index** (`scrollback index`): SQLite FTS5,
|
|
50
|
+
incremental, stored in a disposable cache DB; the source data is never
|
|
51
|
+
modified, and search falls back to a lexical scan without it.
|
|
52
|
+
- **Launching without the terminal**: `scrollback-web` / `scrollback-app`
|
|
53
|
+
console entry points; `install-launcher` drops a double-clickable
|
|
54
|
+
launcher (macOS `.command` / `.app`, Windows `.bat`, Linux `.desktop`);
|
|
55
|
+
a native desktop window via pywebview that frees the port on close.
|
|
56
|
+
- App icon (macOS `.app` + web favicon) and macOS app identity (menu name,
|
|
57
|
+
About panel with version and a clickable repo link).
|
|
58
|
+
- Configurable host/port via flags or `SCROLLBACK_HOST` / `SCROLLBACK_PORT`,
|
|
59
|
+
with automatic free-port selection.
|
|
60
|
+
|
|
61
|
+
### Security
|
|
62
|
+
|
|
63
|
+
- Sanitize rendered Markdown (DOMPurify) to prevent transcript content from
|
|
64
|
+
injecting scripts into the web UI.
|
|
65
|
+
- Host-header allowlist guarding against DNS-rebinding (loopback-only by
|
|
66
|
+
default); loud warning on non-loopback binds.
|
|
67
|
+
- Path-traversal containment for Claude subagent id resolution.
|
|
68
|
+
|
|
69
|
+
### Performance
|
|
70
|
+
|
|
71
|
+
- Cache Claude Code metadata scans by file mtime (repeated listings go from
|
|
72
|
+
~1.2s to ~0.01s).
|
|
73
|
+
- Byte-offset paging index for Claude transcripts (deep pages on a
|
|
74
|
+
31k-message session: ~1s to ~2ms).
|
|
75
|
+
- Lazy per-session metadata resolution on the indexed search path.
|
|
76
|
+
|
|
77
|
+
### Fixed
|
|
78
|
+
|
|
79
|
+
- Timezone-naive timestamps no longer crash session sorting.
|
|
80
|
+
- Subagent folding no longer drops self-referential or cross-source records.
|
|
81
|
+
- Reliable downloads and printing from the native desktop window.
|
|
82
|
+
- Negative pagination arguments are rejected; clearer errors for unknown
|
|
83
|
+
sources, failed exports, and unavailable data sources.
|
|
84
|
+
|
|
85
|
+
[Unreleased]: https://github.com/a-attia/scrollback/compare/v0.1.0...HEAD
|
|
86
|
+
[0.1.0]: https://github.com/a-attia/scrollback/releases/tag/v0.1.0
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Contributing to scrollback
|
|
2
|
+
|
|
3
|
+
Thanks for your interest. scrollback is a small, local-first, read-only
|
|
4
|
+
tool, and contributions that keep it that way are very welcome.
|
|
5
|
+
|
|
6
|
+
## Development setup
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
git clone https://github.com/a-attia/scrollback
|
|
10
|
+
cd scrollback
|
|
11
|
+
python -m pip install -e ".[web,dev]" # editable install + web + dev tools
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The bare package has **no runtime dependencies** (stdlib only); the web app
|
|
15
|
+
and developer tooling come from extras. Requires Python 3.10+.
|
|
16
|
+
|
|
17
|
+
## Running the checks
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pytest -q # the test suite
|
|
21
|
+
ruff check src tests # lint
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Both must pass before a change is merged. The test suite is fast (~2s) and
|
|
25
|
+
runs against synthetic fixtures plus, where present, your real local data
|
|
26
|
+
(those tests skip gracefully when no data is available).
|
|
27
|
+
|
|
28
|
+
## Project conventions
|
|
29
|
+
|
|
30
|
+
- **Read-only, always.** Nothing in scrollback may write to, lock for
|
|
31
|
+
writing, or upload a user's agent data. The opencode SQLite DB is opened
|
|
32
|
+
with `mode=ro`; JSONL files are read-only. Tests assert this invariant.
|
|
33
|
+
- **Lightest tool that does the job.** Prefer the stdlib. New runtime
|
|
34
|
+
dependencies for the core CLI are a hard sell; put optional features
|
|
35
|
+
behind extras (see `[project.optional-dependencies]`).
|
|
36
|
+
- **Platform-agnostic.** Keep OS-specific code guarded by `sys.platform`
|
|
37
|
+
and best-effort (it must degrade, not crash, elsewhere). Window/icon
|
|
38
|
+
handling lives in Python, not baked into per-OS launcher scripts.
|
|
39
|
+
- **Tests for fixes.** Bug fixes should come with a regression test;
|
|
40
|
+
numeric/parsing assertions should be backed by a known-correct value.
|
|
41
|
+
|
|
42
|
+
## Adding a new agent source
|
|
43
|
+
|
|
44
|
+
Implement the `Source` interface in `src/scrollback/sources/base.py` and
|
|
45
|
+
register it in `src/scrollback/sources/registry.py`. Everything else (CLI,
|
|
46
|
+
search, export, web, index) works against the common model automatically.
|
|
47
|
+
See `opencode.py` (SQLite) and `claudecode.py` (JSONL) as references.
|
|
48
|
+
|
|
49
|
+
## Regenerating the README screenshots
|
|
50
|
+
|
|
51
|
+
The images in the README are generated from synthetic, sanitized demo data
|
|
52
|
+
(`scripts/demo_data.py`) — never from real sessions — so they are safe to
|
|
53
|
+
publish. To regenerate them after a UI change:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install -e ".[screenshots]"
|
|
57
|
+
playwright install chromium # one-time headless-browser download
|
|
58
|
+
python scripts/screenshots.py # writes assets/screenshots/{cli.svg,web.png}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The CLI image is rendered with `rich` (no browser); the web image is
|
|
62
|
+
captured with headless Chromium via Playwright. Neither the `screenshots`
|
|
63
|
+
extra nor the browser is needed to run scrollback.
|
|
64
|
+
|
|
65
|
+
## Submitting changes
|
|
66
|
+
|
|
67
|
+
1. Fork and branch.
|
|
68
|
+
2. Make the change with a focused scope and a test.
|
|
69
|
+
3. Run `pytest -q` and `ruff check src tests`.
|
|
70
|
+
4. Open a pull request describing the change and how you verified it.
|
scrollback-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ahmed Attia
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|