1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
# AGENTS.md
Guidance for AI coding agents working in this repository.
## What this is
`sb-builder`: a PyQt6 desktop GUI that drives the SlackBuild tooling on a
Slackware -current build VM (slackrepo, mkhint, sbpull, sbshadow) over ssh.
Two programs: the GUI (`sb_builder/`, PyQt6 incl. QtDBus, stdlib otherwise)
and `remote/sbb-remote`, a single-file stdlib-only Python helper deployed on
the VM. Do not add third-party dependencies to either.
Design and phasing: `docs/superpowers/specs/`. Phase 1 (core) is
`2026-10-19-sb-builder-phase1-design.md`.
## How it works
- Every GUI action is `ssh <target> sbb-remote <cmd> [args]`; the helper
answers with JSON. `sb_builder/remote.py` is the only code that runs ssh
(and local `virsh`). `sb_builder/upstream.py` runs the local `nvchecker`
over the repo's `nvchecker.toml`; the VM never sees the GitHub token.
- Trust boundary: the helper runs only a fixed table of job kinds and builds
argv itself from validated args. Never pass shell strings or paths from the
GUI to the helper.
- Jobs run detached on the VM (`setsid`, `stdin=/dev/null`), one at a time
under a flock, logging to `<state_dir>/jobs/<id>/`. The GUI can disconnect
and reattach with `job-follow <id> <offset>`.
- Exception, by design: `gpg-unlock` reads the signing key passphrase from
stdin (`remote.call(..., stdin=...)`) and feeds it to
`gpg-preset-passphrase`. Every other subprocess in the helper runs with
`stdin=DEVNULL`, so nothing can read the passphrase off the ssh channel.
Never put the passphrase in argv, env, logs or files.
- Hints are parsed (`shlex`), never sourced. `sb_builder/hints.py` mirrors
slackrepo's own rules (`../GITHUB/slackrepo/libexec/functions.d/parsefunctions.sh`);
check that file when changing it.
- Hint editing lives in the helper (`hint-update`, `hint-new` jobs run the
helper's own `_hint` command): text edits, `.bak` before each
write, a final `SBB-RESULT {json}` log line read back with `job-result`.
It replaces mkhint, which stays on the VM for neovim and openvino
(bundled deps, `manifest_packages`) and for the `DELREQUIRES` hints
sbshadow regenerates with `mkhint -F` after every sync, until phase 4b.
- Jobs never get input. Any tool run as a job must be non-interactive
(`--yes`/`--no` style flags). If a tool needs a flag it lacks, add it in
that tool's own repo (e.g. `slackrepo_setup` for sbshadow) rather than
working around it here.
## UI guidelines
Every tab follows these (design: `docs/superpowers/specs/2026-10-20-sb-builder-ui-design.md`).
1. Tabs are permanent and load their data lazily on first show (`loaded`,
`reload()`); the ribbon never opens or closes tabs.
2. Main ribbon: the tab's actions (`tab.actions`) in workflow order. Small
ribbon: view controls (`tab.view_actions`). Build them with
`sb_builder/ui.py` `action()`. Nothing actionable lives inside a view
except navigation links.
3. Change actions act on ticked rows, view actions on the highlighted row. A
row click toggles its tick; Ctrl+A / Ctrl+Shift+A select visible / clear.
4. An inapplicable action is disabled with `ui.enable(action, reason)`, the
reason in its tooltip; never hidden.
5. When a job this session started ends, its next step opens as a non-modal
dialog where the user is (`MainWindow.mine`, `NextStep`,
`HintResultDialog`); starting a job never switches tab; the Jobs tab shows
activity; a dismissed step can be taken again from the ribbon.
6. A modal confirm only for destructive or outward steps (delete hint,
publish, apply shadowing).
## Conventions
- GUI config: `$XDG_CONFIG_HOME/sb-builder/config.toml`. Helper config:
`/etc/sb-builder/config.toml` on the VM. Both read with `tomllib`, both
have shipped `config.example.toml` files, defaults live in code.
- One file per GUI tab.
- Deploying to the VM is outward-facing: confirm before `install.sh --remote`.
- Placeholders only in code, docs, tests and commits.
## Testing
python3 -m unittest discover -s tests
`test_gui.py` forces `QT_QPA_PLATFORM=offscreen` (the desktop session sets
its own, and a modal dialog on the real display blocks the run). A fake
remote must answer every call a test triggers: an unanswered call returns
"ssh: connection failed", which opens a warning dialog that blocks forever
offscreen.
Headless (`test_gui.py` sets `QT_QPA_PLATFORM=offscreen`), no VM needed.
`test_remote.py` loads `remote/sbb-remote` with `SourceFileLoader` (no `.py`
extension) and points it at a temp state dir through `SBB_CONFIG`. Two
`ResourceWarning: subprocess ... is still running` lines are expected: the
detached `_run` is never waited for. To try the helper on the VM without
installing it, copy it to `/tmp` there and run it with `SBB_CONFIG` pointing
at a config whose `state_dir` is under `/tmp`.
## Repo facts
- License: GPLv2 only (`LICENSE`, header in each source file).
- Work directly on master.
- `.superpowers/` is brainstorming scratch, ignored.
|