scrollback 0.1.0__tar.gz → 0.1.2__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 → scrollback-0.1.2}/CHANGELOG.md +21 -1
- {scrollback-0.1.0 → scrollback-0.1.2}/CONTRIBUTING.md +8 -4
- {scrollback-0.1.0 → scrollback-0.1.2}/PKG-INFO +10 -3
- {scrollback-0.1.0 → scrollback-0.1.2}/README.md +9 -2
- scrollback-0.1.2/assets/screenshots/cli.png +0 -0
- scrollback-0.1.2/assets/screenshots/cli.svg +58 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/scripts/screenshots.py +46 -6
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/__init__.py +1 -1
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/cli.py +80 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/launcher_install.py +40 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/tests/test_launcher_install.py +26 -0
- scrollback-0.1.0/assets/screenshots/cli.svg +0 -91
- {scrollback-0.1.0 → scrollback-0.1.2}/.github/workflows/ci.yml +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/.github/workflows/publish.yml +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/.gitignore +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/LICENSE +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/ROADMAP.md +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/assets/icon-512.png +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/assets/icon.icns +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/assets/icon.svg +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/assets/screenshots/web.png +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/pyproject.toml +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/scripts/demo_data.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/assets/icon-256.png +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/assets/icon.icns +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/clipboard.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/export.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/fts.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/highlight.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/katexbundle.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/launchers/scrollback.bat +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/launchers/scrollback.command +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/launchers/scrollback.desktop +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/launchers/scrollback.sh +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/mathspan.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/minimd.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/models.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/serialize.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/serverconfig.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/sources/__init__.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/sources/aider.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/sources/base.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/sources/claudecode.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/sources/codex.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/sources/opencode.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/sources/registry.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/store.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/termrender.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/__init__.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/app.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/app.js +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/apple-touch-icon.png +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/favicon.png +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/favicon.svg +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/index.html +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/style.css +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/highlight.min.js +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/hljs-dark.min.css +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/hljs-light.min.css +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/katex.min.css +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/katex.min.js +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/marked.min.js +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/web/static/vendor/purify.min.js +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/src/scrollback/webopen.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/tests/test_aider.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/tests/test_claude_paging.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/tests/test_claude_subagents.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/tests/test_cli_helpers.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/tests/test_codex.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/tests/test_export.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/tests/test_fts.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/tests/test_highlight.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/tests/test_minimd.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/tests/test_models.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/tests/test_serverconfig.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/tests/test_sources_live.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/tests/test_stats_resume.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/tests/test_store_filters.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/tests/test_web_api.py +0 -0
- {scrollback-0.1.0 → scrollback-0.1.2}/tests/test_webopen.py +0 -0
|
@@ -6,6 +6,24 @@ follow [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.1.2] - 2026-06-30
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `scrollback uninstall`: removes the artifacts scrollback created (Desktop
|
|
14
|
+
launcher, macOS `.app`, optional search index, launcher log) with a
|
|
15
|
+
confirmation prompt (`--yes` / `--dry-run`). It never touches agent data
|
|
16
|
+
and never self-removes the package; it prints the right `pip`/`pipx
|
|
17
|
+
uninstall` command instead.
|
|
18
|
+
|
|
19
|
+
## [0.1.1] - 2026-06-30
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- README images now render on PyPI: use absolute, release-pinned PNG URLs
|
|
24
|
+
(PyPI does not resolve relative paths or display SVGs). Adds a PyPI-
|
|
25
|
+
friendly `cli.png` alongside the GitHub SVG.
|
|
26
|
+
|
|
9
27
|
## [0.1.0] - 2026-06-30
|
|
10
28
|
|
|
11
29
|
The first release. scrollback reads AI coding-agent session history
|
|
@@ -82,5 +100,7 @@ export it from a CLI and a local web app.
|
|
|
82
100
|
- Negative pagination arguments are rejected; clearer errors for unknown
|
|
83
101
|
sources, failed exports, and unavailable data sources.
|
|
84
102
|
|
|
85
|
-
[Unreleased]: https://github.com/a-attia/scrollback/compare/v0.1.
|
|
103
|
+
[Unreleased]: https://github.com/a-attia/scrollback/compare/v0.1.2...HEAD
|
|
104
|
+
[0.1.2]: https://github.com/a-attia/scrollback/compare/v0.1.1...v0.1.2
|
|
105
|
+
[0.1.1]: https://github.com/a-attia/scrollback/compare/v0.1.0...v0.1.1
|
|
86
106
|
[0.1.0]: https://github.com/a-attia/scrollback/releases/tag/v0.1.0
|
|
@@ -55,12 +55,16 @@ publish. To regenerate them after a UI change:
|
|
|
55
55
|
```bash
|
|
56
56
|
pip install -e ".[screenshots]"
|
|
57
57
|
playwright install chromium # one-time headless-browser download
|
|
58
|
-
python scripts/screenshots.py # writes assets/screenshots/{cli.svg,web.png}
|
|
58
|
+
python scripts/screenshots.py # writes assets/screenshots/{cli.svg,cli.png,web.png}
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
The CLI image is rendered with `rich` (
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
The CLI image is rendered with `rich` (SVG for GitHub, plus a PNG for PyPI,
|
|
62
|
+
which does not display SVGs); the web image is captured with headless
|
|
63
|
+
Chromium via Playwright. The README embeds the PNGs via absolute,
|
|
64
|
+
release-pinned `raw.githubusercontent.com` URLs so they render on both
|
|
65
|
+
GitHub and PyPI (relative paths only work on GitHub). When cutting a new
|
|
66
|
+
release, bump the version in those URLs. Neither the `screenshots` extra nor
|
|
67
|
+
the browser is needed to run scrollback.
|
|
64
68
|
|
|
65
69
|
## Submitting changes
|
|
66
70
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scrollback
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Navigate, search, copy, and export your AI coding-agent sessions (opencode, Claude Code, ...) from one local, read-only tool.
|
|
5
5
|
Project-URL: Homepage, https://github.com/a-attia/scrollback
|
|
6
6
|
Project-URL: Repository, https://github.com/a-attia/scrollback
|
|
@@ -73,13 +73,13 @@ modifies, locks for writing, or uploads your data.
|
|
|
73
73
|
You can use it two ways. From the **command line**, list, search, and export
|
|
74
74
|
your sessions in a single scriptable tool:
|
|
75
75
|
|
|
76
|
-

|
|
77
77
|
|
|
78
78
|
Or open the **local web app** to read a transcript in full — with rendered
|
|
79
79
|
Markdown, syntax-highlighted code, and typeset LaTeX math:
|
|
80
80
|
|
|
81
81
|

|
|
82
|
+
rendered Markdown, highlighted code, and typeset equations.](https://raw.githubusercontent.com/a-attia/scrollback/v0.1.2/assets/screenshots/web.png)
|
|
83
83
|
|
|
84
84
|
Both views read the same on-disk session stores, so you can jump between
|
|
85
85
|
them freely. (The screenshots above use synthetic demo data.)
|
|
@@ -299,6 +299,13 @@ window closes. All of this behaviour is decided in Python, so the launcher
|
|
|
299
299
|
scripts stay free of OS-specific assumptions and ship inside the package
|
|
300
300
|
for `pip install` users.
|
|
301
301
|
|
|
302
|
+
To clean up, `scrollback uninstall` removes the artifacts scrollback
|
|
303
|
+
created — the launchers, the macOS `.app`, the optional search index, and the
|
|
304
|
+
launcher log — after a confirmation (`--yes` to skip it, `--dry-run` to
|
|
305
|
+
preview). It never touches your agent data, and it does not remove the Python
|
|
306
|
+
package itself: it prints the right `pip`/`pipx uninstall` command to finish
|
|
307
|
+
the job (a program can't reliably uninstall the package it is running from).
|
|
308
|
+
|
|
302
309
|
## Fast search (optional index)
|
|
303
310
|
|
|
304
311
|
By default, search is a lexical scan over your live data: zero setup,
|
|
@@ -14,13 +14,13 @@ modifies, locks for writing, or uploads your data.
|
|
|
14
14
|
You can use it two ways. From the **command line**, list, search, and export
|
|
15
15
|
your sessions in a single scriptable tool:
|
|
16
16
|
|
|
17
|
-

|
|
18
18
|
|
|
19
19
|
Or open the **local web app** to read a transcript in full — with rendered
|
|
20
20
|
Markdown, syntax-highlighted code, and typeset LaTeX math:
|
|
21
21
|
|
|
22
22
|

|
|
23
|
+
rendered Markdown, highlighted code, and typeset equations.](https://raw.githubusercontent.com/a-attia/scrollback/v0.1.2/assets/screenshots/web.png)
|
|
24
24
|
|
|
25
25
|
Both views read the same on-disk session stores, so you can jump between
|
|
26
26
|
them freely. (The screenshots above use synthetic demo data.)
|
|
@@ -240,6 +240,13 @@ window closes. All of this behaviour is decided in Python, so the launcher
|
|
|
240
240
|
scripts stay free of OS-specific assumptions and ship inside the package
|
|
241
241
|
for `pip install` users.
|
|
242
242
|
|
|
243
|
+
To clean up, `scrollback uninstall` removes the artifacts scrollback
|
|
244
|
+
created — the launchers, the macOS `.app`, the optional search index, and the
|
|
245
|
+
launcher log — after a confirmation (`--yes` to skip it, `--dry-run` to
|
|
246
|
+
preview). It never touches your agent data, and it does not remove the Python
|
|
247
|
+
package itself: it prints the right `pip`/`pipx uninstall` command to finish
|
|
248
|
+
the job (a program can't reliably uninstall the package it is running from).
|
|
249
|
+
|
|
243
250
|
## Fast search (optional index)
|
|
244
251
|
|
|
245
252
|
By default, search is a lexical scan over your live data: zero setup,
|
|
Binary file
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<svg class="rich-terminal" viewBox="0 0 1141 74.4" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<!-- Generated with Rich https://www.textualize.io -->
|
|
3
|
+
<style>
|
|
4
|
+
|
|
5
|
+
@font-face {
|
|
6
|
+
font-family: "Fira Code";
|
|
7
|
+
src: local("FiraCode-Regular"),
|
|
8
|
+
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
|
|
9
|
+
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
|
|
10
|
+
font-style: normal;
|
|
11
|
+
font-weight: 400;
|
|
12
|
+
}
|
|
13
|
+
@font-face {
|
|
14
|
+
font-family: "Fira Code";
|
|
15
|
+
src: local("FiraCode-Bold"),
|
|
16
|
+
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
|
|
17
|
+
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
|
|
18
|
+
font-style: bold;
|
|
19
|
+
font-weight: 700;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.terminal-387712033-matrix {
|
|
23
|
+
font-family: Fira Code, monospace;
|
|
24
|
+
font-size: 20px;
|
|
25
|
+
line-height: 24.4px;
|
|
26
|
+
font-variant-east-asian: full-width;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.terminal-387712033-title {
|
|
30
|
+
font-size: 18px;
|
|
31
|
+
font-weight: bold;
|
|
32
|
+
font-family: arial;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
</style>
|
|
37
|
+
|
|
38
|
+
<defs>
|
|
39
|
+
<clipPath id="terminal-387712033-clip-terminal">
|
|
40
|
+
<rect x="0" y="0" width="1121.3999999999999" height="23.4" />
|
|
41
|
+
</clipPath>
|
|
42
|
+
|
|
43
|
+
</defs>
|
|
44
|
+
|
|
45
|
+
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1139" height="72.4" rx="8"/><text class="terminal-387712033-title" fill="#c5c8c6" text-anchor="middle" x="569" y="27">scrollback</text>
|
|
46
|
+
<g transform="translate(26,22)">
|
|
47
|
+
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
|
|
48
|
+
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
|
|
49
|
+
<circle cx="44" cy="0" r="7" fill="#28c840"/>
|
|
50
|
+
</g>
|
|
51
|
+
|
|
52
|
+
<g transform="translate(9, 41)" clip-path="url(#terminal-387712033-clip-terminal)">
|
|
53
|
+
|
|
54
|
+
<g class="terminal-387712033-matrix">
|
|
55
|
+
|
|
56
|
+
</g>
|
|
57
|
+
</g>
|
|
58
|
+
</svg>
|
|
@@ -32,7 +32,12 @@ OUT.mkdir(parents=True, exist_ok=True)
|
|
|
32
32
|
|
|
33
33
|
# -- CLI screenshot (rich -> SVG, no browser) ----------------------------
|
|
34
34
|
|
|
35
|
-
def
|
|
35
|
+
def render_cli() -> list[Path]:
|
|
36
|
+
"""Render the CLI list to SVG (crisp on GitHub) and PNG (for PyPI).
|
|
37
|
+
|
|
38
|
+
PyPI's README renderer does not display SVG images, so a PNG is needed
|
|
39
|
+
there; GitHub renders both, and SVG stays sharp at any zoom.
|
|
40
|
+
"""
|
|
36
41
|
from rich.console import Console
|
|
37
42
|
from rich.table import Table
|
|
38
43
|
from rich.text import Text
|
|
@@ -66,10 +71,45 @@ def render_cli_svg() -> Path:
|
|
|
66
71
|
|
|
67
72
|
console.print(Text("$ scrollback list --usage", style="bold green"))
|
|
68
73
|
console.print(table)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
|
|
75
|
+
# PyPI-friendly PNG first: rich's save_svg() clears the record buffer, so
|
|
76
|
+
# the HTML export must happen before we write the SVG.
|
|
77
|
+
png_path = OUT / "cli.png"
|
|
78
|
+
try:
|
|
79
|
+
_svg_html_to_png(console, png_path)
|
|
80
|
+
print(f"wrote {png_path}")
|
|
81
|
+
except Exception as exc: # noqa: BLE001
|
|
82
|
+
print(f"cli.png skipped: {exc}", file=sys.stderr)
|
|
83
|
+
|
|
84
|
+
svg_path = OUT / "cli.svg"
|
|
85
|
+
console.save_svg(str(svg_path), title="scrollback")
|
|
86
|
+
print(f"wrote {svg_path}")
|
|
87
|
+
return [png_path, svg_path]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _svg_html_to_png(console, out: Path) -> None:
|
|
91
|
+
from playwright.sync_api import sync_playwright
|
|
92
|
+
|
|
93
|
+
# rich returns a full HTML document; give it a dark terminal-like page and
|
|
94
|
+
# screenshot the <pre> block (which carries the real content dimensions).
|
|
95
|
+
html = console.export_html(
|
|
96
|
+
inline_styles=True,
|
|
97
|
+
code_format=(
|
|
98
|
+
"<!DOCTYPE html><html><head><meta charset='utf-8'>"
|
|
99
|
+
"<style>html,body{{margin:0}}"
|
|
100
|
+
"body{{background:#0d1117;display:inline-block;padding:22px 26px}}"
|
|
101
|
+
"pre{{margin:0;color:#c9d1d9;font:15px/1.5 Menlo,Consolas,monospace}}"
|
|
102
|
+
"</style></head>"
|
|
103
|
+
"<body><pre><code>{code}</code></pre></body></html>"
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
with sync_playwright() as p:
|
|
107
|
+
browser = p.chromium.launch()
|
|
108
|
+
page = browser.new_page(device_scale_factor=2)
|
|
109
|
+
page.set_content(html, wait_until="load")
|
|
110
|
+
# The inline-block body wraps tightly around the terminal block.
|
|
111
|
+
page.query_selector("body").screenshot(path=str(out))
|
|
112
|
+
browser.close()
|
|
73
113
|
|
|
74
114
|
|
|
75
115
|
# -- web screenshot (headless Chromium via Playwright) -------------------
|
|
@@ -132,7 +172,7 @@ def render_web_png() -> Path:
|
|
|
132
172
|
|
|
133
173
|
|
|
134
174
|
def main() -> int:
|
|
135
|
-
|
|
175
|
+
render_cli()
|
|
136
176
|
try:
|
|
137
177
|
render_web_png()
|
|
138
178
|
except Exception as exc: # noqa: BLE001
|
|
@@ -844,6 +844,72 @@ def cmd_install_launcher(args: argparse.Namespace) -> int:
|
|
|
844
844
|
return 0
|
|
845
845
|
|
|
846
846
|
|
|
847
|
+
def _detect_install_tool() -> str:
|
|
848
|
+
"""Best-effort guess of how scrollback was installed, for the hint.
|
|
849
|
+
|
|
850
|
+
Returns the command the user should run to remove the package itself.
|
|
851
|
+
We never run it: a process cannot reliably uninstall the package it is
|
|
852
|
+
executing from, and the right tool (pip / pipx / conda) depends on how
|
|
853
|
+
it was installed.
|
|
854
|
+
"""
|
|
855
|
+
exe = (sys.executable or "").replace("\\", "/")
|
|
856
|
+
if "/pipx/" in exe or "/.local/pipx/" in exe:
|
|
857
|
+
return "pipx uninstall scrollback"
|
|
858
|
+
return "pip uninstall scrollback"
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
def cmd_uninstall(args: argparse.Namespace) -> int:
|
|
862
|
+
"""Remove scrollback-created artifacts; explain how to remove the package.
|
|
863
|
+
|
|
864
|
+
Removes only files scrollback itself created (launchers, the macOS .app,
|
|
865
|
+
the optional search index, the launcher log). It never touches your agent
|
|
866
|
+
data, and it never tries to uninstall the Python package -- that is left
|
|
867
|
+
to pip/pipx, with the exact command printed at the end.
|
|
868
|
+
"""
|
|
869
|
+
from . import fts, launcher_install
|
|
870
|
+
|
|
871
|
+
targets: list = list(launcher_install.installed_artifacts())
|
|
872
|
+
index_path = fts.default_index_path()
|
|
873
|
+
if index_path.exists():
|
|
874
|
+
targets.append(index_path)
|
|
875
|
+
|
|
876
|
+
if not targets:
|
|
877
|
+
_eprint("no scrollback-created artifacts found.")
|
|
878
|
+
_eprint(f"to remove the package itself, run:\n {_detect_install_tool()}")
|
|
879
|
+
return 0
|
|
880
|
+
|
|
881
|
+
label = "would remove" if args.dry_run else "about to remove"
|
|
882
|
+
_eprint(f"{label}:")
|
|
883
|
+
for p in targets:
|
|
884
|
+
_eprint(f" {p}")
|
|
885
|
+
|
|
886
|
+
if args.dry_run:
|
|
887
|
+
return 0
|
|
888
|
+
|
|
889
|
+
if not args.yes:
|
|
890
|
+
try:
|
|
891
|
+
reply = input("remove these? [y/N] ").strip().lower()
|
|
892
|
+
except (EOFError, KeyboardInterrupt):
|
|
893
|
+
reply = ""
|
|
894
|
+
if reply not in ("y", "yes"):
|
|
895
|
+
_eprint("aborted; nothing removed.")
|
|
896
|
+
return 1
|
|
897
|
+
|
|
898
|
+
removed, failed = 0, 0
|
|
899
|
+
for p in targets:
|
|
900
|
+
try:
|
|
901
|
+
launcher_install.remove_path(p)
|
|
902
|
+
_eprint(f"removed {p}")
|
|
903
|
+
removed += 1
|
|
904
|
+
except OSError as exc:
|
|
905
|
+
_eprint(f"could not remove {p}: {exc}")
|
|
906
|
+
failed += 1
|
|
907
|
+
|
|
908
|
+
_eprint(f"\nremoved {removed} item(s)" + (f", {failed} failed" if failed else ""))
|
|
909
|
+
_eprint(f"to remove the package itself, run:\n {_detect_install_tool()}")
|
|
910
|
+
return 1 if failed else 0
|
|
911
|
+
|
|
912
|
+
|
|
847
913
|
class _AppBridge:
|
|
848
914
|
"""JS<->Python API exposed to the pywebview window.
|
|
849
915
|
|
|
@@ -1093,6 +1159,20 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1093
1159
|
"falls back to the Desktop launcher elsewhere)")
|
|
1094
1160
|
sp.set_defaults(func=cmd_install_launcher)
|
|
1095
1161
|
|
|
1162
|
+
# uninstall
|
|
1163
|
+
sp = sub.add_parser(
|
|
1164
|
+
"uninstall",
|
|
1165
|
+
help="remove scrollback-created artifacts (launchers, app, index)",
|
|
1166
|
+
description="Remove files scrollback created (Desktop launcher, macOS "
|
|
1167
|
+
".app, search index, launcher log). Your agent data is never "
|
|
1168
|
+
"touched. The Python package itself is removed with pip/pipx "
|
|
1169
|
+
"-- the exact command is printed at the end.",
|
|
1170
|
+
)
|
|
1171
|
+
sp.add_argument("-y", "--yes", action="store_true", help="skip the confirmation prompt")
|
|
1172
|
+
sp.add_argument("--dry-run", action="store_true",
|
|
1173
|
+
help="show what would be removed, then exit")
|
|
1174
|
+
sp.set_defaults(func=cmd_uninstall)
|
|
1175
|
+
|
|
1096
1176
|
return p
|
|
1097
1177
|
|
|
1098
1178
|
|
|
@@ -207,3 +207,43 @@ def _install_linux(dest: Path | None) -> list[Path]:
|
|
|
207
207
|
_write(sh, _read_bundled("scrollback.sh"), executable=True)
|
|
208
208
|
created.append(sh)
|
|
209
209
|
return created
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# -- uninstall: locate + remove the artifacts install creates --------------
|
|
213
|
+
|
|
214
|
+
def installed_artifacts() -> list[Path]:
|
|
215
|
+
"""Return the launcher/app/log paths scrollback may have created.
|
|
216
|
+
|
|
217
|
+
Covers the default install locations on every platform (a custom
|
|
218
|
+
``--dest`` is not tracked, so those must be removed by hand). Only paths
|
|
219
|
+
that currently exist are returned. Never includes the user's agent data.
|
|
220
|
+
"""
|
|
221
|
+
home = Path.home()
|
|
222
|
+
candidates: list[Path] = []
|
|
223
|
+
|
|
224
|
+
if sys.platform == "darwin":
|
|
225
|
+
candidates += [
|
|
226
|
+
_desktop_dir() / "scrollback.command",
|
|
227
|
+
home / "Applications" / "scrollback.app",
|
|
228
|
+
home / "Library" / "Logs" / "scrollback-launcher.log",
|
|
229
|
+
]
|
|
230
|
+
elif sys.platform == "win32":
|
|
231
|
+
candidates += [_desktop_dir() / "scrollback.bat"]
|
|
232
|
+
else:
|
|
233
|
+
apps = Path(os.environ.get("XDG_DATA_HOME", home / ".local" / "share")) / "applications"
|
|
234
|
+
candidates += [
|
|
235
|
+
apps / "scrollback.desktop",
|
|
236
|
+
_desktop_dir() / "scrollback.sh",
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
return [p for p in candidates if p.exists()]
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def remove_path(path: Path) -> None:
|
|
243
|
+
"""Delete a file or directory (used to remove a .app bundle)."""
|
|
244
|
+
import shutil
|
|
245
|
+
|
|
246
|
+
if path.is_dir() and not path.is_symlink():
|
|
247
|
+
shutil.rmtree(path)
|
|
248
|
+
else:
|
|
249
|
+
path.unlink()
|
|
@@ -99,3 +99,29 @@ def test_command_script_bakes_absolute_interpreter_path():
|
|
|
99
99
|
script = launcher_install._command_script()
|
|
100
100
|
assert sys.executable in script
|
|
101
101
|
assert "scrollback.cli web" in script
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_installed_artifacts_finds_what_install_created(tmp_path, monkeypatch):
|
|
105
|
+
# Point HOME at a temp dir, install to the (temp) Desktop, then confirm
|
|
106
|
+
# installed_artifacts() reports the created launcher and that remove_path
|
|
107
|
+
# actually deletes it. Never touches the real home.
|
|
108
|
+
monkeypatch.setattr(launcher_install.Path, "home", classmethod(lambda cls: tmp_path))
|
|
109
|
+
desktop = tmp_path / "Desktop"
|
|
110
|
+
desktop.mkdir()
|
|
111
|
+
|
|
112
|
+
created = launcher_install.install(desktop, desktop=True)
|
|
113
|
+
assert created and all(p.exists() for p in created)
|
|
114
|
+
|
|
115
|
+
found = launcher_install.installed_artifacts()
|
|
116
|
+
# The platform's Desktop launcher should be discovered.
|
|
117
|
+
assert any(p in found for p in created), (found, created)
|
|
118
|
+
|
|
119
|
+
for p in found:
|
|
120
|
+
launcher_install.remove_path(p)
|
|
121
|
+
assert not p.exists()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_installed_artifacts_empty_when_nothing_installed(tmp_path, monkeypatch):
|
|
125
|
+
monkeypatch.setattr(launcher_install.Path, "home", classmethod(lambda cls: tmp_path))
|
|
126
|
+
(tmp_path / "Desktop").mkdir()
|
|
127
|
+
assert launcher_install.installed_artifacts() == []
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
<svg class="rich-terminal" viewBox="0 0 1141 245.2" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
-
<!-- Generated with Rich https://www.textualize.io -->
|
|
3
|
-
<style>
|
|
4
|
-
|
|
5
|
-
@font-face {
|
|
6
|
-
font-family: "Fira Code";
|
|
7
|
-
src: local("FiraCode-Regular"),
|
|
8
|
-
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
|
|
9
|
-
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
|
|
10
|
-
font-style: normal;
|
|
11
|
-
font-weight: 400;
|
|
12
|
-
}
|
|
13
|
-
@font-face {
|
|
14
|
-
font-family: "Fira Code";
|
|
15
|
-
src: local("FiraCode-Bold"),
|
|
16
|
-
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
|
|
17
|
-
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
|
|
18
|
-
font-style: bold;
|
|
19
|
-
font-weight: 700;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
.terminal-4113685748-matrix {
|
|
23
|
-
font-family: Fira Code, monospace;
|
|
24
|
-
font-size: 20px;
|
|
25
|
-
line-height: 24.4px;
|
|
26
|
-
font-variant-east-asian: full-width;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.terminal-4113685748-title {
|
|
30
|
-
font-size: 18px;
|
|
31
|
-
font-weight: bold;
|
|
32
|
-
font-family: arial;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.terminal-4113685748-r1 { fill: #98a84b;font-weight: bold }
|
|
36
|
-
.terminal-4113685748-r2 { fill: #c5c8c6 }
|
|
37
|
-
.terminal-4113685748-r3 { fill: #c5c8c6;font-weight: bold }
|
|
38
|
-
.terminal-4113685748-r4 { fill: #d0b344 }
|
|
39
|
-
.terminal-4113685748-r5 { fill: #868887 }
|
|
40
|
-
.terminal-4113685748-r6 { fill: #d75f00 }
|
|
41
|
-
</style>
|
|
42
|
-
|
|
43
|
-
<defs>
|
|
44
|
-
<clipPath id="terminal-4113685748-clip-terminal">
|
|
45
|
-
<rect x="0" y="0" width="1121.3999999999999" height="194.2" />
|
|
46
|
-
</clipPath>
|
|
47
|
-
<clipPath id="terminal-4113685748-line-0">
|
|
48
|
-
<rect x="0" y="1.5" width="1122.4" height="24.65"/>
|
|
49
|
-
</clipPath>
|
|
50
|
-
<clipPath id="terminal-4113685748-line-1">
|
|
51
|
-
<rect x="0" y="25.9" width="1122.4" height="24.65"/>
|
|
52
|
-
</clipPath>
|
|
53
|
-
<clipPath id="terminal-4113685748-line-2">
|
|
54
|
-
<rect x="0" y="50.3" width="1122.4" height="24.65"/>
|
|
55
|
-
</clipPath>
|
|
56
|
-
<clipPath id="terminal-4113685748-line-3">
|
|
57
|
-
<rect x="0" y="74.7" width="1122.4" height="24.65"/>
|
|
58
|
-
</clipPath>
|
|
59
|
-
<clipPath id="terminal-4113685748-line-4">
|
|
60
|
-
<rect x="0" y="99.1" width="1122.4" height="24.65"/>
|
|
61
|
-
</clipPath>
|
|
62
|
-
<clipPath id="terminal-4113685748-line-5">
|
|
63
|
-
<rect x="0" y="123.5" width="1122.4" height="24.65"/>
|
|
64
|
-
</clipPath>
|
|
65
|
-
<clipPath id="terminal-4113685748-line-6">
|
|
66
|
-
<rect x="0" y="147.9" width="1122.4" height="24.65"/>
|
|
67
|
-
</clipPath>
|
|
68
|
-
</defs>
|
|
69
|
-
|
|
70
|
-
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1139" height="243.2" rx="8"/><text class="terminal-4113685748-title" fill="#c5c8c6" text-anchor="middle" x="569" y="27">scrollback</text>
|
|
71
|
-
<g transform="translate(26,22)">
|
|
72
|
-
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
|
|
73
|
-
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
|
|
74
|
-
<circle cx="44" cy="0" r="7" fill="#28c840"/>
|
|
75
|
-
</g>
|
|
76
|
-
|
|
77
|
-
<g transform="translate(9, 41)" clip-path="url(#terminal-4113685748-clip-terminal)">
|
|
78
|
-
|
|
79
|
-
<g class="terminal-4113685748-matrix">
|
|
80
|
-
<text class="terminal-4113685748-r1" x="0" y="20" textLength="305" clip-path="url(#terminal-4113685748-line-0)">$ scrollback list --usage</text><text class="terminal-4113685748-r2" x="1122.4" y="20" textLength="12.2" clip-path="url(#terminal-4113685748-line-0)">
|
|
81
|
-
</text><text class="terminal-4113685748-r3" x="0" y="44.4" textLength="122" clip-path="url(#terminal-4113685748-line-1)">source    </text><text class="terminal-4113685748-r3" x="146.4" y="44.4" textLength="146.4" clip-path="url(#terminal-4113685748-line-1)">id          </text><text class="terminal-4113685748-r3" x="317.2" y="44.4" textLength="195.2" clip-path="url(#terminal-4113685748-line-1)">updated         </text><text class="terminal-4113685748-r3" x="536.8" y="44.4" textLength="48.8" clip-path="url(#terminal-4113685748-line-1)">msgs</text><text class="terminal-4113685748-r3" x="610" y="44.4" textLength="61" clip-path="url(#terminal-4113685748-line-1)"> cost</text><text class="terminal-4113685748-r3" x="695.4" y="44.4" textLength="122" clip-path="url(#terminal-4113685748-line-1)">tok in/out</text><text class="terminal-4113685748-r3" x="841.8" y="44.4" textLength="280.6" clip-path="url(#terminal-4113685748-line-1)">title                  </text><text class="terminal-4113685748-r2" x="1122.4" y="44.4" textLength="12.2" clip-path="url(#terminal-4113685748-line-1)">
|
|
82
|
-
</text><text class="terminal-4113685748-r4" x="0" y="68.8" textLength="122" clip-path="url(#terminal-4113685748-line-2)">opencode  </text><text class="terminal-4113685748-r5" x="146.4" y="68.8" textLength="146.4" clip-path="url(#terminal-4113685748-line-2)">ses_demo_hea</text><text class="terminal-4113685748-r5" x="317.2" y="68.8" textLength="195.2" clip-path="url(#terminal-4113685748-line-2)">2026-03-14 09:32</text><text class="terminal-4113685748-r2" x="536.8" y="68.8" textLength="48.8" clip-path="url(#terminal-4113685748-line-2)">   2</text><text class="terminal-4113685748-r2" x="610" y="68.8" textLength="61" clip-path="url(#terminal-4113685748-line-2)">$0.02</text><text class="terminal-4113685748-r2" x="695.4" y="68.8" textLength="122" clip-path="url(#terminal-4113685748-line-2)"> 1.8k/2.3k</text><text class="terminal-4113685748-r2" x="841.8" y="68.8" textLength="280.6" clip-path="url(#terminal-4113685748-line-2)">Derive a stable scheme </text><text class="terminal-4113685748-r2" x="1122.4" y="68.8" textLength="12.2" clip-path="url(#terminal-4113685748-line-2)">
|
|
83
|
-
</text><text class="terminal-4113685748-r2" x="841.8" y="93.2" textLength="280.6" clip-path="url(#terminal-4113685748-line-3)">for the heat equation  </text><text class="terminal-4113685748-r2" x="1122.4" y="93.2" textLength="12.2" clip-path="url(#terminal-4113685748-line-3)">
|
|
84
|
-
</text><text class="terminal-4113685748-r6" x="0" y="117.6" textLength="122" clip-path="url(#terminal-4113685748-line-4)">claudecode</text><text class="terminal-4113685748-r5" x="146.4" y="117.6" textLength="146.4" clip-path="url(#terminal-4113685748-line-4)">ses_demo_ref</text><text class="terminal-4113685748-r5" x="317.2" y="117.6" textLength="195.2" clip-path="url(#terminal-4113685748-line-4)">2026-03-14 08:10</text><text class="terminal-4113685748-r2" x="536.8" y="117.6" textLength="48.8" clip-path="url(#terminal-4113685748-line-4)">   2</text><text class="terminal-4113685748-r2" x="610" y="117.6" textLength="61" clip-path="url(#terminal-4113685748-line-4)">$0.01</text><text class="terminal-4113685748-r2" x="695.4" y="117.6" textLength="122" clip-path="url(#terminal-4113685748-line-4)">  1.2k/900</text><text class="terminal-4113685748-r2" x="841.8" y="117.6" textLength="280.6" clip-path="url(#terminal-4113685748-line-4)">Refactor load_config   </text><text class="terminal-4113685748-r2" x="1122.4" y="117.6" textLength="12.2" clip-path="url(#terminal-4113685748-line-4)">
|
|
85
|
-
</text><text class="terminal-4113685748-r2" x="841.8" y="142" textLength="280.6" clip-path="url(#terminal-4113685748-line-5)">with typed validation  </text><text class="terminal-4113685748-r2" x="1122.4" y="142" textLength="12.2" clip-path="url(#terminal-4113685748-line-5)">
|
|
86
|
-
</text><text class="terminal-4113685748-r4" x="0" y="166.4" textLength="122" clip-path="url(#terminal-4113685748-line-6)">opencode  </text><text class="terminal-4113685748-r5" x="146.4" y="166.4" textLength="146.4" clip-path="url(#terminal-4113685748-line-6)">ses_demo_bug</text><text class="terminal-4113685748-r5" x="317.2" y="166.4" textLength="195.2" clip-path="url(#terminal-4113685748-line-6)">2026-03-13 09:32</text><text class="terminal-4113685748-r2" x="536.8" y="166.4" textLength="48.8" clip-path="url(#terminal-4113685748-line-6)">   2</text><text class="terminal-4113685748-r2" x="610" y="166.4" textLength="61" clip-path="url(#terminal-4113685748-line-6)">$0.01</text><text class="terminal-4113685748-r2" x="695.4" y="166.4" textLength="122" clip-path="url(#terminal-4113685748-line-6)">   640/410</text><text class="terminal-4113685748-r2" x="841.8" y="166.4" textLength="280.6" clip-path="url(#terminal-4113685748-line-6)">Fix timezone-naive     </text><text class="terminal-4113685748-r2" x="1122.4" y="166.4" textLength="12.2" clip-path="url(#terminal-4113685748-line-6)">
|
|
87
|
-
</text><text class="terminal-4113685748-r2" x="841.8" y="190.8" textLength="280.6" clip-path="url(#terminal-4113685748-line-7)">timestamp crash in sort</text><text class="terminal-4113685748-r2" x="1122.4" y="190.8" textLength="12.2" clip-path="url(#terminal-4113685748-line-7)">
|
|
88
|
-
</text>
|
|
89
|
-
</g>
|
|
90
|
-
</g>
|
|
91
|
-
</svg>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|