scrollback 0.1.1__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.
Files changed (99) hide show
  1. {scrollback-0.1.1 → scrollback-0.1.2}/CHANGELOG.md +12 -1
  2. {scrollback-0.1.1 → scrollback-0.1.2}/PKG-INFO +10 -3
  3. {scrollback-0.1.1 → scrollback-0.1.2}/README.md +9 -2
  4. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/__init__.py +1 -1
  5. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/cli.py +80 -0
  6. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/launcher_install.py +40 -0
  7. {scrollback-0.1.1 → scrollback-0.1.2}/tests/test_launcher_install.py +26 -0
  8. {scrollback-0.1.1 → scrollback-0.1.2}/.github/workflows/ci.yml +0 -0
  9. {scrollback-0.1.1 → scrollback-0.1.2}/.github/workflows/publish.yml +0 -0
  10. {scrollback-0.1.1 → scrollback-0.1.2}/.gitignore +0 -0
  11. {scrollback-0.1.1 → scrollback-0.1.2}/CONTRIBUTING.md +0 -0
  12. {scrollback-0.1.1 → scrollback-0.1.2}/LICENSE +0 -0
  13. {scrollback-0.1.1 → scrollback-0.1.2}/ROADMAP.md +0 -0
  14. {scrollback-0.1.1 → scrollback-0.1.2}/assets/icon-512.png +0 -0
  15. {scrollback-0.1.1 → scrollback-0.1.2}/assets/icon.icns +0 -0
  16. {scrollback-0.1.1 → scrollback-0.1.2}/assets/icon.svg +0 -0
  17. {scrollback-0.1.1 → scrollback-0.1.2}/assets/screenshots/cli.png +0 -0
  18. {scrollback-0.1.1 → scrollback-0.1.2}/assets/screenshots/cli.svg +0 -0
  19. {scrollback-0.1.1 → scrollback-0.1.2}/assets/screenshots/web.png +0 -0
  20. {scrollback-0.1.1 → scrollback-0.1.2}/pyproject.toml +0 -0
  21. {scrollback-0.1.1 → scrollback-0.1.2}/scripts/demo_data.py +0 -0
  22. {scrollback-0.1.1 → scrollback-0.1.2}/scripts/screenshots.py +0 -0
  23. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/assets/icon-256.png +0 -0
  24. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/assets/icon.icns +0 -0
  25. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/clipboard.py +0 -0
  26. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/export.py +0 -0
  27. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/fts.py +0 -0
  28. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/highlight.py +0 -0
  29. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/katexbundle.py +0 -0
  30. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/launchers/scrollback.bat +0 -0
  31. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/launchers/scrollback.command +0 -0
  32. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/launchers/scrollback.desktop +0 -0
  33. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/launchers/scrollback.sh +0 -0
  34. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/mathspan.py +0 -0
  35. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/minimd.py +0 -0
  36. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/models.py +0 -0
  37. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/serialize.py +0 -0
  38. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/serverconfig.py +0 -0
  39. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/sources/__init__.py +0 -0
  40. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/sources/aider.py +0 -0
  41. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/sources/base.py +0 -0
  42. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/sources/claudecode.py +0 -0
  43. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/sources/codex.py +0 -0
  44. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/sources/opencode.py +0 -0
  45. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/sources/registry.py +0 -0
  46. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/store.py +0 -0
  47. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/termrender.py +0 -0
  48. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/__init__.py +0 -0
  49. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/app.py +0 -0
  50. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/app.js +0 -0
  51. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/apple-touch-icon.png +0 -0
  52. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/favicon.png +0 -0
  53. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/favicon.svg +0 -0
  54. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/index.html +0 -0
  55. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/style.css +0 -0
  56. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/highlight.min.js +0 -0
  57. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/hljs-dark.min.css +0 -0
  58. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/hljs-light.min.css +0 -0
  59. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  60. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  61. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  62. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  63. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  64. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  65. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  66. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  67. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  68. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  69. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  70. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  71. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  72. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  73. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  74. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  75. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  76. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  77. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  78. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  79. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/katex.min.css +0 -0
  80. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/katex/katex.min.js +0 -0
  81. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/marked.min.js +0 -0
  82. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/web/static/vendor/purify.min.js +0 -0
  83. {scrollback-0.1.1 → scrollback-0.1.2}/src/scrollback/webopen.py +0 -0
  84. {scrollback-0.1.1 → scrollback-0.1.2}/tests/test_aider.py +0 -0
  85. {scrollback-0.1.1 → scrollback-0.1.2}/tests/test_claude_paging.py +0 -0
  86. {scrollback-0.1.1 → scrollback-0.1.2}/tests/test_claude_subagents.py +0 -0
  87. {scrollback-0.1.1 → scrollback-0.1.2}/tests/test_cli_helpers.py +0 -0
  88. {scrollback-0.1.1 → scrollback-0.1.2}/tests/test_codex.py +0 -0
  89. {scrollback-0.1.1 → scrollback-0.1.2}/tests/test_export.py +0 -0
  90. {scrollback-0.1.1 → scrollback-0.1.2}/tests/test_fts.py +0 -0
  91. {scrollback-0.1.1 → scrollback-0.1.2}/tests/test_highlight.py +0 -0
  92. {scrollback-0.1.1 → scrollback-0.1.2}/tests/test_minimd.py +0 -0
  93. {scrollback-0.1.1 → scrollback-0.1.2}/tests/test_models.py +0 -0
  94. {scrollback-0.1.1 → scrollback-0.1.2}/tests/test_serverconfig.py +0 -0
  95. {scrollback-0.1.1 → scrollback-0.1.2}/tests/test_sources_live.py +0 -0
  96. {scrollback-0.1.1 → scrollback-0.1.2}/tests/test_stats_resume.py +0 -0
  97. {scrollback-0.1.1 → scrollback-0.1.2}/tests/test_store_filters.py +0 -0
  98. {scrollback-0.1.1 → scrollback-0.1.2}/tests/test_web_api.py +0 -0
  99. {scrollback-0.1.1 → scrollback-0.1.2}/tests/test_webopen.py +0 -0
