solstone-linux 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.
Files changed (62) hide show
  1. solstone_linux-0.1.0/.gitignore +10 -0
  2. solstone_linux-0.1.0/AGENTS.md +158 -0
  3. solstone_linux-0.1.0/CHANGELOG.md +27 -0
  4. solstone_linux-0.1.0/CLAUDE.md +1 -0
  5. solstone_linux-0.1.0/INSTALL.md +153 -0
  6. solstone_linux-0.1.0/LICENSE +661 -0
  7. solstone_linux-0.1.0/Makefile +165 -0
  8. solstone_linux-0.1.0/PKG-INFO +73 -0
  9. solstone_linux-0.1.0/README.md +58 -0
  10. solstone_linux-0.1.0/contrib/icons/hicolor/index.theme +12 -0
  11. solstone_linux-0.1.0/contrib/icons/hicolor/scalable/status/solstone-error.svg +17 -0
  12. solstone_linux-0.1.0/contrib/icons/hicolor/scalable/status/solstone-paused.svg +17 -0
  13. solstone_linux-0.1.0/contrib/icons/hicolor/scalable/status/solstone-recording.svg +7 -0
  14. solstone_linux-0.1.0/contrib/icons/hicolor/scalable/status/solstone-syncing.svg +16 -0
  15. solstone_linux-0.1.0/pyproject.toml +29 -0
  16. solstone_linux-0.1.0/scripts/extract_changelog.sh +37 -0
  17. solstone_linux-0.1.0/scripts/release.sh +117 -0
  18. solstone_linux-0.1.0/src/solstone_linux/__init__.py +6 -0
  19. solstone_linux-0.1.0/src/solstone_linux/activity.py +384 -0
  20. solstone_linux-0.1.0/src/solstone_linux/audio_detect.py +79 -0
  21. solstone_linux-0.1.0/src/solstone_linux/audio_mute.py +47 -0
  22. solstone_linux-0.1.0/src/solstone_linux/audio_recorder.py +186 -0
  23. solstone_linux-0.1.0/src/solstone_linux/chat_bridge.py +493 -0
  24. solstone_linux-0.1.0/src/solstone_linux/cli.py +489 -0
  25. solstone_linux-0.1.0/src/solstone_linux/config.py +130 -0
  26. solstone_linux-0.1.0/src/solstone_linux/dbus_service.py +149 -0
  27. solstone_linux-0.1.0/src/solstone_linux/dbusmenu.py +242 -0
  28. solstone_linux-0.1.0/src/solstone_linux/doctor.py +277 -0
  29. solstone_linux-0.1.0/src/solstone_linux/icons/hicolor/index.theme +12 -0
  30. solstone_linux-0.1.0/src/solstone_linux/icons/hicolor/scalable/status/solstone-error.svg +17 -0
  31. solstone_linux-0.1.0/src/solstone_linux/icons/hicolor/scalable/status/solstone-paused.svg +17 -0
  32. solstone_linux-0.1.0/src/solstone_linux/icons/hicolor/scalable/status/solstone-recording.svg +7 -0
  33. solstone_linux-0.1.0/src/solstone_linux/icons/hicolor/scalable/status/solstone-syncing.svg +16 -0
  34. solstone_linux-0.1.0/src/solstone_linux/install_guard.py +210 -0
  35. solstone_linux-0.1.0/src/solstone_linux/monitor_positions.py +110 -0
  36. solstone_linux-0.1.0/src/solstone_linux/observer.py +757 -0
  37. solstone_linux-0.1.0/src/solstone_linux/recovery.py +175 -0
  38. solstone_linux-0.1.0/src/solstone_linux/screencast.py +572 -0
  39. solstone_linux-0.1.0/src/solstone_linux/session_env.py +92 -0
  40. solstone_linux-0.1.0/src/solstone_linux/sni.py +250 -0
  41. solstone_linux-0.1.0/src/solstone_linux/solstone-linux.service.in +17 -0
  42. solstone_linux-0.1.0/src/solstone_linux/streams.py +87 -0
  43. solstone_linux-0.1.0/src/solstone_linux/sync.py +497 -0
  44. solstone_linux-0.1.0/src/solstone_linux/tray.py +577 -0
  45. solstone_linux-0.1.0/src/solstone_linux/upload.py +290 -0
  46. solstone_linux-0.1.0/tests/__init__.py +0 -0
  47. solstone_linux-0.1.0/tests/test_activity.py +644 -0
  48. solstone_linux-0.1.0/tests/test_chat_bridge.py +601 -0
  49. solstone_linux-0.1.0/tests/test_cli.py +208 -0
  50. solstone_linux-0.1.0/tests/test_config.py +86 -0
  51. solstone_linux-0.1.0/tests/test_dbus_service.py +281 -0
  52. solstone_linux-0.1.0/tests/test_dbusmenu.py +87 -0
  53. solstone_linux-0.1.0/tests/test_doctor.py +329 -0
  54. solstone_linux-0.1.0/tests/test_extract_changelog.py +64 -0
  55. solstone_linux-0.1.0/tests/test_install_guard.py +192 -0
  56. solstone_linux-0.1.0/tests/test_monitor_positions.py +58 -0
  57. solstone_linux-0.1.0/tests/test_observer.py +80 -0
  58. solstone_linux-0.1.0/tests/test_screencast.py +203 -0
  59. solstone_linux-0.1.0/tests/test_session_env.py +45 -0
  60. solstone_linux-0.1.0/tests/test_streams.py +46 -0
  61. solstone_linux-0.1.0/tests/test_sync.py +853 -0
  62. solstone_linux-0.1.0/tests/test_tray.py +351 -0
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .pytest_cache/
7
+ .mypy_cache/
8
+ .venv/
9
+ .installed
10
+ uv.lock
@@ -0,0 +1,158 @@
1
+ # AGENTS.md
2
+
3
+ Development guidelines for solstone-linux, a standalone Linux desktop observer.
4
+
5
+ ## Project Overview
6
+
7
+ solstone-linux is a companion app that runs alongside the main [solstone](https://solstone.app) journal. It is one of the owner's observers — it experiences screen and audio along with the owner on a Linux desktop using PipeWire and GStreamer, stores segments locally, and syncs them to your solstone journal. It runs as a systemd user service on GNOME Wayland sessions.
8
+
9
+ This is **not** part of the solstone monorepo. It is a standalone package with its own release lifecycle, installed via pipx alongside system-provided PyGObject/GStreamer bindings.
10
+
11
+ ## Source Layout
12
+
13
+ ```
14
+ src/solstone_linux/
15
+ __init__.py Package version
16
+ cli.py CLI entry point (run, setup, install-service, status)
17
+ solstone-linux.service.in Systemd unit template (rendered by install-service)
18
+ config.py Config loading/persistence (~/.local/share/solstone-linux/)
19
+ observer.py Main capture loop — state machine (idle/screencast), audio + video
20
+ screencast.py Portal-based multi-monitor recording (xdg-desktop-portal + GStreamer)
21
+ audio_recorder.py Stereo audio recording (mic + system via soundcard)
22
+ audio_detect.py Audio device detection via ultrasonic tone
23
+ audio_mute.py PulseAudio mute state detection
24
+ activity.py Cross-desktop activity detection (screen lock, power save) via DBus
25
+ monitor_positions.py Monitor position assignment from geometry
26
+ session_env.py Desktop session environment checks and recovery
27
+ streams.py Stream name derivation (hostname-based)
28
+ sync.py Background sync service — uploads completed segments to server
29
+ upload.py HTTP upload client for solstone ingest server
30
+ recovery.py Crash recovery for orphaned .incomplete segments
31
+
32
+ tests/ pytest test suite
33
+ contrib/ Reference icons for development fallback
34
+ ```
35
+
36
+ ## Architecture
37
+
38
+ The observer runs a single asyncio event loop with three concurrent concerns:
39
+
40
+ 1. **Capture loop** (`observer.py`) — Checks activity status every 5 seconds, records audio continuously, manages screencast recording via GStreamer. Creates 5-minute segments in `~/.local/share/solstone-linux/captures/YYYYMMDD/stream/HHMMSS_DDD/`. Segment directories start as `.incomplete` and are renamed on finalization.
41
+
42
+ 2. **Sync service** (`sync.py`) — Background asyncio task that walks the captures directory, queries the server for existing segments, and uploads missing ones. Circuit breaker pattern with error-type-aware thresholds.
43
+
44
+ 3. **Chat bridge** (`chat_bridge.py`) — Background asyncio task that consumes server-sent callosum chat events, mirrors request/clear messages to an optional local FIFO, and fires click-capturing `notify-send` subprocesses when server opt-in allows Linux desktop notifications.
45
+
46
+ State machine has two modes: `screencast` (screen active, recording video) and `idle` (screen inactive). Mode transitions, mute state changes, and 5-minute intervals all trigger segment boundaries.
47
+
48
+ The capture loop never makes network calls. It writes locally; sync handles all uploads.
49
+
50
+ ## Commands
51
+
52
+ ```bash
53
+ make install # Create venv, install package + dev tools (pytest, ruff) via uv
54
+ make test # Run all tests
55
+ make test-only TEST=tests/test_config.py # Run specific test
56
+ make format # Auto-format with ruff
57
+ make ci # Lint + format check + tests
58
+ make install-service # Smart install-or-upgrade: guards against cross-repo contamination; runs CI in upgrade mode
59
+ make service-restart # systemctl restart wrapper
60
+ make service-status # systemctl status wrapper
61
+ make service-logs # systemctl log tail wrapper
62
+ make uninstall-service # Disable + remove unit + pipx uninstall
63
+ make clean # Remove build artifacts and caches
64
+ make versions # Show installed package versions
65
+ ```
66
+
67
+ ## Releasing
68
+
69
+ solstone-linux ships to PyPI via `scripts/release.sh`. The operator runs the
70
+ release from a clean checkout; there is no CI publish path.
71
+
72
+ ```bash
73
+ make release-test # upload to TestPyPI (requires TESTPYPI_TOKEN)
74
+ make release # upload to PyPI (requires PYPI_TOKEN)
75
+ ```
76
+
77
+ The script refuses to run on a dirty tree, builds an sdist + a
78
+ `py3-none-any` wheel with `uv build`, runs `uvx twine check`, uploads,
79
+ tags the commit `vX.Y.Z`, pushes the tag, and creates a matching GitHub
80
+ Release with the artifacts attached and the CHANGELOG block as release
81
+ notes.
82
+
83
+ Before releasing, bump the version in BOTH `pyproject.toml` (`[project].version`)
84
+ and `src/solstone_linux/__init__.py` (`__version__`) — they must match — and add
85
+ a `## [X.Y.Z] - YYYY-MM-DD` block to `CHANGELOG.md`.
86
+
87
+ Set `RELEASE_DRY_RUN=1` to walk the full flow without uploading, tagging,
88
+ pushing, or publishing a GitHub Release; the build and `twine check` still
89
+ run for real.
90
+
91
+ ## Development Principles
92
+
93
+ - **Simple code.** Prefer plain functions over classes. Use dataclasses for structured data. Only use classes when managing stateful lifecycle (Observer, Screencaster, SyncService, AudioRecorder).
94
+ - **Async by default.** The main loop is asyncio. DBus calls, subprocess management, and sync all use async. Audio recording uses a dedicated thread because soundcard is blocking.
95
+ - **No network in the capture loop.** The observer writes segments locally. The sync service uploads asynchronously. This keeps capture reliable even when the server is down.
96
+ - **Atomic directory operations.** Segments start as `HHMMSS.incomplete/`, are renamed to `HHMMSS_DDD/` on completion, or `HHMMSS.failed/` on recovery failure.
97
+ - **System site-packages required.** PyGObject and GStreamer bindings come from system packages. The venv (and pipx) must use `--system-site-packages`.
98
+
99
+ ## File Headers
100
+
101
+ All `.py` source files must include this header as the first two lines:
102
+
103
+ ```python
104
+ # SPDX-License-Identifier: AGPL-3.0-only
105
+ # Copyright (c) 2026 sol pbc
106
+ ```
107
+
108
+ Add this header to new `.py` files in `src/solstone_linux/` and `tests/`. Do not add headers to markdown, TOML, or config files.
109
+
110
+ ## Runtime Dependencies
111
+
112
+ System packages (not pip-installable):
113
+ - `python3-gobject` / `python3-gi` — PyGObject for GTK4 and GDK
114
+ - GStreamer with PipeWire plugin (`gst-launch-1.0 pipewiresrc`)
115
+ - PipeWire running
116
+ - `pactl` (PulseAudio utils) for mute detection
117
+ - xdg-desktop-portal with ScreenCast support
118
+
119
+ Python packages (in pyproject.toml):
120
+ - `requests` — HTTP upload client
121
+ - `numpy` — Audio buffer manipulation and RMS computation
122
+ - `soundfile` — FLAC encoding
123
+ - `soundcard` — Audio device enumeration and recording
124
+ - `dbus-next` — Async DBus client for portal and activity detection
125
+ - `PyGObject` — GDK monitor geometry (installed from system)
126
+
127
+ ## Data Paths
128
+
129
+ - Config: `~/.local/share/solstone-linux/config/config.json`
130
+ - Captures: `~/.local/share/solstone-linux/captures/`
131
+ - State: `~/.local/share/solstone-linux/state/`
132
+ - Restore token: `~/.local/share/solstone-linux/config/restore_token`
133
+ - Install source marker: `~/.config/solstone-linux/.install-source` (tracks which repo clone owns the pipx install)
134
+
135
+ ## Key Patterns
136
+
137
+ - **Activity detection is cross-desktop.** Uses ordered DBus fallback chains for screen lock (freedesktop.org ScreenSaver → GNOME ScreenSaver) and power save (Mutter DisplayConfig → KDE Solid PowerManagement). All backends degrade gracefully to safe defaults.
138
+ - **Audio is stereo-interleaved.** Left channel = microphone, right channel = system audio. When muted, channels are split into separate mono FLAC files.
139
+ - **Screencast uses xdg-desktop-portal.** Session persistence via restore tokens avoids re-prompting the user. GStreamer subprocess (`gst-launch-1.0`) handles the actual PipeWire recording.
140
+ - **Crash recovery runs on startup.** `recovery.py` scans for orphaned `.incomplete` directories older than 2 minutes and finalizes or marks them as failed.
141
+
142
+ ## Testing
143
+
144
+ Tests use pytest with standard mocking. No system dependencies required for tests — audio devices, DBus, and GStreamer are mocked. Run `make test` to execute the full suite.
145
+
146
+ ## Brand canon
147
+
148
+ - **solstone-linux is an observer.** Owner-facing canon describes solstone as observers + journal; sol is the keeper who lives in and tends your journal. In engineering architecture, `observers + sol agent + journal` is the running software this repo's code talks to. This repo implements one of those observers.
149
+ - **The canon lives elsewhere.** Owner-facing terminology comes from sol pbc's internal brand canon (system anatomy + voice terminology guides). This repo's branded prose follows it; the canon itself is not vendored here.
150
+ - **Use co-experience language in branded prose.** In README, INSTALL, onboarding text, settings copy, and error messages, describe solstone-linux as something that experiences screen and audio along with the owner. Never describe it as watching, recording, monitoring, or tracking the owner.
151
+ - **Keep code language in code-only contexts.** Internal architecture terms such as `Capture loop`, the capture pipeline, module names, and data-path names are canon-permitted here and must not be renamed just to match branded prose.
152
+ - **Edit with the surface in mind.** If the owner sees the string, follow the canon. If the text is naming code, pipelines, modules, or storage artifacts for engineers, the existing internal vocabulary stays.
153
+
154
+ Canon source of truth: sol pbc's internal brand canon (system-anatomy guide).
155
+
156
+ ## License
157
+
158
+ AGPL-3.0-only -- Copyright (c) 2026 sol pbc
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ All notable changes to solstone-linux are documented here.
4
+ The format is based on Keep a Changelog (https://keepachangelog.com/),
5
+ and this project adheres to Semantic Versioning.
6
+
7
+ ## [0.1.0] - 2026-05-19
8
+
9
+ First public release of solstone-linux — the Linux desktop observer
10
+ for your solstone journal.
11
+
12
+ solstone-linux runs as a systemd user service in your GNOME Wayland
13
+ session. It experiences screen and audio along with you, holds short
14
+ segments locally, and uploads them to your journal in the background.
15
+
16
+ ### Install paths
17
+
18
+ - From PyPI: `pipx install --system-site-packages solstone-linux`,
19
+ then `solstone-linux install-service` to register the systemd unit.
20
+ - From a clone: `git clone` this repo and run `make install-service`
21
+ for development or unreleased changes.
22
+
23
+ Both paths rely on host packages for PyGObject, GStreamer with the
24
+ PipeWire plugin, PipeWire itself, `pactl`, and `xdg-desktop-portal`
25
+ with ScreenCast support. PyGObject and the GStreamer bindings ride
26
+ along from system site-packages — that is why `--system-site-packages`
27
+ matters.
@@ -0,0 +1 @@
1
+ AGENTS.md
@@ -0,0 +1,153 @@
1
+ # installing solstone-linux
2
+
3
+ these instructions are for a coding agent and human working together. solstone-linux is a standalone observer that experiences your screen and audio along with you on linux desktops using PipeWire and GStreamer, and uploads to your solstone journal.
4
+
5
+ solstone must already be installed and running. if it isn't, start there: https://solstone.app/install
6
+
7
+ > **most users should run `sol observer install` from the solstone host instead of following this file by hand.** that one command handles the clone, the system-package preflight, the build, the registration, and the systemd unit — including a `--dry-run` mode to preview every step. the instructions below are for developers building from source or troubleshooting the install.
8
+
9
+ ## before you begin
10
+
11
+ if `sol` is not in PATH, check `~/.local/bin/sol`.
12
+
13
+ check if solstone-linux is already installed and running:
14
+
15
+ ```
16
+ systemctl --user status solstone-linux
17
+ sol remote list
18
+ ```
19
+
20
+ if it's already active and connected, you're done.
21
+
22
+ ## what to sort out together
23
+
24
+ - **system dependencies.** the observer needs PyGObject, GStreamer, and PipeWire bindings from system packages. installing these requires sudo.
25
+ - **stream name.** this identifies this observer's stream. the machine's hostname is the typical choice.
26
+
27
+ ### remote sol
28
+
29
+ The observer connects to your solstone journal over HTTPS — colocation is optional. For remote-sol setups:
30
+
31
+ - clone anywhere; the `$(sol root)/observers` path in step 2 only applies when sol is installed locally.
32
+ - `solstone-linux setup` will prompt for the journal URL (since local `sol remote list` isn't available) and auto-register the observer with your journal via HTTP, persisting the returned key. No manual key handoff is needed if the journal's observer-registration endpoint is reachable.
33
+ - otherwise, the install sequence below is the same.
34
+
35
+ ## install sequence
36
+
37
+ 1. install system dependencies for your distro, including `pipx`. if you need sudo, walk your human through it.
38
+
39
+ **fedora:**
40
+ ```
41
+ sudo dnf install python3-gobject gtk4 gstreamer1-plugins-base gstreamer1-plugin-pipewire pipewire-gstreamer alsa-lib-devel pulseaudio-utils pipewire-pulseaudio xdg-desktop-portal pipx
42
+ ```
43
+
44
+ **debian / ubuntu:**
45
+ ```
46
+ sudo apt install python3-gi gir1.2-gdk-4.0 gir1.2-gtk-4.0 gstreamer1.0-pipewire libasound2-dev pulseaudio-utils pipewire-pulse xdg-desktop-portal pipx
47
+ ```
48
+
49
+ **arch:**
50
+ ```
51
+ sudo pacman -S python-gobject gtk4 gstreamer gst-plugin-pipewire libpulse alsa-lib xdg-desktop-portal pipx
52
+ ```
53
+
54
+ **opensuse:**
55
+ ```
56
+ sudo zypper install python3-gobject python3-gobject-Gdk typelib-1_0-Gtk-4_0 \
57
+ gtk4-tools gstreamer-plugins-base gstreamer-plugin-pipewire \
58
+ pipewire-pulseaudio pulseaudio-utils alsa-devel \
59
+ xdg-desktop-portal python3-pipx
60
+ ```
61
+ note: package names diverge from Fedora — `typelib-1_0-Gtk-4_0` (not `gtk4`), `gstreamer-plugin-pipewire` (singular), and `alsa-devel` (not `alsa-lib-devel`).
62
+
63
+ 2. If you have local sol, cloning into `$(sol root)/observers` keeps observers colocated with your solstone journal. For remote-sol setups, clone anywhere — the observer runs independently of your journal at runtime:
64
+ ```
65
+ cd "$(sol root)/observers"
66
+ git clone https://github.com/solpbc/solstone-linux.git
67
+ cd solstone-linux
68
+ make install-service
69
+ ```
70
+ `make install-service` is a smart install-or-upgrade: detects fresh-install vs upgrade via a marker file, runs CI in upgrade mode, guards against cross-repo contamination.
71
+
72
+ 3. run the interactive setup:
73
+ ```
74
+ solstone-linux setup
75
+ ```
76
+ this prompts for the journal URL and auto-registers via `sol` when available.
77
+
78
+ 4. verify the service is running:
79
+ ```
80
+ systemctl --user status solstone-linux
81
+ ```
82
+
83
+ ## updating after a code change
84
+
85
+ ```
86
+ git pull && make install-service
87
+ ```
88
+
89
+ ## notes
90
+
91
+ - activity detection (idle timeout, screen lock, power save) works on both GNOME and KDE. on other desktops the observer still experiences your screen and audio fine, but activity-based segment boundaries won't trigger.
92
+ - the tray icon uses the StatusNotifierItem (SNI) D-Bus protocol. it works on KDE natively and GNOME with the AppIndicator extension. if no SNI host is available, the observer runs normally without a tray icon.
93
+
94
+ ## appendix: GNOME tray support
95
+
96
+ the system tray icon appears automatically when the observer starts in a graphical session. on KDE Plasma this works out of the box. on GNOME, the AppIndicator extension is required.
97
+
98
+ GNOME removed native system tray support. the AppIndicator extension restores it via the same StatusNotifierItem protocol KDE uses. without it, the observer runs fine but has no tray icon.
99
+
100
+ **ubuntu:** already installed and enabled by default — skip this step.
101
+
102
+ **fedora:**
103
+ ```
104
+ sudo dnf install gnome-shell-extension-appindicator
105
+ ```
106
+ then log out and back in, or restart GNOME Shell (Alt+F2, type `r`, enter). enable the extension in GNOME Extensions app if not auto-enabled.
107
+
108
+ **arch:**
109
+ ```
110
+ sudo pacman -S gnome-shell-extension-appindicator
111
+ ```
112
+
113
+ **other distros (openSUSE, etc.):**
114
+
115
+ if your distro doesn't ship an AppIndicator extension package, install it from extensions.gnome.org via the CLI:
116
+
117
+ ```
118
+ curl -LO https://extensions.gnome.org/extension-data/appindicatorsupportrgcjonas.gmail.com.v64.shell-extension.zip
119
+ gnome-extensions install appindicatorsupportrgcjonas.gmail.com.v64.shell-extension.zip
120
+ gnome-extensions enable appindicatorsupport@rgcjonas.gmail.com
121
+ ```
122
+
123
+ then restart GNOME Shell — on Wayland, log out and back in; on X11, press Alt+F2 and type `r`. v64 supports GNOME Shell 45–50; check https://extensions.gnome.org/extension/615/appindicator-support/ for a newer build if you're on a later shell.
124
+
125
+ to check if it's working: `gnome-extensions list | grep appindicator` should show it. if the tray icon still doesn't appear, verify it's enabled: `gnome-extensions enable appindicatorsupport@rgcjonas.gmail.com`
126
+
127
+ ## Troubleshooting
128
+
129
+ Common install-time errors and their fixes:
130
+
131
+ - **`pkg-config: command not found` or `cairo` pkg-config failure**
132
+ - fedora: `sudo dnf install pkgconf-pkg-config cairo-devel`
133
+ - debian/ubuntu: `sudo apt install pkg-config libcairo2-dev`
134
+ - arch: `sudo pacman -S pkgconf cairo`
135
+ - opensuse: `sudo zypper install pkgconf-pkg-config cairo-devel`
136
+
137
+ - **`girepository-2.0` missing or `pygobject` build failure**
138
+ - fedora: `sudo dnf install gobject-introspection-devel`
139
+ - debian/ubuntu: `sudo apt install libgirepository1.0-dev`
140
+ - arch: `sudo pacman -S gobject-introspection`
141
+ - opensuse: `sudo zypper install gobject-introspection-devel`
142
+
143
+ - **`Python.h: No such file or directory`**
144
+ - fedora: `sudo dnf install python3-devel`
145
+ - debian/ubuntu: `sudo apt install python3-dev`
146
+ - arch: already bundled in `python` package
147
+ - opensuse: `sudo zypper install python3-devel`
148
+
149
+ - **`pipx: command not found`**
150
+ - fedora: `sudo dnf install pipx`
151
+ - debian/ubuntu: `sudo apt install pipx`
152
+ - arch: `sudo pacman -S python-pipx`
153
+ - opensuse: `sudo zypper install python3-pipx`