@@ -6,6 +6,16 @@ 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
+
9
19
  ## [0.1.1] - 2026-06-30
10
20
 
11
21
  ### Fixed
@@ -90,6 +100,7 @@ export it from a CLI and a local web app.
90
100
  - Negative pagination arguments are rejected; clearer errors for unknown
91
101
  sources, failed exports, and unavailable data sources.
92
102
 
93
- [Unreleased]: https://github.com/a-attia/scrollback/compare/v0.1.1...HEAD
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
94
105
  [0.1.1]: https://github.com/a-attia/scrollback/compare/v0.1.0...v0.1.1
95
106
  [0.1.0]: https://github.com/a-attia/scrollback/releases/tag/v0.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scrollback
3
- Version: 0.1.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
- ![scrollback listing recent sessions in the terminal.](https://raw.githubusercontent.com/a-attia/scrollback/v0.1.1/assets/screenshots/cli.png)
76
+ ![scrollback listing recent sessions in the terminal.](https://raw.githubusercontent.com/a-attia/scrollback/v0.1.2/assets/screenshots/cli.png)
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
  ![The scrollback web app showing a session list beside a transcript with
82
- rendered Markdown, highlighted code, and typeset equations.](https://raw.githubusercontent.com/a-attia/scrollback/v0.1.1/assets/screenshots/web.png)
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
- ![scrollback listing recent sessions in the terminal.](https://raw.githubusercontent.com/a-attia/scrollback/v0.1.1/assets/screenshots/cli.png)
17
+ ![scrollback listing recent sessions in the terminal.](https://raw.githubusercontent.com/a-attia/scrollback/v0.1.2/assets/screenshots/cli.png)
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
  ![The scrollback web app showing a session list beside a transcript with
23
- rendered Markdown, highlighted code, and typeset equations.](https://raw.githubusercontent.com/a-attia/scrollback/v0.1.1/assets/screenshots/web.png)
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,
@@ -5,4 +5,4 @@ coding agents (opencode, Claude Code, ...) through a common adapter
5
5
  interface and lets you list, view, search, and export those sessions.
6
6
  """
7
7
 
8
- __version__ = "0.1.1"
8
+ __version__ = "0.1.2"
@@ -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() == []
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes